Added support for a Listener Manager
- I anticipated having to bring up new listeners as tunnel-clients connect allowing different port use for a single domain - completed the code, however did not see any port information coming back from WSS client. - opened an issue with AJ. - all listeners are now dynamically generated. The run method takes an initial port, once functioning, the runner send down the channel a registration for the initial port.
This commit is contained in:
parent
b122a6091b
commit
493477179b
266
README.md
266
README.md
|
@ -1,190 +1,134 @@
|
||||||
# RVPN Server
|
# RVPN Server
|
||||||
|
|
||||||
Admin Interface
|
## restructured-http
|
||||||
---------------
|
|
||||||
Developing the API calls and buliding the structure behind it.
|
|
||||||
|
|
||||||
- build get mux router package `go get "github.com/gorilla/mux"`
|
- connection handling has been totally rewritten.
|
||||||
- run `go-rvpn-server`
|
- on a specific port RVPN can determine the following:
|
||||||
- execute stunnel (as below)
|
- if a connection is encrypted or not encrypted
|
||||||
- browse to <https://127.0.0.2:8000/api/servers>
|
- 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)
|
||||||
|
|
||||||
```json
|
To accomplish the above RVPN uses raw TCP sockets, buffered readers, and a custom Listener. This allows protocol detection (multiple services on one port)
|
||||||
{"Servers":[{"ServerName":"0xc420019500","Domains":[{"Domain":"127.0.0.1","BytesIn":0,"BytesOut":2607},{"Domain":"hfc.daplie.me","BytesIn":0,"BytesOut":0},{"Domain":"test.hfc.daplie.me","BytesIn":0,"BytesOut":0}],"Duration":372.34454292,"BytesIn":65,"BytesOut":2607}]}
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
The above is telemetry from the RVPN server. (now supports domain byte tracking)
|
A new listener is created by sending a NewListenerRegistration on the channel.
|
||||||
|
|
||||||
Work is continuing, please review make sure it is what you are looking for.
|
```go
|
||||||
|
|
||||||
We will need to deal with server name, I am placing the point address for now, not sure how to handle the name.
|
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
|
||||||
Traffic Testing
|
|
||||||
---------------
|
|
||||||
* run go-rvpn-server
|
|
||||||
* execute stunnel.js
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
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:3502 --secret abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
* Browse to http://127.0.0.1:8080
|
hcamacho@Hanks-MBP:go-rvpn-server $ go get
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO: connection: 2017/02/13 20:46:07.815340 connection.go:95: &{0xc420017350 0xc420181540 0xc42014acc0 127.0.0.1:49412 91 3390 0xc42014ad20 [hfc.daplie.me test.hfc.daplie.me 127.0.0.1]}
|
|
||||||
```
|
### Execute RVPN
|
||||||
91, and 3390 are bytes in and bytes out.
|
|
||||||
|
```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
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
INFO: external: 2017/02/13 20:46:07.814294 listener_webrequest.go:75: connState
|
|
||||||
&{{0xc42015e690}} 127.0.0.1:8080 127.0.0.1:49416
|
### Connect Tunnel client
|
||||||
active
|
|
||||||
INFO: external: 2017/02/13 20:46:07.814327 listener_webrequest.go:24: handlerWebRequestExternal
|
```bash
|
||||||
INFO: external: 2017/02/13 20:46:07.814358 listener_webrequest.go:30: "GET /favicon.ico HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nAccept: image/webp,image/*,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, sdch, br\r\nAccept-Language: en-US,en;q=0.8\r\nCache-Control: no-cache\r\nConnection: keep-alive\r\nPragma: no-cache\r\nReferer: http://127.0.0.1:8080/\r\nUser-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\r\n\r\n"
|
|
||||||
INFO: external: 2017/02/13 20:46:07.814372 listener_webrequest.go:50: &{0xc420017350 0xc420181540 0xc42014acc0 127.0.0.1:49412 78 2963 0xc42014ad20 [hfc.daplie.me test.hfc.daplie.me 127.0.0.1]} 127.0.0.1 49416
|
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
|
||||||
header: ipv4,127.0.0.2,49416,398,na
|
[local proxy] http://hfc.daplie.me:8443
|
||||||
meta: {[254 27] 0 [0 0 0 0] [254 27 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 0}
|
[local proxy] http://test.hfc.daplie.me:3001
|
||||||
Data: GET /favicon.ico HTTP/1.1
|
[local proxy] http://127.0.0.1:8080
|
||||||
Host: 127.0.0.1:8080
|
[connect] 'wss://localhost.daplie.me:8443'
|
||||||
Accept: image/webp,image/*,*/*;q=0.8
|
[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-Encoding: gzip, deflate, sdch, br
|
||||||
Accept-Language: en-US,en;q=0.8
|
Accept-Language: en-US,en;q=0.8
|
||||||
Cache-Control: no-cache
|
|
||||||
Connection: keep-alive
|
|
||||||
Pragma: no-cache
|
[exit] loop closed 0
|
||||||
Referer: http://127.0.0.1:8080/
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
[error] closing ',undefined,undefined' because 'unsupported service 'undefined'' (-1 clients)
|
|
||||||
[_onLocalClose]
|
|
||||||
<Buffer fe 0a 2c 2c 2c 31 2c 65 72 72 6f 72 00>
|
|
||||||
not v1 (or data is corrupt)
|
|
||||||
[error] closing ',undefined,undefined' because 'unsupported service 'undefined'' (-1 clients)
|
|
||||||
[_onLocalClose]
|
|
||||||
<Buffer fe 0a 2c 2c 2c 31 2c 65 72 72 6f 72 00>
|
|
||||||
not v1 (or data is corrupt)
|
|
||||||
```
|
|
||||||
Above is an attempt to connection...
|
|
||||||
|
|
||||||
```
|
Looks like it aborts for some reaon. I have this problem on on a new installation as well.
|
||||||
INFO: external: 2017/02/13 20:50:45.883267 listener_webrequest.go:75: connState
|
|
||||||
&{{0xc42015e690}} 127.0.0.1:8080 127.0.0.1:49416
|
|
||||||
active
|
|
||||||
INFO: external: 2017/02/13 20:50:45.883298 listener_webrequest.go:24: handlerWebRequestExternal
|
|
||||||
INFO: external: 2017/02/13 20:50:45.883331 listener_webrequest.go:30: "GET /favicon.ico HTTP/1.1\r\nHost: 127.0.0.1:8080\r\nAccept: image/webp,image/*,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, sdch, br\r\nAccept-Language: en-US,en;q=0.8\r\nConnection: keep-alive\r\nReferer: http://127.0.0.1:8080/\r\nUser-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\r\n\r\n"
|
|
||||||
INFO: external: 2017/02/13 20:50:45.883338 listener_webrequest.go:46: unable to match 127.0.0.1 to an existing connection
|
|
||||||
INFO: external: 2017/02/13 20:50:45.883362 listener_webrequest.go:75: connState
|
|
||||||
&{{0xc42015e690}} 127.0.0.1:8080 127.0.0.1:49416
|
|
||||||
```
|
|
||||||
The above is a connection to a domain that was not registered.
|
|
||||||
|
|
||||||
|
|
||||||
Branch: restructured
|
|
||||||
--------------------
|
|
||||||
* restructure into various packages, removing items from package "main"
|
|
||||||
* used caddy source as a guide
|
|
||||||
* weird debugging issue introduced, will not halt, break point unverified.
|
|
||||||
* although a test project debugs just fin
|
|
||||||
|
|
||||||
listener_client — the WSS client
|
|
||||||
--------------------------------
|
|
||||||
- removed support for anything admin
|
|
||||||
- injected the domains from the claim
|
|
||||||
- domains are now included as initialDomains
|
|
||||||
- registration performans as normal but includes adding the domains to a map of domains, and a collection of domains on the connection.
|
|
||||||
- the system now supports look up fast in either direction, not sure if it will be needed.
|
|
||||||
- reads a chan during registration before allowing traffic, making sure all is well.
|
|
||||||
- registration returns a true on the channel if all is well. If it is not, false. Likely will add some text to pass back.
|
|
||||||
|
|
||||||
Connection
|
|
||||||
----------
|
|
||||||
- added support for boolean channel
|
|
||||||
- support for initial domains in a slice, these are brought back from the JWT as a interface and then are type asserted into the map
|
|
||||||
- removed all the old timer sender dwell stuff as a POC for traffic counts.
|
|
||||||
|
|
||||||
ConnectionTable
|
|
||||||
---------------
|
|
||||||
- added support for domain announcement after the WSS is connection. Not sure if we will need these. They have not been implemented.
|
|
||||||
- I assume all domains are registered with JWT unless I hear differently which would require a new WSS session
|
|
||||||
- expanded NewTable constructor
|
|
||||||
- populating domains into the domain map, and into the connection slice.
|
|
||||||
- added support for removing domains when a connection is removed.
|
|
||||||
|
|
||||||
Packer
|
|
||||||
------
|
|
||||||
- added support for a PackerHeader type and PackerData type
|
|
||||||
- these are connected in a Packer type
|
|
||||||
- support for calculated address family based on ip address property
|
|
||||||
- service field is set to "na"
|
|
||||||
|
|
||||||
Logging
|
|
||||||
-------
|
|
||||||
- unified package logging based on a package init. Will likely need to remove this
|
|
||||||
|
|
||||||
Tests
|
|
||||||
-----
|
|
||||||
- stared to structure project for tests.
|
|
||||||
|
|
||||||
|
|
||||||
Build Instructions
|
-=-=-=-=-=-=
|
||||||
------------------
|
|
||||||
|
|
||||||
Create a subinterface:
|
|
||||||
```bash
|
|
||||||
sudo ifconfig lo0 alias 127.0.0.2 up
|
|
||||||
```
|
|
||||||
The above creates an alias the code is able to bind against for admin. Admin is still in progress.
|
|
||||||
|
|
||||||
Get the dependencies
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/gorilla/websocket
|
|
||||||
go get github.com/dgrijalva/jwt-go
|
|
||||||
|
|
||||||
git clone git@git.daplie.com:Daplie/localhost.daplie.me-certificates.git
|
|
||||||
ln -s localhost.daplie.me-certificates/certs/localhost.daplie.me certs
|
|
||||||
```
|
|
||||||
|
|
||||||
Run the VPN
|
|
||||||
```bash
|
|
||||||
go build && ./go-rvpn-server
|
|
||||||
```
|
|
||||||
|
|
||||||
In another terminal execute the client
|
|
||||||
``` bash
|
|
||||||
bin/stunnel.js --locals http:hfc.daplie.me:3000,http://test.hfc.daplie.me:3001 --stunneld wss://localhost.daplie.me:8000 --secret abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
A good authentication
|
|
||||||
```
|
|
||||||
INFO: 2017/02/02 21:22:22 vpn-server.go:88: startup
|
|
||||||
INFO: 2017/02/02 21:22:22 vpn-server.go:90: :8000
|
|
||||||
INFO: 2017/02/02 21:22:22 vpn-server.go:73: starting Listener
|
|
||||||
INFO: 2017/02/02 21:22:22 connection_table.go:19: ConnectionTable starting
|
|
||||||
INFO: 2017/02/02 21:22:24 connection.go:113: websocket opening 127.0.0.1:55469
|
|
||||||
INFO: 2017/02/02 21:22:24 connection.go:127: access_token valid
|
|
||||||
INFO: 2017/02/02 21:22:24 connection.go:130: processing domains [hfc.daplie.me test.hfc.daplie.me]
|
|
||||||
```
|
|
||||||
|
|
||||||
Change the key on the tunnel client to test a valid secret
|
|
||||||
``` bash
|
|
||||||
INFO: 2017/02/02 21:24:13 vpn-server.go:88: startup
|
|
||||||
INFO: 2017/02/02 21:24:13 vpn-server.go:90: :8000
|
|
||||||
INFO: 2017/02/02 21:24:13 vpn-server.go:73: starting Listener
|
|
||||||
INFO: 2017/02/02 21:24:13 connection_table.go:19: ConnectionTable starting
|
|
||||||
INFO: 2017/02/02 21:24:15 connection.go:113: websocket opening 127.0.0.1:55487
|
|
||||||
INFO: 2017/02/02 21:24:15 connection.go:123: access_token invalid...closing connection
|
|
||||||
```
|
|
||||||
|
|
||||||
Connection to the External Interface.
|
|
||||||
http://127.0.0.1:8080
|
|
||||||
|
|
||||||
The request is dumped to stdio. This is in preparation of taking that request and sending it back to the designated WSS connection
|
|
||||||
The system needs to track the response coming back, decouple it, and place it back on the wire in the form of a response stream. Since
|
|
||||||
|
|
||||||
A Poor Man's Reverse VPN written in Go
|
A Poor Man's Reverse VPN written in Go
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,12 @@ func NewWedgeConnSize(c net.Conn, size int) (p *WedgeConn) {
|
||||||
//Peek - Get a number of bytes outof the buffer, but allow the buffer to be replayed once read
|
//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) {
|
func (w *WedgeConn) Peek(n int) ([]byte, error) {
|
||||||
loginfo.Println("Peek")
|
loginfo.Println("Peek")
|
||||||
loginfo.Println("buffered=", w.reader.Buffered())
|
|
||||||
return w.reader.Peek(n)
|
return w.reader.Peek(n)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Read -- A normal reader.
|
//Read -- A normal reader.
|
||||||
func (w *WedgeConn) Read(p []byte) (int, error) {
|
func (w *WedgeConn) Read(p []byte) (int, error) {
|
||||||
loginfo.Println("read", w.Conn)
|
loginfo.Println("Read")
|
||||||
cnt, err := w.reader.Read(p)
|
cnt, err := w.reader.Read(p)
|
||||||
return cnt, err
|
return cnt, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
jwt "github.com/dgrijalva/jwt-go"
|
||||||
|
@ -17,7 +21,9 @@ import (
|
||||||
|
|
||||||
"bufio"
|
"bufio"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/admin"
|
||||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/connection"
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/connection"
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
type contextKey string
|
type contextKey string
|
||||||
|
@ -26,6 +32,8 @@ const (
|
||||||
ctxSecretKey contextKey = "secretKey"
|
ctxSecretKey contextKey = "secretKey"
|
||||||
ctxConnectionTable contextKey = "connectionTable"
|
ctxConnectionTable contextKey = "connectionTable"
|
||||||
ctxConfig contextKey = "config"
|
ctxConfig contextKey = "config"
|
||||||
|
ctxDeadTime contextKey = "deadtime"
|
||||||
|
ctxListenerRegistration contextKey = "listenerRegistration"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -42,14 +50,13 @@ const (
|
||||||
// - leaverage the wedgeConn to peek into the buffer.
|
// - leaverage the wedgeConn to peek into the buffer.
|
||||||
// - if TLS, consume connection with TLS certbundle, pass to request identifier
|
// - if TLS, consume connection with TLS certbundle, pass to request identifier
|
||||||
// - else, just pass to the request identififer
|
// - else, just pass to the request identififer
|
||||||
func GenericListenAndServe(ctx context.Context, connectionTable *connection.Table, secretKey string, serverBinding string, certbundle tls.Certificate, deadTime int) {
|
func GenericListenAndServe(ctx context.Context, listenerRegistration *ListenerRegistration) {
|
||||||
config := &tls.Config{Certificates: []tls.Certificate{certbundle}}
|
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, ctxSecretKey, secretKey)
|
loginfo.Println(":" + string(listenerRegistration.port))
|
||||||
ctx = context.WithValue(ctx, ctxConnectionTable, connectionTable)
|
|
||||||
ctx = context.WithValue(ctx, ctxConfig, config)
|
listenAddr, err := net.ResolveTCPAddr("tcp", ":"+strconv.Itoa(listenerRegistration.port))
|
||||||
|
deadTime := ctx.Value(ctxDeadTime).(int)
|
||||||
|
|
||||||
listenAddr, err := net.ResolveTCPAddr("tcp", serverBinding)
|
|
||||||
if nil != err {
|
if nil != err {
|
||||||
loginfo.Println(err)
|
loginfo.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -58,9 +65,15 @@ func GenericListenAndServe(ctx context.Context, connectionTable *connection.Tabl
|
||||||
ln, err := net.ListenTCP("tcp", listenAddr)
|
ln, err := net.ListenTCP("tcp", listenAddr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println("unable to bind", err)
|
loginfo.Println("unable to bind", err)
|
||||||
|
listenerRegistration.status = listenerFault
|
||||||
|
listenerRegistration.err = err
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
listenerRegistration.status = listenerAdded
|
||||||
|
listenerRegistration.commCh <- listenerRegistration
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
|
@ -188,9 +201,7 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
loginfo.Println(r)
|
// do we have a valid wss_client?
|
||||||
|
|
||||||
//now we have a request. Check to see if it is one of our own
|
|
||||||
secretKey := ctx.Value(ctxSecretKey).(string)
|
secretKey := ctx.Value(ctxSecretKey).(string)
|
||||||
tokenString := r.URL.Query().Get("access_token")
|
tokenString := r.URL.Query().Get("access_token")
|
||||||
result, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
result, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
@ -201,8 +212,19 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
loginfo.Println("Valid WSS dected...sending to handler")
|
loginfo.Println("Valid WSS dected...sending to handler")
|
||||||
oneConn := &oneConnListener{wConn}
|
oneConn := &oneConnListener{wConn}
|
||||||
handleWssClient(ctx, oneConn)
|
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 {
|
} else {
|
||||||
loginfo.Println("not a valid WSS connection")
|
loginfo.Println("default connection")
|
||||||
|
handleExternalHTTPRequest(ctx, wConn)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -210,6 +232,132 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
loginfo.Println(hex.Dump(peek[0:]))
|
loginfo.Println(hex.Dump(peek[0:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//handleExternalHTTPRequest -
|
||||||
|
// - get a wConn and start processing requests
|
||||||
|
func handleExternalHTTPRequest(ctx context.Context, conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
connectionTable := ctx.Value(ctxConnectionTable).(*connection.Table)
|
||||||
|
|
||||||
|
var buffer [512]byte
|
||||||
|
|
||||||
|
for {
|
||||||
|
cnt, err := conn.Read(buffer[0:])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("conn ", conn)
|
||||||
|
loginfo.Println("byte read", cnt)
|
||||||
|
|
||||||
|
readBuffer := bytes.NewBuffer(buffer[0:cnt])
|
||||||
|
reader := bufio.NewReader(readBuffer)
|
||||||
|
r, err := http.ReadRequest(reader)
|
||||||
|
|
||||||
|
loginfo.Println(r)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
loginfo.Println("error parsing request")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := r.Host
|
||||||
|
loginfo.Println("Host: ", hostname)
|
||||||
|
|
||||||
|
if strings.Contains(hostname, ":") {
|
||||||
|
arr := strings.Split(hostname, ":")
|
||||||
|
hostname = arr[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
loginfo.Println("Remote: ", conn.RemoteAddr().String())
|
||||||
|
|
||||||
|
remoteSplit := strings.Split(conn.RemoteAddr().String(), ":")
|
||||||
|
rAddr := remoteSplit[0]
|
||||||
|
rPort := remoteSplit[1]
|
||||||
|
|
||||||
|
if conn, ok := connectionTable.ConnByDomain(hostname); !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)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
loginfo.Println("Domain Accepted")
|
||||||
|
loginfo.Println(conn, rAddr, rPort)
|
||||||
|
p := packer.NewPacker()
|
||||||
|
p.Header.SetAddress(rAddr)
|
||||||
|
p.Header.Port, err = strconv.Atoi(rPort)
|
||||||
|
p.Header.Port = 8080
|
||||||
|
p.Header.Service = "http"
|
||||||
|
p.Data.AppendBytes(buffer[0:cnt])
|
||||||
|
buf := p.PackV1()
|
||||||
|
|
||||||
|
sendTrack := connection.NewSendTrack(buf.Bytes(), hostname)
|
||||||
|
conn.SendCh() <- sendTrack
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//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).(*connection.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 := admin.NewServerAPIContainer()
|
||||||
|
|
||||||
|
for c := range connectionTable.Connections() {
|
||||||
|
serverAPI := admin.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 -
|
//handleWssClient -
|
||||||
// - expecting an existing oneConnListener with a qualified wss client connected.
|
// - expecting an existing oneConnListener with a qualified wss client connected.
|
||||||
// - auth will happen again since we were just peeking at the token.
|
// - auth will happen again since we were just peeking at the token.
|
||||||
|
@ -241,6 +389,8 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
claims := result.Claims.(jwt.MapClaims)
|
claims := result.Claims.(jwt.MapClaims)
|
||||||
domains, ok := claims["domains"].([]interface{})
|
domains, ok := claims["domains"].([]interface{})
|
||||||
|
|
||||||
|
loginfo.Println("domains", domains)
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
|
@ -283,5 +433,4 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
loginfo.Println("Cancel signal hit")
|
loginfo.Println("Cancel signal hit")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,123 @@
|
||||||
|
package genericlistener
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/connection"
|
||||||
|
)
|
||||||
|
|
||||||
|
//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 *connection.Table
|
||||||
|
secretKey string
|
||||||
|
certbundle tls.Certificate
|
||||||
|
deadTime int
|
||||||
|
register chan *ListenerRegistration
|
||||||
|
genericListeners *GenericListeners
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewGenerListeners --
|
||||||
|
func NewGenerListeners(ctx context.Context, connectionTable *connection.Table, secretKey string, certbundle tls.Certificate, deadTime int) (p *GenericListeners) {
|
||||||
|
p = new(GenericListeners)
|
||||||
|
p.listeners = make(map[*net.Listener]int)
|
||||||
|
p.ctx = ctx
|
||||||
|
p.connnectionTable = connectionTable
|
||||||
|
p.secretKey = secretKey
|
||||||
|
p.certbundle = certbundle
|
||||||
|
p.deadTime = deadTime
|
||||||
|
p.register = make(chan *ListenerRegistration)
|
||||||
|
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)
|
||||||
|
ctx = context.WithValue(ctx, ctxConfig, config)
|
||||||
|
ctx = context.WithValue(ctx, ctxDeadTime, gl.deadTime)
|
||||||
|
ctx = context.WithValue(ctx, ctxListenerRegistration, gl.register)
|
||||||
|
|
||||||
|
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.status = listenerExists
|
||||||
|
registration.commCh <- registration
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loginfo.Println("listener starting up ", registration.port)
|
||||||
|
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
|
||||||
|
}
|
|
@ -70,9 +70,12 @@ func Run() {
|
||||||
// - if tls, establish, protocol peek buffer, else decrypted
|
// - if tls, establish, protocol peek buffer, else decrypted
|
||||||
// - match protocol
|
// - match protocol
|
||||||
|
|
||||||
go genericlistener.GenericListenAndServe(ctx, connectionTable, secretKey, argGenericBinding, certbundle, argDeadTime)
|
genericListeners := genericlistener.NewGenerListeners(ctx, connectionTable, secretKey, certbundle, argDeadTime)
|
||||||
|
go genericListeners.Run(ctx, 8443)
|
||||||
|
|
||||||
time.Sleep(20 * time.Second)
|
//go genericlistener.GenericListenAndServe(ctx, connectionTable, secretKey, argGenericBinding, certbundle, argDeadTime)
|
||||||
|
|
||||||
|
time.Sleep(300 * time.Second)
|
||||||
cancelContext()
|
cancelContext()
|
||||||
time.Sleep(60 * time.Second)
|
time.Sleep(60 * time.Second)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue