Merge branch 'passing-traffic-sni' into 'master'

Passing traffic sni

See merge request !1
This commit is contained in:
Henry 2017-03-13 16:40:18 +00:00
commit 020df4301b
34 changed files with 2499 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
/go-rvpn-server
/m
/debug

236
README.md
View File

@ -1,5 +1,241 @@
# 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
Context

1
admin/html/index.html Normal file
View File

@ -0,0 +1 @@
Hank

1
admin/static/test.html Normal file
View File

@ -0,0 +1 @@
Test.html

4
d Executable file
View File

@ -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

16
go-rvpn-server.yaml Normal file
View File

@ -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

110
html/admin.html Executable file
View File

@ -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>

132
html/client.html Executable file
View File

@ -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>

100
main.go Normal file
View File

@ -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()
}

View File

@ -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

92
rvpn-docker/readme.md Normal file
View File

@ -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

View File

@ -0,0 +1,5 @@
FROM golang:1.7.5
LABEL maintainer "henry.f.camacho@gmail.com"
WORKDIR "/go-rvpn-server"

4
rvpn-docker/rvpn/run.sh Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
docker-compose run --entrypoint "/bin/bash" rvpn

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
// }

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

146
rvpn/packer/packer.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

15
rvpn/packer/setup.go Normal file
View File

@ -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)
}

17
rvpn/xlate/setup.go Normal file
View File

@ -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)
}

5
start.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
go build && ./go-rvpn-server