diff --git a/connection.go b/connection.go new file mode 100755 index 0000000..bbd02a9 --- /dev/null +++ b/connection.go @@ -0,0 +1,129 @@ +package main + +import ( + "log" + "net/http" + + "time" + + "github.com/gorilla/websocket" +) + +var upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024, +} + +// Connection track websocket and faciliates in and out data +type Connection struct { + connectionTable *ConnectionTable + + // The websocket connection. + conn *websocket.Conn + + // Buffered channel of outbound messages. + send chan []byte + + // Address of the Remote End Point + source string + + // admin flag. Grants access to admin features + admin bool + + // bytes in + bytesIn int64 + + // bytes out + bytesOut int64 +} + +func (c *Connection) addIn(num int64) { + c.bytesIn = c.bytesIn + num +} + +func (c *Connection) addOut(num int64) { + c.bytesOut = c.bytesOut + num +} + +func (c *Connection) reader() { + defer func() { + c.connectionTable.unregister <- c + c.conn.Close() + }() + c.conn.SetReadLimit(maxMessageSize) + for { + _, message, err := c.conn.ReadMessage() + if err != nil { + if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) { + log.Printf("error: %v", err) + } + break + } + loginfo.Println(message) + c.addIn(int64(len(message))) + + loginfo.Println(c) + } +} + +func (c *Connection) writer() { + dwell := time.NewTicker(5 * time.Second) + loginfo.Println("activate timer", dwell) + defer func() { + c.conn.Close() + }() + for { + select { + + case message := <-c.send: + w, err := c.conn.NextWriter(websocket.TextMessage) + if err != nil { + return + } + w.Write(message) + + if err := w.Close(); err != nil { + return + } + + c.addOut(int64(len(message))) + } + } +} + +func (c *Connection) sender() { + dwell := time.NewTicker(5 * time.Second) + loginfo.Println("activate timer", dwell) + defer func() { + c.conn.Close() + }() + for { + select { + case <-dwell.C: + loginfo.Println("Dwell Activated") + c.send <- []byte("This is a test") + } + } +} + +// handleConnectionWebSocket handles websocket requests from the peer. +func handleConnectionWebSocket(connectionTable *ConnectionTable, w http.ResponseWriter, r *http.Request, admin bool) { + loginfo.Println("websocket opening ", r.RemoteAddr) + + if admin == true { + loginfo.Println("Recognized Admin connection, waiting authentication") + } else { + loginfo.Println("Recognized connection, waiting authentication") + } + + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + loginfo.Println("WebSocket upgrade failed", err) + return + } + connection := &Connection{connectionTable: connectionTable, conn: conn, send: make(chan []byte, 256), source: r.RemoteAddr, admin: admin} + connection.connectionTable.register <- connection + go connection.writer() + go connection.sender() + connection.reader() +} diff --git a/connection_table.go b/connection_table.go new file mode 100755 index 0000000..cfa4e81 --- /dev/null +++ b/connection_table.go @@ -0,0 +1,37 @@ +package main + +// ConnectionTable maintains the set of connections +type ConnectionTable struct { + connections map[*Connection]bool + register chan *Connection + unregister chan *Connection +} + +func newConnectionTable() *ConnectionTable { + return &ConnectionTable{ + register: make(chan *Connection), + unregister: make(chan *Connection), + connections: make(map[*Connection]bool), + } +} + +func (c *ConnectionTable) run() { + loginfo.Println("ConnectionTable starting") + for { + select { + case connection := <-c.register: + loginfo.Println("register fired") + c.connections[connection] = true + + for conn := range c.connections { + loginfo.Println(conn) + } + + case connection := <-c.unregister: + if _, ok := c.connections[connection]; ok { + delete(c.connections, connection) + close(connection.send) + } + } + } +} diff --git a/go-rvpn-server b/go-rvpn-server new file mode 100755 index 0000000..02dc2a8 Binary files /dev/null and b/go-rvpn-server differ diff --git a/html/admin.html b/html/admin.html new file mode 100755 index 0000000..3f376e7 --- /dev/null +++ b/html/admin.html @@ -0,0 +1,110 @@ + + + +Websock VPN Instrumentation + + + +
+
VPN Instrumentation
+
+
+
Control Plane
+
+ +
+
+
+ + + + +
+
+
+ +
+ +
+
+
+ + + + +
+
+
+
+
+
+
Data
+
+

