Merge branch 'load-balance' into 'master'
Load balance See merge request !2
This commit is contained in:
commit
e637277bb5
322
README.md
322
README.md
|
@ -1,16 +1,17 @@
|
||||||
# RVPN Server
|
# RVPN Server
|
||||||
|
|
||||||
## branch: passing-traffic
|
## branch: load-balancing
|
||||||
|
|
||||||
- code now passes traffic using just daplie tools
|
- code now passes traffic using just daplie tools
|
||||||
- this will require serve-https and node-tunnel-client to work
|
- this will require serve-https and node-tunnel-client to work
|
||||||
|
- the system supports round-robin load balancing
|
||||||
|
|
||||||
|
|
||||||
### Build RVPN
|
### Build RVPN
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hcamacho@Hanks-MBP:go-rvpn-server $ go get
|
go-rvpn-server $ go get
|
||||||
hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
go-rvpn-server $ go build
|
||||||
```
|
```
|
||||||
### Setup Some Entries
|
### Setup Some Entries
|
||||||
|
|
||||||
|
@ -20,20 +21,20 @@ hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
||||||
|
|
||||||
### Start Up Webserver
|
### Start Up Webserver
|
||||||
```bash
|
```bash
|
||||||
hcamacho@Hanks-MBP:tmp $ cd /tmp
|
tmp $ cd /tmp
|
||||||
hcamacho@Hanks-MBP:tmp $ vi index.html --- Place some index content
|
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
|
tmp $ serve-https -p 8080 -d /tmp --servername hfc.daplie.me --agree-tos --email henry.f.camacho@gmail.com
|
||||||
```
|
```
|
||||||
|
|
||||||
### Start Tunnel Client
|
### Start Tunnel Client
|
||||||
```bash
|
```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
|
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
|
### Execute RVPN
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
hcamacho@Hanks-MBP:go-rvpn-server $ ./go-rvpn-server
|
go-rvpn-server $ ./go-rvpn-server
|
||||||
INFO: packer: 2017/03/02 19:16:52.652109 run.go:47: startup
|
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.652777 manager.go:77: ConnectionTable starting
|
||||||
|
@ -50,242 +51,83 @@ INFO: genericlistener: 2017/03/02 19:16:52.652869 conn_tracking.go:25: Tracking
|
||||||
|
|
||||||
https://hfc.daplie.me:8443
|
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
|
### Test Load Balancing
|
||||||
- The traffic is getting back to the client.
|
|
||||||
|
In a new terminal
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
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
|
||||||
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.
|
### Check Results
|
||||||
- need to work through why serve-https thinks the traffic is inecure.
|
- you should see traffic going to both node-clients hitting the single webserver on the back end.
|
||||||
|
- Browse: https://rvpn.daplie.invalid:8443/api/com.daplie.rvpn/servers
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
"type": "servers/GET",
|
||||||
|
"schema": "",
|
||||||
|
"txts": 1490473843,
|
||||||
|
"txid": 8,
|
||||||
|
"error": "ok",
|
||||||
|
"error_description": "",
|
||||||
|
"error_uri": "",
|
||||||
|
"result": {
|
||||||
|
"servers": [{
|
||||||
|
"server_name": "0xc42014a0c0",
|
||||||
|
"server_id": 1,
|
||||||
|
"domains": [{
|
||||||
|
"domain_name": "hfc.daplie.me",
|
||||||
|
"server_id": 1,
|
||||||
|
"bytes_in": 4055,
|
||||||
|
"bytes_out": 8119,
|
||||||
|
"requests": 12,
|
||||||
## restructured-http
|
"responses": 12,
|
||||||
|
"source_addr": "127.0.0.1:55875"
|
||||||
- connection handling has been totally rewritten.
|
}, {
|
||||||
- on a specific port RVPN can determine the following:
|
"domain_name": "test1.hfc.daplie.me",
|
||||||
- if a connection is encrypted or not encrypted
|
"server_id": 1,
|
||||||
- if a request is a wss_client
|
"bytes_in": 0,
|
||||||
- if a request is an admin/api request
|
"bytes_out": 0,
|
||||||
- if a request is a plain (to be forwarded) http request
|
"requests": 0,
|
||||||
- or if a request is a different protocol (perhaps SSH)
|
"responses": 0,
|
||||||
|
"source_addr": "127.0.0.1:55875"
|
||||||
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.
|
"duration": 182.561747754,
|
||||||
It is possible now to meter any point of the connection (not Interface Level, rather TCP)
|
"idle": 21.445976033,
|
||||||
|
"bytes_in": 8119,
|
||||||
There is now a connection manager that dynamically allows new GenericListeners to start on different ports when needed....
|
"bytes_out": 4055,
|
||||||
|
"requests": 12,
|
||||||
```go
|
"responses": 12,
|
||||||
newListener := NewListenerRegistration(initialPort)
|
"source_address": "127.0.0.1:55875"
|
||||||
gl.register <- newListener
|
}, {
|
||||||
```
|
"server_name": "0xc4200ea3c0",
|
||||||
|
"server_id": 2,
|
||||||
A new listener is created by sending a NewListenerRegistration on the channel.
|
"domains": [{
|
||||||
|
"domain_name": "hfc.daplie.me",
|
||||||
```go
|
"server_id": 2,
|
||||||
|
"bytes_in": 1098,
|
||||||
ln, err := net.ListenTCP("tcp", listenAddr)
|
"bytes_out": 62,
|
||||||
if err != nil {
|
"requests": 2,
|
||||||
loginfo.Println("unable to bind", err)
|
"responses": 2,
|
||||||
listenerRegistration.status = listenerFault
|
"source_addr": "127.0.0.1:56318"
|
||||||
listenerRegistration.err = err
|
}, {
|
||||||
listenerRegistration.commCh <- listenerRegistration
|
"domain_name": "test1.hfc.daplie.me",
|
||||||
return
|
"server_id": 2,
|
||||||
|
"bytes_in": 0,
|
||||||
|
"bytes_out": 0,
|
||||||
|
"requests": 0,
|
||||||
|
"responses": 0,
|
||||||
|
"source_addr": "127.0.0.1:56318"
|
||||||
|
}],
|
||||||
|
"duration": 65.481814913,
|
||||||
|
"idle": 23.589609269,
|
||||||
|
"bytes_in": 62,
|
||||||
|
"bytes_out": 1098,
|
||||||
|
"requests": 2,
|
||||||
|
"responses": 2,
|
||||||
|
"source_address": "127.0.0.1:56318"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
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
|
|
||||||
-------
|
|
||||||
|
|
||||||
Even in the worst of conditions the fanciest of firewalls can't stop a WebSocket
|
|
||||||
running over https from creating a secure tunnel.
|
|
||||||
|
|
||||||
Whether at home behind a router that lacks UPnP compliance, at school, work,
|
|
||||||
the library - or even on an airplane, we want any device (or even a browser or
|
|
||||||
app) to be able to serve from anywhere.
|
|
||||||
|
|
||||||
Motivation
|
|
||||||
----------
|
|
||||||
|
|
||||||
We originally wrote this in node.js as
|
|
||||||
[node-tunnel-server](https://git.daplie.com/Daplie/node-tunnel-server),
|
|
||||||
but there are a few problems:
|
|
||||||
|
|
||||||
* metering
|
|
||||||
* resource utilization
|
|
||||||
* binary transfer
|
|
||||||
|
|
||||||
### metering
|
|
||||||
|
|
||||||
We want to be able to meter all traffic on a socket.
|
|
||||||
In node.js it wasn't feasible to be able to track the original socket handle
|
|
||||||
all the way back from the web socket authentication through the various
|
|
||||||
wrappers.
|
|
||||||
|
|
||||||
A user connects via a websocket to the tunnel server
|
|
||||||
and an authentication token is presented.
|
|
||||||
If the connection is established the socket should then be metered and reported
|
|
||||||
including total bytes sent and received and size of payload bytes sent and
|
|
||||||
received (because the tunnelling adds some overhead).
|
|
||||||
|
|
||||||
### resource utilization
|
|
||||||
|
|
||||||
node.js does not support usage of multiple cores in-process.
|
|
||||||
The overhead of passing socket connections between processes seemed non-trivial
|
|
||||||
at best and likely much less efficient, and impossible at worst.
|
|
||||||
|
|
||||||
### binary transfer
|
|
||||||
|
|
||||||
node.js doesn't handle binary data very well. People will be transferring
|
|
||||||
gigabytes of data.
|
|
||||||
|
|
||||||
Short Term Goal
|
|
||||||
----
|
|
||||||
|
|
||||||
Build a server compatible with the node.js client (JWT authentication)
|
|
||||||
that can meter authenticated connections, utilize multiple cores efficiently,
|
|
||||||
and efficienty garbage collect gigabytes upon gigabytes of transfer.
|
|
|
@ -1 +0,0 @@
|
||||||
Hank
|
|
|
@ -1 +0,0 @@
|
||||||
Test.html
|
|
|
@ -1,9 +1,10 @@
|
||||||
rvpn:
|
rvpn:
|
||||||
|
serverName: rvpn1
|
||||||
wssdomain: localhost.daplie.me
|
wssdomain: localhost.daplie.me
|
||||||
admindomain: rvpn.daplie.invalid
|
admindomain: rvpn.daplie.invalid
|
||||||
genericlistener: 9999
|
genericlistener: 8443
|
||||||
deadtime:
|
deadtime:
|
||||||
dwell: 600
|
dwell: 120
|
||||||
idle: 60
|
idle: 60
|
||||||
cancelcheck: 10
|
cancelcheck: 10
|
||||||
domains:
|
domains:
|
||||||
|
@ -13,4 +14,7 @@ rvpn:
|
||||||
secret: abc123
|
secret: abc123
|
||||||
test3.daplie.me:
|
test3.daplie.me:
|
||||||
secret: abc123
|
secret: abc123
|
||||||
|
loadbalancing:
|
||||||
|
defaultmethod: round-robin
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Page Not Found</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<style>
|
||||||
|
|
||||||
|
* {
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color: #888;
|
||||||
|
display: table;
|
||||||
|
font-family: sans-serif;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
display: table-cell;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin: 2em auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: #555;
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 280px) {
|
||||||
|
|
||||||
|
body, p {
|
||||||
|
width: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin: 0 0 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Page Not Found</h1>
|
||||||
|
<p>Sorry, but the page you were trying to view does not exist.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
<!-- IE needs 512+ bytes: http://blogs.msdn.com/b/ieinternals/archive/2010/08/19/http-error-pages-in-internet-explorer.aspx -->
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Please read: https://msdn.microsoft.com/en-us/library/ie/dn455106.aspx -->
|
||||||
|
<browserconfig>
|
||||||
|
<msapplication>
|
||||||
|
<tile>
|
||||||
|
<square70x70logo src="tile.png"/>
|
||||||
|
<square150x150logo src="tile.png"/>
|
||||||
|
<wide310x150logo src="tile-wide.png"/>
|
||||||
|
<square310x310logo src="tile.png"/>
|
||||||
|
</tile>
|
||||||
|
</msapplication>
|
||||||
|
</browserconfig>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||||
|
<cross-domain-policy>
|
||||||
|
<!-- Read this: https://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html -->
|
||||||
|
|
||||||
|
<!-- Most restrictive policy: -->
|
||||||
|
<site-control permitted-cross-domain-policies="none"/>
|
||||||
|
|
||||||
|
<!-- Least restrictive policy: -->
|
||||||
|
<!--
|
||||||
|
<site-control permitted-cross-domain-policies="all"/>
|
||||||
|
<allow-access-from domain="*" to-ports="*" secure="false"/>
|
||||||
|
<allow-http-request-headers-from domain="*" headers="*" secure="false"/>
|
||||||
|
-->
|
||||||
|
</cross-domain-policy>
|
|
@ -0,0 +1,282 @@
|
||||||
|
/*! HTML5 Boilerplate v5.3.0 | MIT License | https://html5boilerplate.com/ */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* What follows is the result of much research on cross-browser styling.
|
||||||
|
* Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
|
||||||
|
* Kroc Camen, and the H5BP dev community and team.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Base styles: opinionated defaults
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
html {
|
||||||
|
color: #222;
|
||||||
|
font-size: 1em;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove text-shadow in selection highlight:
|
||||||
|
* https://twitter.com/miketaylr/status/12228805301
|
||||||
|
*
|
||||||
|
* These selection rule sets have to be separate.
|
||||||
|
* Customize the background color to match your design.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-moz-selection {
|
||||||
|
background: #b3d4fc;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: #b3d4fc;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A better looking default horizontal rule
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
display: block;
|
||||||
|
height: 1px;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #ccc;
|
||||||
|
margin: 1em 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove the gap between audio, canvas, iframes,
|
||||||
|
* images, videos and the bottom of their containers:
|
||||||
|
* https://github.com/h5bp/html5-boilerplate/issues/440
|
||||||
|
*/
|
||||||
|
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
iframe,
|
||||||
|
img,
|
||||||
|
svg,
|
||||||
|
video {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove default fieldset styles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Allow only vertical resizing of textareas.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Browser Upgrade Prompt
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
.browserupgrade {
|
||||||
|
margin: 0.2em 0;
|
||||||
|
background: #ccc;
|
||||||
|
color: #000;
|
||||||
|
padding: 0.2em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Author's custom styles
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Helper classes
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hide visually and from screen readers
|
||||||
|
*/
|
||||||
|
|
||||||
|
.hidden {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hide only visually, but have it available for screen readers:
|
||||||
|
* http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
|
||||||
|
*/
|
||||||
|
|
||||||
|
.visuallyhidden {
|
||||||
|
border: 0;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Extends the .visuallyhidden class to allow the element
|
||||||
|
* to be focusable when navigated to via the keyboard:
|
||||||
|
* https://www.drupal.org/node/897638
|
||||||
|
*/
|
||||||
|
|
||||||
|
.visuallyhidden.focusable:active,
|
||||||
|
.visuallyhidden.focusable:focus {
|
||||||
|
clip: auto;
|
||||||
|
height: auto;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
position: static;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Hide visually and from screen readers, but maintain layout
|
||||||
|
*/
|
||||||
|
|
||||||
|
.invisible {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clearfix: contain floats
|
||||||
|
*
|
||||||
|
* For modern browsers
|
||||||
|
* 1. The space content is one way to avoid an Opera bug when the
|
||||||
|
* `contenteditable` attribute is included anywhere else in the document.
|
||||||
|
* Otherwise it causes space to appear at the top and bottom of elements
|
||||||
|
* that receive the `clearfix` class.
|
||||||
|
* 2. The use of `table` rather than `block` is only necessary if using
|
||||||
|
* `:before` to contain the top-margins of child elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.clearfix:before,
|
||||||
|
.clearfix:after {
|
||||||
|
content: " "; /* 1 */
|
||||||
|
display: table; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.clearfix:after {
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
EXAMPLE Media Queries for Responsive Design.
|
||||||
|
These examples override the primary ('mobile first') styles.
|
||||||
|
Modify as content requires.
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
@media only screen and (min-width: 35em) {
|
||||||
|
/* Style adjustments for viewports that meet the condition */
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print,
|
||||||
|
(-webkit-min-device-pixel-ratio: 1.25),
|
||||||
|
(min-resolution: 1.25dppx),
|
||||||
|
(min-resolution: 120dpi) {
|
||||||
|
/* Style adjustments for high resolution devices */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
Print styles.
|
||||||
|
Inlined to avoid the additional HTTP request:
|
||||||
|
http://www.phpied.com/delay-loading-your-print-css/
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after,
|
||||||
|
*:first-letter,
|
||||||
|
*:first-line {
|
||||||
|
background: transparent !important;
|
||||||
|
color: #000 !important; /* Black prints faster:
|
||||||
|
http://www.sanbeiji.com/archives/953 */
|
||||||
|
box-shadow: none !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a[href]:after {
|
||||||
|
content: " (" attr(href) ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title]:after {
|
||||||
|
content: " (" attr(title) ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Don't show links that are fragment identifiers,
|
||||||
|
* or use the `javascript:` pseudo protocol
|
||||||
|
*/
|
||||||
|
|
||||||
|
a[href^="#"]:after,
|
||||||
|
a[href^="javascript:"]:after {
|
||||||
|
content: "";
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
blockquote {
|
||||||
|
border: 1px solid #999;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Printing Tables:
|
||||||
|
* http://css-discuss.incutio.com/wiki/Printing_Tables
|
||||||
|
*/
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr,
|
||||||
|
img {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
orphans: 3;
|
||||||
|
widows: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,424 @@
|
||||||
|
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Set default font family to sans-serif.
|
||||||
|
* 2. Prevent iOS and IE text size adjust after device orientation change,
|
||||||
|
* without disabling user zoom.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif; /* 1 */
|
||||||
|
-ms-text-size-adjust: 100%; /* 2 */
|
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove default margin.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* HTML5 display definitions
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct `block` display not defined for any HTML5 element in IE 8/9.
|
||||||
|
* Correct `block` display not defined for `details` or `summary` in IE 10/11
|
||||||
|
* and Firefox.
|
||||||
|
* Correct `block` display not defined for `main` in IE 11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
main,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section,
|
||||||
|
summary {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct `inline-block` display not defined in IE 8/9.
|
||||||
|
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
progress,
|
||||||
|
video {
|
||||||
|
display: inline-block; /* 1 */
|
||||||
|
vertical-align: baseline; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent modern browsers from displaying `audio` without controls.
|
||||||
|
* Remove excess height in iOS 5 devices.
|
||||||
|
*/
|
||||||
|
|
||||||
|
audio:not([controls]) {
|
||||||
|
display: none;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address `[hidden]` styling not present in IE 8/9/10.
|
||||||
|
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden],
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the gray background color from active links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Improve readability of focused elements when they are also in an
|
||||||
|
* active/hover state.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a:active,
|
||||||
|
a:hover {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-level semantics
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: 1px dotted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address styling not present in Safari and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
dfn {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address variable `h1` font-size and margin within `section` and `article`
|
||||||
|
* contexts in Firefox 4+, Safari, and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address styling not present in IE 8/9.
|
||||||
|
*/
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background: #ff0;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address inconsistent and variable font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove border when inside `a` element in IE 8/9/10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct overflow not hidden in IE 9/10/11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address margin not present in IE 8/9 and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 1em 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address differences between Firefox and other browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contain overflow in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address odd `em`-unit font size rendering in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Known limitation: by default, Chrome and Safari on OS X allow very limited
|
||||||
|
* styling of `select`, unless a `border` property is set.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct color not being inherited.
|
||||||
|
* Known issue: affects color of disabled elements.
|
||||||
|
* 2. Correct font properties not being inherited.
|
||||||
|
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
color: inherit; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
margin: 0; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address `overflow` set to `hidden` in IE 8/9/10/11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address inconsistent `text-transform` inheritance for `button` and `select`.
|
||||||
|
* All other form control elements do not inherit `text-transform` values.
|
||||||
|
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
|
||||||
|
* Correct `select` style inheritance in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
|
||||||
|
* and `video` controls.
|
||||||
|
* 2. Correct inability to style clickable `input` types in iOS.
|
||||||
|
* 3. Improve usability and consistency of cursor style between image-type
|
||||||
|
* `input` and others.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
html input[type="button"], /* 1 */
|
||||||
|
input[type="reset"],
|
||||||
|
input[type="submit"] {
|
||||||
|
-webkit-appearance: button; /* 2 */
|
||||||
|
cursor: pointer; /* 3 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Re-set default cursor for disabled elements.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button[disabled],
|
||||||
|
html input[disabled] {
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove inner padding and border in Firefox 4+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
|
||||||
|
* the UA stylesheet.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* It's recommended that you don't attempt to style these elements.
|
||||||
|
* Firefox's implementation doesn't respect box-sizing, padding, or width.
|
||||||
|
*
|
||||||
|
* 1. Address box sizing set to `content-box` in IE 8/9/10.
|
||||||
|
* 2. Remove excess padding in IE 8/9/10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
padding: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
|
||||||
|
* `font-size` values of the `input`, it causes the cursor style of the
|
||||||
|
* decrement button to change from `default` to `text`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
|
||||||
|
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
box-sizing: content-box; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
|
||||||
|
* Safari (but not Chrome) clips the cancel button when the search input has
|
||||||
|
* padding (and `textfield` appearance).
|
||||||
|
*/
|
||||||
|
|
||||||
|
input[type="search"]::-webkit-search-cancel-button,
|
||||||
|
input[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define consistent border, margin, and padding.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #c0c0c0;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0.35em 0.625em 0.75em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct `color` not being inherited in IE 8/9/10/11.
|
||||||
|
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
|
||||||
|
*/
|
||||||
|
|
||||||
|
legend {
|
||||||
|
border: 0; /* 1 */
|
||||||
|
padding: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove default vertical scrollbar in IE 8/9/10/11.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Don't inherit the `font-weight` (applied by a rule above).
|
||||||
|
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
|
||||||
|
*/
|
||||||
|
|
||||||
|
optgroup {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tables
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove most spacing between table cells.
|
||||||
|
*/
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
padding: 0;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 766 B |
|
@ -0,0 +1,15 @@
|
||||||
|
# humanstxt.org/
|
||||||
|
# The humans responsible & technology colophon
|
||||||
|
|
||||||
|
# TEAM
|
||||||
|
|
||||||
|
<name> -- <role> -- <twitter>
|
||||||
|
|
||||||
|
# THANKS
|
||||||
|
|
||||||
|
<name>
|
||||||
|
|
||||||
|
# TECHNOLOGY COLOPHON
|
||||||
|
|
||||||
|
CSS3, HTML5
|
||||||
|
Apache Server Configs, jQuery, Modernizr, Normalize.css
|
|
@ -0,0 +1,76 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html class="no-js" lang="">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
||||||
|
<title></title>
|
||||||
|
<meta name="description" content="">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
|
||||||
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
|
<!-- Place favicon.ico in the root directory -->
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/normalize.css">
|
||||||
|
<link rel="stylesheet" href="css/main.css">
|
||||||
|
<script src="js/vendor/modernizr-2.8.3.min.js"></script>
|
||||||
|
<base href="/">
|
||||||
|
</head>
|
||||||
|
<body ng-app="rvpnApp">
|
||||||
|
<!--[if lt IE 8]>
|
||||||
|
<p class="browserupgrade">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
|
||||||
|
<![endif]-->
|
||||||
|
|
||||||
|
<!-- Admin GUI Begins Here -->
|
||||||
|
<header class="bs-docs-nav navbar navbar-static-top" id="top">
|
||||||
|
<div class="container">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a href="/admin/index.html" target="_parent" class="navbar-brand">RVPN Admin</a>
|
||||||
|
</div>
|
||||||
|
<nav class="collapse navbar-collapse" id="bs-navbar">
|
||||||
|
<ul class="nav navbar-nav">
|
||||||
|
<li> <a href="/admin/status">Status</a> </li>
|
||||||
|
<li> <a href="/admin/servers">Servers</a> </li>
|
||||||
|
<li> <a href="/admin/#domains">Domains</a> </li>
|
||||||
|
<li> <a href="/admin/#connections">Connections</a> </li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
<li><a href="#">Help</a></li>
|
||||||
|
<li><a href="#">Login</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div ng-view> </div>
|
||||||
|
|
||||||
|
|
||||||
|
<script src="https://code.jquery.com/jquery-1.12.0.min.js"></script>
|
||||||
|
<script>window.jQuery || document.write('<script src="admin/js/vendor/jquery-1.12.0.min.js"><\/script>')</script>
|
||||||
|
<script src="admin/js/plugins.js"></script>
|
||||||
|
<script src="admin/js/main.js"></script>
|
||||||
|
|
||||||
|
<!-- Google Analytics: change UA-XXXXX-X to be your site's ID. -->
|
||||||
|
<script>
|
||||||
|
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
|
||||||
|
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
|
||||||
|
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
|
||||||
|
e.src='https://www.google-analytics.com/analytics.js';
|
||||||
|
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
|
||||||
|
ga('create','UA-XXXXX-X','auto');ga('send','pageview');
|
||||||
|
</script>
|
||||||
|
</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.js"></script>
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular-route.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 src="/admin/js/vendor/filter.js"></script>
|
||||||
|
<script src="/admin/js/app.js"></script>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,132 @@
|
||||||
|
console.log("app.sh startup")
|
||||||
|
|
||||||
|
var app = angular.module("rvpnApp", ["ngRoute", "angular-duration-format"]);
|
||||||
|
|
||||||
|
app.config(function($routeProvider, $locationProvider) {
|
||||||
|
$routeProvider
|
||||||
|
|
||||||
|
.when("/admin/status/", {
|
||||||
|
templateUrl : "admin/partials/status.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
.when("/admin/index.html", {
|
||||||
|
templateUrl : "admin/partials/servers.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
.when("/admin/servers/", {
|
||||||
|
templateUrl : "admin/partials/servers.html"
|
||||||
|
})
|
||||||
|
|
||||||
|
.when("/admin/#domains", {
|
||||||
|
templateUrl : "green.htm"
|
||||||
|
})
|
||||||
|
|
||||||
|
.when("/blue", {
|
||||||
|
templateUrl : "blue.htm"
|
||||||
|
});
|
||||||
|
$locationProvider.html5Mode(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.filter('bytes', function() {
|
||||||
|
return function(bytes, precision) {
|
||||||
|
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-';
|
||||||
|
if (typeof precision === 'undefined') precision = 1;
|
||||||
|
var units = ['bytes', 'kB', 'MB', 'GB', 'TB', 'PB'],
|
||||||
|
number = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||||
|
return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.filter('hfcduration', function() {
|
||||||
|
return function(duration, precision) {
|
||||||
|
remain = duration
|
||||||
|
duration_day = 24*60*60
|
||||||
|
duration_hour = 60*60
|
||||||
|
duration_minute = 60
|
||||||
|
duration_str = ""
|
||||||
|
|
||||||
|
days = Math.floor(remain / duration_day)
|
||||||
|
if (days > 0) {
|
||||||
|
remain = remain - (days * duration_day)
|
||||||
|
duration_str = duration_str + days + 'd'
|
||||||
|
}
|
||||||
|
|
||||||
|
hours = Math.floor(remain / duration_hour)
|
||||||
|
if (hours > 0) {
|
||||||
|
remain = remain - (hours * duration_hour)
|
||||||
|
duration_str = duration_str + hours + 'h'
|
||||||
|
}
|
||||||
|
|
||||||
|
mins = Math.floor(remain / duration_minute)
|
||||||
|
if (mins > 0) {
|
||||||
|
remain = remain - (mins * duration_minute)
|
||||||
|
duration_str = duration_str + mins + 'm'
|
||||||
|
}
|
||||||
|
|
||||||
|
secs = Math.floor(remain)
|
||||||
|
duration_str = duration_str + secs + 's'
|
||||||
|
|
||||||
|
return (duration_str);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.controller('statusController', function ($scope, $http) {
|
||||||
|
console.log("statusController");
|
||||||
|
$scope.status_search = "";
|
||||||
|
|
||||||
|
var api = '/api/com.daplie.tunnel/status'
|
||||||
|
|
||||||
|
$scope.updateView = function() {
|
||||||
|
$http.get(api).then(function(response) {
|
||||||
|
console.log(response);
|
||||||
|
data = response.data;
|
||||||
|
if (data.error == 'ok' ){
|
||||||
|
$scope.status = data.result;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.updateView()
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
app.controller('serverController', function ($scope, $http) {
|
||||||
|
$scope.servers = [];
|
||||||
|
$scope.servers_search = "";
|
||||||
|
$scope.servers_trigger_details = [];
|
||||||
|
$scope.filtered
|
||||||
|
|
||||||
|
var api = '/api/com.daplie.tunnel/servers'
|
||||||
|
|
||||||
|
$scope.updateView = function() {
|
||||||
|
$http.get(api).then(function(response) {
|
||||||
|
//console.log(response);
|
||||||
|
data = response.data;
|
||||||
|
if (data.error == 'ok' ){
|
||||||
|
$scope.servers = data.result.servers;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope.triggerDetail = function(id) {
|
||||||
|
//console.log("triggerDetail ", id, $scope.servers_trigger_details[id])
|
||||||
|
if ($scope.servers_trigger_details[id] == true) {
|
||||||
|
$scope.servers_trigger_details[id] = false;
|
||||||
|
} else {
|
||||||
|
$scope.servers_trigger_details[id] = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.checkDetail = function(id) {
|
||||||
|
//console.log("checkDetail ", id, $scope.servers_trigger_details[id])
|
||||||
|
if ($scope.servers_trigger_details[id] == true) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$scope.updateView()
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Avoid `console` errors in browsers that lack a console.
|
||||||
|
(function() {
|
||||||
|
var method;
|
||||||
|
var noop = function () {};
|
||||||
|
var methods = [
|
||||||
|
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
|
||||||
|
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
|
||||||
|
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
|
||||||
|
'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
|
||||||
|
];
|
||||||
|
var length = methods.length;
|
||||||
|
var console = (window.console = window.console || {});
|
||||||
|
|
||||||
|
while (length--) {
|
||||||
|
method = methods[length];
|
||||||
|
|
||||||
|
// Only stub undefined methods.
|
||||||
|
if (!console[method]) {
|
||||||
|
console[method] = noop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
// Place any jQuery/helper plugins in here.
|
|
@ -0,0 +1,148 @@
|
||||||
|
// ### filter.js >>
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('angular-duration-format.filter', [ ])
|
||||||
|
.filter('duration', function() {
|
||||||
|
var DURATION_FORMATS_SPLIT = /((?:[^ydhms']+)|(?:'(?:[^']|'')*')|(?:y+|d+|h+|m+|s+))(.*)/;
|
||||||
|
var DURATION_FORMATS = {
|
||||||
|
y: { // years
|
||||||
|
// "longer" years are not supported
|
||||||
|
value: 365 * 24 * 60 * 60 * 1000,
|
||||||
|
},
|
||||||
|
yy: {
|
||||||
|
value: 'y',
|
||||||
|
pad: 2,
|
||||||
|
},
|
||||||
|
d: { // days
|
||||||
|
value: 24 * 60 * 60 * 1000,
|
||||||
|
},
|
||||||
|
dd: {
|
||||||
|
value: 'd',
|
||||||
|
pad: 2,
|
||||||
|
},
|
||||||
|
h: { // hours
|
||||||
|
value: 60 * 60 * 1000,
|
||||||
|
},
|
||||||
|
hh: { // padded hours
|
||||||
|
value: 'h',
|
||||||
|
pad: 2,
|
||||||
|
},
|
||||||
|
m: { // minutes
|
||||||
|
value: 60 * 1000,
|
||||||
|
},
|
||||||
|
mm: { // padded minutes
|
||||||
|
value: 'm',
|
||||||
|
pad: 2,
|
||||||
|
},
|
||||||
|
s: { // seconds
|
||||||
|
value: 1000,
|
||||||
|
},
|
||||||
|
ss: { // padded seconds
|
||||||
|
value: 's',
|
||||||
|
pad: 2,
|
||||||
|
},
|
||||||
|
sss: { // milliseconds
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
ssss: { // padded milliseconds
|
||||||
|
value: 'sss',
|
||||||
|
pad: 4,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function _parseFormat(string) {
|
||||||
|
// @inspiration AngularJS date filter
|
||||||
|
var parts = [];
|
||||||
|
var format = string ? string.toString() : '';
|
||||||
|
|
||||||
|
while (format) {
|
||||||
|
var match = DURATION_FORMATS_SPLIT.exec(format);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
parts = parts.concat(match.slice(1));
|
||||||
|
|
||||||
|
format = parts.pop();
|
||||||
|
} else {
|
||||||
|
parts.push(format);
|
||||||
|
|
||||||
|
format = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatDuration(timestamp, format) {
|
||||||
|
var text = '';
|
||||||
|
var values = { };
|
||||||
|
|
||||||
|
format.filter(function(format) { // filter only value parts of format
|
||||||
|
return DURATION_FORMATS.hasOwnProperty(format);
|
||||||
|
}).map(function(format) { // get formats with values only
|
||||||
|
var config = DURATION_FORMATS[format];
|
||||||
|
|
||||||
|
if (config.hasOwnProperty('pad')) {
|
||||||
|
return config.value;
|
||||||
|
} else {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}).filter(function(format, index, arr) { // remove duplicates
|
||||||
|
return (arr.indexOf(format) === index);
|
||||||
|
}).map(function(format) { // get format configurations with values
|
||||||
|
return angular.extend({
|
||||||
|
name: format,
|
||||||
|
}, DURATION_FORMATS[format]);
|
||||||
|
}).sort(function(a, b) { // sort formats descending by value
|
||||||
|
return b.value - a.value;
|
||||||
|
}).forEach(function(format) { // create values for format parts
|
||||||
|
var value = values[format.name] = Math.floor(timestamp / format.value);
|
||||||
|
|
||||||
|
timestamp = timestamp - (value * format.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
format.forEach(function(part) {
|
||||||
|
var format = DURATION_FORMATS[part];
|
||||||
|
|
||||||
|
if (format) {
|
||||||
|
var value = values[format.value];
|
||||||
|
|
||||||
|
text += (format.hasOwnProperty('pad') ? _padNumber(value, Math.max(format.pad, value.toString().length)) : values[part]);
|
||||||
|
} else {
|
||||||
|
text += part.replace(/(^'|'$)/g, '').replace(/''/g, '\'');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _padNumber(number, len) {
|
||||||
|
return ((new Array(len + 1)).join('0') + number).slice(-len);
|
||||||
|
}
|
||||||
|
|
||||||
|
return function(value, format) {
|
||||||
|
var parsedValue = parseFloat(value, 10);
|
||||||
|
var parsedFormat = _parseFormat(format);
|
||||||
|
|
||||||
|
if (isNaN(parsedValue) || (parsedFormat.length === 0)) {
|
||||||
|
return value;
|
||||||
|
} else {
|
||||||
|
return _formatDuration(parsedValue, parsedFormat);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// ### << filter.js
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// ### main.js >>
|
||||||
|
|
||||||
|
angular
|
||||||
|
.module('angular-duration-format', [
|
||||||
|
'angular-duration-format.filter',
|
||||||
|
]);
|
||||||
|
|
||||||
|
|
||||||
|
// ### << main.js
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,89 @@
|
||||||
|
<div class="panel panel-default" data-ng-controller="serverController">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
Servers
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<form class="form-inline pull-right">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="search">Search:</label>
|
||||||
|
<input type="text" class="form-control" id="search" data-ng-model="servers_search">
|
||||||
|
</div>
|
||||||
|
<button type="button" title="Refresh" class="btn btn-default" aria-label="Refresh">
|
||||||
|
<span class="glyphicon glyphicon-refresh" title="Refresh" aria-hidden="false" ng-click="updateView()"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-body">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<th width="3%">ID</th>
|
||||||
|
<th width="10%">Name</th>
|
||||||
|
<th width="10%">Address</th>
|
||||||
|
<th width="10%">Xfer (in/out)</th>
|
||||||
|
<th width="10%">Req/Resp</th>
|
||||||
|
<th width="5%">Duration</th>
|
||||||
|
<th width="5%">State</th>
|
||||||
|
<th width="5%">Idle</th>
|
||||||
|
<th width="1%"><center><span class="glyphicon glyphicon-option-vertical" aria-hidden="true"></span></center></th>
|
||||||
|
|
||||||
|
|
||||||
|
<tr ng-repeat="s in servers | filter:servers_search | orderBy:'server_id'">
|
||||||
|
<td>{{ s.server_id }}</td>
|
||||||
|
<td>{{ s.server_name }}</td>
|
||||||
|
<td>
|
||||||
|
{{ s.source_address }}
|
||||||
|
<div ng-hide="checkDetail(s.server_id)">
|
||||||
|
domains({{ s.domains.length}})
|
||||||
|
<div ng-repeat="d in s.domains | orderBy:'domain_name'">
|
||||||
|
   {{ d.domain_name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ s.bytes_in | bytes }}/{{ s.bytes_out | bytes }}
|
||||||
|
<div ng-hide="checkDetail(s.server_id)">
|
||||||
|
 
|
||||||
|
<div ng-repeat="d in s.domains | orderBy:'domain_name'">
|
||||||
|
   {{ d.bytes_in | bytes }}/{{ d.bytes_out | bytes }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ s.requests }}/{{ s.responses }}
|
||||||
|
<div ng-hide="checkDetail(s.server_id)">
|
||||||
|
 
|
||||||
|
<div ng-repeat="d in s.domains | orderBy:'domain_name'">
|
||||||
|
   {{ d.requests }}/{{ d.responses }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>{{ s.duration | hfcduration }}</td>
|
||||||
|
<td>{{ s.server_state }}</td>
|
||||||
|
<td>{{ s.idle | hfcduration }}</td>
|
||||||
|
<td>
|
||||||
|
<span class="glyphicon glyphicon-zoom-in" title="Detail" aria-hidden="false" ng-click="triggerDetail(s.server_id)"></span>
|
||||||
|
<div ng-hide="checkDetail(s.server_id)">
|
||||||
|
 
|
||||||
|
<div ng-repeat="d in s.domains | orderBy:'domain_name'">
|
||||||
|
<span class="glyphicon glyphicon-zoom-in" title="Detail" aria-hidden="false" ng-click="triggerDetail(s.server_id+d.domain_name)"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<div class="panel panel-default" data-ng-controller="statusController">
|
||||||
|
<div class="panel-heading">
|
||||||
|
<div class="panel-title">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-6">
|
||||||
|
Status
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<form class="form-inline pull-right">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="search">Search:</label>
|
||||||
|
<input type="text" class="form-control" id="search" data-ng-model="servers_search">
|
||||||
|
</div>
|
||||||
|
<button type="button" title="Refresh" class="btn btn-default" aria-label="Refresh">
|
||||||
|
<span class="glyphicon glyphicon-refresh" title="Refresh" aria-hidden="false" ng-click="updateView()"></span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-8">Server Name: {{ status.name }} (Uptime: {{ status.uptime | hfcduration }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-8">Administrative Domain: {{ status.admin_domain }} </div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-8">Server Domain: {{ status.wss_domain }} </div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-8">Default LB Method: {{ status.loadbalance_default_method }} </div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-4"> </div>
|
||||||
|
<div class="col-md-8">Deadtime: dwell:{{ status.dead_time.dwell}} idle:{{ status.dead_time.idle}} cancel:{{ status.dead_time.cancel_check}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
# www.robotstxt.org/
|
||||||
|
|
||||||
|
# Allow crawling of all content
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
89
main.go
89
main.go
|
@ -1,21 +1,27 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||||
|
|
||||||
"context"
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/server"
|
||||||
|
|
||||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/genericlistener"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
logfile = "stdout"
|
||||||
|
configPath = "./"
|
||||||
|
configFile = "go-rvpn-server"
|
||||||
|
|
||||||
loginfo *log.Logger
|
loginfo *log.Logger
|
||||||
logdebug *log.Logger
|
logdebug *log.Logger
|
||||||
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
@ -25,29 +31,57 @@ var (
|
||||||
argServerAdminBinding string
|
argServerAdminBinding string
|
||||||
argServerExternalBinding string
|
argServerExternalBinding string
|
||||||
argDeadTime int
|
argDeadTime int
|
||||||
connectionTable *genericlistener.Table
|
connectionTable *server.Table
|
||||||
secretKey = "abc123"
|
secretKey = "abc123"
|
||||||
wssHostName = "localhost.daplie.me"
|
wssHostName = "localhost.daplie.me"
|
||||||
adminHostName = "rvpn.daplie.invalid"
|
adminHostName = "rvpn.daplie.invalid"
|
||||||
idle int
|
idle int
|
||||||
dwell int
|
dwell int
|
||||||
cancelcheck int
|
cancelcheck int
|
||||||
|
lbDefaultMethod string
|
||||||
|
serverName string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
flag.StringVar(&logfile, "log", logfile, "Log file (or stdout/stderr; empty for none)")
|
||||||
|
flag.StringVar(&configPath, "config-path", configPath, "Configuration File Path")
|
||||||
|
flag.StringVar(&configFile, "config-file", configFile, "Configuration File Name")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var logoutput io.Writer
|
||||||
|
|
||||||
//Main -- main entry point
|
//Main -- main entry point
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
loginfo = log.New(os.Stdout, "INFO: main: ", logFlags)
|
switch logfile {
|
||||||
logdebug = log.New(os.Stdout, "DEBUG: main:", logFlags)
|
case "stdout":
|
||||||
viper.SetConfigName("go-rvpn-server")
|
logoutput = os.Stdout
|
||||||
|
case "stderr":
|
||||||
|
logoutput = os.Stderr
|
||||||
|
case "":
|
||||||
|
logoutput = ioutil.Discard
|
||||||
|
default:
|
||||||
|
logoutput = &lumberjack.Logger{
|
||||||
|
Filename: logfile,
|
||||||
|
MaxSize: 100,
|
||||||
|
MaxAge: 120,
|
||||||
|
MaxBackups: 10,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send the output io.Writing to the other packages
|
||||||
|
server.InitLogging(logoutput)
|
||||||
|
|
||||||
|
loginfo = log.New(logoutput, "INFO: main: ", logFlags)
|
||||||
|
logdebug = log.New(logoutput, "DEBUG: main:", logFlags)
|
||||||
|
|
||||||
|
viper.SetConfigName(configFile)
|
||||||
|
viper.AddConfigPath(configPath)
|
||||||
viper.AddConfigPath("./")
|
viper.AddConfigPath("./")
|
||||||
err := viper.ReadInConfig()
|
err := viper.ReadInConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Errorf("Fatal error config file: %s \n", err))
|
panic(fmt.Errorf("fatal error config file: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.IntVar(&argDeadTime, "dead-time-counter", 5, "deadtime counter in seconds")
|
flag.IntVar(&argDeadTime, "dead-time-counter", 5, "deadtime counter in seconds")
|
||||||
|
@ -55,18 +89,15 @@ func main() {
|
||||||
wssHostName = viper.Get("rvpn.wssdomain").(string)
|
wssHostName = viper.Get("rvpn.wssdomain").(string)
|
||||||
adminHostName = viper.Get("rvpn.admindomain").(string)
|
adminHostName = viper.Get("rvpn.admindomain").(string)
|
||||||
argGenericBinding = viper.GetInt("rvpn.genericlistener")
|
argGenericBinding = viper.GetInt("rvpn.genericlistener")
|
||||||
deadtime := viper.Get("rvpn.deadtime")
|
deadtime := viper.Get("rvpn.deadtime").(map[string]interface{})
|
||||||
idle = deadtime.(map[string]interface{})["idle"].(int)
|
idle = deadtime["idle"].(int)
|
||||||
dwell = deadtime.(map[string]interface{})["dwell"].(int)
|
dwell = deadtime["dwell"].(int)
|
||||||
cancelcheck = deadtime.(map[string]interface{})["cancelcheck"].(int)
|
cancelcheck = deadtime["cancelcheck"].(int)
|
||||||
|
lbDefaultMethod = viper.Get("rvpn.loadbalancing.defaultmethod").(string)
|
||||||
|
serverName = viper.Get("rvpn.serverName").(string)
|
||||||
|
|
||||||
loginfo.Println("startup")
|
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")
|
certbundle, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println(err)
|
loginfo.Println(err)
|
||||||
|
@ -76,6 +107,14 @@ func main() {
|
||||||
ctx, cancelContext := context.WithCancel(context.Background())
|
ctx, cancelContext := context.WithCancel(context.Background())
|
||||||
defer cancelContext()
|
defer cancelContext()
|
||||||
|
|
||||||
|
serverStatus := server.NewStatus(ctx)
|
||||||
|
serverStatus.AdminDomain = adminHostName
|
||||||
|
serverStatus.WssDomain = wssHostName
|
||||||
|
serverStatus.Name = serverName
|
||||||
|
serverStatus.StartTime = time.Now()
|
||||||
|
serverStatus.DeadTime = server.NewStatusDeadTime(dwell, idle, cancelcheck)
|
||||||
|
serverStatus.LoadbalanceDefaultMethod = lbDefaultMethod
|
||||||
|
|
||||||
// Setup for GenericListenServe.
|
// Setup for GenericListenServe.
|
||||||
// - establish context for the generic listener
|
// - establish context for the generic listener
|
||||||
// - startup listener
|
// - startup listener
|
||||||
|
@ -85,16 +124,18 @@ func main() {
|
||||||
// - if tls, establish, protocol peek buffer, else decrypted
|
// - if tls, establish, protocol peek buffer, else decrypted
|
||||||
// - match protocol
|
// - match protocol
|
||||||
|
|
||||||
connectionTracking := genericlistener.NewTracking()
|
connectionTracking := server.NewTracking()
|
||||||
|
serverStatus.ConnectionTracking = connectionTracking
|
||||||
go connectionTracking.Run(ctx)
|
go connectionTracking.Run(ctx)
|
||||||
|
|
||||||
connectionTable = genericlistener.NewTable(dwell, idle)
|
connectionTable = server.NewTable(dwell, idle)
|
||||||
go connectionTable.Run(ctx)
|
serverStatus.ConnectionTable = connectionTable
|
||||||
|
go connectionTable.Run(ctx, lbDefaultMethod)
|
||||||
|
|
||||||
|
genericListeners := server.NewGenerListeners(ctx, secretKey, certbundle, serverStatus)
|
||||||
|
//serverStatus.GenericListeners = genericListeners
|
||||||
|
|
||||||
genericListeners := genericlistener.NewGenerListeners(ctx, connectionTable, connectionTracking, secretKey, certbundle, wssHostName, adminHostName, cancelcheck)
|
|
||||||
go genericListeners.Run(ctx, argGenericBinding)
|
go genericListeners.Run(ctx, argGenericBinding)
|
||||||
|
|
||||||
//Run for 10 minutes and then shutdown cleanly
|
select {}
|
||||||
time.Sleep(600 * time.Second)
|
|
||||||
cancelContext()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package envelope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Envelope -- Standard daplie response structure
|
||||||
|
type Envelope struct {
|
||||||
|
TransactionType string `json:"type"`
|
||||||
|
Schema string `json:"schema"`
|
||||||
|
TransactionTimeStamp int64 `json:"txts"`
|
||||||
|
TransactionID int64 `json:"txid"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
ErrorURI string `json:"error_uri"`
|
||||||
|
Result interface{} `json:"result"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewEnvelope -- Constructor
|
||||||
|
func NewEnvelope(transactionType string) (p *Envelope) {
|
||||||
|
transactionID++
|
||||||
|
|
||||||
|
p = new(Envelope)
|
||||||
|
p.TransactionType = transactionType
|
||||||
|
p.TransactionID = transactionID
|
||||||
|
p.TransactionTimeStamp = time.Now().Unix()
|
||||||
|
p.Error = "ok"
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Generate -- encode into JSON and return string
|
||||||
|
func (e *Envelope) Generate() string {
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
json.NewEncoder(buf).Encode(e)
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
//GenerateWriter --
|
||||||
|
func (e *Envelope) GenerateWriter(w io.Writer) {
|
||||||
|
json.NewEncoder(w).Encode(e)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package envelope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
transactionID int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: envelope: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: envelope:", logFlags)
|
||||||
|
transactionID = 1
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
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
|
|
||||||
// }
|
|
|
@ -1,39 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
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)
|
|
||||||
}
|
|
|
@ -5,11 +5,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
packerV1 byte = 255 - 1
|
_ = iota // skip the iota value of 0
|
||||||
packerV2 byte = 255 - 2
|
packerV1 byte = 255 - iota
|
||||||
|
packerV2
|
||||||
)
|
)
|
||||||
|
|
||||||
//Packer -- contains both header and data
|
//Packer -- contains both header and data
|
||||||
|
@ -26,121 +28,93 @@ func NewPacker() (p *Packer) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func splitHeader(header []byte, names []string) (map[string]string, error) {
|
||||||
|
parts := strings.Split(string(header), ",")
|
||||||
|
if p, n := len(parts), len(names); p > n {
|
||||||
|
return nil, fmt.Errorf("Header contains %d extra fields", p-n)
|
||||||
|
} else if p < n {
|
||||||
|
return nil, fmt.Errorf("Header missing fields %q", names[p:])
|
||||||
|
}
|
||||||
|
|
||||||
|
result := make(map[string]string, len(names))
|
||||||
|
for ind, key := range names {
|
||||||
|
result[key] = parts[ind]
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
//ReadMessage -
|
//ReadMessage -
|
||||||
func ReadMessage(b []byte) (p *Packer, err error) {
|
func ReadMessage(b []byte) (*Packer, error) {
|
||||||
fmt.Println("ReadMessage")
|
// Detect protocol in use
|
||||||
var pos int
|
|
||||||
|
|
||||||
err = nil
|
|
||||||
// detect protocol in use
|
|
||||||
if b[0] == packerV1 {
|
if b[0] == packerV1 {
|
||||||
p = NewPacker()
|
// Separate the header and body using the header length in the second byte.
|
||||||
|
p := NewPacker()
|
||||||
|
header := b[2 : b[1]+2]
|
||||||
|
data := b[b[1]+2:]
|
||||||
|
|
||||||
// Handle Header Length
|
// Handle the different parts of the header.
|
||||||
pos = pos + 1
|
parts, err := splitHeader(header, []string{"address family", "address", "port", "data length", "service"})
|
||||||
p.Header.HeaderLen = b[pos]
|
if err != nil {
|
||||||
|
|
||||||
//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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
bAddrFamily := b[pos : pos+end]
|
if familyText := parts["address family"]; familyText == addressFamilyText[FamilyIPv4] {
|
||||||
if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv4]) {
|
|
||||||
p.Header.family = FamilyIPv4
|
p.Header.family = FamilyIPv4
|
||||||
} else if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv6]) {
|
} else if familyText == addressFamilyText[FamilyIPv6] {
|
||||||
p.Header.family = FamilyIPv6
|
p.Header.family = FamilyIPv6
|
||||||
} else {
|
} else {
|
||||||
err = fmt.Errorf("Address family not supported %d", bAddrFamily)
|
return nil, fmt.Errorf("Address family %q not supported", familyText)
|
||||||
}
|
}
|
||||||
|
|
||||||
//handle address
|
p.Header.address = net.ParseIP(parts["address"])
|
||||||
pos = pos + end + 1
|
if p.Header.address == nil {
|
||||||
end = bytes.IndexAny(b[pos:], ",")
|
return nil, fmt.Errorf("Invalid network address %q", parts["address"])
|
||||||
if end == -1 {
|
} else if p.Header.Family() == FamilyIPv4 && p.Header.address.To4() == nil {
|
||||||
err = fmt.Errorf("missing , while parsing address")
|
return nil, fmt.Errorf("Address %q is not in address family %s", parts["address"], p.Header.FamilyText())
|
||||||
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]))
|
//handle port
|
||||||
if err != nil {
|
if port, err := strconv.Atoi(parts["port"]); err != nil {
|
||||||
err = fmt.Errorf("error converting port %s", err)
|
return nil, fmt.Errorf("Error converting port %q: %v", parts["port"], err)
|
||||||
|
} else if port <= 0 || port > 65535 {
|
||||||
|
return nil, fmt.Errorf("Port %d out of range", port)
|
||||||
|
} else {
|
||||||
|
p.Header.Port = port
|
||||||
}
|
}
|
||||||
|
|
||||||
//handle data length
|
//handle data length
|
||||||
pos = pos + end + 1
|
if dataLen, err := strconv.Atoi(parts["data length"]); err != nil {
|
||||||
end = bytes.IndexAny(b[pos:], ",")
|
return nil, fmt.Errorf("Error converting data length %q: %v", parts["data length"], err)
|
||||||
if end == -1 {
|
} else if dataLen != len(data) {
|
||||||
err = fmt.Errorf("missing , while parsing address")
|
return nil, fmt.Errorf("Data length %d doesn't match received length %d", dataLen, len(data))
|
||||||
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
|
//handle Service
|
||||||
pos = pos + end + 1
|
p.Header.Service = parts["service"]
|
||||||
end = pos + int(p.Header.HeaderLen)
|
|
||||||
p.Header.Service = string(b[pos : p.Header.HeaderLen+2])
|
|
||||||
|
|
||||||
//handle payload
|
//handle payload
|
||||||
pos = int(p.Header.HeaderLen + 2)
|
p.Data.AppendBytes(data)
|
||||||
p.Data.AppendBytes(b[pos:])
|
return p, nil
|
||||||
|
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Version %d not supported", b[0:0])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil, fmt.Errorf("Version %d not supported", 255-b[0])
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//PackV1 -- Outputs version 1 of packer
|
//PackV1 -- Outputs version 1 of packer
|
||||||
func (p *Packer) PackV1() (b bytes.Buffer) {
|
func (p *Packer) PackV1() bytes.Buffer {
|
||||||
version := packerV1
|
header := strings.Join([]string{
|
||||||
|
p.Header.FamilyText(),
|
||||||
var headerBuf bytes.Buffer
|
p.Header.AddressString(),
|
||||||
headerBuf.WriteString(p.Header.FamilyText())
|
strconv.Itoa(p.Header.Port),
|
||||||
headerBuf.WriteString(",")
|
strconv.Itoa(p.Data.DataLen()),
|
||||||
headerBuf.Write([]byte(p.Header.Address().String()))
|
p.Header.Service,
|
||||||
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
|
var buf bytes.Buffer
|
||||||
buf.Write(metaBuf.Bytes())
|
buf.WriteByte(packerV1)
|
||||||
buf.Write(headerBuf.Bytes())
|
buf.WriteByte(byte(len(header)))
|
||||||
buf.Write(p.Data.buffer.Bytes())
|
buf.WriteString(header)
|
||||||
|
buf.Write(p.Data.Data())
|
||||||
|
|
||||||
//fmt.Println("header: ", headerBuf.String())
|
return buf
|
||||||
//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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,31 +1,31 @@
|
||||||
package packer
|
package packer
|
||||||
|
|
||||||
import "bytes"
|
import (
|
||||||
|
"bytes"
|
||||||
|
)
|
||||||
|
|
||||||
//packerData -- Contains packer data
|
//packerData -- Contains packer data
|
||||||
type packerData struct {
|
type packerData struct {
|
||||||
buffer *bytes.Buffer
|
buffer bytes.Buffer
|
||||||
DataLen int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPackerData() (p *packerData) {
|
func newPackerData() *packerData {
|
||||||
p = new(packerData)
|
return new(packerData)
|
||||||
p.buffer = new(bytes.Buffer)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerData) AppendString(dataString string) (n int, err error) {
|
func (p *packerData) AppendString(dataString string) (int, error) {
|
||||||
n, err = p.buffer.WriteString(dataString)
|
return p.buffer.WriteString(dataString)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerData) AppendBytes(dataBytes []byte) (n int, err error) {
|
func (p *packerData) AppendBytes(dataBytes []byte) (int, error) {
|
||||||
n, err = p.buffer.Write(dataBytes)
|
return p.buffer.Write(dataBytes)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Data --
|
//Data --
|
||||||
func (p *packerData) Data() (b []byte) {
|
func (p *packerData) Data() []byte {
|
||||||
b = p.buffer.Bytes()
|
return p.buffer.Bytes()
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func (p *packerData) DataLen() int {
|
||||||
|
return p.buffer.Len()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package packer
|
package packer
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
import "fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
type addressFamily int
|
type addressFamily int
|
||||||
|
|
||||||
|
@ -11,7 +13,6 @@ type packerHeader struct {
|
||||||
address net.IP
|
address net.IP
|
||||||
Port int
|
Port int
|
||||||
Service string
|
Service string
|
||||||
HeaderLen byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Family -- ENUM for Address Family
|
//Family -- ENUM for Address Family
|
||||||
|
@ -30,52 +31,42 @@ func newPackerHeader() (p *packerHeader) {
|
||||||
p.SetAddress("127.0.0.1")
|
p.SetAddress("127.0.0.1")
|
||||||
p.Port = 65535
|
p.Port = 65535
|
||||||
p.Service = "na"
|
p.Service = "na"
|
||||||
p.HeaderLen = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//SetAddress -- Set Address. which sets address family automatically
|
//SetAddress -- Set Address. which sets address family automatically
|
||||||
func (p *packerHeader) SetAddress(addr string) {
|
func (p *packerHeader) SetAddress(addr string) {
|
||||||
p.address = net.ParseIP(addr)
|
p.address = net.ParseIP(addr)
|
||||||
err := p.address.To4()
|
|
||||||
|
|
||||||
if err != nil {
|
if p.address.To4() != nil {
|
||||||
p.family = FamilyIPv4
|
p.family = FamilyIPv4
|
||||||
} else {
|
} else if p.address.To16() != nil {
|
||||||
err := p.address.To16()
|
|
||||||
if err != nil {
|
|
||||||
p.family = FamilyIPv6
|
p.family = FamilyIPv6
|
||||||
} else {
|
} else {
|
||||||
panic(fmt.Sprintf("setAddress does not support %s", addr))
|
panic(fmt.Sprintf("setAddress does not support %q", addr))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerHeader) AddressBytes() (b []byte) {
|
func (p *packerHeader) AddressBytes() []byte {
|
||||||
b = make([]byte, 16)
|
if ip4 := p.address.To4(); ip4 != nil {
|
||||||
|
p.address = ip4
|
||||||
switch {
|
|
||||||
case p.address.To4() != nil:
|
|
||||||
b = make([]byte, 4)
|
|
||||||
for pos := range b {
|
|
||||||
b[pos] = p.address[pos+12]
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
return []byte(p.address)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerHeader) Address() (address net.IP) {
|
func (p *packerHeader) AddressString() string {
|
||||||
address = p.address
|
return p.address.String()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerHeader) Family() (family addressFamily) {
|
func (p *packerHeader) Address() net.IP {
|
||||||
family = p.family
|
return p.address
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *packerHeader) FamilyText() (familyText string) {
|
func (p *packerHeader) Family() addressFamily {
|
||||||
familyText = addressFamilyText[p.family]
|
return p.family
|
||||||
return
|
}
|
||||||
|
|
||||||
|
func (p *packerHeader) FamilyText() string {
|
||||||
|
return addressFamilyText[p.family]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
package packer
|
package packer
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
import "os"
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
loginfo *log.Logger
|
loginfo *log.Logger
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//ConnectionStatsAPI --
|
||||||
|
type ConnectionStatsAPI struct {
|
||||||
|
Connections int64 `json:"current_connections"`
|
||||||
|
TotalConnections int64 `json:"total_connections"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewConnectionStatsAPI -- Consttuctor
|
||||||
|
func NewConnectionStatsAPI(connections int64, totalConnections int64) (p *ConnectionStatsAPI) {
|
||||||
|
p = new(ConnectionStatsAPI)
|
||||||
|
p.Connections = connections
|
||||||
|
p.TotalConnections = totalConnections
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//DomainsAPI -- A collections of all the domains
|
||||||
|
//List of Domains -> DomainAPI
|
||||||
|
//DomainsAPI -> DomainServerAPI
|
||||||
|
//
|
||||||
|
|
||||||
|
//DomainServerAPI -- Container for Server Stats related to a domain
|
||||||
|
type DomainServerAPI struct {
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
Traffic TrafficAPI `json:"traffic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainServerAPI -- Constructor
|
||||||
|
func NewDomainServerAPI(domain string, conn *Connection) (p *DomainServerAPI) {
|
||||||
|
p = new(DomainServerAPI)
|
||||||
|
dt := conn.DomainTrack[domain]
|
||||||
|
p.Traffic.BytesIn = dt.BytesIn()
|
||||||
|
p.Traffic.BytesOut = dt.BytesOut()
|
||||||
|
p.Traffic.Requests = dt.Requests()
|
||||||
|
p.Traffic.Responses = dt.Responses()
|
||||||
|
p.ServerName = conn.ServerName()
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//DomainAPI -- Container for domain and related servers
|
||||||
|
type DomainAPI struct {
|
||||||
|
DomainName string `json:"domain_name"`
|
||||||
|
TotalServers int `json:"server_total"`
|
||||||
|
Servers []*DomainServerAPI `json:"servers"`
|
||||||
|
Traffic TrafficAPI `json:"traffic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainAPI -- Constructor
|
||||||
|
func NewDomainAPI(domain string, domainLoadBalance *DomainLoadBalance) (p *DomainAPI) {
|
||||||
|
p = new(DomainAPI)
|
||||||
|
p.DomainName = domain
|
||||||
|
for pos := range domainLoadBalance.connections {
|
||||||
|
ds := NewDomainServerAPI(domain, domainLoadBalance.connections[pos])
|
||||||
|
p.Servers = append(p.Servers, ds)
|
||||||
|
p.TotalServers++
|
||||||
|
p.Traffic.BytesIn += domainLoadBalance.connections[pos].BytesIn()
|
||||||
|
p.Traffic.BytesOut += domainLoadBalance.connections[pos].BytesOut()
|
||||||
|
p.Traffic.Requests += domainLoadBalance.connections[pos].requests
|
||||||
|
p.Traffic.Responses += domainLoadBalance.connections[pos].responses
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//DomainsAPI -- Container for Domains
|
||||||
|
type DomainsAPI struct {
|
||||||
|
TotalDomains int `json:"domain_total"`
|
||||||
|
Domains []*DomainAPI `json:"domains"`
|
||||||
|
Traffic TrafficAPI `json:"traffic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainsAPI -- Constructor
|
||||||
|
func NewDomainsAPI(domains map[string]*DomainLoadBalance) (p *DomainsAPI) {
|
||||||
|
p = new(DomainsAPI)
|
||||||
|
for domain := range domains {
|
||||||
|
d := NewDomainAPI(domain, domains[domain])
|
||||||
|
p.Domains = append(p.Domains, d)
|
||||||
|
p.Traffic.BytesIn += d.Traffic.BytesIn
|
||||||
|
p.Traffic.BytesOut += d.Traffic.BytesOut
|
||||||
|
p.Traffic.Requests += d.Traffic.Requests
|
||||||
|
p.Traffic.Responses += d.Traffic.Responses
|
||||||
|
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
//ServerAPI -- Structure to support the server API
|
||||||
|
type ServerAPI struct {
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
ServerID int64 `json:"server_id"`
|
||||||
|
Domains []*ServerDomainAPI `json:"domains"`
|
||||||
|
Duration float64 `json:"duration"`
|
||||||
|
Idle float64 `json:"idle"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
Source string `json:"source_address"`
|
||||||
|
State bool `json:"server_state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerAPI - Constructor
|
||||||
|
func NewServerAPI(c *Connection) (s *ServerAPI) {
|
||||||
|
s = new(ServerAPI)
|
||||||
|
s.ServerName = c.ServerName()
|
||||||
|
s.ServerID = c.ConnectionID()
|
||||||
|
s.Domains = make([]*ServerDomainAPI, 0)
|
||||||
|
s.Duration = time.Since(c.ConnectTime()).Seconds()
|
||||||
|
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
||||||
|
s.BytesIn = c.BytesIn()
|
||||||
|
s.BytesOut = c.BytesOut()
|
||||||
|
s.Source = c.source
|
||||||
|
|
||||||
|
for domainName := range c.DomainTrack {
|
||||||
|
|
||||||
|
domainAPI := NewServerDomainAPI(c, c.DomainTrack[domainName])
|
||||||
|
s.Domains = append(s.Domains, domainAPI)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//ServerDomainsAPI -- Structure to support the server API
|
||||||
|
type ServerDomainsAPI struct {
|
||||||
|
DomainName string `json:"domain_name"`
|
||||||
|
ServerID int64 `json:"server_id"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Responses int64 `json:"responses"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerDomainsAPI - Constructor
|
||||||
|
func NewServerDomainsAPI(c *Connection, d *DomainTrack) (s *ServerDomainsAPI) {
|
||||||
|
s = new(ServerDomainsAPI)
|
||||||
|
s.DomainName = d.DomainName
|
||||||
|
s.ServerID = c.ConnectionID()
|
||||||
|
s.BytesIn = d.BytesIn()
|
||||||
|
s.BytesOut = d.BytesOut()
|
||||||
|
s.Requests = d.requests
|
||||||
|
s.Responses = d.responses
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ServerDomainsAPIContainer -- Holder for all the Servers
|
||||||
|
type ServerDomainsAPIContainer struct {
|
||||||
|
Domains []*ServerDomainsAPI `json:"domains"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerDomainsAPIContainer -- Constructor
|
||||||
|
func NewServerDomainsAPIContainer() (p *ServerDomainsAPIContainer) {
|
||||||
|
p = new(ServerDomainsAPIContainer)
|
||||||
|
p.Domains = make([]*ServerDomainsAPI, 0)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
//ServerDomainAPI -- Structure to support the server API
|
||||||
|
type ServerDomainAPI struct {
|
||||||
|
DomainName string `json:"domain_name"`
|
||||||
|
ServerID int64 `json:"server_id"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Responses int64 `json:"responses"`
|
||||||
|
Source string `json:"source_addr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerDomainAPI - Constructor
|
||||||
|
func NewServerDomainAPI(c *Connection, d *DomainTrack) (s *ServerDomainAPI) {
|
||||||
|
s = new(ServerDomainAPI)
|
||||||
|
s.DomainName = d.DomainName
|
||||||
|
s.ServerID = c.ConnectionID()
|
||||||
|
s.BytesIn = d.BytesIn()
|
||||||
|
s.BytesOut = d.BytesOut()
|
||||||
|
s.Requests = d.requests
|
||||||
|
s.Responses = d.responses
|
||||||
|
s.Source = c.Source()
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,53 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
//ServersAPI -- Structure to support the server API
|
||||||
|
type ServersAPI struct {
|
||||||
|
ServerName string `json:"server_name"`
|
||||||
|
ServerID int64 `json:"server_id"`
|
||||||
|
Domains []*ServerDomainAPI `json:"domains"`
|
||||||
|
Duration float64 `json:"duration"`
|
||||||
|
Idle float64 `json:"idle"`
|
||||||
|
BytesIn int64 `json:"bytes_in"`
|
||||||
|
BytesOut int64 `json:"bytes_out"`
|
||||||
|
Requests int64 `json:"requests"`
|
||||||
|
Responses int64 `json:"responses"`
|
||||||
|
Source string `json:"source_address"`
|
||||||
|
State bool `json:"server_state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServersAPI - Constructor
|
||||||
|
func NewServersAPI(c *Connection) (s *ServersAPI) {
|
||||||
|
s = new(ServersAPI)
|
||||||
|
s.ServerName = c.ServerName()
|
||||||
|
s.ServerID = c.ConnectionID()
|
||||||
|
s.Domains = make([]*ServerDomainAPI, 0)
|
||||||
|
s.Duration = time.Since(c.ConnectTime()).Seconds()
|
||||||
|
s.Idle = time.Since(c.LastUpdate()).Seconds()
|
||||||
|
s.BytesIn = c.BytesIn()
|
||||||
|
s.BytesOut = c.BytesOut()
|
||||||
|
s.Requests = c.requests
|
||||||
|
s.Responses = c.responses
|
||||||
|
s.Source = c.Source()
|
||||||
|
s.State = c.State()
|
||||||
|
|
||||||
|
for d := range c.DomainTrack {
|
||||||
|
dt := c.DomainTrack[d]
|
||||||
|
domainAPI := NewServerDomainAPI(c, dt)
|
||||||
|
s.Domains = append(s.Domains, domainAPI)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//ServerAPIContainer -- Holder for all the Servers
|
||||||
|
type ServerAPIContainer struct {
|
||||||
|
Servers []*ServersAPI `json:"servers"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewServerAPIContainer -- Constructor
|
||||||
|
func NewServerAPIContainer() (p *ServerAPIContainer) {
|
||||||
|
p = new(ServerAPIContainer)
|
||||||
|
p.Servers = make([]*ServersAPI, 0)
|
||||||
|
return p
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//StatusAPI -- Structure to support the server API
|
||||||
|
type StatusAPI struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Uptime float64 `json:"uptime"`
|
||||||
|
WssDomain string `json:"wss_domain"`
|
||||||
|
AdminDomain string `json:"admin_domain"`
|
||||||
|
LoadbalanceDefaultMethod string `json:"loadbalance_default_method"`
|
||||||
|
DeadTime *StatusDeadTimeAPI `json:"dead_time"`
|
||||||
|
AdminStats *TrafficAPI `json:"admin_traffic"`
|
||||||
|
TrafficStats *TrafficAPI `json:"traffic"`
|
||||||
|
ExtConnections *ConnectionStatsAPI `json:"ext_connections"`
|
||||||
|
WSSConnections *ConnectionStatsAPI `json:"wss_connections"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewStatusAPI - Constructor
|
||||||
|
func NewStatusAPI(c *Status) (s *StatusAPI) {
|
||||||
|
s = new(StatusAPI)
|
||||||
|
s.Name = c.Name
|
||||||
|
s.Uptime = time.Since(c.StartTime).Seconds()
|
||||||
|
s.WssDomain = c.WssDomain
|
||||||
|
s.AdminDomain = c.AdminDomain
|
||||||
|
s.LoadbalanceDefaultMethod = c.LoadbalanceDefaultMethod
|
||||||
|
s.DeadTime = NewStatusDeadTimeAPI(c.DeadTime.dwell, c.DeadTime.idle, c.DeadTime.cancelcheck)
|
||||||
|
s.AdminStats = NewTrafficAPI(c.AdminStats.Requests, c.AdminStats.Responses, c.AdminStats.BytesIn, c.AdminStats.BytesOut)
|
||||||
|
s.TrafficStats = NewTrafficAPI(c.TrafficStats.Requests, c.TrafficStats.Responses, c.TrafficStats.BytesIn, c.TrafficStats.BytesOut)
|
||||||
|
s.ExtConnections = NewConnectionStatsAPI(c.ExtConnections.Connections, c.ExtConnections.TotalConnections)
|
||||||
|
s.WSSConnections = NewConnectionStatsAPI(c.WSSConnections.Connections, c.ExtConnections.TotalConnections)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//StatusDeadTimeAPI -- structure for deadtime configuration
|
||||||
|
type StatusDeadTimeAPI struct {
|
||||||
|
Dwell int `json:"dwell"`
|
||||||
|
Idle int `json:"idle"`
|
||||||
|
Cancelcheck int `json:"cancel_check"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewStatusDeadTimeAPI -- constructor
|
||||||
|
func NewStatusDeadTimeAPI(dwell int, idle int, cancelcheck int) (p *StatusDeadTimeAPI) {
|
||||||
|
p = new(StatusDeadTimeAPI)
|
||||||
|
p.Dwell = dwell
|
||||||
|
p.Idle = idle
|
||||||
|
p.Cancelcheck = cancelcheck
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//TrafficStats --
|
||||||
|
type TrafficAPI struct {
|
||||||
|
Requests int64
|
||||||
|
Responses int64
|
||||||
|
BytesIn int64
|
||||||
|
BytesOut int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTrafficStats -- Consttuctor
|
||||||
|
func NewTrafficAPI(requests, responses, bytesIn, bytesOut int64) (p *TrafficAPI) {
|
||||||
|
p = new(TrafficAPI)
|
||||||
|
p.Requests = requests
|
||||||
|
p.Responses = responses
|
||||||
|
p.BytesIn = bytesIn
|
||||||
|
p.BytesOut = bytesOut
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,205 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
|
||||||
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/envelope"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
endPointPrefix = "/api/com.daplie.tunnel/"
|
||||||
|
)
|
||||||
|
|
||||||
|
var connectionTable *Table
|
||||||
|
var serverStatus *Status
|
||||||
|
var serverStatusAPI *Status
|
||||||
|
|
||||||
|
//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) {
|
||||||
|
serverStatus = ctx.Value(ctxServerStatus).(*Status)
|
||||||
|
|
||||||
|
connectionTable = serverStatus.ConnectionTable
|
||||||
|
serverStatusAPI = serverStatus
|
||||||
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
|
|
||||||
|
router.PathPrefix("/admin/").Handler(http.StripPrefix("/admin/", http.FileServer(http.Dir("html/admin"))))
|
||||||
|
|
||||||
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
loginfo.Println("HandleFunc /")
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
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)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
http.Error(w, "Not Found", 404)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.HandleFunc(endPointPrefix+"domains", getDomainsEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"domain/", getDomainEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"domain/{domain-name}", getDomainEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"servers", getServersEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"server/", getServerEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"server/{server-id}", getServerEndpoint).Methods("GET")
|
||||||
|
router.HandleFunc(endPointPrefix+"status/", getStatusEndpoint).Methods("GET")
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStatusEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
loginfo.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
statusContainer := NewStatusAPI(serverStatusAPI)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
|
env := envelope.NewEnvelope("domains/GET")
|
||||||
|
env.Result = statusContainer
|
||||||
|
env.GenerateWriter(w)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDomainsEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
loginfo.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
domainsContainer := NewDomainsAPI(connectionTable.domains)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
|
env := envelope.NewEnvelope("domains/GET")
|
||||||
|
env.Result = domainsContainer
|
||||||
|
env.GenerateWriter(w)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDomainEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
loginfo.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
env := envelope.NewEnvelope("domain/GET")
|
||||||
|
|
||||||
|
params := mux.Vars(r)
|
||||||
|
if id, ok := params["domain-name"]; !ok {
|
||||||
|
env.Error = "domain-name is missing"
|
||||||
|
env.ErrorURI = r.RequestURI
|
||||||
|
env.ErrorDescription = "domain API requires a domain-name"
|
||||||
|
} else {
|
||||||
|
domainName := id
|
||||||
|
if domainLB, ok := connectionTable.domains[domainName]; !ok {
|
||||||
|
env.Error = "domain-name was not found"
|
||||||
|
env.ErrorURI = r.RequestURI
|
||||||
|
env.ErrorDescription = "domain-name not found"
|
||||||
|
} else {
|
||||||
|
var domainAPIContainer []*ServerDomainAPI
|
||||||
|
conns := domainLB.Connections()
|
||||||
|
for pos := range conns {
|
||||||
|
conn := conns[pos]
|
||||||
|
domainAPI := NewServerDomainAPI(conn, conn.DomainTrack[domainName])
|
||||||
|
domainAPIContainer = append(domainAPIContainer, domainAPI)
|
||||||
|
}
|
||||||
|
env.Result = domainAPIContainer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
env.GenerateWriter(w)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServersEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
loginfo.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
serverContainer := NewServerAPIContainer()
|
||||||
|
|
||||||
|
for c := range connectionTable.Connections() {
|
||||||
|
serverAPI := NewServersAPI(c)
|
||||||
|
serverContainer.Servers = append(serverContainer.Servers, serverAPI)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
|
||||||
|
env := envelope.NewEnvelope("servers/GET")
|
||||||
|
env.Result = serverContainer
|
||||||
|
env.GenerateWriter(w)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||||
|
pc, _, _, _ := runtime.Caller(0)
|
||||||
|
loginfo.Println(runtime.FuncForPC(pc).Name())
|
||||||
|
|
||||||
|
serverStatus.AdminStats.IncRequests()
|
||||||
|
|
||||||
|
env := envelope.NewEnvelope("server/GET")
|
||||||
|
|
||||||
|
params := mux.Vars(r)
|
||||||
|
if id, ok := params["server-id"]; !ok {
|
||||||
|
env.Error = "server-id is missing"
|
||||||
|
env.ErrorURI = r.RequestURI
|
||||||
|
env.ErrorDescription = "server API requires a server-id"
|
||||||
|
} else {
|
||||||
|
serverID, err := strconv.Atoi(id)
|
||||||
|
if err != nil {
|
||||||
|
env.Error = "server-id is not an integer"
|
||||||
|
env.ErrorURI = r.RequestURI
|
||||||
|
env.ErrorDescription = "server API requires a server-id"
|
||||||
|
|
||||||
|
} else {
|
||||||
|
conn, err := connectionTable.GetConnection(int64(serverID))
|
||||||
|
if err != nil {
|
||||||
|
env.Error = "server-id was not found"
|
||||||
|
env.ErrorURI = r.RequestURI
|
||||||
|
env.ErrorDescription = "missing server-id, make sure desired service-id is in servers"
|
||||||
|
} else {
|
||||||
|
loginfo.Println("test")
|
||||||
|
serverAPI := NewServerAPI(conn)
|
||||||
|
env.Result = serverAPI
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
|
||||||
|
env.GenerateWriter(w)
|
||||||
|
serverStatus.AdminStats.IncResponses()
|
||||||
|
}
|
|
@ -1,8 +1,11 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import "net"
|
import (
|
||||||
import "context"
|
"context"
|
||||||
import "fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
//Track -- used to track connection + domain
|
//Track -- used to track connection + domain
|
||||||
type Track struct {
|
type Track struct {
|
||||||
|
@ -20,6 +23,7 @@ func NewTrack(conn net.Conn, domain string) (p *Track) {
|
||||||
|
|
||||||
//Tracking --
|
//Tracking --
|
||||||
type Tracking struct {
|
type Tracking struct {
|
||||||
|
mutex sync.Mutex
|
||||||
connections map[string]*Track
|
connections map[string]*Track
|
||||||
register chan *Track
|
register chan *Track
|
||||||
unregister chan net.Conn
|
unregister chan net.Conn
|
||||||
|
@ -46,18 +50,22 @@ func (p *Tracking) Run(ctx context.Context) {
|
||||||
return
|
return
|
||||||
|
|
||||||
case connection := <-p.register:
|
case connection := <-p.register:
|
||||||
|
p.mutex.Lock()
|
||||||
key := connection.conn.RemoteAddr().String()
|
key := connection.conn.RemoteAddr().String()
|
||||||
loginfo.Println("register fired", key)
|
loginfo.Println("register fired", key)
|
||||||
p.connections[key] = connection
|
p.connections[key] = connection
|
||||||
p.list()
|
p.list()
|
||||||
|
p.mutex.Unlock()
|
||||||
|
|
||||||
case connection := <-p.unregister:
|
case connection := <-p.unregister:
|
||||||
|
p.mutex.Lock()
|
||||||
key := connection.RemoteAddr().String()
|
key := connection.RemoteAddr().String()
|
||||||
loginfo.Println("unregister fired", key)
|
loginfo.Println("unregister fired", key)
|
||||||
if _, ok := p.connections[key]; ok {
|
if _, ok := p.connections[key]; ok {
|
||||||
delete(p.connections, key)
|
delete(p.connections, key)
|
||||||
}
|
}
|
||||||
p.list()
|
p.list()
|
||||||
|
p.mutex.Unlock()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -70,12 +78,12 @@ func (p *Tracking) list() {
|
||||||
|
|
||||||
//Lookup --
|
//Lookup --
|
||||||
// - get connection from key
|
// - get connection from key
|
||||||
func (p *Tracking) Lookup(key string) (c *Track, err error) {
|
func (p *Tracking) Lookup(key string) (*Track, error) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
if _, ok := p.connections[key]; ok {
|
if _, ok := p.connections[key]; ok {
|
||||||
c = p.connections[key]
|
return p.connections[key], nil
|
||||||
} else {
|
|
||||||
err = fmt.Errorf("Lookup failed for %s", key)
|
|
||||||
c = nil
|
|
||||||
}
|
}
|
||||||
return
|
return nil, fmt.Errorf("Lookup failed for %s", key)
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
@ -28,7 +28,7 @@ func NewWedgeConnSize(c net.Conn, size int) (p *WedgeConn) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Discard - discard a number of bytes, perhaps after peeking at the
|
//Discard - discard a number of bytes, perhaps after peeking at the
|
||||||
func (w *WedgeConn) Discard(n int) (discarded int, err error) {
|
func (w *WedgeConn) Discard(n int) (int, error) {
|
||||||
return w.reader.Discard(n)
|
return w.reader.Discard(n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,8 +44,7 @@ func (w *WedgeConn) ReadByte() (byte, error) {
|
||||||
|
|
||||||
//Read -- A normal reader.
|
//Read -- A normal reader.
|
||||||
func (w *WedgeConn) Read(p []byte) (int, error) {
|
func (w *WedgeConn) Read(p []byte) (int, error) {
|
||||||
cnt, err := w.reader.Read(p)
|
return w.reader.Read(p)
|
||||||
return cnt, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Buffered --
|
//Buffered --
|
||||||
|
@ -56,13 +55,12 @@ func (w *WedgeConn) Buffered() int {
|
||||||
//PeekAll --
|
//PeekAll --
|
||||||
// - get all the chars available
|
// - get all the chars available
|
||||||
// - pass then back
|
// - pass then back
|
||||||
func (w *WedgeConn) PeekAll() (buf []byte, err error) {
|
func (w *WedgeConn) PeekAll() ([]byte, error) {
|
||||||
|
// We first peek with 1 so that if there is no buffered data the reader will
|
||||||
_, err = w.Peek(1)
|
// fill the buffer before we read how much data is buffered.
|
||||||
if err != nil {
|
if _, err := w.Peek(1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err = w.Peek(w.Buffered())
|
return w.Peek(w.Buffered())
|
||||||
return
|
|
||||||
}
|
}
|
|
@ -1,30 +1,20 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
|
||||||
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
)
|
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||||
ReadBufferSize: 4096,
|
)
|
||||||
WriteBufferSize: 4096,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connection track websocket and faciliates in and out data
|
// Connection track websocket and faciliates in and out data
|
||||||
type Connection struct {
|
type Connection struct {
|
||||||
mutex *sync.Mutex
|
mutex sync.Mutex
|
||||||
|
|
||||||
// The main connection table (should be just one of these created at startup)
|
// The main connection table (should be just one of these created at startup)
|
||||||
connectionTable *Table
|
connectionTable *Table
|
||||||
|
@ -44,12 +34,21 @@ type Connection struct {
|
||||||
// Address of the Remote End Point
|
// Address of the Remote End Point
|
||||||
source string
|
source string
|
||||||
|
|
||||||
|
// serverName -- Name of the server, at this point 1st domain registered. Will likely change with JWT
|
||||||
|
serverName string
|
||||||
|
|
||||||
// bytes in
|
// bytes in
|
||||||
bytesIn int64
|
bytesIn int64
|
||||||
|
|
||||||
// bytes out
|
// bytes out
|
||||||
bytesOut int64
|
bytesOut int64
|
||||||
|
|
||||||
|
// requests
|
||||||
|
requests int64
|
||||||
|
|
||||||
|
// response
|
||||||
|
responses int64
|
||||||
|
|
||||||
// Connect Time
|
// Connect Time
|
||||||
connectTime time.Time
|
connectTime time.Time
|
||||||
|
|
||||||
|
@ -63,28 +62,38 @@ type Connection struct {
|
||||||
|
|
||||||
///wssState tracks a highlevel status of the connection, false means do nothing.
|
///wssState tracks a highlevel status of the connection, false means do nothing.
|
||||||
wssState bool
|
wssState bool
|
||||||
|
|
||||||
|
//connectionID
|
||||||
|
connectionID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewConnection -- Constructor
|
//NewConnection -- Constructor
|
||||||
func NewConnection(connectionTable *Table, conn *websocket.Conn, remoteAddress string, initialDomains []interface{}, connectionTrack *Tracking) (p *Connection) {
|
func NewConnection(connectionTable *Table, conn *websocket.Conn, remoteAddress string,
|
||||||
|
initialDomains []interface{}, connectionTrack *Tracking, serverName string) (p *Connection) {
|
||||||
|
connectionID = connectionID + 1
|
||||||
|
|
||||||
p = new(Connection)
|
p = new(Connection)
|
||||||
p.mutex = &sync.Mutex{}
|
|
||||||
p.connectionTable = connectionTable
|
p.connectionTable = connectionTable
|
||||||
p.conn = conn
|
p.conn = conn
|
||||||
p.source = remoteAddress
|
p.source = remoteAddress
|
||||||
|
p.serverName = serverName
|
||||||
p.bytesIn = 0
|
p.bytesIn = 0
|
||||||
p.bytesOut = 0
|
p.bytesOut = 0
|
||||||
|
p.requests = 0
|
||||||
|
p.responses = 0
|
||||||
p.send = make(chan *SendTrack)
|
p.send = make(chan *SendTrack)
|
||||||
p.connectTime = time.Now()
|
p.connectTime = time.Now()
|
||||||
p.initialDomains = initialDomains
|
p.initialDomains = initialDomains
|
||||||
p.connectionTrack = connectionTrack
|
p.connectionTrack = connectionTrack
|
||||||
p.DomainTrack = make(map[string]*DomainTrack)
|
p.DomainTrack = make(map[string]*DomainTrack)
|
||||||
|
p.lastUpdate = time.Now()
|
||||||
|
|
||||||
for _, domain := range initialDomains {
|
for _, domain := range initialDomains {
|
||||||
p.AddTrackedDomain(string(domain.(string)))
|
p.AddTrackedDomain(string(domain.(string)))
|
||||||
}
|
}
|
||||||
|
|
||||||
p.State(true)
|
p.SetState(true)
|
||||||
|
p.connectionID = connectionID
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,28 +104,34 @@ func (c *Connection) AddTrackedDomain(domain string) {
|
||||||
c.DomainTrack[domain] = p
|
c.DomainTrack[domain] = p
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//ServerName -- Property
|
||||||
|
func (c *Connection) ServerName() string {
|
||||||
|
return c.serverName
|
||||||
|
}
|
||||||
|
|
||||||
|
//SetServerName -- Setter
|
||||||
|
func (c *Connection) SetServerName(serverName string) {
|
||||||
|
c.serverName = serverName
|
||||||
|
}
|
||||||
|
|
||||||
//InitialDomains -- Property
|
//InitialDomains -- Property
|
||||||
func (c *Connection) InitialDomains() (i []interface{}) {
|
func (c *Connection) InitialDomains() []interface{} {
|
||||||
i = c.initialDomains
|
return c.initialDomains
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ConnectTime -- Property
|
//ConnectTime -- Property
|
||||||
func (c *Connection) ConnectTime() (t time.Time) {
|
func (c *Connection) ConnectTime() time.Time {
|
||||||
t = c.connectTime
|
return c.connectTime
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//BytesIn -- Property
|
//BytesIn -- Property
|
||||||
func (c *Connection) BytesIn() (b int64) {
|
func (c *Connection) BytesIn() int64 {
|
||||||
b = c.bytesIn
|
return c.bytesIn
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//BytesOut -- Property
|
//BytesOut -- Property
|
||||||
func (c *Connection) BytesOut() (b int64) {
|
func (c *Connection) BytesOut() int64 {
|
||||||
b = c.bytesOut
|
return c.bytesOut
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//SendCh -- property to sending channel
|
//SendCh -- property to sending channel
|
||||||
|
@ -124,6 +139,11 @@ func (c *Connection) SendCh() chan *SendTrack {
|
||||||
return c.send
|
return c.send
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Source --
|
||||||
|
func (c *Connection) Source() string {
|
||||||
|
return c.source
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Connection) addIn(num int64) {
|
func (c *Connection) addIn(num int64) {
|
||||||
c.bytesIn = c.bytesIn + num
|
c.bytesIn = c.bytesIn + num
|
||||||
}
|
}
|
||||||
|
@ -132,59 +152,74 @@ func (c *Connection) addOut(num int64) {
|
||||||
c.bytesOut = c.bytesOut + num
|
c.bytesOut = c.bytesOut + num
|
||||||
}
|
}
|
||||||
|
|
||||||
//ConnectionTable -- property
|
func (c *Connection) addRequests() {
|
||||||
func (c *Connection) ConnectionTable() (table *Table) {
|
c.requests = c.requests + 1
|
||||||
table = c.connectionTable
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetState -- Get state of Socket...this is a high level state.
|
func (c *Connection) addResponse() {
|
||||||
func (c *Connection) GetState() bool {
|
c.responses = c.responses + 1
|
||||||
defer func() {
|
}
|
||||||
c.mutex.Unlock()
|
|
||||||
}()
|
//ConnectionTable -- property
|
||||||
|
func (c *Connection) ConnectionTable() *Table {
|
||||||
|
return c.connectionTable
|
||||||
|
}
|
||||||
|
|
||||||
|
//State -- Get state of Socket...this is a high level state.
|
||||||
|
func (c *Connection) State() bool {
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
return c.wssState
|
return c.wssState
|
||||||
}
|
}
|
||||||
|
|
||||||
//State -- Set the set of the high level connection
|
//SetState -- Set the set of the high level connection
|
||||||
func (c *Connection) State(state bool) {
|
func (c *Connection) SetState(state bool) {
|
||||||
defer func() {
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
c.wssState = state
|
c.wssState = state
|
||||||
}
|
}
|
||||||
|
|
||||||
//Update -- updates the lastUpdate property tracking idle time
|
//Update -- updates the lastUpdate property tracking idle time
|
||||||
func (c *Connection) Update() {
|
func (c *Connection) Update() {
|
||||||
defer func() {
|
|
||||||
c.mutex.Unlock()
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.mutex.Lock()
|
c.mutex.Lock()
|
||||||
|
defer c.mutex.Unlock()
|
||||||
|
|
||||||
c.lastUpdate = time.Now()
|
c.lastUpdate = time.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//LastUpdate -- retrieve last update
|
||||||
|
func (c *Connection) LastUpdate() time.Time {
|
||||||
|
return c.lastUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
//ConnectionID - Get
|
||||||
|
func (c *Connection) ConnectionID() int64 {
|
||||||
|
return c.connectionID
|
||||||
|
}
|
||||||
|
|
||||||
//NextWriter -- Wrapper to allow a high level state check before offering NextWriter
|
//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
|
//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
|
//A synchronised state is maintained
|
||||||
func (c Connection) NextWriter(wssMessageType int) (w io.WriteCloser, err error) {
|
func (c *Connection) NextWriter(wssMessageType int) (io.WriteCloser, error) {
|
||||||
if c.GetState() == true {
|
if c.State() {
|
||||||
w, err = c.conn.NextWriter(wssMessageType)
|
return c.conn.NextWriter(wssMessageType)
|
||||||
} else {
|
|
||||||
loginfo.Println("NextWriter aborted, state is not true")
|
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
// Is returning a nil error actually the proper thing to do here?
|
||||||
|
loginfo.Println("NextWriter aborted, state is not true")
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Write -- Wrapper to allow a high level state check before allowing a write to the socket.
|
//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) {
|
func (c *Connection) Write(w io.WriteCloser, message []byte) (int, error) {
|
||||||
if c.GetState() == true {
|
if c.State() {
|
||||||
cnt, err = w.Write(message)
|
return w.Write(message)
|
||||||
}
|
}
|
||||||
return
|
|
||||||
|
// Is returning a nil error actually the proper thing to do here?
|
||||||
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
//Reader -- export the reader function
|
//Reader -- export the reader function
|
||||||
|
@ -199,17 +234,17 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
|
|
||||||
loginfo.Println("Reader Start ", c)
|
loginfo.Println("Reader Start ", c)
|
||||||
|
|
||||||
c.conn.SetReadLimit(65535)
|
//c.conn.SetReadLimit(65535)
|
||||||
for {
|
for {
|
||||||
msgType, message, err := c.conn.ReadMessage()
|
_, message, err := c.conn.ReadMessage()
|
||||||
|
|
||||||
loginfo.Println("ReadMessage", msgType, err)
|
//loginfo.Println("ReadMessage", msgType, err)
|
||||||
|
|
||||||
c.Update()
|
c.Update()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||||
c.State(false)
|
c.SetState(false)
|
||||||
loginfo.Printf("error: %v", err)
|
loginfo.Printf("error: %v", err)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -220,31 +255,31 @@ func (c *Connection) Reader(ctx context.Context) {
|
||||||
key := p.Header.Address().String() + ":" + strconv.Itoa(p.Header.Port)
|
key := p.Header.Address().String() + ":" + strconv.Itoa(p.Header.Port)
|
||||||
track, err := connectionTrack.Lookup(key)
|
track, err := connectionTrack.Lookup(key)
|
||||||
|
|
||||||
loginfo.Println(hex.Dump(p.Data.Data()))
|
//loginfo.Println(hex.Dump(p.Data.Data()))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
loginfo.Println("Unable to locate Tracking for ", key)
|
//loginfo.Println("Unable to locate Tracking for ", key)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
//Support for tracking outbound traffic based on domain.
|
//Support for tracking outbound traffic based on domain.
|
||||||
if domainTrack, ok := c.DomainTrack[track.domain]; ok {
|
if domainTrack, ok := c.DomainTrack[track.domain]; ok {
|
||||||
//if ok then add to structure, else warn there is something wrong
|
//if ok then add to structure, else warn there is something wrong
|
||||||
domainTrack.AddIn(int64(len(message)))
|
domainTrack.AddOut(int64(len(message)))
|
||||||
|
domainTrack.AddResponses()
|
||||||
}
|
}
|
||||||
|
|
||||||
track.conn.Write(p.Data.Data())
|
track.conn.Write(p.Data.Data())
|
||||||
|
|
||||||
c.addIn(int64(len(message)))
|
c.addIn(int64(len(message)))
|
||||||
loginfo.Println("end of read")
|
c.addResponse()
|
||||||
|
//loginfo.Println("end of read")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Writer -- expoer the writer function
|
//Writer -- expoer the writer function
|
||||||
func (c *Connection) Writer() {
|
func (c *Connection) Writer() {
|
||||||
defer func() {
|
defer c.conn.Close()
|
||||||
c.conn.Close()
|
|
||||||
}()
|
|
||||||
|
|
||||||
loginfo.Println("Writer Start ", c)
|
loginfo.Println("Writer Start ", c)
|
||||||
|
|
||||||
|
@ -255,6 +290,7 @@ func (c *Connection) Writer() {
|
||||||
w, err := c.NextWriter(websocket.BinaryMessage)
|
w, err := c.NextWriter(websocket.BinaryMessage)
|
||||||
loginfo.Println("next writer ", w)
|
loginfo.Println("next writer ", w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.SetState(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -269,11 +305,13 @@ func (c *Connection) Writer() {
|
||||||
messageLen := int64(len(message.data))
|
messageLen := int64(len(message.data))
|
||||||
|
|
||||||
c.addOut(messageLen)
|
c.addOut(messageLen)
|
||||||
|
c.addRequests()
|
||||||
|
|
||||||
//Support for tracking outbound traffic based on domain.
|
//Support for tracking outbound traffic based on domain.
|
||||||
if domainTrack, ok := c.DomainTrack[message.domain]; ok {
|
if domainTrack, ok := c.DomainTrack[message.domain]; ok {
|
||||||
//if ok then add to structure, else warn there is something wrong
|
//if ok then add to structure, else warn there is something wrong
|
||||||
domainTrack.AddOut(messageLen)
|
domainTrack.AddIn(messageLen)
|
||||||
|
domainTrack.AddRequests()
|
||||||
loginfo.Println("adding ", messageLen, " to ", message.domain)
|
loginfo.Println("adding ", messageLen, " to ", message.domain)
|
||||||
} else {
|
} else {
|
||||||
logdebug.Println("attempting to add bytes to ", message.domain, "it does not exist")
|
logdebug.Println("attempting to add bytes to ", message.domain, "it does not exist")
|
|
@ -1,6 +1,8 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import "github.com/gorilla/websocket"
|
import (
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
)
|
||||||
|
|
||||||
//Registration -- A connection registration structure used to bring up a connection
|
//Registration -- A connection registration structure used to bring up a connection
|
||||||
//connection table will then handle additing and sdtarting up the various readers
|
//connection table will then handle additing and sdtarting up the various readers
|
||||||
|
@ -12,6 +14,9 @@ type Registration struct {
|
||||||
// Address of the Remote End Point
|
// Address of the Remote End Point
|
||||||
source string
|
source string
|
||||||
|
|
||||||
|
// serverName
|
||||||
|
serverName string
|
||||||
|
|
||||||
// communications channel between go routines
|
// communications channel between go routines
|
||||||
commCh chan bool
|
commCh chan bool
|
||||||
|
|
||||||
|
@ -22,10 +27,11 @@ type Registration struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewRegistration -- Constructor
|
//NewRegistration -- Constructor
|
||||||
func NewRegistration(conn *websocket.Conn, remoteAddress string, initialDomains []interface{}, connectionTrack *Tracking) (p *Registration) {
|
func NewRegistration(conn *websocket.Conn, remoteAddress string, initialDomains []interface{}, connectionTrack *Tracking, serverName string) (p *Registration) {
|
||||||
p = new(Registration)
|
p = new(Registration)
|
||||||
p.conn = conn
|
p.conn = conn
|
||||||
p.source = remoteAddress
|
p.source = remoteAddress
|
||||||
|
p.serverName = serverName
|
||||||
p.commCh = make(chan bool)
|
p.commCh = make(chan bool)
|
||||||
p.initialDomains = initialDomains
|
p.initialDomains = initialDomains
|
||||||
p.connectionTrack = connectionTrack
|
p.connectionTrack = connectionTrack
|
|
@ -1,8 +1,10 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
import "time"
|
"context"
|
||||||
import "context"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
initialDomains = 0
|
initialDomains = 0
|
||||||
|
@ -12,7 +14,7 @@ const (
|
||||||
//Table maintains the set of connections
|
//Table maintains the set of connections
|
||||||
type Table struct {
|
type Table struct {
|
||||||
connections map[*Connection][]string
|
connections map[*Connection][]string
|
||||||
domains map[string]*Connection
|
domains map[string]*DomainLoadBalance
|
||||||
register chan *Registration
|
register chan *Registration
|
||||||
unregister chan *Connection
|
unregister chan *Connection
|
||||||
domainAnnounce chan *DomainMapping
|
domainAnnounce chan *DomainMapping
|
||||||
|
@ -22,10 +24,10 @@ type Table struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewTable -- consructor
|
//NewTable -- consructor
|
||||||
func NewTable(dwell int, idle int) (p *Table) {
|
func NewTable(dwell, idle int) (p *Table) {
|
||||||
p = new(Table)
|
p = new(Table)
|
||||||
p.connections = make(map[*Connection][]string)
|
p.connections = make(map[*Connection][]string)
|
||||||
p.domains = make(map[string]*Connection)
|
p.domains = make(map[string]*DomainLoadBalance)
|
||||||
p.register = make(chan *Registration)
|
p.register = make(chan *Registration)
|
||||||
p.unregister = make(chan *Connection)
|
p.unregister = make(chan *Connection)
|
||||||
p.domainAnnounce = make(chan *DomainMapping)
|
p.domainAnnounce = make(chan *DomainMapping)
|
||||||
|
@ -36,15 +38,23 @@ func NewTable(dwell int, idle int) (p *Table) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Connections Property
|
//Connections Property
|
||||||
func (c *Table) Connections() (table map[*Connection][]string) {
|
func (c *Table) Connections() map[*Connection][]string {
|
||||||
table = c.connections
|
return c.connections
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//ConnByDomain -- Obtains a connection from a domain announcement.
|
//ConnByDomain -- Obtains a connection from a domain announcement. A domain may be announced more than once
|
||||||
func (c *Table) ConnByDomain(domain string) (conn *Connection, ok bool) {
|
//if that is the case the system stores these connections and then sends traffic back round-robin
|
||||||
conn, ok = c.domains[domain]
|
//back to the WSS connections
|
||||||
return
|
func (c *Table) ConnByDomain(domain string) (*Connection, bool) {
|
||||||
|
for dn := range c.domains {
|
||||||
|
loginfo.Println(dn, domain)
|
||||||
|
}
|
||||||
|
if domainsLB, ok := c.domains[domain]; ok {
|
||||||
|
loginfo.Println("found")
|
||||||
|
conn := domainsLB.NextMember()
|
||||||
|
return conn, ok
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
//reaper --
|
//reaper --
|
||||||
|
@ -56,7 +66,7 @@ func (c *Table) reaper(delay int, idle int) {
|
||||||
|
|
||||||
loginfo.Println("Running scanning ", len(c.connections))
|
loginfo.Println("Running scanning ", len(c.connections))
|
||||||
for d := range c.connections {
|
for d := range c.connections {
|
||||||
if d.GetState() == false {
|
if !d.State() {
|
||||||
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
||||||
loginfo.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
loginfo.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
||||||
delete(c.connections, d)
|
delete(c.connections, d)
|
||||||
|
@ -66,8 +76,19 @@ func (c *Table) reaper(delay int, idle int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//GetConnection -- find connection by server-id
|
||||||
|
func (c *Table) GetConnection(serverID int64) (*Connection, error) {
|
||||||
|
for conn := range c.connections {
|
||||||
|
if conn.ConnectionID() == serverID {
|
||||||
|
return conn, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Server-id %d not found", serverID)
|
||||||
|
}
|
||||||
|
|
||||||
//Run -- Execute
|
//Run -- Execute
|
||||||
func (c *Table) Run(ctx context.Context) {
|
func (c *Table) Run(ctx context.Context, defaultMethod string) {
|
||||||
loginfo.Println("ConnectionTable starting")
|
loginfo.Println("ConnectionTable starting")
|
||||||
|
|
||||||
go c.reaper(c.dwell, c.idle)
|
go c.reaper(c.dwell, c.idle)
|
||||||
|
@ -82,7 +103,8 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
case registration := <-c.register:
|
case registration := <-c.register:
|
||||||
loginfo.Println("register fired")
|
loginfo.Println("register fired")
|
||||||
|
|
||||||
connection := NewConnection(c, registration.conn, registration.source, registration.initialDomains, registration.connectionTrack)
|
connection := NewConnection(c, registration.conn, registration.source, registration.initialDomains,
|
||||||
|
registration.connectionTrack, registration.serverName)
|
||||||
c.connections[connection] = make([]string, initialDomains)
|
c.connections[connection] = make([]string, initialDomains)
|
||||||
registration.commCh <- true
|
registration.commCh <- true
|
||||||
|
|
||||||
|
@ -92,7 +114,17 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
|
|
||||||
newDomain := string(domain.(string))
|
newDomain := string(domain.(string))
|
||||||
loginfo.Println("adding domain ", newDomain, " to connection ", connection.conn.RemoteAddr().String())
|
loginfo.Println("adding domain ", newDomain, " to connection ", connection.conn.RemoteAddr().String())
|
||||||
c.domains[newDomain] = connection
|
|
||||||
|
//check to see if domain is already present.
|
||||||
|
if _, ok := c.domains[newDomain]; ok {
|
||||||
|
|
||||||
|
//append to a list of connections for that domain
|
||||||
|
c.domains[newDomain].AddConnection(connection)
|
||||||
|
} else {
|
||||||
|
//if not, then add as the 1st to the list of connections
|
||||||
|
c.domains[newDomain] = NewDomainLoadBalance(defaultMethod)
|
||||||
|
c.domains[newDomain].AddConnection(connection)
|
||||||
|
}
|
||||||
|
|
||||||
// add to the connection domain list
|
// add to the connection domain list
|
||||||
s := c.connections[connection]
|
s := c.connections[connection]
|
||||||
|
@ -103,13 +135,28 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
|
|
||||||
case connection := <-c.unregister:
|
case connection := <-c.unregister:
|
||||||
loginfo.Println("closing connection ", connection.conn.RemoteAddr().String())
|
loginfo.Println("closing connection ", connection.conn.RemoteAddr().String())
|
||||||
|
|
||||||
|
//does connection exist in the connection table -- should never be an issue
|
||||||
if _, ok := c.connections[connection]; ok {
|
if _, ok := c.connections[connection]; ok {
|
||||||
|
|
||||||
|
//iterate over the connections for the domain
|
||||||
for _, domain := range c.connections[connection] {
|
for _, domain := range c.connections[connection] {
|
||||||
fmt.Println("removing domain ", domain)
|
loginfo.Println("remove domain", domain)
|
||||||
|
|
||||||
|
//removing domain, make sure it is present (should never be a problem)
|
||||||
if _, ok := c.domains[domain]; ok {
|
if _, ok := c.domains[domain]; ok {
|
||||||
|
|
||||||
|
domainLB := c.domains[domain]
|
||||||
|
domainLB.RemoveConnection(connection)
|
||||||
|
|
||||||
|
//check to see if domain is free of connections, if yes, delete map entry
|
||||||
|
if domainLB.count > 0 {
|
||||||
|
//ignore...perhaps we will do something here dealing wtih the lb method
|
||||||
|
} else {
|
||||||
delete(c.domains, domain)
|
delete(c.domains, domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//delete(c.connections, connection)
|
//delete(c.connections, connection)
|
||||||
//close(connection.send)
|
//close(connection.send)
|
||||||
|
@ -129,7 +176,6 @@ func (c *Table) Run(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Register -- Property
|
//Register -- Property
|
||||||
func (c *Table) Register() (r chan *Registration) {
|
func (c *Table) Register() chan *Registration {
|
||||||
r = c.register
|
return c.register
|
||||||
return
|
|
||||||
}
|
}
|
|
@ -0,0 +1,107 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
lbmUnSupported string = "unsuported"
|
||||||
|
lbmRoundRobin string = "round-robin"
|
||||||
|
lbmLeastConnections string = "least-connections"
|
||||||
|
)
|
||||||
|
|
||||||
|
//DomainLoadBalance -- Use as a structure for domain connections
|
||||||
|
//and load balancing those connections. Initial modes are round-robin
|
||||||
|
//but suspect we will need least-connections, and sticky
|
||||||
|
type DomainLoadBalance struct {
|
||||||
|
mutex sync.Mutex
|
||||||
|
|
||||||
|
//lb method, supported round robin.
|
||||||
|
method string
|
||||||
|
|
||||||
|
//the last connection based on calculation
|
||||||
|
lastmember int
|
||||||
|
|
||||||
|
// a list of connections in this load balancing context
|
||||||
|
connections []*Connection
|
||||||
|
|
||||||
|
//a counter to track total connections, so we aren't calling len all the time
|
||||||
|
count int
|
||||||
|
|
||||||
|
//true if the system belives a recalcuation is required
|
||||||
|
recalc bool
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainLoadBalance -- Constructor
|
||||||
|
func NewDomainLoadBalance(defaultMethod string) (p *DomainLoadBalance) {
|
||||||
|
p = new(DomainLoadBalance)
|
||||||
|
p.method = defaultMethod
|
||||||
|
p.lastmember = 0
|
||||||
|
p.count = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//Connections -- Access connections
|
||||||
|
func (p *DomainLoadBalance) Connections() []*Connection {
|
||||||
|
return p.connections
|
||||||
|
}
|
||||||
|
|
||||||
|
//NextMember -- increments the lastmember, and then checks if >= to count, if true
|
||||||
|
//the last is reset to 0
|
||||||
|
func (p *DomainLoadBalance) NextMember() (conn *Connection) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
//check for round robin, if not RR then drop out and call calculate
|
||||||
|
loginfo.Println("NextMember:", p)
|
||||||
|
if p.method == lbmRoundRobin {
|
||||||
|
p.lastmember++
|
||||||
|
if p.lastmember >= p.count {
|
||||||
|
p.lastmember = 0
|
||||||
|
}
|
||||||
|
nextConn := p.connections[p.lastmember]
|
||||||
|
return nextConn
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not round robin
|
||||||
|
switch method := p.method; method {
|
||||||
|
default:
|
||||||
|
panic(fmt.Errorf("fatal unsupported loadbalance method %s", method))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddConnection -- Add an additional connection to the list of connections for this domain
|
||||||
|
//this should not affect the next member calculation in RR. However it many in other
|
||||||
|
//methods
|
||||||
|
func (p *DomainLoadBalance) AddConnection(conn *Connection) []*Connection {
|
||||||
|
loginfo.Println("AddConnection", fmt.Sprintf("%p", conn))
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
p.connections = append(p.connections, conn)
|
||||||
|
p.count++
|
||||||
|
loginfo.Println("AddConnection", p)
|
||||||
|
return p.connections
|
||||||
|
}
|
||||||
|
|
||||||
|
//RemoveConnection -- removes a matching connection from the list. This may
|
||||||
|
//affect the nextmember calculation if found so the recalc flag is set.
|
||||||
|
func (p *DomainLoadBalance) RemoveConnection(conn *Connection) {
|
||||||
|
loginfo.Println("RemoveConnection", fmt.Sprintf("%p", conn))
|
||||||
|
|
||||||
|
p.mutex.Lock()
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
//scan all the connections
|
||||||
|
for pos := range p.connections {
|
||||||
|
loginfo.Println("RemoveConnection", pos, len(p.connections), p.count)
|
||||||
|
if p.connections[pos] == conn {
|
||||||
|
//found connection remove it
|
||||||
|
loginfo.Printf("found connection %p", conn)
|
||||||
|
p.connections[pos], p.connections = p.connections[len(p.connections)-1], p.connections[:len(p.connections)-1]
|
||||||
|
p.count--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loginfo.Println("RemoveConnection:", p)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
//DomainMapping --
|
//DomainMapping --
|
||||||
type DomainMapping struct {
|
type DomainMapping struct {
|
|
@ -0,0 +1,61 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//DomainTrack -- Tracking specifics for domains
|
||||||
|
type DomainTrack struct {
|
||||||
|
DomainName string
|
||||||
|
bytesIn int64
|
||||||
|
bytesOut int64
|
||||||
|
requests int64
|
||||||
|
responses int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewDomainTrack -- Constructor
|
||||||
|
func NewDomainTrack(domainName string) (p *DomainTrack) {
|
||||||
|
p = new(DomainTrack)
|
||||||
|
p.DomainName = domainName
|
||||||
|
p.bytesIn = 0
|
||||||
|
p.bytesOut = 0
|
||||||
|
p.requests = 0
|
||||||
|
p.responses = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesIn -- Property
|
||||||
|
func (c *DomainTrack) BytesIn() int64 {
|
||||||
|
return c.bytesIn
|
||||||
|
}
|
||||||
|
|
||||||
|
//BytesOut -- Property
|
||||||
|
func (c *DomainTrack) BytesOut() int64 {
|
||||||
|
return c.bytesOut
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddIn - Property
|
||||||
|
func (c *DomainTrack) AddIn(num int64) {
|
||||||
|
c.bytesIn = c.bytesIn + num
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddOut -- Property
|
||||||
|
func (c *DomainTrack) AddOut(num int64) {
|
||||||
|
c.bytesOut = c.bytesOut + num
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddRequests - Property
|
||||||
|
func (c *DomainTrack) AddRequests() {
|
||||||
|
c.requests = c.requests + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddResponses - Property
|
||||||
|
func (c *DomainTrack) AddResponses() {
|
||||||
|
c.responses = c.responses + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
//Requests -- Property
|
||||||
|
func (c *DomainTrack) Requests() int64 {
|
||||||
|
return c.requests
|
||||||
|
}
|
||||||
|
|
||||||
|
//Responses -- Property
|
||||||
|
func (c *DomainTrack) Responses() int64 {
|
||||||
|
return c.responses
|
||||||
|
}
|
|
@ -1,26 +1,22 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwt "github.com/dgrijalva/jwt-go"
|
"github.com/dgrijalva/jwt-go"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"bufio"
|
|
||||||
|
|
||||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,13 +25,17 @@ type contextKey string
|
||||||
//CtxConnectionTrack
|
//CtxConnectionTrack
|
||||||
const (
|
const (
|
||||||
ctxSecretKey contextKey = "secretKey"
|
ctxSecretKey contextKey = "secretKey"
|
||||||
ctxConnectionTable contextKey = "connectionTable"
|
ctxServerStatus contextKey = "serverstatus"
|
||||||
|
|
||||||
|
//ctxConnectionTable contextKey = "connectionTable"
|
||||||
|
|
||||||
ctxConfig contextKey = "config"
|
ctxConfig contextKey = "config"
|
||||||
ctxListenerRegistration contextKey = "listenerRegistration"
|
ctxListenerRegistration contextKey = "listenerRegistration"
|
||||||
ctxConnectionTrack contextKey = "connectionTrack"
|
ctxConnectionTrack contextKey = "connectionTrack"
|
||||||
ctxWssHostName contextKey = "wsshostname"
|
ctxWssHostName contextKey = "wsshostname"
|
||||||
ctxAdminHostName contextKey = "adminHostName"
|
ctxAdminHostName contextKey = "adminHostName"
|
||||||
ctxCancelCheck contextKey = "cancelcheck"
|
ctxCancelCheck contextKey = "cancelcheck"
|
||||||
|
ctxLoadbalanceDefaultMethod contextKey = "lbdefaultmethod"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -271,17 +271,17 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
|
||||||
|
|
||||||
//handleExternalHTTPRequest -
|
//handleExternalHTTPRequest -
|
||||||
// - get a wConn and start processing requests
|
// - get a wConn and start processing requests
|
||||||
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname string, service string) {
|
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname, service string) {
|
||||||
connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||||
|
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
connectionTracking.unregister <- extConn
|
serverStatus.ExtConnectionUnregister(extConn)
|
||||||
extConn.Close()
|
extConn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
|
||||||
//find the connection by domain name
|
//find the connection by domain name
|
||||||
conn, ok := connectionTable.ConnByDomain(hostname)
|
conn, ok := serverStatus.ConnectionTable.ConnByDomain(hostname)
|
||||||
if !ok {
|
if !ok {
|
||||||
//matching connection can not be found based on ConnByDomain
|
//matching connection can not be found based on ConnByDomain
|
||||||
loginfo.Println("unable to match ", hostname, " to an existing connection")
|
loginfo.Println("unable to match ", hostname, " to an existing connection")
|
||||||
|
@ -290,7 +290,7 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
}
|
}
|
||||||
|
|
||||||
track := NewTrack(extConn, hostname)
|
track := NewTrack(extConn, hostname)
|
||||||
connectionTracking.register <- track
|
serverStatus.ExtConnectionRegister(track)
|
||||||
|
|
||||||
loginfo.Println("Domain Accepted", hostname, extConn.RemoteAddr().String())
|
loginfo.Println("Domain Accepted", hostname, extConn.RemoteAddr().String())
|
||||||
|
|
||||||
|
@ -323,10 +323,11 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
p.Data.AppendBytes(buffer[0:cnt])
|
p.Data.AppendBytes(buffer[0:cnt])
|
||||||
buf := p.PackV1()
|
buf := p.PackV1()
|
||||||
|
|
||||||
loginfo.Println(hex.Dump(buf.Bytes()))
|
//loginfo.Println(hex.Dump(buf.Bytes()))
|
||||||
|
|
||||||
|
//Bundle up the send request and dispatch
|
||||||
sendTrack := NewSendTrack(buf.Bytes(), hostname)
|
sendTrack := NewSendTrack(buf.Bytes(), hostname)
|
||||||
conn.SendCh() <- sendTrack
|
serverStatus.SendExtRequest(conn, sendTrack)
|
||||||
|
|
||||||
_, err = extConn.Discard(cnt)
|
_, err = extConn.Discard(cnt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -337,71 +338,14 @@ func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//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 -
|
//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.
|
||||||
func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
secretKey := ctx.Value(ctxSecretKey).(string)
|
secretKey := ctx.Value(ctxSecretKey).(string)
|
||||||
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
||||||
|
|
||||||
|
//connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||||
|
|
||||||
router := mux.NewRouter().StrictSlash(true)
|
router := mux.NewRouter().StrictSlash(true)
|
||||||
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -426,8 +370,8 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
domains, ok := claims["domains"].([]interface{})
|
domains, ok := claims["domains"].([]interface{})
|
||||||
|
|
||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 65535,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 65535,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
|
@ -438,11 +382,11 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||||
|
|
||||||
loginfo.Println("before connection table")
|
loginfo.Println("before connection table")
|
||||||
|
|
||||||
//newConnection := connection.NewConnection(connectionTable, conn, r.RemoteAddr, domains)
|
serverName := domains[0].(string)
|
||||||
|
|
||||||
|
newRegistration := NewRegistration(conn, r.RemoteAddr, domains, serverStatus.ConnectionTracking, serverName)
|
||||||
|
serverStatus.WSSConnectionRegister(newRegistration)
|
||||||
|
|
||||||
connectionTrack := ctx.Value(ctxConnectionTrack).(*Tracking)
|
|
||||||
newRegistration := NewRegistration(conn, r.RemoteAddr, domains, connectionTrack)
|
|
||||||
connectionTable.Register() <- newRegistration
|
|
||||||
ok = <-newRegistration.CommCh()
|
ok = <-newRegistration.CommCh()
|
||||||
if !ok {
|
if !ok {
|
||||||
loginfo.Println("connection registration failed ", newRegistration)
|
loginfo.Println("connection registration failed ", newRegistration)
|
|
@ -1,4 +1,4 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -43,8 +43,8 @@ func NewListenerRegistration(port int) (p *ListenerRegistration) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//GenericListeners -
|
//servers -
|
||||||
type GenericListeners struct {
|
type servers struct {
|
||||||
listeners map[*net.Listener]int
|
listeners map[*net.Listener]int
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
connnectionTable *Table
|
connnectionTable *Table
|
||||||
|
@ -52,38 +52,41 @@ type GenericListeners struct {
|
||||||
secretKey string
|
secretKey string
|
||||||
certbundle tls.Certificate
|
certbundle tls.Certificate
|
||||||
register chan *ListenerRegistration
|
register chan *ListenerRegistration
|
||||||
genericListeners *GenericListeners
|
servers *servers
|
||||||
wssHostName string
|
wssHostName string
|
||||||
adminHostName string
|
adminHostName string
|
||||||
cancelCheck int
|
cancelCheck int
|
||||||
|
lbDefaultMethod string
|
||||||
|
serverStatus *Status
|
||||||
}
|
}
|
||||||
|
|
||||||
//NewGenerListeners --
|
//NewGenerListeners --
|
||||||
func NewGenerListeners(ctx context.Context, connectionTable *Table, connectionTrack *Tracking, secretKey string, certbundle tls.Certificate, wssHostName string, adminHostName string, cancelCheck int) (p *GenericListeners) {
|
func NewGenerListeners(ctx context.Context, secretKey string, certbundle tls.Certificate, serverStatus *Status) (p *servers) {
|
||||||
p = new(GenericListeners)
|
p = new(servers)
|
||||||
p.listeners = make(map[*net.Listener]int)
|
p.listeners = make(map[*net.Listener]int)
|
||||||
p.ctx = ctx
|
p.ctx = ctx
|
||||||
p.connnectionTable = connectionTable
|
p.connnectionTable = serverStatus.ConnectionTable
|
||||||
p.connectionTracking = connectionTrack
|
p.connectionTracking = serverStatus.ConnectionTracking
|
||||||
p.secretKey = secretKey
|
p.secretKey = secretKey
|
||||||
p.certbundle = certbundle
|
p.certbundle = certbundle
|
||||||
p.register = make(chan *ListenerRegistration)
|
p.register = make(chan *ListenerRegistration)
|
||||||
p.wssHostName = wssHostName
|
p.wssHostName = serverStatus.WssDomain
|
||||||
p.adminHostName = adminHostName
|
p.adminHostName = serverStatus.AdminDomain
|
||||||
p.cancelCheck = cancelCheck
|
p.cancelCheck = serverStatus.DeadTime.cancelcheck
|
||||||
|
p.lbDefaultMethod = serverStatus.LoadbalanceDefaultMethod
|
||||||
|
p.serverStatus = serverStatus
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
//Run -- Execute
|
//Run -- Execute
|
||||||
// - execute the GenericLister
|
// - execute the GenericLister
|
||||||
// - pass initial port, we'll announce that
|
// - pass initial port, we'll announce that
|
||||||
func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
|
func (gl *servers) Run(ctx context.Context, initialPort int) {
|
||||||
loginfo.Println("ConnectionTable starting")
|
loginfo.Println("ConnectionTable starting")
|
||||||
|
|
||||||
config := &tls.Config{Certificates: []tls.Certificate{gl.certbundle}}
|
config := &tls.Config{Certificates: []tls.Certificate{gl.certbundle}}
|
||||||
|
|
||||||
ctx = context.WithValue(ctx, ctxSecretKey, gl.secretKey)
|
ctx = context.WithValue(ctx, ctxSecretKey, gl.secretKey)
|
||||||
ctx = context.WithValue(ctx, ctxConnectionTable, gl.connnectionTable)
|
|
||||||
|
|
||||||
loginfo.Println(gl.connectionTracking)
|
loginfo.Println(gl.connectionTracking)
|
||||||
|
|
||||||
|
@ -93,6 +96,8 @@ func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
|
||||||
ctx = context.WithValue(ctx, ctxWssHostName, gl.wssHostName)
|
ctx = context.WithValue(ctx, ctxWssHostName, gl.wssHostName)
|
||||||
ctx = context.WithValue(ctx, ctxAdminHostName, gl.adminHostName)
|
ctx = context.WithValue(ctx, ctxAdminHostName, gl.adminHostName)
|
||||||
ctx = context.WithValue(ctx, ctxCancelCheck, gl.cancelCheck)
|
ctx = context.WithValue(ctx, ctxCancelCheck, gl.cancelCheck)
|
||||||
|
ctx = context.WithValue(ctx, ctxLoadbalanceDefaultMethod, gl.lbDefaultMethod)
|
||||||
|
ctx = context.WithValue(ctx, ctxServerStatus, gl.serverStatus)
|
||||||
|
|
||||||
go func(ctx context.Context) {
|
go func(ctx context.Context) {
|
||||||
for {
|
for {
|
|
@ -1,4 +1,4 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
@ -9,18 +9,16 @@ type oneConnListener struct {
|
||||||
conn net.Conn
|
conn net.Conn
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *oneConnListener) Accept() (c net.Conn, err error) {
|
func (l *oneConnListener) Accept() (net.Conn, error) {
|
||||||
c = l.conn
|
if l.conn == nil {
|
||||||
|
loginfo.Println("Accept EOF")
|
||||||
if c == nil {
|
return nil, io.EOF
|
||||||
err = io.EOF
|
|
||||||
loginfo.Println("Accept")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
err = nil
|
|
||||||
|
c := l.conn
|
||||||
l.conn = nil
|
l.conn = nil
|
||||||
loginfo.Println("Accept", c.LocalAddr().String(), c.RemoteAddr().String())
|
loginfo.Println("Accept", c.LocalAddr().String(), c.RemoteAddr().String())
|
||||||
return
|
return c, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *oneConnListener) Close() error {
|
func (l *oneConnListener) Close() error {
|
|
@ -1,4 +1,4 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
//SendTrack -- Used as a channel communication to id domain asssociated to domain for outbound WSS
|
//SendTrack -- Used as a channel communication to id domain asssociated to domain for outbound WSS
|
||||||
type SendTrack struct {
|
type SendTrack struct {
|
|
@ -0,0 +1,29 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
//Logoutput -- passing the output writer from main
|
||||||
|
loginfo *log.Logger
|
||||||
|
logdebug *log.Logger
|
||||||
|
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||||
|
connectionID int64
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
loginfo = log.New(os.Stdout, "INFO: server: ", logFlags)
|
||||||
|
logdebug = log.New(os.Stdout, "DEBUG: server:", logFlags)
|
||||||
|
connectionID = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
//InitLogging -- after main sets up output, it will init all packages InitLogging
|
||||||
|
//I am sure I am doing this wrong, but I could not find a way to have package level
|
||||||
|
//logging with the flags I wanted and the ability to run lumberjack file management
|
||||||
|
func InitLogging(logoutput io.Writer) {
|
||||||
|
loginfo.SetOutput(logoutput)
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
//Status --
|
||||||
|
type Status struct {
|
||||||
|
ctx context.Context
|
||||||
|
Name string
|
||||||
|
StartTime time.Time
|
||||||
|
WssDomain string
|
||||||
|
AdminDomain string
|
||||||
|
DeadTime *StatusDeadTime
|
||||||
|
ConnectionTracking *Tracking
|
||||||
|
ConnectionTable *Table
|
||||||
|
servers *servers
|
||||||
|
LoadbalanceDefaultMethod string
|
||||||
|
AdminStats *TrafficStats
|
||||||
|
AdminReqTyoe *AdminReqType
|
||||||
|
TrafficStats *TrafficStats
|
||||||
|
ExtConnections *ConnectionStats
|
||||||
|
WSSConnections *ConnectionStats
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewStatus --
|
||||||
|
func NewStatus(ctx context.Context) (p *Status) {
|
||||||
|
p = new(Status)
|
||||||
|
p.ctx = ctx
|
||||||
|
p.AdminStats = new(TrafficStats)
|
||||||
|
p.TrafficStats = new(TrafficStats)
|
||||||
|
p.ExtConnections = new(ConnectionStats)
|
||||||
|
p.WSSConnections = new(ConnectionStats)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// South Facing Functions
|
||||||
|
|
||||||
|
//WSSConnectionRegister --
|
||||||
|
func (p *Status) WSSConnectionRegister(newRegistration *Registration) {
|
||||||
|
p.ConnectionTable.Register() <- newRegistration
|
||||||
|
p.WSSConnections.IncConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
//WSSConnectionUnregister --
|
||||||
|
//unregisters a south facing connection
|
||||||
|
//intercept and update global statistics
|
||||||
|
func (p *Status) WSSConnectionUnregister() {
|
||||||
|
}
|
||||||
|
|
||||||
|
// External Facing Functions
|
||||||
|
|
||||||
|
//ExtConnectionRegister --
|
||||||
|
//registers an ext facing connection
|
||||||
|
//intercept and update global statistics
|
||||||
|
func (p *Status) ExtConnectionRegister(newTrack *Track) {
|
||||||
|
p.ConnectionTracking.register <- newTrack
|
||||||
|
p.ExtConnections.IncConnections()
|
||||||
|
}
|
||||||
|
|
||||||
|
//ExtConnectionUnregister --
|
||||||
|
//unregisters an ext facing connection
|
||||||
|
//intercept and update global statistics
|
||||||
|
func (p *Status) ExtConnectionUnregister(extConn *WedgeConn) {
|
||||||
|
p.ConnectionTracking.unregister <- extConn
|
||||||
|
p.ExtConnections.DecConnections()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//SendExtRequest --
|
||||||
|
//sends a request to a south facing connection
|
||||||
|
//intercept the send, update our global stats
|
||||||
|
func (p *Status) SendExtRequest(conn *Connection, sendTrack *SendTrack) {
|
||||||
|
p.TrafficStats.IncRequests()
|
||||||
|
p.TrafficStats.AddBytesOut(int64(len(sendTrack.data)))
|
||||||
|
conn.SendCh() <- sendTrack
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//StatusDeadTime -- structure for deadtime configuration
|
||||||
|
type StatusDeadTime struct {
|
||||||
|
dwell int
|
||||||
|
idle int
|
||||||
|
cancelcheck int
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewStatusDeadTime -- constructor
|
||||||
|
func NewStatusDeadTime(dwell, idle, cancelcheck int) (p *StatusDeadTime) {
|
||||||
|
p = new(StatusDeadTime)
|
||||||
|
p.dwell = dwell
|
||||||
|
p.idle = idle
|
||||||
|
p.cancelcheck = cancelcheck
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "sync"
|
||||||
|
|
||||||
|
type adminReqType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
adminStatus adminReqType = "admin_status"
|
||||||
|
adminDomain adminReqType = "admin_domain"
|
||||||
|
adminDomains adminReqType = "admin_domains"
|
||||||
|
adminServer adminReqType = "admin_server"
|
||||||
|
adminServers adminReqType = "admin_servers"
|
||||||
|
)
|
||||||
|
|
||||||
|
//AdminReqType --
|
||||||
|
type AdminReqType struct {
|
||||||
|
mutex *sync.Mutex
|
||||||
|
RequestType map[adminReqType]int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewAdminReqType -- Constructor
|
||||||
|
func NewAdminReqType() (p *AdminReqType) {
|
||||||
|
p = new(AdminReqType)
|
||||||
|
p.mutex = &sync.Mutex{}
|
||||||
|
p.RequestType = make(map[adminReqType]int64)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AdminReqType) add(reqType adminReqType) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
if _, ok := p.RequestType[reqType]; ok {
|
||||||
|
p.RequestType[reqType]++
|
||||||
|
} else {
|
||||||
|
p.RequestType[reqType] = int64(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *AdminReqType) get(reqType adminReqType) (total int64) {
|
||||||
|
p.mutex.Lock()
|
||||||
|
|
||||||
|
defer p.mutex.Unlock()
|
||||||
|
|
||||||
|
if _, ok := p.RequestType[reqType]; ok {
|
||||||
|
total = p.RequestType[reqType]
|
||||||
|
} else {
|
||||||
|
total = 0
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//ConnectionStats --
|
||||||
|
type ConnectionStats struct {
|
||||||
|
Connections int64
|
||||||
|
TotalConnections int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewConnectionStats -- Consttuctor
|
||||||
|
func NewConnectionStats() (p *ConnectionStats) {
|
||||||
|
p = new(ConnectionStats)
|
||||||
|
p.Connections = 0
|
||||||
|
p.TotalConnections = 0
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//IncConnections --
|
||||||
|
func (p *ConnectionStats) IncConnections() {
|
||||||
|
p.Connections++
|
||||||
|
p.TotalConnections++
|
||||||
|
}
|
||||||
|
|
||||||
|
//DecConnections --
|
||||||
|
func (p *ConnectionStats) DecConnections() {
|
||||||
|
if p.Connections > 0 {
|
||||||
|
p.Connections--
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
//TrafficStats --
|
||||||
|
type TrafficStats struct {
|
||||||
|
Requests int64
|
||||||
|
Responses int64
|
||||||
|
BytesIn int64
|
||||||
|
BytesOut int64
|
||||||
|
}
|
||||||
|
|
||||||
|
//NewTrafficStats -- Consttuctor
|
||||||
|
func NewTrafficStats() (p *TrafficStats) {
|
||||||
|
p = new(TrafficStats)
|
||||||
|
p.Requests = 0
|
||||||
|
p.Responses = 0
|
||||||
|
p.BytesIn = 0
|
||||||
|
p.BytesOut = 0
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
//IncRequests --
|
||||||
|
func (p *TrafficStats) IncRequests() {
|
||||||
|
p.Requests++
|
||||||
|
}
|
||||||
|
|
||||||
|
//IncResponses --
|
||||||
|
func (p *TrafficStats) IncResponses() {
|
||||||
|
p.Responses++
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddBytesIn --
|
||||||
|
func (p *TrafficStats) AddBytesIn(count int64) {
|
||||||
|
p.BytesIn = p.BytesIn + count
|
||||||
|
}
|
||||||
|
|
||||||
|
//AddBytesOut --
|
||||||
|
func (p *TrafficStats) AddBytesOut(count int64) {
|
||||||
|
p.BytesOut = p.BytesOut + count
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
package genericlistener
|
package server
|
||||||
|
|
||||||
import "errors"
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
func getHello(b []byte) (string, error) {
|
func getHello(b []byte) (string, error) {
|
||||||
rest := b[5:]
|
rest := b[5:]
|
|
@ -1,17 +0,0 @@
|
||||||
package xlate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
loginfo *log.Logger
|
|
||||||
logdebug *log.Logger
|
|
||||||
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
loginfo = log.New(os.Stdout, "INFO: xlate: ", logFlags)
|
|
||||||
logdebug = log.New(os.Stdout, "DEBUG: xlate:", logFlags)
|
|
||||||
}
|
|
Loading…
Reference in New Issue