Bidirectional traffic with counters at the web socket level. (POC)
This commit is contained in:
parent
cd60718397
commit
53ab28f48f
|
@ -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()
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -0,0 +1,110 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Websock VPN Instrumentation</title>
|
||||
</head>
|
||||
<body ng-app="vpnAdmin" ng-controller="vpnInstrumentationController">
|
||||
|
||||
<div class="panel panel-default panel-primary">
|
||||
<div class="panel-heading">VPN Instrumentation</div>
|
||||
<div class="panel-body">
|
||||
<div class="panel panel-default panel-info">
|
||||
<div class="panel-heading ">Control Plane</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row"> <!-- Auth -->
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="startWebSocket()">Start WebSocket</button>
|
||||
</span>
|
||||
<button class="btn btn-default" type="button" ng-class="conn == false && 'btn-danger' || 'btn-success'">
|
||||
{[{ conn == false && 'False' || 'True' }]}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">Auth</button>
|
||||
</span>
|
||||
<input type="text" class="form-control" placeholder="Enter auth data here">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default panel-info">
|
||||
<div class="panel-heading">Data</div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="text in log_elements">{[{text}]}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
console.log("startup");
|
||||
|
||||
var vpnAdmin = angular.module('vpnAdmin',[]);
|
||||
vpnAdmin.config(function($interpolateProvider) {
|
||||
console.log("vpnTest Config");
|
||||
$interpolateProvider.startSymbol('{[{');
|
||||
$interpolateProvider.endSymbol('}]}');
|
||||
});
|
||||
vpnAdmin.controller('vpnInstrumentationController', function ($scope) {
|
||||
console.log("vpnInstrumentationController startup");
|
||||
|
||||
$scope.log_elements = [];
|
||||
$scope.auth_key = "";
|
||||
$scope.conn = false;
|
||||
|
||||
$scope.webSocketStatus = function() {
|
||||
if ($scope.conn == false) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
$scope.startWebSocket = function() {
|
||||
console.log("Start webSocket {{$}}");
|
||||
if (window["WebSocket"]) {
|
||||
$scope.conn = new WebSocket("wss://{{$}}/ws/admin");
|
||||
$scope.append_log("Websocket opened");
|
||||
$scope.conn.onclose = function (evt) {
|
||||
$scope.append_log("Connection closed.");
|
||||
}
|
||||
$scope.conn.onmessage = function (evt) {
|
||||
$scope.append_log(evt.data)
|
||||
}
|
||||
}
|
||||
else {
|
||||
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
|
||||
}
|
||||
};
|
||||
|
||||
$scope.auth_click = function() {
|
||||
$scope.append_log($scope.auth_key)
|
||||
};
|
||||
|
||||
$scope.append_log = function(txt) {
|
||||
$scope.log_elements.push(txt)
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,132 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Websock VPN Test Client</title>
|
||||
</head>
|
||||
<body ng-app="vpnTest" ng-controller="vpnTestController">
|
||||
|
||||
<div class="panel panel-default panel-primary">
|
||||
<div class="panel-heading">WebSocket Client Test</div>
|
||||
<div class="panel-body">
|
||||
<div class="panel panel-default panel-info">
|
||||
<div class="panel-heading ">Control Plane</div>
|
||||
<div class="panel-body">
|
||||
|
||||
<div class="row"> <!-- Auth -->
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button" ng-click="startWebSocket()">Start WebSocket</button>
|
||||
</span>
|
||||
<button class="btn btn-default" type="button" ng-class="conn == false && 'btn-danger' || 'btn-success'">
|
||||
{[{ conn == false && 'False' || 'True' }]}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" type="button">Auth</button>
|
||||
</span>
|
||||
<input type="text" class="form-control" placeholder="Enter auth data here">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-6">
|
||||
<div class="input-group">
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-default" ng-click="send_click()" type="button">Send</button>
|
||||
</span>
|
||||
<input ng-model="send_data" type="text" class="form-control" placeholder="Enter send data here">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default panel-danger">
|
||||
<div class="panel-heading">Messages</div>
|
||||
<div class="panel-body">
|
||||
<p ng-repeat="text in log_elements">{[{text}]}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
|
||||
|
||||
<script type="text/javascript">
|
||||
console.log("startup");
|
||||
|
||||
var vpnTest = angular.module('vpnTest',[]);
|
||||
vpnTest.config(function($interpolateProvider) {
|
||||
console.log("vpnTest Config");
|
||||
$interpolateProvider.startSymbol('{[{');
|
||||
$interpolateProvider.endSymbol('}]}');
|
||||
});
|
||||
vpnTest.controller('vpnTestController', function ($scope) {
|
||||
console.log("vpnTestController startup");
|
||||
|
||||
$scope.log_elements = [];
|
||||
$scope.auth_key = "";
|
||||
$scope.conn = false;
|
||||
$scope.send_data = ""
|
||||
|
||||
$scope.webSocketStatus = function() {
|
||||
if ($scope.conn == false) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
$scope.startWebSocket = function() {
|
||||
console.log("Start webSocket {{$}}");
|
||||
if (window["WebSocket"]) {
|
||||
$scope.conn = new WebSocket("wss://{{$}}/ws/client");
|
||||
$scope.append_log("Websocket opened");
|
||||
$scope.conn.onclose = function (evt) {
|
||||
$scope.append_log("Connection closed.");
|
||||
}
|
||||
$scope.conn.onmessage = function (evt) {
|
||||
console.log(evt.data)
|
||||
$scope.append_log(evt.data)
|
||||
}
|
||||
}
|
||||
else {
|
||||
appendLog($("<div><b>Your browser does not support WebSockets.</b></div>"))
|
||||
}
|
||||
};
|
||||
|
||||
$scope.auth_click = function() {
|
||||
$scope.append_log($scope.auth_key)
|
||||
};
|
||||
|
||||
$scope.send_click = function() {
|
||||
console.log("send_click")
|
||||
$scope.conn.send($scope.send_data)
|
||||
};
|
||||
|
||||
$scope.append_log = function(txt) {
|
||||
$scope.log_elements.push(txt)
|
||||
};
|
||||
|
||||
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
</html>
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
Loading…
Reference in New Issue