Merge branch 'passing-traffic-sni' into 'master'
Passing traffic sni See merge request !1
This commit is contained in:
commit
020df4301b
|
@ -0,0 +1,5 @@
|
||||||
|
/go-rvpn-server
|
||||||
|
/m
|
||||||
|
/debug
|
||||||
|
|
||||||
|
|
236
README.md
236
README.md
|
@ -1,5 +1,241 @@
|
||||||
# RVPN Server
|
# RVPN Server
|
||||||
|
|
||||||
|
## branch: passing-traffic
|
||||||
|
|
||||||
|
- code now passes traffic using just daplie tools
|
||||||
|
- this will require serve-https and node-tunnel-client to work
|
||||||
|
|
||||||
|
|
||||||
|
### Build RVPN
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ go get
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
||||||
|
```
|
||||||
|
### Setup Some Entries
|
||||||
|
|
||||||
|
```bash
|
||||||
|
127.0.0.1 tunnel.example.com rvpn.daplie.invalid hfc2.daplie.me hfc.daplie.me
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start Up Webserver
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:tmp $ cd /tmp
|
||||||
|
hcamacho@Hanks-MBP:tmp $ vi index.html --- Place some index content
|
||||||
|
hcamacho@Hanks-MBP:tmp $ serve-https -p 8080 -d /tmp --servername hfc.daplie.me --agree-tos --email henry.f.camacho@gmail.com
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start Tunnel Client
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:node-tunnel-client $ bin/stunnel.js --locals http://hfc.daplie.me:8080,http://test1.hfc.daplie.me:8080 --stunneld wss://localhost.daplie.me:8443 --secret abc123
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute RVPN
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ ./go-rvpn-server
|
||||||
|
INFO: packer: 2017/03/02 19:16:52.652109 run.go:47: startup
|
||||||
|
-=-=-=-=-=-=-=-=-=-=
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652777 manager.go:77: ConnectionTable starting
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652806 connection_table.go:67: ConnectionTable starting
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652826 manager.go:84: &{map[] 0xc420072420 0xc420072480}
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652832 connection_table.go:50: Reaper waiting for 300 seconds
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652856 manager.go:100: register fired 8443
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652862 manager.go:110: listener starting up 8443
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652868 manager.go:111: &{map[] 0xc420072420 0xc420072480}
|
||||||
|
INFO: genericlistener: 2017/03/02 19:16:52.652869 conn_tracking.go:25: Tracking Running
|
||||||
|
```
|
||||||
|
|
||||||
|
### Browse via tunnel
|
||||||
|
|
||||||
|
https://hfc.daplie.me:8443
|
||||||
|
|
||||||
|
- You'll notice that the browser is redirected to 8080 after accepting the cert. I see a meta-refresh coming back from the serve-https
|
||||||
|
- The traffic is getting back to the client.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
INFO: genericlistener: 2017/03/02 21:24:48.472312 connection.go:207: 00000000 fe 1d 49 50 76 34 2c 31 32 37 2e 30 2e 30 2e 31 |..IPv4,127.0.0.1|
|
||||||
|
00000010 2c 35 33 35 35 39 2c 33 36 38 2c 68 74 74 70 48 |,53559,368,httpH|
|
||||||
|
00000020 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d 0a |TTP/1.1 200 OK..|
|
||||||
|
00000030 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 |Content-Type: te|
|
||||||
|
00000040 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 |xt/html; charset|
|
||||||
|
00000050 3d 75 74 66 2d 38 0d 0a 44 61 74 65 3a 20 46 72 |=utf-8..Date: Fr|
|
||||||
|
00000060 69 2c 20 30 33 20 4d 61 72 20 32 30 31 37 20 30 |i, 03 Mar 2017 0|
|
||||||
|
00000070 33 3a 32 34 3a 34 38 20 47 4d 54 0d 0a 43 6f 6e |3:24:48 GMT..Con|
|
||||||
|
00000080 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 70 2d 61 6c |nection: keep-al|
|
||||||
|
00000090 69 76 65 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e |ive..Content-Len|
|
||||||
|
000000a0 67 74 68 3a 20 32 32 37 0d 0a 0d 0a 3c 68 74 6d |gth: 227....<htm|
|
||||||
|
000000b0 6c 3e 0a 3c 68 65 61 64 3e 0a 20 20 3c 4d 45 54 |l>.<head>. <MET|
|
||||||
|
000000c0 41 20 68 74 74 70 2d 65 71 75 69 76 3d 22 72 65 |A http-equiv="re|
|
||||||
|
000000d0 66 72 65 73 68 22 20 63 6f 6e 74 65 6e 74 3d 22 |fresh" content="|
|
||||||
|
000000e0 30 3b 55 52 4c 3d 27 68 74 74 70 73 3a 2f 2f 68 |0;URL='https://h|
|
||||||
|
000000f0 66 63 2e 64 61 70 6c 69 65 2e 6d 65 3a 38 30 38 |fc.daplie.me:808|
|
||||||
|
00000100 30 2f 27 22 3e 0a 3c 2f 68 65 61 64 3e 0a 3c 62 |0/'">.</head>.<b|
|
||||||
|
00000110 6f 64 79 3e 0a 3c 21 2d 2d 20 48 65 6c 6c 6f 20 |ody>.<!-- Hello |
|
||||||
|
00000120 4d 72 20 44 65 76 65 6c 6f 70 65 72 21 20 57 65 |Mr Developer! We|
|
||||||
|
00000130 20 64 6f 6e 27 74 20 73 65 72 76 65 20 69 6e 73 | don't serve ins|
|
||||||
|
00000140 65 63 75 72 65 20 72 65 73 6f 75 72 63 65 73 20 |ecure resources |
|
||||||
|
00000150 61 72 6f 75 6e 64 20 68 65 72 65 2e 0a 20 20 20 |around here.. |
|
||||||
|
00000160 20 50 6c 65 61 73 65 20 75 73 65 20 48 54 54 50 | Please use HTTP|
|
||||||
|
00000170 53 20 69 6e 73 74 65 61 64 2e 20 2d 2d 3e 0a 3c |S instead. -->.<|
|
||||||
|
00000180 2f 62 6f 64 79 3e 0a 3c 2f 68 74 6d 6c 3e 0a |/body>.</html>.|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- this set of code works great if I am running the node-tunnel-client on a different machine with apache as a web server.
|
||||||
|
- need to work through why serve-https thinks the traffic is inecure.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## restructured-http
|
||||||
|
|
||||||
|
- connection handling has been totally rewritten.
|
||||||
|
- on a specific port RVPN can determine the following:
|
||||||
|
- if a connection is encrypted or not encrypted
|
||||||
|
- if a request is a wss_client
|
||||||
|
- if a request is an admin/api request
|
||||||
|
- if a request is a plain (to be forwarded) http request
|
||||||
|
- or if a request is a different protocol (perhaps SSH)
|
||||||
|
|
||||||
|
To accomplish the above RVPN uses raw TCP sockets, buffered readers, and a custom Listener. This allows protocol detection (multiple services on one port)
|
||||||
|
If we expose 443 and 80 to maximize the ability for tunnel clients and south bound traffic, the RVPN is able to deal with this traffic on a limited number of ports, and the most popular ports.
|
||||||
|
It is possible now to meter any point of the connection (not Interface Level, rather TCP)
|
||||||
|
|
||||||
|
There is now a connection manager that dynamically allows new GenericListeners to start on different ports when needed....
|
||||||
|
|
||||||
|
```go
|
||||||
|
newListener := NewListenerRegistration(initialPort)
|
||||||
|
gl.register <- newListener
|
||||||
|
```
|
||||||
|
|
||||||
|
A new listener is created by sending a NewListenerRegistration on the channel.
|
||||||
|
|
||||||
|
```go
|
||||||
|
|
||||||
|
ln, err := net.ListenTCP("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("unable to bind", err)
|
||||||
|
listenerRegistration.status = listenerFault
|
||||||
|
listenerRegistration.err = err
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerRegistration.status = listenerAdded
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Once the lister is fired up, I sends back a regisration status to the manager along with the new Listener and status.
|
||||||
|
|
||||||
|
|
||||||
|
### Build
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ go get
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Execute RVPN
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
hcamacho@Hanks-MBP:go-rvpn-server $ ./go-rvpn-server
|
||||||
|
INFO: packer: 2017/02/26 12:43:53.978133 run.go:48: startup
|
||||||
|
-=-=-=-=-=-=-=-=-=-=
|
||||||
|
INFO: connection: 2017/02/26 12:43:53.978959 connection_table.go:67: ConnectionTable starting
|
||||||
|
INFO: connection: 2017/02/26 12:43:53.979000 connection_table.go:50: Reaper waiting for 300 seconds
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connect Tunnel client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
hcamacho@Hanks-MBP:node-tunnel-client $ bin/stunnel.js --locals http://hfc.daplie.me:8443,http://test.hfc.daplie.me:3001,http://127.0.0.1:8080 --stunneld wss://localhost.daplie.me:8443 --secret abc123
|
||||||
|
[local proxy] http://hfc.daplie.me:8443
|
||||||
|
[local proxy] http://test.hfc.daplie.me:3001
|
||||||
|
[local proxy] http://127.0.0.1:8080
|
||||||
|
[connect] 'wss://localhost.daplie.me:8443'
|
||||||
|
[open] connected to 'wss://localhost.daplie.me:8443'
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Connect Admin
|
||||||
|
|
||||||
|
- add a host entry
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
127.0.0.1 tunnel.example.com rvpn.daplie.invalid
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
browse https://rvpn.daplie.invalid:8443
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Send some traffic
|
||||||
|
|
||||||
|
- run the RVPN (as above)
|
||||||
|
- run the tunnel client (as above)
|
||||||
|
- browse http://127.0.0.1:8443 && https://127.0.0.1:8443
|
||||||
|
- observe
|
||||||
|
|
||||||
|
```bash
|
||||||
|
|
||||||
|
hcamacho@Hanks-MBP:node-tunnel-client $ bin/stunnel.js --locals http://hfc.daplie.me:8443,http://test.hfc.daplie.me:3001,http://127.0.0.1:8080 --stunneld wss://localhost.daplie.me:8443 --secret abc123
|
||||||
|
[local proxy] http://hfc.daplie.me:8443
|
||||||
|
[local proxy] http://test.hfc.daplie.me:3001
|
||||||
|
[local proxy] http://127.0.0.1:8080
|
||||||
|
[connect] 'wss://localhost.daplie.me:8443'
|
||||||
|
[open] connected to 'wss://localhost.daplie.me:8443'
|
||||||
|
hello
|
||||||
|
fe1c495076342c3132372e302e302e312c383038302c3431332c68747470474554202f20485454502f312e310d0a486f73743a203132372e302e302e313a383434330d0a436f6e6e656374696f6e3a206b6565702d616c6976650d0a43616368652d436f6e74726f6c3a206d61782d6167653d300d0a557067726164652d496e7365637572652d52657175657374733a20310d0a557365722d4167656e743a204d6f7a696c6c612f352e3020284d6163696e746f73683b20496e74656c204d6163204f5320582031305f31325f3329204170706c655765624b69742f3533372e333620284b48544d4c2c206c696b65204765636b6f29204368726f6d652f35362e302e323932342e3837205361666172692f3533372e33360d0a4163636570743a20746578742f68746d6c2c6170706c69636174696f6e2f7868746d6c2b786d6c2c6170706c69636174696f6e2f786d6c3b713d302e392c696d6167652f776562702c2a2f2a3b713d302e380d0a4163636570742d456e636f64696e673a20677a69702c206465666c6174652c20736463682c2062720d0a4163636570742d4c616e67756167653a20656e2d55532c656e3b713d302e380d0a0d0a
|
||||||
|
<EFBFBD>IPv4,127.0.0.1,8080,413,httpGET / HTTP/1.1
|
||||||
|
Host: 127.0.0.1:8443
|
||||||
|
Connection: keep-alive
|
||||||
|
Cache-Control: max-age=0
|
||||||
|
Upgrade-Insecure-Requests: 1
|
||||||
|
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36
|
||||||
|
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
|
||||||
|
Accept-Encoding: gzip, deflate, sdch, br
|
||||||
|
Accept-Language: en-US,en;q=0.8
|
||||||
|
|
||||||
|
|
||||||
|
[exit] loop closed 0
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Looks like it aborts for some reaon. I have this problem on on a new installation as well.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-=-=-=-=-=-=
|
||||||
|
|
||||||
A Poor Man's Reverse VPN written in Go
|
A Poor Man's Reverse VPN written in Go
|
||||||
|
|
||||||
Context
|
Context
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Hank
|
|
@ -0,0 +1 @@
|
||||||
|
Test.html
|
|
@ -0,0 +1,4 @@
|
||||||
|
godebug build -instrument git.daplie.com/Daplie/go-rvpn-server/rvpn/connection,\
|
||||||
|
git.daplie.com/Daplie/go-rvpn-server/rvpn/connection \
|
||||||
|
-o debug main.go
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
rvpn:
|
||||||
|
wssdomain: localhost.daplie.me
|
||||||
|
admindomain: rvpn.daplie.invalid
|
||||||
|
genericlistener: 9999
|
||||||
|
deadtime:
|
||||||
|
dwell: 600
|
||||||
|
idle: 60
|
||||||
|
cancelcheck: 10
|
||||||
|
domains:
|
||||||
|
test.daplie.me:
|
||||||
|
secret: abc123
|
||||||
|
test2.daplie.me:
|
||||||
|
secret: abc123
|
||||||
|
test3.daplie.me:
|
||||||
|
secret: abc123
|
||||||
|
|
|
@ -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,100 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/genericlistener"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
argWssClientListener string
|
||||||
|
argGenericBinding int
|
||||||
|
argServerBinding string
|
||||||
|
argServerAdminBinding string
|
||||||
|
argServerExternalBinding string
|
||||||
|
argDeadTime int
|
||||||
|
connectionTable *genericlistener.Table
|
||||||
|
secretKey = "abc123"
|
||||||
|
wssHostName = "localhost.daplie.me"
|
||||||
|
adminHostName = "rvpn.daplie.invalid"
|
||||||
|
idle int
|
||||||
|
dwell int
|
||||||
|
cancelcheck int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//Main -- main entry point
|
||||||
|
func main() {
|
||||||
|
flag.Parse()
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: main: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: main:", logFlags)
|
||||||
|
viper.SetConfigName("go-rvpn-server")
|
||||||
|
viper.AddConfigPath("./")
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
flag.IntVar(&argDeadTime, "dead-time-counter", 5, "deadtime counter in seconds")
|
||||||
|
|
||||||
|
wssHostName = viper.Get("rvpn.wssdomain").(string)
|
||||||
|
adminHostName = viper.Get("rvpn.admindomain").(string)
|
||||||
|
argGenericBinding = viper.GetInt("rvpn.genericlistener")
|
||||||
|
deadtime := viper.Get("rvpn.deadtime")
|
||||||
|
idle = deadtime.(map[string]interface{})["idle"].(int)
|
||||||
|
dwell = deadtime.(map[string]interface{})["dwell"].(int)
|
||||||
|
cancelcheck = deadtime.(map[string]interface{})["cancelcheck"].(int)
|
||||||
|
|
||||||
|
loginfo.Println("startup")
|
||||||
|
|
||||||
|
loginfo.Println(viper.Get("rvpn.genericlisteners"))
|
||||||
|
loginfo.Println(viper.Get("rvpn.domains"))
|
||||||
|
|
||||||
|
fmt.Println("-=-=-=-=-=-=-=-=-=-=")
|
||||||
|
|
||||||
|
certbundle, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem")
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancelContext := context.WithCancel(context.Background())
|
||||||
|
defer cancelContext()
|
||||||
|
|
||||||
|
// Setup for GenericListenServe.
|
||||||
|
// - establish context for the generic listener
|
||||||
|
// - startup listener
|
||||||
|
// - accept with peek buffer.
|
||||||
|
// - peek at the 1st 30 bytes.
|
||||||
|
// - check for tls
|
||||||
|
// - if tls, establish, protocol peek buffer, else decrypted
|
||||||
|
// - match protocol
|
||||||
|
|
||||||
|
connectionTracking := genericlistener.NewTracking()
|
||||||
|
go connectionTracking.Run(ctx)
|
||||||
|
|
||||||
|
connectionTable = genericlistener.NewTable(dwell, idle)
|
||||||
|
go connectionTable.Run(ctx)
|
||||||
|
|
||||||
|
genericListeners := genericlistener.NewGenerListeners(ctx, connectionTable, connectionTracking, secretKey, certbundle, wssHostName, adminHostName, cancelcheck)
|
||||||
|
go genericListeners.Run(ctx, argGenericBinding)
|
||||||
|
|
||||||
|
//Run for 10 minutes and then shutdown cleanly
|
||||||
|
time.Sleep(600 * time.Second)
|
||||||
|
cancelContext()
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
version: '2.0'
|
||||||
|
services:
|
||||||
|
rvpn:
|
||||||
|
build: rvpn/.
|
||||||
|
ports:
|
||||||
|
- "8443:8443"
|
||||||
|
entrypoint:
|
||||||
|
- ./start.sh
|
||||||
|
expose:
|
||||||
|
- "8843"
|
||||||
|
volumes:
|
||||||
|
- $GOPATH/:/go
|
||||||
|
- ../:/go-rvpn-server
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Docker Deployment for RVPN
|
||||||
|
|
||||||
|
- install docker 1.13, or the latest stable CE release (Testing on MAC using 17.03.0-ce-mac1 (15583))
|
||||||
|
- validate installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:rvpn-docker $ docker-compose --version
|
||||||
|
docker-compose version 1.11.2, build dfed245
|
||||||
|
|
||||||
|
hcamacho@Hanks-MBP:rvpn-docker $ docker --version
|
||||||
|
Docker version 17.03.0-ce, build 60ccb22
|
||||||
|
```
|
||||||
|
- checkout code into gopath
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd $GOPATH/src/git.daplie.com/Daplie
|
||||||
|
git clone git@git.daplie.com:Daplie/go-rvpn-server.git
|
||||||
|
|
||||||
|
cd go-rvpn-server
|
||||||
|
go get
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execute Container Deployment
|
||||||
|
|
||||||
|
- prep
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd rvpn-docker
|
||||||
|
hcamacho@Hanks-MBP:rvpn-docker $ docker-compose build
|
||||||
|
Building rvpn
|
||||||
|
Step 1/3 : FROM golang:1.7.5
|
||||||
|
---> 5cfb16b630ef
|
||||||
|
Step 2/3 : LABEL maintainer "henry.f.camacho@gmail.com"
|
||||||
|
---> Running in 5cdffef8e33d
|
||||||
|
---> f7e09c097612
|
||||||
|
Removing intermediate container 5cdffef8e33d
|
||||||
|
Step 3/3 : WORKDIR "/go-rvpn-server"
|
||||||
|
---> 182aa9c814f2
|
||||||
|
Removing intermediate container f136550d6d48
|
||||||
|
Successfully built 182aa9c814f2
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
- execute container
|
||||||
|
|
||||||
|
```bash
|
||||||
|
hcamacho@Hanks-MBP:rvpn-docker $ docker-compose up
|
||||||
|
Creating network "rvpndocker_default" with the default driver
|
||||||
|
Creating rvpndocker_rvpn_1
|
||||||
|
Attaching to rvpndocker_rvpn_1
|
||||||
|
rvpn_1 | INFO: packer: 2017/03/04 18:13:00.994955 main.go:47: startup
|
||||||
|
rvpn_1 | -=-=-=-=-=-=-=-=-=-=
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000063 conn_tracking.go:25: Tracking Running
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000067 connection_table.go:67: ConnectionTable starting
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000214 connection_table.go:50: Reaper waiting for 300 seconds
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:00.999757 manager.go:77: ConnectionTable starting
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000453 manager.go:84: &{map[] 0xc4200124e0 0xc420012540}
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000505 manager.go:100: register fired 8443
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000613 manager.go:110: listener starting up 8443
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000638 manager.go:111: &{map[] 0xc4200124e0 0xc420012540}
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:01.000696 listener_generic.go:55: :
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242287 listener_generic.go:87: Deadtime reached
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242596 listener_generic.go:114: conn &{0xc420120000 0xc42011e000} 172.18.0.2:8443 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242627 listener_generic.go:131: TLS10
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242641 listener_generic.go:148: Handle Encryption
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242654 one_conn.go:22: Accept 172.18.0.2:8443 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242699 listener_generic.go:177: handle Stream
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.242722 listener_generic.go:178: conn &{0xc420120060 0xc420126000} 172.18.0.2:8443 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.266803 listener_generic.go:191: identifed HTTP
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.267797 listener_generic.go:207: Valid WSS dected...sending to handler
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.267926 one_conn.go:32: addr
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.267947 one_conn.go:22: Accept 172.18.0.2:8443 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.268045 one_conn.go:17: Accept
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.268062 one_conn.go:27: close
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.268066 listener_generic.go:421: Serve error: EOF
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.268707 listener_generic.go:366: HandleFunc /
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.268727 listener_generic.go:369: websocket opening 172.18.0.1:38148 localhost.daplie.me:8443
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.269264 listener_generic.go:397: before connection table
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.269321 connection_table.go:79: register fired
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.269523 connection_table.go:90: adding domain hfc.daplie.me to connection 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.269602 connection_table.go:90: adding domain test1.hfc.daplie.me to connection 172.18.0.1:38148
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.269821 listener_generic.go:410: connection registration accepted &{0xc42012af00 172.18.0.1:38148 0xc420120ea0 [hfc.daplie.me test1.hfc.daplie.me] 0xc4200f49c0}
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.270168 connection.go:200: Reader Start &{0xc420104990 0xc420077560 map[hfc.daplie.me:0xc4201ee7a0 test1.hfc.daplie.me:0xc4201ee7c0] 0xc42012af00 0xc420120f00 172.18.0.1:38148 0 0 {63624247982 269492963 0x8392a0} {0 0 <nil>} [hfc.daplie.me test1.hfc.daplie.me] 0xc4200f49c0 true}
|
||||||
|
rvpn_1 | INFO: genericlistener: 2017/03/04 18:13:02.270281 connection.go:242: Writer Start &{0xc420104990 0xc420077560 map[hfc.daplie.me:0xc4201ee7a0 test1.hfc.daplie.me:0xc4201ee7c0] 0xc42012af00 0xc420120f00 172.18.0.1:38148 0 0 {63624247982 269492963 0x8392a0} {0 0 <nil>} [hfc.daplie.me test1.hfc.daplie.me] 0xc4200f49c0 true}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The line "Connection Registration Accepted indicates a client WSS registered, was authenticated and registered its domains with the RVPN
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
FROM golang:1.7.5
|
||||||
|
LABEL maintainer "henry.f.camacho@gmail.com"
|
||||||
|
|
||||||
|
|
||||||
|
WORKDIR "/go-rvpn-server"
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
docker-compose run --entrypoint "/bin/bash" rvpn
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
import "context"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
//Track -- used to track connection + domain
|
||||||
|
type Track struct {
|
||||||
|
conn net.Conn
|
||||||
|
domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTrack -- Constructor
|
||||||
|
func NewTrack(conn net.Conn, domain string) (p *Track) {
|
||||||
|
p = new(Track)
|
||||||
|
p.conn = conn
|
||||||
|
p.domain = domain
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Tracking --
|
||||||
|
type Tracking struct {
|
||||||
|
connections map[string]*Track
|
||||||
|
register chan *Track
|
||||||
|
unregister chan net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTracking -- Constructor
|
||||||
|
func NewTracking() (p *Tracking) {
|
||||||
|
p = new(Tracking)
|
||||||
|
p.connections = make(map[string]*Track)
|
||||||
|
p.register = make(chan *Track)
|
||||||
|
p.unregister = make(chan net.Conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Run -
|
||||||
|
func (p *Tracking) Run(ctx context.Context) {
|
||||||
|
loginfo.Println("Tracking Running")
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
|
||||||
|
case connection := <-p.register:
|
||||||
|
key := connection.conn.RemoteAddr().String()
|
||||||
|
loginfo.Println("register fired", key)
|
||||||
|
p.connections[key] = connection
|
||||||
|
p.list()
|
||||||
|
|
||||||
|
case connection := <-p.unregister:
|
||||||
|
key := connection.RemoteAddr().String()
|
||||||
|
loginfo.Println("unregister fired", key)
|
||||||
|
if _, ok := p.connections[key]; ok {
|
||||||
|
delete(p.connections, key)
|
||||||
|
}
|
||||||
|
p.list()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Tracking) list() {
|
||||||
|
for c := range p.connections {
|
||||||
|
loginfo.Println(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Lookup --
|
||||||
|
// - get connection from key
|
||||||
|
func (p *Tracking) Lookup(key string) (c *Track, err error) {
|
||||||
|
if _, ok := p.connections[key]; ok {
|
||||||
|
c = p.connections[key]
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Lookup failed for %s", key)
|
||||||
|
c = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,68 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
//WedgeConn -- A buffered IO infront of a connection allowing peeking, and switching connections.
|
||||||
|
type WedgeConn struct {
|
||||||
|
reader *bufio.Reader
|
||||||
|
net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewWedgeConn -- Constructor
|
||||||
|
func NewWedgeConn(c net.Conn) (p *WedgeConn) {
|
||||||
|
p = new(WedgeConn)
|
||||||
|
p.reader = bufio.NewReader(c)
|
||||||
|
p.Conn = c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewWedgeConnSize -- Constructor
|
||||||
|
func NewWedgeConnSize(c net.Conn, size int) (p *WedgeConn) {
|
||||||
|
p = new(WedgeConn)
|
||||||
|
p.reader = bufio.NewReaderSize(c, size)
|
||||||
|
p.Conn = c
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Discard - discard a number of bytes, perhaps after peeking at the
|
||||||
|
func (w *WedgeConn) Discard(n int) (discarded int, err error) {
|
||||||
|
return w.reader.Discard(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Peek - Get a number of bytes outof the buffer, but allow the buffer to be replayed once read
|
||||||
|
func (w *WedgeConn) Peek(n int) ([]byte, error) {
|
||||||
|
return w.reader.Peek(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
//ReadByte -- A normal reader.
|
||||||
|
func (w *WedgeConn) ReadByte() (byte, error) {
|
||||||
|
return w.reader.ReadByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
//Read -- A normal reader.
|
||||||
|
func (w *WedgeConn) Read(p []byte) (int, error) {
|
||||||
|
cnt, err := w.reader.Read(p)
|
||||||
|
return cnt, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//Buffered --
|
||||||
|
func (w *WedgeConn) Buffered() int {
|
||||||
|
return w.reader.Buffered()
|
||||||
|
}
|
||||||
|
|
||||||
|
//PeekAll --
|
||||||
|
// - get all the chars available
|
||||||
|
// - pass then back
|
||||||
|
func (w *WedgeConn) PeekAll() (buf []byte, err error) {
|
||||||
|
|
||||||
|
_, err = w.Peek(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf, err = w.Peek(w.Buffered())
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,285 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||||
|
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"encoding/hex"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 4096,
|
||||||
|
WriteBufferSize: 4096,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connection track websocket and faciliates in and out data
|
||||||
|
type Connection struct {
|
||||||
|
mutex *sync.Mutex
|
||||||
|
|
||||||
|
// The main connection table (should be just one of these created at startup)
|
||||||
|
connectionTable *Table
|
||||||
|
|
||||||
|
//used to track traffic for a domain. Not use for lookup or validation only for tracking
|
||||||
|
DomainTrack map[string]*DomainTrack
|
||||||
|
|
||||||
|
// The websocket connection.
|
||||||
|
conn *websocket.Conn
|
||||||
|
|
||||||
|
// Buffered channel of outbound messages.
|
||||||
|
send chan *SendTrack
|
||||||
|
|
||||||
|
// WssState channel
|
||||||
|
// Must check state via channel before xmit
|
||||||
|
|
||||||
|
// Address of the Remote End Point
|
||||||
|
source string
|
||||||
|
|
||||||
|
// bytes in
|
||||||
|
bytesIn int64
|
||||||
|
|
||||||
|
// bytes out
|
||||||
|
bytesOut int64
|
||||||
|
|
||||||
|
// Connect Time
|
||||||
|
connectTime time.Time
|
||||||
|
|
||||||
|
//lastUpdate
|
||||||
|
lastUpdate time.Time
|
||||||
|
|
||||||
|
//initialDomains - a list of domains from the JWT
|
||||||
|
initialDomains []interface{}
|
||||||
|
|
||||||
|
connectionTrack *Tracking
|
||||||
|
|
||||||
|
///wssState tracks a highlevel status of the connection, false means do nothing.
|
||||||
|
wssState bool
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewConnection -- Constructor
|
||||||
|
func NewConnection(connectionTable *Table, conn *websocket.Conn, remoteAddress string, initialDomains []interface{}, connectionTrack *Tracking) (p *Connection) {
|
||||||
|
p = new(Connection)
|
||||||
|
p.mutex = &sync.Mutex{}
|
||||||
|
p.connectionTable = connectionTable
|
||||||
|
p.conn = conn
|
||||||
|
p.source = remoteAddress
|
||||||
|
p.bytesIn = 0
|
||||||
|
p.bytesOut = 0
|
||||||
|
p.send = make(chan *SendTrack)
|
||||||
|
p.connectTime = time.Now()
|
||||||
|
p.initialDomains = initialDomains
|
||||||
|
p.connectionTrack = connectionTrack
|
||||||
|
p.DomainTrack = make(map[string]*DomainTrack)
|
||||||
|
|
||||||
|
for _, domain := range initialDomains {
|
||||||
|
p.AddTrackedDomain(string(domain.(string)))
|
||||||
|
}
|
||||||
|
|
||||||
|
p.State(true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddTrackedDomain -- Add a tracked domain
|
||||||
|
func (c *Connection) AddTrackedDomain(domain string) {
|
||||||
|
p := new(DomainTrack)
|
||||||
|
p.DomainName = domain
|
||||||
|
c.DomainTrack[domain] = p
|
||||||
|
}
|
||||||
|
|
||||||
|
//InitialDomains -- Property
|
||||||
|
func (c *Connection) InitialDomains() (i []interface{}) {
|
||||||
|
i = c.initialDomains
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConnectTime -- Property
|
||||||
|
func (c *Connection) ConnectTime() (t time.Time) {
|
||||||
|
t = c.connectTime
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesIn -- Property
|
||||||
|
func (c *Connection) BytesIn() (b int64) {
|
||||||
|
b = c.bytesIn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesOut -- Property
|
||||||
|
func (c *Connection) BytesOut() (b int64) {
|
||||||
|
b = c.bytesOut
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//SendCh -- property to sending channel
|
||||||
|
func (c *Connection) SendCh() chan *SendTrack {
|
||||||
|
return c.send
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) addIn(num int64) {
|
||||||
|
c.bytesIn = c.bytesIn + num
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) addOut(num int64) {
|
||||||
|
c.bytesOut = c.bytesOut + num
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConnectionTable -- property
|
||||||
|
func (c *Connection) ConnectionTable() (table *Table) {
|
||||||
|
table = c.connectionTable
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//GetState -- Get state of Socket...this is a high level state.
|
||||||
|
func (c *Connection) GetState() bool {
|
||||||
|
defer func() {
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
c.mutex.Lock()
|
||||||
|
return c.wssState
|
||||||
|
}
|
||||||
|
|
||||||
|
//State -- Set the set of the high level connection
|
||||||
|
func (c *Connection) State(state bool) {
|
||||||
|
defer func() {
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.mutex.Lock()
|
||||||
|
c.wssState = state
|
||||||
|
}
|
||||||
|
|
||||||
|
//Update -- updates the lastUpdate property tracking idle time
|
||||||
|
func (c *Connection) Update() {
|
||||||
|
defer func() {
|
||||||
|
c.mutex.Unlock()
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.mutex.Lock()
|
||||||
|
c.lastUpdate = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
//NextWriter -- Wrapper to allow a high level state check before offering NextWriter
|
||||||
|
//The libary failes if client abends during write-cycle. a fast moving write is not caught before socket state bubbles up
|
||||||
|
//A synchronised state is maintained
|
||||||
|
func (c Connection) NextWriter(wssMessageType int) (w io.WriteCloser, err error) {
|
||||||
|
if c.GetState() == true {
|
||||||
|
w, err = c.conn.NextWriter(wssMessageType)
|
||||||
|
} else {
|
||||||
|
loginfo.Println("NextWriter aborted, state is not true")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Write -- Wrapper to allow a high level state check before allowing a write to the socket.
|
||||||
|
func (c *Connection) Write(w io.WriteCloser, message []byte) (cnt int, err error) {
|
||||||
|
if c.GetState() == true {
|
||||||
|
cnt, err = w.Write(message)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Reader -- export the reader function
|
||||||
|
func (c *Connection) Reader(ctx context.Context) {
|
||||||
|
connectionTrack := c.connectionTrack
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
c.connectionTable.unregister <- c
|
||||||
|
c.conn.Close()
|
||||||
|
loginfo.Println("reader defer", c)
|
||||||
|
}()
|
||||||
|
|
||||||
|
loginfo.Println("Reader Start ", c)
|
||||||
|
|
||||||
|
c.conn.SetReadLimit(65535)
|
||||||
|
for {
|
||||||
|
msgType, message, err := c.conn.ReadMessage()
|
||||||
|
|
||||||
|
loginfo.Println("ReadMessage", msgType, err)
|
||||||
|
|
||||||
|
c.Update()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
|
c.State(false)
|
||||||
|
loginfo.Printf("error: %v", err)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// unpack the message.
|
||||||
|
p, err := packer.ReadMessage(message)
|
||||||
|
key := p.Header.Address().String() + ":" + strconv.Itoa(p.Header.Port)
|
||||||
|
track, err := connectionTrack.Lookup(key)
|
||||||
|
|
||||||
|
loginfo.Println(hex.Dump(p.Data.Data()))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("Unable to locate Tracking for ", key)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Support for tracking outbound traffic based on domain.
|
||||||
|
if domainTrack, ok := c.DomainTrack[track.domain]; ok {
|
||||||
|
//if ok then add to structure, else warn there is something wrong
|
||||||
|
domainTrack.AddIn(int64(len(message)))
|
||||||
|
}
|
||||||
|
|
||||||
|
track.conn.Write(p.Data.Data())
|
||||||
|
|
||||||
|
c.addIn(int64(len(message)))
|
||||||
|
loginfo.Println("end of read")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Writer -- expoer the writer function
|
||||||
|
func (c *Connection) Writer() {
|
||||||
|
defer func() {
|
||||||
|
c.conn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
loginfo.Println("Writer Start ", c)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case message := <-c.send:
|
||||||
|
w, err := c.NextWriter(websocket.BinaryMessage)
|
||||||
|
loginfo.Println("next writer ", w)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Update()
|
||||||
|
|
||||||
|
_, err = c.Write(w, message.data)
|
||||||
|
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
messageLen := int64(len(message.data))
|
||||||
|
|
||||||
|
c.addOut(messageLen)
|
||||||
|
|
||||||
|
//Support for tracking outbound traffic based on domain.
|
||||||
|
if domainTrack, ok := c.DomainTrack[message.domain]; ok {
|
||||||
|
//if ok then add to structure, else warn there is something wrong
|
||||||
|
domainTrack.AddOut(messageLen)
|
||||||
|
loginfo.Println("adding ", messageLen, " to ", message.domain)
|
||||||
|
} else {
|
||||||
|
logdebug.Println("attempting to add bytes to ", message.domain, "it does not exist")
|
||||||
|
logdebug.Println(c.DomainTrack)
|
||||||
|
}
|
||||||
|
loginfo.Println(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import "github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
//Registration -- A connection registration structure used to bring up a connection
|
||||||
|
//connection table will then handle additing and sdtarting up the various readers
|
||||||
|
//else error.
|
||||||
|
type Registration struct {
|
||||||
|
// The websocket connection.
|
||||||
|
conn *websocket.Conn
|
||||||
|
|
||||||
|
// Address of the Remote End Point
|
||||||
|
source string
|
||||||
|
|
||||||
|
// communications channel between go routines
|
||||||
|
commCh chan bool
|
||||||
|
|
||||||
|
//initialDomains - a list of domains from the JWT
|
||||||
|
initialDomains []interface{}
|
||||||
|
|
||||||
|
connectionTrack *Tracking
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewRegistration -- Constructor
|
||||||
|
func NewRegistration(conn *websocket.Conn, remoteAddress string, initialDomains []interface{}, connectionTrack *Tracking) (p *Registration) {
|
||||||
|
p = new(Registration)
|
||||||
|
p.conn = conn
|
||||||
|
p.source = remoteAddress
|
||||||
|
p.commCh = make(chan bool)
|
||||||
|
p.initialDomains = initialDomains
|
||||||
|
p.connectionTrack = connectionTrack
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//CommCh -- Property
|
||||||
|
func (c *Registration) CommCh() chan bool {
|
||||||
|
return c.commCh
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "time"
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
const (
|
||||||
|
initialDomains = 0
|
||||||
|
incrementDomains = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
//Table maintains the set of connections
|
||||||
|
type Table struct {
|
||||||
|
connections map[*Connection][]string
|
||||||
|
domains map[string]*Connection
|
||||||
|
register chan *Registration
|
||||||
|
unregister chan *Connection
|
||||||
|
domainAnnounce chan *DomainMapping
|
||||||
|
domainRevoke chan *DomainMapping
|
||||||
|
dwell int
|
||||||
|
idle int
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTable -- consructor
|
||||||
|
func NewTable(dwell int, idle int) (p *Table) {
|
||||||
|
p = new(Table)
|
||||||
|
p.connections = make(map[*Connection][]string)
|
||||||
|
p.domains = make(map[string]*Connection)
|
||||||
|
p.register = make(chan *Registration)
|
||||||
|
p.unregister = make(chan *Connection)
|
||||||
|
p.domainAnnounce = make(chan *DomainMapping)
|
||||||
|
p.domainRevoke = make(chan *DomainMapping)
|
||||||
|
p.dwell = dwell
|
||||||
|
p.idle = idle
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connections Property
|
||||||
|
func (c *Table) Connections() (table map[*Connection][]string) {
|
||||||
|
table = c.connections
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConnByDomain -- Obtains a connection from a domain announcement.
|
||||||
|
func (c *Table) ConnByDomain(domain string) (conn *Connection, ok bool) {
|
||||||
|
conn, ok = c.domains[domain]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//reaper --
|
||||||
|
func (c *Table) reaper(delay int, idle int) {
|
||||||
|
_ = "breakpoint"
|
||||||
|
for {
|
||||||
|
loginfo.Println("Reaper waiting for ", delay, " seconds")
|
||||||
|
time.Sleep(time.Duration(delay) * time.Second)
|
||||||
|
|
||||||
|
loginfo.Println("Running scanning ", len(c.connections))
|
||||||
|
for d := range c.connections {
|
||||||
|
if d.GetState() == false {
|
||||||
|
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
||||||
|
loginfo.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
||||||
|
delete(c.connections, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Run -- Execute
|
||||||
|
func (c *Table) Run(ctx context.Context) {
|
||||||
|
loginfo.Println("ConnectionTable starting")
|
||||||
|
|
||||||
|
go c.reaper(c.dwell, c.idle)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
|
||||||
|
case registration := <-c.register:
|
||||||
|
loginfo.Println("register fired")
|
||||||
|
|
||||||
|
connection := NewConnection(c, registration.conn, registration.source, registration.initialDomains, registration.connectionTrack)
|
||||||
|
c.connections[connection] = make([]string, initialDomains)
|
||||||
|
registration.commCh <- true
|
||||||
|
|
||||||
|
// handle initial domain additions
|
||||||
|
for _, domain := range connection.initialDomains {
|
||||||
|
// add to the domains regirstation
|
||||||
|
|
||||||
|
newDomain := string(domain.(string))
|
||||||
|
loginfo.Println("adding domain ", newDomain, " to connection ", connection.conn.RemoteAddr().String())
|
||||||
|
c.domains[newDomain] = connection
|
||||||
|
|
||||||
|
// add to the connection domain list
|
||||||
|
s := c.connections[connection]
|
||||||
|
c.connections[connection] = append(s, newDomain)
|
||||||
|
}
|
||||||
|
go connection.Writer()
|
||||||
|
go connection.Reader(ctx)
|
||||||
|
|
||||||
|
case connection := <-c.unregister:
|
||||||
|
loginfo.Println("closing connection ", connection.conn.RemoteAddr().String())
|
||||||
|
if _, ok := c.connections[connection]; ok {
|
||||||
|
for _, domain := range c.connections[connection] {
|
||||||
|
fmt.Println("removing domain ", domain)
|
||||||
|
if _, ok := c.domains[domain]; ok {
|
||||||
|
delete(c.domains, domain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//delete(c.connections, connection)
|
||||||
|
//close(connection.send)
|
||||||
|
}
|
||||||
|
|
||||||
|
case domainMapping := <-c.domainAnnounce:
|
||||||
|
loginfo.Println("domainMapping fired ", domainMapping)
|
||||||
|
//check to make sure connection is already regiered, you can no register a domain without an apporved connection
|
||||||
|
//if connection, ok := connections[domainMapping.connection]; ok {
|
||||||
|
|
||||||
|
//} else {
|
||||||
|
|
||||||
|
//}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Register -- Property
|
||||||
|
func (c *Table) Register() (r chan *Registration) {
|
||||||
|
r = c.register
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
//DomainAPI -- Structure to hold the domain tracking for JSON
|
||||||
|
type DomainAPI struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainAPI - Constructor
|
||||||
|
func NewDomainAPI(domain string, bytesin int64, bytesout int64) (d *DomainAPI) {
|
||||||
|
d = new(DomainAPI)
|
||||||
|
d.Domain = domain
|
||||||
|
d.BytesIn = bytesin
|
||||||
|
d.BytesOut = bytesout
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// //DomainAPIContainer --
|
||||||
|
// type DomainAPIContainer struct {
|
||||||
|
// Domains []*DomainAPI
|
||||||
|
// }
|
||||||
|
|
||||||
|
// //NewDomainAPIContainer -- Constructor
|
||||||
|
// func NewDomainAPIContainer() (p *DomainAPIContainer) {
|
||||||
|
// p = new(DomainAPIContainer)
|
||||||
|
// p.Domains = make([]*DomainAPI, 0)
|
||||||
|
// return p
|
||||||
|
// }
|
|
@ -0,0 +1,24 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
//DomainMapping --
|
||||||
|
type DomainMapping struct {
|
||||||
|
connection *Connection
|
||||||
|
domainName string
|
||||||
|
err int
|
||||||
|
connCh chan bool
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConnCh -- Property
|
||||||
|
func (c *DomainMapping) ConnCh() chan bool {
|
||||||
|
return c.connCh
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainMapping -- Constructor
|
||||||
|
func NewDomainMapping(connection *Connection, domain string) (p *DomainMapping) {
|
||||||
|
p = new(DomainMapping)
|
||||||
|
p.connection = connection
|
||||||
|
p.domainName = domain
|
||||||
|
p.err = -1
|
||||||
|
p.connCh = make(chan bool)
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
//DomainTrack -- Tracking specifics for domains
|
||||||
|
type DomainTrack struct {
|
||||||
|
DomainName string
|
||||||
|
bytesIn int64
|
||||||
|
bytesOut int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainTrack -- Constructor
|
||||||
|
func NewDomainTrack(domainName string) (p *DomainTrack) {
|
||||||
|
p = new(DomainTrack)
|
||||||
|
p.DomainName = domainName
|
||||||
|
p.bytesIn = 0
|
||||||
|
p.bytesOut = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesIn -- Property
|
||||||
|
func (c *DomainTrack) BytesIn() (b int64) {
|
||||||
|
b = c.bytesIn
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesOut -- Property
|
||||||
|
func (c *DomainTrack) BytesOut() (b int64) {
|
||||||
|
b = c.bytesOut
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddIn - Porperty
|
||||||
|
func (c *DomainTrack) AddIn(num int64) {
|
||||||
|
c.bytesIn = c.bytesIn + num
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddOut -- Property
|
||||||
|
func (c *DomainTrack) AddOut(num int64) {
|
||||||
|
c.bytesOut = c.bytesOut + num
|
||||||
|
}
|
|
@ -0,0 +1,471 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"bufio"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
//CtxConnectionTrack
|
||||||
|
const (
|
||||||
|
ctxSecretKey contextKey = "secretKey"
|
||||||
|
ctxConnectionTable contextKey = "connectionTable"
|
||||||
|
ctxConfig contextKey = "config"
|
||||||
|
ctxListenerRegistration contextKey = "listenerRegistration"
|
||||||
|
ctxConnectionTrack contextKey = "connectionTrack"
|
||||||
|
ctxWssHostName contextKey = "wsshostname"
|
||||||
|
ctxAdminHostName contextKey = "adminHostName"
|
||||||
|
ctxCancelCheck contextKey = "cancelcheck"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
encryptNone int = iota
|
||||||
|
encryptSSLV2
|
||||||
|
encryptSSLV3
|
||||||
|
encryptTLS10
|
||||||
|
encryptTLS11
|
||||||
|
encryptTLS12
|
||||||
|
)
|
||||||
|
|
||||||
|
//GenericListenAndServe -- used to lisen for any https traffic on 443 (8443)
|
||||||
|
// - setup generic TCP listener, unencrypted TCP, with a Deadtime out
|
||||||
|
// - leaverage the wedgeConn to peek into the buffer.
|
||||||
|
// - if TLS, consume connection with TLS certbundle, pass to request identifier
|
||||||
|
// - else, just pass to the request identififer
|
||||||
|
func GenericListenAndServe(ctx context.Context, listenerRegistration *ListenerRegistration) {
|
||||||
|
loginfo.Println(":" + string(listenerRegistration.port))
|
||||||
|
cancelCheck := ctx.Value(ctxCancelCheck).(int)
|
||||||
|
|
||||||
|
listenAddr, err := net.ResolveTCPAddr("tcp", ":"+strconv.Itoa(listenerRegistration.port))
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
loginfo.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.ListenTCP("tcp", listenAddr)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("unable to bind", err)
|
||||||
|
listenerRegistration.status = listenerFault
|
||||||
|
listenerRegistration.err = err
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
listenerRegistration.status = listenerAdded
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
ln.SetDeadline(time.Now().Add(time.Duration(cancelCheck) * time.Second))
|
||||||
|
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
|
||||||
|
if nil != err {
|
||||||
|
if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wedgeConn := NewWedgeConn(conn)
|
||||||
|
go handleConnection(ctx, wedgeConn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//handleConnection -
|
||||||
|
// - accept a wedgeConnection along with all the other required attritvues
|
||||||
|
// - peek into the buffer, determine TLS or unencrypted
|
||||||
|
// - if TSL, then terminate with a TLS endpoint, pass to handleStream
|
||||||
|
// - if clearText, pass to handleStream
|
||||||
|
func handleConnection(ctx context.Context, wConn *WedgeConn) {
|
||||||
|
defer wConn.Close()
|
||||||
|
peekCnt := 10
|
||||||
|
|
||||||
|
encryptMode := encryptNone
|
||||||
|
|
||||||
|
loginfo.Println("conn", wConn, wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
||||||
|
peek, err := wConn.Peek(peekCnt)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("error while peeking")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//take a look for a TLS header.
|
||||||
|
if bytes.Contains(peek[0:0], []byte{0x80}) && bytes.Contains(peek[2:4], []byte{0x01, 0x03}) {
|
||||||
|
encryptMode = encryptSSLV2
|
||||||
|
|
||||||
|
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x00}) {
|
||||||
|
encryptMode = encryptSSLV3
|
||||||
|
|
||||||
|
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x01}) {
|
||||||
|
encryptMode = encryptTLS10
|
||||||
|
loginfo.Println("TLS10")
|
||||||
|
|
||||||
|
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x02}) {
|
||||||
|
encryptMode = encryptTLS11
|
||||||
|
|
||||||
|
} else if bytes.Contains(peek[0:3], []byte{0x16, 0x03, 0x03}) {
|
||||||
|
encryptMode = encryptTLS12
|
||||||
|
}
|
||||||
|
|
||||||
|
oneConn := &oneConnListener{wConn}
|
||||||
|
config := ctx.Value(ctxConfig).(*tls.Config)
|
||||||
|
|
||||||
|
if encryptMode == encryptSSLV2 {
|
||||||
|
loginfo.Println("SSLv2 is not accepted")
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if encryptMode != encryptNone {
|
||||||
|
loginfo.Println("Handle Encryption")
|
||||||
|
|
||||||
|
// check SNI heading
|
||||||
|
// if matched, then looks like a WSS connection
|
||||||
|
// else external don't pull off TLS.
|
||||||
|
|
||||||
|
peek, err := wConn.PeekAll()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("error while peeking")
|
||||||
|
loginfo.Println(hex.Dump(peek[0:]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
wssHostName := ctx.Value(ctxWssHostName).(string)
|
||||||
|
adminHostName := ctx.Value(ctxAdminHostName).(string)
|
||||||
|
|
||||||
|
sniHostName, err := getHello(peek)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("sni:", sniHostName)
|
||||||
|
|
||||||
|
if sniHostName == wssHostName {
|
||||||
|
//handle WSS Path
|
||||||
|
tlsListener := tls.NewListener(oneConn, config)
|
||||||
|
|
||||||
|
conn, err := tlsListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsWedgeConn := NewWedgeConn(conn)
|
||||||
|
handleStream(ctx, tlsWedgeConn)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else if sniHostName == adminHostName {
|
||||||
|
// handle admin path
|
||||||
|
tlsListener := tls.NewListener(oneConn, config)
|
||||||
|
|
||||||
|
conn, err := tlsListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
tlsWedgeConn := NewWedgeConn(conn)
|
||||||
|
handleStream(ctx, tlsWedgeConn)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else {
|
||||||
|
//traffic not terminating on the rvpn do not decrypt
|
||||||
|
loginfo.Println("processing non terminating traffic", wssHostName, sniHostName)
|
||||||
|
handleExternalHTTPRequest(ctx, wConn, sniHostName, "https")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("Handle Unencrypted")
|
||||||
|
handleStream(ctx, wConn)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//handleStream --
|
||||||
|
// - we have an unencrypted stream connection with the ability to peek
|
||||||
|
// - attempt to identify HTTP
|
||||||
|
// - handle http
|
||||||
|
// - attempt to identify as WSS session
|
||||||
|
// - attempt to identify as ADMIN/API session
|
||||||
|
// - else handle as raw http
|
||||||
|
// - handle other?
|
||||||
|
func handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
|
loginfo.Println("handle Stream")
|
||||||
|
loginfo.Println("conn", wConn.LocalAddr().String(), wConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
peek, err := wConn.PeekAll()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("error while peeking", err)
|
||||||
|
loginfo.Println(hex.Dump(peek[0:]))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP Identifcation
|
||||||
|
if bytes.Contains(peek[:], []byte{0x0d, 0x0a}) {
|
||||||
|
//string protocol
|
||||||
|
if bytes.ContainsAny(peek[:], "HTTP/") {
|
||||||
|
loginfo.Println("identifed HTTP")
|
||||||
|
|
||||||
|
r, err := http.ReadRequest(bufio.NewReader(bytes.NewReader(peek)))
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("identifed as HTTP, failed request parsing", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// do we have a valid wss_client?
|
||||||
|
secretKey := ctx.Value(ctxSecretKey).(string)
|
||||||
|
tokenString := r.URL.Query().Get("access_token")
|
||||||
|
result, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secretKey), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err == nil && result.Valid {
|
||||||
|
loginfo.Println("Valid WSS dected...sending to handler")
|
||||||
|
oneConn := &oneConnListener{wConn}
|
||||||
|
handleWssClient(ctx, oneConn)
|
||||||
|
|
||||||
|
//do we have a invalid domain indicating Admin?
|
||||||
|
//if yes, prep the oneConn and send it to the handler
|
||||||
|
} else if strings.Contains(r.Host, "rvpn.daplie.invalid") {
|
||||||
|
loginfo.Println("admin")
|
||||||
|
oneConn := &oneConnListener{wConn}
|
||||||
|
handleAdminClient(ctx, oneConn)
|
||||||
|
return
|
||||||
|
|
||||||
|
} else {
|
||||||
|
loginfo.Println("unsupported")
|
||||||
|
loginfo.Println(hex.Dump(peek))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//handleExternalHTTPRequest -
|
||||||
|
// - get a wConn and start processing requests
|
||||||
|
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname string, service string) {
|
||||||
|
connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
connectionTracking.unregister <- extConn
|
||||||
|
extConn.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||||
|
//find the connection by domain name
|
||||||
|
conn, ok := connectionTable.ConnByDomain(hostname)
|
||||||
|
if !ok {
|
||||||
|
//matching connection can not be found based on ConnByDomain
|
||||||
|
loginfo.Println("unable to match ", hostname, " to an existing connection")
|
||||||
|
//http.Error(, "Domain not supported", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
track := NewTrack(extConn, hostname)
|
||||||
|
connectionTracking.register <- track
|
||||||
|
|
||||||
|
loginfo.Println("Domain Accepted", hostname, extConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
rAddr, rPort, err := net.SplitHostPort(extConn.RemoteAddr().String())
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("unable to decode hostport", extConn.RemoteAddr().String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
buffer, err := extConn.PeekAll()
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("unable to peekAll", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("Before Packer", hex.Dump(buffer))
|
||||||
|
|
||||||
|
cnt := len(buffer)
|
||||||
|
|
||||||
|
p := packer.NewPacker()
|
||||||
|
p.Header.SetAddress(rAddr)
|
||||||
|
p.Header.Port, err = strconv.Atoi(rPort)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("Unable to set Remote port", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Header.Service = service
|
||||||
|
p.Data.AppendBytes(buffer[0:cnt])
|
||||||
|
buf := p.PackV1()
|
||||||
|
|
||||||
|
loginfo.Println(hex.Dump(buf.Bytes()))
|
||||||
|
|
||||||
|
sendTrack := NewSendTrack(buf.Bytes(), hostname)
|
||||||
|
conn.SendCh() <- sendTrack
|
||||||
|
|
||||||
|
_, err = extConn.Discard(cnt)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("unable to discard", cnt, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//handleAdminClient -
|
||||||
|
// - expecting an existing oneConnListener with a qualified wss client connected.
|
||||||
|
// - auth will happen again since we were just peeking at the token.
|
||||||
|
func handleAdminClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
|
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||||
|
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
loginfo.Println("HandleFunc /")
|
||||||
|
switch url := r.URL.Path; url {
|
||||||
|
case "/":
|
||||||
|
// check to see if we are using the administrative Host
|
||||||
|
if strings.Contains(r.Host, "rvpn.daplie.invalid") {
|
||||||
|
http.Redirect(w, r, "/admin", 301)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "Not Found", 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
fmt.Fprintln(w, "<html>Welcome..press <a href=/api/servers>Servers</a> to access stats</html>")
|
||||||
|
})
|
||||||
|
|
||||||
|
router.HandleFunc("/api/servers", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
fmt.Println("here")
|
||||||
|
serverContainer := NewServerAPIContainer()
|
||||||
|
|
||||||
|
for c := range connectionTable.Connections() {
|
||||||
|
serverAPI := NewServerAPI(c)
|
||||||
|
serverContainer.Servers = append(serverContainer.Servers, serverAPI)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
json.NewEncoder(w).Encode(serverContainer)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":80",
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.Serve(oneConn)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("Serve error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//handleWssClient -
|
||||||
|
// - expecting an existing oneConnListener with a qualified wss client connected.
|
||||||
|
// - auth will happen again since we were just peeking at the token.
|
||||||
|
func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
|
secretKey := ctx.Value(ctxSecretKey).(string)
|
||||||
|
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||||
|
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
loginfo.Println("HandleFunc /")
|
||||||
|
switch url := r.URL.Path; url {
|
||||||
|
case "/":
|
||||||
|
loginfo.Println("websocket opening ", r.RemoteAddr, " ", r.Host)
|
||||||
|
|
||||||
|
tokenString := r.URL.Query().Get("access_token")
|
||||||
|
result, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secretKey), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil || !result.Valid {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
w.Write([]byte("Not Authorized"))
|
||||||
|
loginfo.Println("access_token invalid...closing connection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
claims := result.Claims.(jwt.MapClaims)
|
||||||
|
domains, ok := claims["domains"].([]interface{})
|
||||||
|
|
||||||
|
var upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("WebSocket upgrade failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("before connection table")
|
||||||
|
|
||||||
|
//newConnection := connection.NewConnection(connectionTable, conn, r.RemoteAddr, domains)
|
||||||
|
|
||||||
|
connectionTrack := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||||
|
newRegistration := NewRegistration(conn, r.RemoteAddr, domains, connectionTrack)
|
||||||
|
connectionTable.Register() <- newRegistration
|
||||||
|
ok = <-newRegistration.CommCh()
|
||||||
|
if !ok {
|
||||||
|
loginfo.Println("connection registration failed ", newRegistration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("connection registration accepted ", newRegistration)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
s := &http.Server{
|
||||||
|
Addr: ":80",
|
||||||
|
Handler: router,
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.Serve(oneConn)
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("Serve error: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ListenerRegistrationStatus - post registration status
|
||||||
|
type ListenerRegistrationStatus int
|
||||||
|
|
||||||
|
const (
|
||||||
|
listenerAdded ListenerRegistrationStatus = iota
|
||||||
|
listenerExists
|
||||||
|
listenerFault
|
||||||
|
)
|
||||||
|
|
||||||
|
//ListenerRegistration -- A connection registration structure used to bring up a connection
|
||||||
|
//connection table will then handle additing and sdtarting up the various readers
|
||||||
|
//else error.
|
||||||
|
type ListenerRegistration struct {
|
||||||
|
// The websocket connection.
|
||||||
|
listener *net.Listener
|
||||||
|
|
||||||
|
// The listener port
|
||||||
|
port int
|
||||||
|
|
||||||
|
// The status
|
||||||
|
status ListenerRegistrationStatus
|
||||||
|
|
||||||
|
// The error
|
||||||
|
err error
|
||||||
|
|
||||||
|
// communications channel between go routines
|
||||||
|
commCh chan *ListenerRegistration
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewListenerRegistration -- Constructor
|
||||||
|
func NewListenerRegistration(port int) (p *ListenerRegistration) {
|
||||||
|
p = new(ListenerRegistration)
|
||||||
|
p.port = port
|
||||||
|
p.commCh = make(chan *ListenerRegistration)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//GenericListeners -
|
||||||
|
type GenericListeners struct {
|
||||||
|
listeners map[*net.Listener]int
|
||||||
|
ctx context.Context
|
||||||
|
connnectionTable *Table
|
||||||
|
connectionTracking *Tracking
|
||||||
|
secretKey string
|
||||||
|
certbundle tls.Certificate
|
||||||
|
register chan *ListenerRegistration
|
||||||
|
genericListeners *GenericListeners
|
||||||
|
wssHostName string
|
||||||
|
adminHostName string
|
||||||
|
cancelCheck int
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewGenerListeners --
|
||||||
|
func NewGenerListeners(ctx context.Context, connectionTable *Table, connectionTrack *Tracking, secretKey string, certbundle tls.Certificate, wssHostName string, adminHostName string, cancelCheck int) (p *GenericListeners) {
|
||||||
|
p = new(GenericListeners)
|
||||||
|
p.listeners = make(map[*net.Listener]int)
|
||||||
|
p.ctx = ctx
|
||||||
|
p.connnectionTable = connectionTable
|
||||||
|
p.connectionTracking = connectionTrack
|
||||||
|
p.secretKey = secretKey
|
||||||
|
p.certbundle = certbundle
|
||||||
|
p.register = make(chan *ListenerRegistration)
|
||||||
|
p.wssHostName = wssHostName
|
||||||
|
p.adminHostName = adminHostName
|
||||||
|
p.cancelCheck = cancelCheck
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Run -- Execute
|
||||||
|
// - execute the GenericLister
|
||||||
|
// - pass initial port, we'll announce that
|
||||||
|
func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
|
||||||
|
loginfo.Println("ConnectionTable starting")
|
||||||
|
|
||||||
|
config := &tls.Config{Certificates: []tls.Certificate{gl.certbundle}}
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, ctxSecretKey, gl.secretKey)
|
||||||
|
ctx = context.WithValue(ctx, ctxConnectionTable, gl.connnectionTable)
|
||||||
|
|
||||||
|
loginfo.Println(gl.connectionTracking)
|
||||||
|
|
||||||
|
ctx = context.WithValue(ctx, ctxConnectionTrack, gl.connectionTracking)
|
||||||
|
ctx = context.WithValue(ctx, ctxConfig, config)
|
||||||
|
ctx = context.WithValue(ctx, ctxListenerRegistration, gl.register)
|
||||||
|
ctx = context.WithValue(ctx, ctxWssHostName, gl.wssHostName)
|
||||||
|
ctx = context.WithValue(ctx, ctxAdminHostName, gl.adminHostName)
|
||||||
|
ctx = context.WithValue(ctx, ctxCancelCheck, gl.cancelCheck)
|
||||||
|
|
||||||
|
go func(ctx context.Context) {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
|
||||||
|
case <-ctx.Done():
|
||||||
|
loginfo.Println("Cancel signal hit")
|
||||||
|
return
|
||||||
|
|
||||||
|
case registration := <-gl.register:
|
||||||
|
loginfo.Println("register fired", registration.port)
|
||||||
|
|
||||||
|
// check to see if port is already running
|
||||||
|
for listener := range gl.listeners {
|
||||||
|
if gl.listeners[listener] == registration.port {
|
||||||
|
loginfo.Println("listener already running", registration.port)
|
||||||
|
registration.status = listenerExists
|
||||||
|
registration.commCh <- registration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loginfo.Println("listener starting up ", registration.port)
|
||||||
|
loginfo.Println(ctx.Value(ctxConnectionTrack).(*Tracking))
|
||||||
|
go GenericListenAndServe(ctx, registration)
|
||||||
|
|
||||||
|
status := <-registration.commCh
|
||||||
|
if status.status == listenerAdded {
|
||||||
|
gl.listeners[status.listener] = status.port
|
||||||
|
} else if status.status == listenerFault {
|
||||||
|
loginfo.Println("Unable to create a new listerer", registration.port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}(ctx)
|
||||||
|
|
||||||
|
newListener := NewListenerRegistration(initialPort)
|
||||||
|
gl.register <- newListener
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type oneConnListener struct {
|
||||||
|
conn net.Conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *oneConnListener) Accept() (c net.Conn, err error) {
|
||||||
|
c = l.conn
|
||||||
|
|
||||||
|
if c == nil {
|
||||||
|
err = io.EOF
|
||||||
|
loginfo.Println("Accept")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = nil
|
||||||
|
l.conn = nil
|
||||||
|
loginfo.Println("Accept", c.LocalAddr().String(), c.RemoteAddr().String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *oneConnListener) Close() error {
|
||||||
|
loginfo.Println("close")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *oneConnListener) Addr() net.Addr {
|
||||||
|
loginfo.Println("addr")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
//SendTrack -- Used as a channel communication to id domain asssociated to domain for outbound WSS
|
||||||
|
type SendTrack struct {
|
||||||
|
data []byte
|
||||||
|
domain string
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewSendTrack -- Constructor
|
||||||
|
func NewSendTrack(data []byte, domain string) (p *SendTrack) {
|
||||||
|
p = new(SendTrack)
|
||||||
|
p.data = data
|
||||||
|
p.domain = domain
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//ServerAPI -- Structure to support the server API
|
||||||
|
type ServerAPI struct {
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
Domains []*DomainAPI `json:"domains"`
|
||||||
|
Duration float64 `json:"duration"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerAPI - Constructor
|
||||||
|
func NewServerAPI(c *Connection) (s *ServerAPI) {
|
||||||
|
s = new(ServerAPI)
|
||||||
|
s.ServerName = fmt.Sprintf("%p", c)
|
||||||
|
s.Domains = make([]*DomainAPI, 0)
|
||||||
|
s.Duration = time.Since(c.ConnectTime()).Seconds()
|
||||||
|
s.BytesIn = c.BytesIn()
|
||||||
|
s.BytesOut = c.BytesOut()
|
||||||
|
|
||||||
|
for d := range c.DomainTrack {
|
||||||
|
dt := c.DomainTrack[d]
|
||||||
|
domainAPI := NewDomainAPI(dt.DomainName, dt.BytesIn(), dt.BytesOut())
|
||||||
|
s.Domains = append(s.Domains, domainAPI)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ServerAPIContainer -- Holder for all the Servers
|
||||||
|
type ServerAPIContainer struct {
|
||||||
|
Servers []*ServerAPI `json:"servers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerAPIContainer -- Constructor
|
||||||
|
func NewServerAPIContainer() (p *ServerAPIContainer) {
|
||||||
|
p = new(ServerAPIContainer)
|
||||||
|
p.Servers = make([]*ServerAPI, 0)
|
||||||
|
return p
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: genericlistener: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: genericlistener:", logFlags)
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
func getHello(b []byte) (string, error) {
|
||||||
|
rest := b[5:]
|
||||||
|
current := 0
|
||||||
|
handshakeType := rest[0]
|
||||||
|
current++
|
||||||
|
if handshakeType != 0x1 {
|
||||||
|
return "", errors.New("Not a ClientHello")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip over another length
|
||||||
|
current += 3
|
||||||
|
// Skip over protocolversion
|
||||||
|
current += 2
|
||||||
|
// Skip over random number
|
||||||
|
current += 4 + 28
|
||||||
|
// Skip over session ID
|
||||||
|
sessionIDLength := int(rest[current])
|
||||||
|
current++
|
||||||
|
current += sessionIDLength
|
||||||
|
|
||||||
|
cipherSuiteLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
current += cipherSuiteLength
|
||||||
|
|
||||||
|
compressionMethodLength := int(rest[current])
|
||||||
|
current++
|
||||||
|
current += compressionMethodLength
|
||||||
|
|
||||||
|
if current > len(rest) {
|
||||||
|
return "", errors.New("no extensions")
|
||||||
|
}
|
||||||
|
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
hostname := ""
|
||||||
|
for current < len(rest) && hostname == "" {
|
||||||
|
extensionType := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
extensionDataLength := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
if extensionType == 0 {
|
||||||
|
|
||||||
|
// Skip over number of names as we're assuming there's just one
|
||||||
|
current += 2
|
||||||
|
|
||||||
|
nameType := rest[current]
|
||||||
|
current++
|
||||||
|
if nameType != 0 {
|
||||||
|
return "", errors.New("Not a hostname")
|
||||||
|
}
|
||||||
|
nameLen := (int(rest[current]) << 8) + int(rest[current+1])
|
||||||
|
current += 2
|
||||||
|
hostname = string(rest[current : current+nameLen])
|
||||||
|
}
|
||||||
|
|
||||||
|
current += extensionDataLength
|
||||||
|
}
|
||||||
|
if hostname == "" {
|
||||||
|
return "", errors.New("No hostname")
|
||||||
|
}
|
||||||
|
return hostname, nil
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,146 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
packerV1 byte = 255 - 1
|
||||||
|
packerV2 byte = 255 - 2
|
||||||
|
)
|
||||||
|
|
||||||
|
//Packer -- contains both header and data
|
||||||
|
type Packer struct {
|
||||||
|
Header *packerHeader
|
||||||
|
Data *packerData
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewPacker -- Structre
|
||||||
|
func NewPacker() (p *Packer) {
|
||||||
|
p = new(Packer)
|
||||||
|
p.Header = newPackerHeader()
|
||||||
|
p.Data = newPackerData()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ReadMessage -
|
||||||
|
func ReadMessage(b []byte) (p *Packer, err error) {
|
||||||
|
fmt.Println("ReadMessage")
|
||||||
|
var pos int
|
||||||
|
|
||||||
|
err = nil
|
||||||
|
// detect protocol in use
|
||||||
|
if b[0] == packerV1 {
|
||||||
|
p = NewPacker()
|
||||||
|
|
||||||
|
// Handle Header Length
|
||||||
|
pos = pos + 1
|
||||||
|
p.Header.HeaderLen = b[pos]
|
||||||
|
|
||||||
|
//handle address family
|
||||||
|
pos = pos + 1
|
||||||
|
end := bytes.IndexAny(b[pos:], ",")
|
||||||
|
if end == -1 {
|
||||||
|
err = fmt.Errorf("missing , while parsing address family")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
bAddrFamily := b[pos : pos+end]
|
||||||
|
if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv4]) {
|
||||||
|
p.Header.family = FamilyIPv4
|
||||||
|
} else if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv6]) {
|
||||||
|
p.Header.family = FamilyIPv6
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Address family not supported %d", bAddrFamily)
|
||||||
|
}
|
||||||
|
|
||||||
|
//handle address
|
||||||
|
pos = pos + end + 1
|
||||||
|
end = bytes.IndexAny(b[pos:], ",")
|
||||||
|
if end == -1 {
|
||||||
|
err = fmt.Errorf("missing , while parsing address")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
p.Header.address = net.ParseIP(string(b[pos : pos+end]))
|
||||||
|
|
||||||
|
//handle import
|
||||||
|
pos = pos + end + 1
|
||||||
|
end = bytes.IndexAny(b[pos:], ",")
|
||||||
|
if end == -1 {
|
||||||
|
err = fmt.Errorf("missing , while parsing address")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Header.Port, err = strconv.Atoi(string(b[pos : pos+end]))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error converting port %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//handle data length
|
||||||
|
pos = pos + end + 1
|
||||||
|
end = bytes.IndexAny(b[pos:], ",")
|
||||||
|
if end == -1 {
|
||||||
|
err = fmt.Errorf("missing , while parsing address")
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Data.DataLen, err = strconv.Atoi(string(b[pos : pos+end]))
|
||||||
|
if err != nil {
|
||||||
|
err = fmt.Errorf("error converting data length %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
//handle Service
|
||||||
|
pos = pos + end + 1
|
||||||
|
end = pos + int(p.Header.HeaderLen)
|
||||||
|
p.Header.Service = string(b[pos : p.Header.HeaderLen+2])
|
||||||
|
|
||||||
|
//handle payload
|
||||||
|
pos = int(p.Header.HeaderLen + 2)
|
||||||
|
p.Data.AppendBytes(b[pos:])
|
||||||
|
|
||||||
|
} else {
|
||||||
|
err = fmt.Errorf("Version %d not supported", b[0:0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//PackV1 -- Outputs version 1 of packer
|
||||||
|
func (p *Packer) PackV1() (b bytes.Buffer) {
|
||||||
|
version := packerV1
|
||||||
|
|
||||||
|
var headerBuf bytes.Buffer
|
||||||
|
headerBuf.WriteString(p.Header.FamilyText())
|
||||||
|
headerBuf.WriteString(",")
|
||||||
|
headerBuf.Write([]byte(p.Header.Address().String()))
|
||||||
|
headerBuf.WriteString(",")
|
||||||
|
headerBuf.WriteString(fmt.Sprintf("%d", p.Header.Port))
|
||||||
|
headerBuf.WriteString(",")
|
||||||
|
headerBuf.WriteString(fmt.Sprintf("%d", p.Data.buffer.Len()))
|
||||||
|
headerBuf.WriteString(",")
|
||||||
|
headerBuf.WriteString(p.Header.Service)
|
||||||
|
|
||||||
|
var metaBuf bytes.Buffer
|
||||||
|
metaBuf.WriteByte(version)
|
||||||
|
metaBuf.WriteByte(byte(headerBuf.Len()))
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write(metaBuf.Bytes())
|
||||||
|
buf.Write(headerBuf.Bytes())
|
||||||
|
buf.Write(p.Data.buffer.Bytes())
|
||||||
|
|
||||||
|
//fmt.Println("header: ", headerBuf.String())
|
||||||
|
//fmt.Println("meta: ", metaBuf)
|
||||||
|
//fmt.Println("Data: ", p.Data.buffer)
|
||||||
|
//fmt.Println("Buffer: ", buf.Bytes())
|
||||||
|
//fmt.Println("Buffer: ", hex.Dump(buf.Bytes()))
|
||||||
|
//fmt.Printf("Buffer %s", buf.Bytes())
|
||||||
|
|
||||||
|
b = buf
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import "bytes"
|
||||||
|
|
||||||
|
//packerData -- Contains packer data
|
||||||
|
type packerData struct {
|
||||||
|
buffer *bytes.Buffer
|
||||||
|
DataLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPackerData() (p *packerData) {
|
||||||
|
p = new(packerData)
|
||||||
|
p.buffer = new(bytes.Buffer)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerData) AppendString(dataString string) (n int, err error) {
|
||||||
|
n, err = p.buffer.WriteString(dataString)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerData) AppendBytes(dataBytes []byte) (n int, err error) {
|
||||||
|
n, err = p.buffer.Write(dataBytes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Data --
|
||||||
|
func (p *packerData) Data() (b []byte) {
|
||||||
|
b = p.buffer.Bytes()
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,81 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type addressFamily int
|
||||||
|
|
||||||
|
// packerHeader structure to hold our header information.
|
||||||
|
type packerHeader struct {
|
||||||
|
family addressFamily
|
||||||
|
address net.IP
|
||||||
|
Port int
|
||||||
|
Service string
|
||||||
|
HeaderLen byte
|
||||||
|
}
|
||||||
|
|
||||||
|
//Family -- ENUM for Address Family
|
||||||
|
const (
|
||||||
|
FamilyIPv4 addressFamily = iota
|
||||||
|
FamilyIPv6
|
||||||
|
)
|
||||||
|
|
||||||
|
var addressFamilyText = [...]string{
|
||||||
|
"IPv4",
|
||||||
|
"IPv6",
|
||||||
|
}
|
||||||
|
|
||||||
|
func newPackerHeader() (p *packerHeader) {
|
||||||
|
p = new(packerHeader)
|
||||||
|
p.SetAddress("127.0.0.1")
|
||||||
|
p.Port = 65535
|
||||||
|
p.Service = "na"
|
||||||
|
p.HeaderLen = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetAddress -- Set Address. which sets address family automatically
|
||||||
|
func (p *packerHeader) SetAddress(addr string) {
|
||||||
|
p.address = net.ParseIP(addr)
|
||||||
|
err := p.address.To4()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
p.family = FamilyIPv4
|
||||||
|
} else {
|
||||||
|
err := p.address.To16()
|
||||||
|
if err != nil {
|
||||||
|
p.family = FamilyIPv6
|
||||||
|
} else {
|
||||||
|
panic(fmt.Sprintf("setAddress does not support %s", addr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerHeader) AddressBytes() (b []byte) {
|
||||||
|
b = make([]byte, 16)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case p.address.To4() != nil:
|
||||||
|
b = make([]byte, 4)
|
||||||
|
for pos := range b {
|
||||||
|
b[pos] = p.address[pos+12]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerHeader) Address() (address net.IP) {
|
||||||
|
address = p.address
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerHeader) Family() (family addressFamily) {
|
||||||
|
family = p.family
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *packerHeader) FamilyText() (familyText string) {
|
||||||
|
familyText = addressFamilyText[p.family]
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: packer: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: packer:", logFlags)
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package xlate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: xlate: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: xlate:", logFlags)
|
||||||
|
}
|
Loading…
Reference in New Issue