{[{text}]}

+
+
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/html/client.html b/html/client.html new file mode 100755 index 0000000..cfbdc39 --- /dev/null +++ b/html/client.html @@ -0,0 +1,132 @@ + + + +Websock VPN Test Client + + + +
+
WebSocket Client Test
+
+
+
Control Plane
+
+ +
+
+
+ + + + +
+
+
+ +
+ +
+
+
+ + + + +
+
+
+ +
+ +
+
+
+ + + + +
+
+
+ + +
+
+
+
Messages
+
+

{[{text}]}

+
+
+
+
+ + + + + + + + + + + diff --git a/instrumentation.go b/instrumentation.go new file mode 100755 index 0000000..506f955 --- /dev/null +++ b/instrumentation.go @@ -0,0 +1,12 @@ +package main + +// InstrumentationTable holds points to various structures making them available for instrumentation. +type InstrumentationTable struct { + connectionTable *ConnectionTable +} + +func newInstrumentationTable(connectionTable *ConnectionTable) *InstrumentationTable { + return &InstrumentationTable{ + connectionTable: connectionTable, + } +} diff --git a/vpn-server.go b/vpn-server.go new file mode 100755 index 0000000..43bae8d --- /dev/null +++ b/vpn-server.go @@ -0,0 +1,92 @@ +package main + +import ( + "flag" + "html/template" + "io" + "log" + "net/http" + "os" + "time" +) + +const ( + // Time allowed to write a message to the peer. + writeWait = 10 * time.Second + + // Time allowed to read the next pong message from the peer. + pongWait = 60 * time.Second + + // Send pings to peer with this period. Must be less than pongWait. + pingPeriod = (pongWait * 9) / 10 + + // Maximum message size allowed from peer. + maxMessageSize = 512 +) + +var ( + //Info .. + loginfo *log.Logger + logfatal *log.Logger + logFlags = log.Ldate | log.Ltime | log.Lshortfile + argServerPort = flag.String("server-port", ":8000", "serverPort listener") + connectionTable *ConnectionTable +) + +func logInit(infoHandle io.Writer) { + loginfo = log.New(infoHandle, "INFO: ", logFlags) + logfatal = log.New(infoHandle, "FATAL : ", logFlags) +} + +/* +handlerServeContent -- Handles generic URI paths / +"/" - normal client activities for websocket, marked admin=false +"/admin" - marks incoming connection as admin, however must authenticate +"/ws/client" & "/ws/admin" websocket terminations +*/ +func handlerServeContent(w http.ResponseWriter, r *http.Request) { + switch url := r.URL.Path; url { + case "/": + w.Header().Set("Content-Type", "text/html; charset=utf-8") + template.Must(template.ParseFiles("html/client.html")).Execute(w, r.Host) + + case "/admin": + w.Header().Set("Content-Type", "text/html; charset=utf-8") + template.Must(template.ParseFiles("html/admin.html")).Execute(w, r.Host) + + case "/ws/client": + handleConnectionWebSocket(connectionTable, w, r, false) + + case "/ws/admin": + handleConnectionWebSocket(connectionTable, w, r, true) + + default: + http.Error(w, "Not Found", 404) + + } +} + +//launchListener - starts up http listeners and handles various URI paths +func launchListener() { + loginfo.Println("starting Listener") + + connectionTable = newConnectionTable() + go connectionTable.run() + http.HandleFunc("/", handlerServeContent) + + err := http.ListenAndServeTLS(*argServerPort, "server.crt", "server.key", nil) + if err != nil { + logfatal.Println("ListenAndServe: ", err) + panic(err) + } +} + +func main() { + logInit(os.Stdout) + loginfo.Println("startup") + flag.Parse() + loginfo.Println(*argServerPort) + + go launchListener() + time.Sleep(600 * time.Second) +}