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
|
||||
|
||||
## branch: passing-traffic
|
||||
## branch: load-balancing
|
||||
|
||||
- code now passes traffic using just daplie tools
|
||||
- this will require serve-https and node-tunnel-client to work
|
||||
- the system supports round-robin load balancing
|
||||
|
||||
|
||||
### Build RVPN
|
||||
|
||||
```bash
|
||||
hcamacho@Hanks-MBP:go-rvpn-server $ go get
|
||||
hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
||||
go-rvpn-server $ go get
|
||||
go-rvpn-server $ go build
|
||||
```
|
||||
### Setup Some Entries
|
||||
|
||||
|
@ -20,20 +21,20 @@ hcamacho@Hanks-MBP:go-rvpn-server $ go build
|
|||
|
||||
### Start Up Webserver
|
||||
```bash
|
||||
hcamacho@Hanks-MBP:tmp $ cd /tmp
|
||||
hcamacho@Hanks-MBP:tmp $ vi index.html --- Place some index content
|
||||
hcamacho@Hanks-MBP:tmp $ serve-https -p 8080 -d /tmp --servername hfc.daplie.me --agree-tos --email henry.f.camacho@gmail.com
|
||||
tmp $ cd /tmp
|
||||
tmp $ vi index.html --- Place some index content
|
||||
tmp $ serve-https -p 8080 -d /tmp --servername hfc.daplie.me --agree-tos --email henry.f.camacho@gmail.com
|
||||
```
|
||||
|
||||
### Start Tunnel Client
|
||||
```bash
|
||||
hcamacho@Hanks-MBP:node-tunnel-client $ bin/stunnel.js --locals http://hfc.daplie.me:8080,http://test1.hfc.daplie.me:8080 --stunneld wss://localhost.daplie.me:8443 --secret abc123
|
||||
node-tunnel-client $ bin/stunnel.js --locals http://hfc.daplie.me:8080,http://test1.hfc.daplie.me:8080 --stunneld wss://localhost.daplie.me:8443 --secret abc123
|
||||
```
|
||||
|
||||
### Execute RVPN
|
||||
|
||||
```bash
|
||||
hcamacho@Hanks-MBP:go-rvpn-server $ ./go-rvpn-server
|
||||
go-rvpn-server $ ./go-rvpn-server
|
||||
INFO: packer: 2017/03/02 19:16:52.652109 run.go:47: startup
|
||||
-=-=-=-=-=-=-=-=-=-=
|
||||
INFO: genericlistener: 2017/03/02 19:16:52.652777 manager.go:77: ConnectionTable starting
|
||||
|
@ -50,242 +51,83 @@ INFO: genericlistener: 2017/03/02 19:16:52.652869 conn_tracking.go:25: Tracking
|
|||
|
||||
https://hfc.daplie.me:8443
|
||||
|
||||
- You'll notice that the browser is redirected to 8080 after accepting the cert. I see a meta-refresh coming back from the serve-https
|
||||
- The traffic is getting back to the client.
|
||||
### Test Load Balancing
|
||||
|
||||
In a new terminal
|
||||
|
||||
```bash
|
||||
|
||||
INFO: genericlistener: 2017/03/02 21:24:48.472312 connection.go:207: 00000000 fe 1d 49 50 76 34 2c 31 32 37 2e 30 2e 30 2e 31 |..IPv4,127.0.0.1|
|
||||
00000010 2c 35 33 35 35 39 2c 33 36 38 2c 68 74 74 70 48 |,53559,368,httpH|
|
||||
00000020 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d 0a |TTP/1.1 200 OK..|
|
||||
00000030 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 |Content-Type: te|
|
||||
00000040 78 74 2f 68 74 6d 6c 3b 20 63 68 61 72 73 65 74 |xt/html; charset|
|
||||
00000050 3d 75 74 66 2d 38 0d 0a 44 61 74 65 3a 20 46 72 |=utf-8..Date: Fr|
|
||||
00000060 69 2c 20 30 33 20 4d 61 72 20 32 30 31 37 20 30 |i, 03 Mar 2017 0|
|
||||
00000070 33 3a 32 34 3a 34 38 20 47 4d 54 0d 0a 43 6f 6e |3:24:48 GMT..Con|
|
||||
00000080 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 70 2d 61 6c |nection: keep-al|
|
||||
00000090 69 76 65 0d 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e |ive..Content-Len|
|
||||
000000a0 67 74 68 3a 20 32 32 37 0d 0a 0d 0a 3c 68 74 6d |gth: 227....<htm|
|
||||
000000b0 6c 3e 0a 3c 68 65 61 64 3e 0a 20 20 3c 4d 45 54 |l>.<head>. <MET|
|
||||
000000c0 41 20 68 74 74 70 2d 65 71 75 69 76 3d 22 72 65 |A http-equiv="re|
|
||||
000000d0 66 72 65 73 68 22 20 63 6f 6e 74 65 6e 74 3d 22 |fresh" content="|
|
||||
000000e0 30 3b 55 52 4c 3d 27 68 74 74 70 73 3a 2f 2f 68 |0;URL='https://h|
|
||||
000000f0 66 63 2e 64 61 70 6c 69 65 2e 6d 65 3a 38 30 38 |fc.daplie.me:808|
|
||||
00000100 30 2f 27 22 3e 0a 3c 2f 68 65 61 64 3e 0a 3c 62 |0/'">.</head>.<b|
|
||||
00000110 6f 64 79 3e 0a 3c 21 2d 2d 20 48 65 6c 6c 6f 20 |ody>.<!-- Hello |
|
||||
00000120 4d 72 20 44 65 76 65 6c 6f 70 65 72 21 20 57 65 |Mr Developer! We|
|
||||
00000130 20 64 6f 6e 27 74 20 73 65 72 76 65 20 69 6e 73 | don't serve ins|
|
||||
00000140 65 63 75 72 65 20 72 65 73 6f 75 72 63 65 73 20 |ecure resources |
|
||||
00000150 61 72 6f 75 6e 64 20 68 65 72 65 2e 0a 20 20 20 |around here.. |
|
||||
00000160 20 50 6c 65 61 73 65 20 75 73 65 20 48 54 54 50 | Please use HTTP|
|
||||
00000170 53 20 69 6e 73 74 65 61 64 2e 20 2d 2d 3e 0a 3c |S instead. -->.<|
|
||||
00000180 2f 62 6f 64 79 3e 0a 3c 2f 68 74 6d 6c 3e 0a |/body>.</html>.|
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
- this set of code works great if I am running the node-tunnel-client on a different machine with apache as a web server.
|
||||
- need to work through why serve-https thinks the traffic is inecure.
|
||||
### Check Results
|
||||
- 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
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## restructured-http
|
||||
|
||||
- connection handling has been totally rewritten.
|
||||
- on a specific port RVPN can determine the following:
|
||||
- if a connection is encrypted or not encrypted
|
||||
- if a request is a wss_client
|
||||
- if a request is an admin/api request
|
||||
- if a request is a plain (to be forwarded) http request
|
||||
- or if a request is a different protocol (perhaps SSH)
|
||||
|
||||
To accomplish the above RVPN uses raw TCP sockets, buffered readers, and a custom Listener. This allows protocol detection (multiple services on one port)
|
||||
If we expose 443 and 80 to maximize the ability for tunnel clients and south bound traffic, the RVPN is able to deal with this traffic on a limited number of ports, and the most popular ports.
|
||||
It is possible now to meter any point of the connection (not Interface Level, rather TCP)
|
||||
|
||||
There is now a connection manager that dynamically allows new GenericListeners to start on different ports when needed....
|
||||
|
||||
```go
|
||||
newListener := NewListenerRegistration(initialPort)
|
||||
gl.register <- newListener
|
||||
```
|
||||
|
||||
A new listener is created by sending a NewListenerRegistration on the channel.
|
||||
|
||||
```go
|
||||
|
||||
ln, err := net.ListenTCP("tcp", listenAddr)
|
||||
if err != nil {
|
||||
loginfo.Println("unable to bind", err)
|
||||
listenerRegistration.status = listenerFault
|
||||
listenerRegistration.err = err
|
||||
listenerRegistration.commCh <- listenerRegistration
|
||||
return
|
||||
```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,
|
||||
"responses": 12,
|
||||
"source_addr": "127.0.0.1:55875"
|
||||
}, {
|
||||
"domain_name": "test1.hfc.daplie.me",
|
||||
"server_id": 1,
|
||||
"bytes_in": 0,
|
||||
"bytes_out": 0,
|
||||
"requests": 0,
|
||||
"responses": 0,
|
||||
"source_addr": "127.0.0.1:55875"
|
||||
}],
|
||||
"duration": 182.561747754,
|
||||
"idle": 21.445976033,
|
||||
"bytes_in": 8119,
|
||||
"bytes_out": 4055,
|
||||
"requests": 12,
|
||||
"responses": 12,
|
||||
"source_address": "127.0.0.1:55875"
|
||||
}, {
|
||||
"server_name": "0xc4200ea3c0",
|
||||
"server_id": 2,
|
||||
"domains": [{
|
||||
"domain_name": "hfc.daplie.me",
|
||||
"server_id": 2,
|
||||
"bytes_in": 1098,
|
||||
"bytes_out": 62,
|
||||
"requests": 2,
|
||||
"responses": 2,
|
||||
"source_addr": "127.0.0.1:56318"
|
||||
}, {
|
||||
"domain_name": "test1.hfc.daplie.me",
|
||||
"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:
|
||||
serverName: rvpn1
|
||||
wssdomain: localhost.daplie.me
|
||||
admindomain: rvpn.daplie.invalid
|
||||
genericlistener: 9999
|
||||
genericlistener: 8443
|
||||
deadtime:
|
||||
dwell: 600
|
||||
dwell: 120
|
||||
idle: 60
|
||||
cancelcheck: 10
|
||||
domains:
|
||||
|
@ -13,4 +14,7 @@ rvpn:
|
|||
secret: abc123
|
||||
test3.daplie.me:
|
||||
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
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
lumberjack "gopkg.in/natefinch/lumberjack.v2"
|
||||
|
||||
"context"
|
||||
|
||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/genericlistener"
|
||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/server"
|
||||
)
|
||||
|
||||
var (
|
||||
logfile = "stdout"
|
||||
configPath = "./"
|
||||
configFile = "go-rvpn-server"
|
||||
|
||||
loginfo *log.Logger
|
||||
logdebug *log.Logger
|
||||
logFlags = log.Ldate | log.Lmicroseconds | log.Lshortfile
|
||||
|
@ -25,29 +31,57 @@ var (
|
|||
argServerAdminBinding string
|
||||
argServerExternalBinding string
|
||||
argDeadTime int
|
||||
connectionTable *genericlistener.Table
|
||||
connectionTable *server.Table
|
||||
secretKey = "abc123"
|
||||
wssHostName = "localhost.daplie.me"
|
||||
adminHostName = "rvpn.daplie.invalid"
|
||||
idle int
|
||||
dwell int
|
||||
cancelcheck int
|
||||
lbDefaultMethod string
|
||||
serverName string
|
||||
)
|
||||
|
||||
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
|
||||
func main() {
|
||||
flag.Parse()
|
||||
loginfo = log.New(os.Stdout, "INFO: main: ", logFlags)
|
||||
logdebug = log.New(os.Stdout, "DEBUG: main:", logFlags)
|
||||
viper.SetConfigName("go-rvpn-server")
|
||||
switch logfile {
|
||||
case "stdout":
|
||||
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("./")
|
||||
err := viper.ReadInConfig()
|
||||
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")
|
||||
|
@ -55,18 +89,15 @@ func main() {
|
|||
wssHostName = viper.Get("rvpn.wssdomain").(string)
|
||||
adminHostName = viper.Get("rvpn.admindomain").(string)
|
||||
argGenericBinding = viper.GetInt("rvpn.genericlistener")
|
||||
deadtime := viper.Get("rvpn.deadtime")
|
||||
idle = deadtime.(map[string]interface{})["idle"].(int)
|
||||
dwell = deadtime.(map[string]interface{})["dwell"].(int)
|
||||
cancelcheck = deadtime.(map[string]interface{})["cancelcheck"].(int)
|
||||
deadtime := viper.Get("rvpn.deadtime").(map[string]interface{})
|
||||
idle = deadtime["idle"].(int)
|
||||
dwell = deadtime["dwell"].(int)
|
||||
cancelcheck = deadtime["cancelcheck"].(int)
|
||||
lbDefaultMethod = viper.Get("rvpn.loadbalancing.defaultmethod").(string)
|
||||
serverName = viper.Get("rvpn.serverName").(string)
|
||||
|
||||
loginfo.Println("startup")
|
||||
|
||||
loginfo.Println(viper.Get("rvpn.genericlisteners"))
|
||||
loginfo.Println(viper.Get("rvpn.domains"))
|
||||
|
||||
fmt.Println("-=-=-=-=-=-=-=-=-=-=")
|
||||
|
||||
certbundle, err := tls.LoadX509KeyPair("certs/fullchain.pem", "certs/privkey.pem")
|
||||
if err != nil {
|
||||
loginfo.Println(err)
|
||||
|
@ -76,6 +107,14 @@ func main() {
|
|||
ctx, cancelContext := context.WithCancel(context.Background())
|
||||
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.
|
||||
// - establish context for the generic listener
|
||||
// - startup listener
|
||||
|
@ -85,16 +124,18 @@ func main() {
|
|||
// - if tls, establish, protocol peek buffer, else decrypted
|
||||
// - match protocol
|
||||
|
||||
connectionTracking := genericlistener.NewTracking()
|
||||
connectionTracking := server.NewTracking()
|
||||
serverStatus.ConnectionTracking = connectionTracking
|
||||
go connectionTracking.Run(ctx)
|
||||
|
||||
connectionTable = genericlistener.NewTable(dwell, idle)
|
||||
go connectionTable.Run(ctx)
|
||||
connectionTable = server.NewTable(dwell, idle)
|
||||
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)
|
||||
|
||||
//Run for 10 minutes and then shutdown cleanly
|
||||
time.Sleep(600 * time.Second)
|
||||
cancelContext()
|
||||
select {}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
packerV1 byte = 255 - 1
|
||||
packerV2 byte = 255 - 2
|
||||
_ = iota // skip the iota value of 0
|
||||
packerV1 byte = 255 - iota
|
||||
packerV2
|
||||
)
|
||||
|
||||
//Packer -- contains both header and data
|
||||
|
@ -26,121 +28,93 @@ func NewPacker() (p *Packer) {
|
|||
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 -
|
||||
func ReadMessage(b []byte) (p *Packer, err error) {
|
||||
fmt.Println("ReadMessage")
|
||||
var pos int
|
||||
|
||||
err = nil
|
||||
// detect protocol in use
|
||||
func ReadMessage(b []byte) (*Packer, error) {
|
||||
// Detect protocol in use
|
||||
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
|
||||
pos = pos + 1
|
||||
p.Header.HeaderLen = b[pos]
|
||||
|
||||
//handle address family
|
||||
pos = pos + 1
|
||||
end := bytes.IndexAny(b[pos:], ",")
|
||||
if end == -1 {
|
||||
err = fmt.Errorf("missing , while parsing address family")
|
||||
// Handle the different parts of the header.
|
||||
parts, err := splitHeader(header, []string{"address family", "address", "port", "data length", "service"})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bAddrFamily := b[pos : pos+end]
|
||||
if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv4]) {
|
||||
if familyText := parts["address family"]; familyText == addressFamilyText[FamilyIPv4] {
|
||||
p.Header.family = FamilyIPv4
|
||||
} else if bytes.ContainsAny(bAddrFamily, addressFamilyText[FamilyIPv6]) {
|
||||
} else if familyText == addressFamilyText[FamilyIPv6] {
|
||||
p.Header.family = FamilyIPv6
|
||||
} else {
|
||||
err = fmt.Errorf("Address family not supported %d", bAddrFamily)
|
||||
return nil, fmt.Errorf("Address family %q not supported", familyText)
|
||||
}
|
||||
|
||||
//handle address
|
||||
pos = pos + end + 1
|
||||
end = bytes.IndexAny(b[pos:], ",")
|
||||
if end == -1 {
|
||||
err = fmt.Errorf("missing , while parsing address")
|
||||
return nil, err
|
||||
}
|
||||
p.Header.address = net.ParseIP(string(b[pos : pos+end]))
|
||||
|
||||
//handle import
|
||||
pos = pos + end + 1
|
||||
end = bytes.IndexAny(b[pos:], ",")
|
||||
if end == -1 {
|
||||
err = fmt.Errorf("missing , while parsing address")
|
||||
return nil, err
|
||||
p.Header.address = net.ParseIP(parts["address"])
|
||||
if p.Header.address == nil {
|
||||
return nil, fmt.Errorf("Invalid network address %q", parts["address"])
|
||||
} else if p.Header.Family() == FamilyIPv4 && p.Header.address.To4() == nil {
|
||||
return nil, fmt.Errorf("Address %q is not in address family %s", parts["address"], p.Header.FamilyText())
|
||||
}
|
||||
|
||||
p.Header.Port, err = strconv.Atoi(string(b[pos : pos+end]))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error converting port %s", err)
|
||||
//handle port
|
||||
if port, err := strconv.Atoi(parts["port"]); err != nil {
|
||||
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
|
||||
pos = pos + end + 1
|
||||
end = bytes.IndexAny(b[pos:], ",")
|
||||
if end == -1 {
|
||||
err = fmt.Errorf("missing , while parsing address")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Data.DataLen, err = strconv.Atoi(string(b[pos : pos+end]))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error converting data length %s", err)
|
||||
if dataLen, err := strconv.Atoi(parts["data length"]); err != nil {
|
||||
return nil, fmt.Errorf("Error converting data length %q: %v", parts["data length"], err)
|
||||
} else if dataLen != len(data) {
|
||||
return nil, fmt.Errorf("Data length %d doesn't match received length %d", dataLen, len(data))
|
||||
}
|
||||
|
||||
//handle Service
|
||||
pos = pos + end + 1
|
||||
end = pos + int(p.Header.HeaderLen)
|
||||
p.Header.Service = string(b[pos : p.Header.HeaderLen+2])
|
||||
p.Header.Service = parts["service"]
|
||||
|
||||
//handle payload
|
||||
pos = int(p.Header.HeaderLen + 2)
|
||||
p.Data.AppendBytes(b[pos:])
|
||||
|
||||
} else {
|
||||
err = fmt.Errorf("Version %d not supported", b[0:0])
|
||||
p.Data.AppendBytes(data)
|
||||
return p, nil
|
||||
}
|
||||
|
||||
return
|
||||
|
||||
return nil, fmt.Errorf("Version %d not supported", 255-b[0])
|
||||
}
|
||||
|
||||
//PackV1 -- Outputs version 1 of packer
|
||||
func (p *Packer) PackV1() (b bytes.Buffer) {
|
||||
version := packerV1
|
||||
|
||||
var headerBuf bytes.Buffer
|
||||
headerBuf.WriteString(p.Header.FamilyText())
|
||||
headerBuf.WriteString(",")
|
||||
headerBuf.Write([]byte(p.Header.Address().String()))
|
||||
headerBuf.WriteString(",")
|
||||
headerBuf.WriteString(fmt.Sprintf("%d", p.Header.Port))
|
||||
headerBuf.WriteString(",")
|
||||
headerBuf.WriteString(fmt.Sprintf("%d", p.Data.buffer.Len()))
|
||||
headerBuf.WriteString(",")
|
||||
headerBuf.WriteString(p.Header.Service)
|
||||
|
||||
var metaBuf bytes.Buffer
|
||||
metaBuf.WriteByte(version)
|
||||
metaBuf.WriteByte(byte(headerBuf.Len()))
|
||||
func (p *Packer) PackV1() bytes.Buffer {
|
||||
header := strings.Join([]string{
|
||||
p.Header.FamilyText(),
|
||||
p.Header.AddressString(),
|
||||
strconv.Itoa(p.Header.Port),
|
||||
strconv.Itoa(p.Data.DataLen()),
|
||||
p.Header.Service,
|
||||
}, ",")
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(metaBuf.Bytes())
|
||||
buf.Write(headerBuf.Bytes())
|
||||
buf.Write(p.Data.buffer.Bytes())
|
||||
buf.WriteByte(packerV1)
|
||||
buf.WriteByte(byte(len(header)))
|
||||
buf.WriteString(header)
|
||||
buf.Write(p.Data.Data())
|
||||
|
||||
//fmt.Println("header: ", headerBuf.String())
|
||||
//fmt.Println("meta: ", metaBuf)
|
||||
//fmt.Println("Data: ", p.Data.buffer)
|
||||
//fmt.Println("Buffer: ", buf.Bytes())
|
||||
//fmt.Println("Buffer: ", hex.Dump(buf.Bytes()))
|
||||
//fmt.Printf("Buffer %s", buf.Bytes())
|
||||
|
||||
b = buf
|
||||
|
||||
return
|
||||
return buf
|
||||
}
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
package packer
|
||||
|
||||
import "bytes"
|
||||
import (
|
||||
"bytes"
|
||||
)
|
||||
|
||||
//packerData -- Contains packer data
|
||||
type packerData struct {
|
||||
buffer *bytes.Buffer
|
||||
DataLen int
|
||||
buffer bytes.Buffer
|
||||
}
|
||||
|
||||
func newPackerData() (p *packerData) {
|
||||
p = new(packerData)
|
||||
p.buffer = new(bytes.Buffer)
|
||||
return
|
||||
func newPackerData() *packerData {
|
||||
return new(packerData)
|
||||
}
|
||||
|
||||
func (p *packerData) AppendString(dataString string) (n int, err error) {
|
||||
n, err = p.buffer.WriteString(dataString)
|
||||
return
|
||||
func (p *packerData) AppendString(dataString string) (int, error) {
|
||||
return p.buffer.WriteString(dataString)
|
||||
}
|
||||
|
||||
func (p *packerData) AppendBytes(dataBytes []byte) (n int, err error) {
|
||||
n, err = p.buffer.Write(dataBytes)
|
||||
return
|
||||
func (p *packerData) AppendBytes(dataBytes []byte) (int, error) {
|
||||
return p.buffer.Write(dataBytes)
|
||||
}
|
||||
|
||||
//Data --
|
||||
func (p *packerData) Data() (b []byte) {
|
||||
b = p.buffer.Bytes()
|
||||
return
|
||||
func (p *packerData) Data() []byte {
|
||||
return p.buffer.Bytes()
|
||||
}
|
||||
|
||||
func (p *packerData) DataLen() int {
|
||||
return p.buffer.Len()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package packer
|
||||
|
||||
import "net"
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type addressFamily int
|
||||
|
||||
|
@ -11,7 +13,6 @@ type packerHeader struct {
|
|||
address net.IP
|
||||
Port int
|
||||
Service string
|
||||
HeaderLen byte
|
||||
}
|
||||
|
||||
//Family -- ENUM for Address Family
|
||||
|
@ -30,52 +31,42 @@ func newPackerHeader() (p *packerHeader) {
|
|||
p.SetAddress("127.0.0.1")
|
||||
p.Port = 65535
|
||||
p.Service = "na"
|
||||
p.HeaderLen = 0
|
||||
return
|
||||
}
|
||||
|
||||
//SetAddress -- Set Address. which sets address family automatically
|
||||
func (p *packerHeader) SetAddress(addr string) {
|
||||
p.address = net.ParseIP(addr)
|
||||
err := p.address.To4()
|
||||
|
||||
if err != nil {
|
||||
if p.address.To4() != nil {
|
||||
p.family = FamilyIPv4
|
||||
} else {
|
||||
err := p.address.To16()
|
||||
if err != nil {
|
||||
} else if p.address.To16() != nil {
|
||||
p.family = FamilyIPv6
|
||||
} 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) {
|
||||
b = make([]byte, 16)
|
||||
|
||||
switch {
|
||||
case p.address.To4() != nil:
|
||||
b = make([]byte, 4)
|
||||
for pos := range b {
|
||||
b[pos] = p.address[pos+12]
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
func (p *packerHeader) AddressBytes() []byte {
|
||||
if ip4 := p.address.To4(); ip4 != nil {
|
||||
p.address = ip4
|
||||
}
|
||||
|
||||
func (p *packerHeader) Address() (address net.IP) {
|
||||
address = p.address
|
||||
return
|
||||
return []byte(p.address)
|
||||
}
|
||||
|
||||
func (p *packerHeader) Family() (family addressFamily) {
|
||||
family = p.family
|
||||
return
|
||||
func (p *packerHeader) AddressString() string {
|
||||
return p.address.String()
|
||||
}
|
||||
|
||||
func (p *packerHeader) FamilyText() (familyText string) {
|
||||
familyText = addressFamilyText[p.family]
|
||||
return
|
||||
func (p *packerHeader) Address() net.IP {
|
||||
return p.address
|
||||
}
|
||||
|
||||
func (p *packerHeader) Family() addressFamily {
|
||||
return p.family
|
||||
}
|
||||
|
||||
func (p *packerHeader) FamilyText() string {
|
||||
return addressFamilyText[p.family]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package packer
|
||||
|
||||
import "log"
|
||||
import "os"
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
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 "context"
|
||||
import "fmt"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
//Track -- used to track connection + domain
|
||||
type Track struct {
|
||||
|
@ -20,6 +23,7 @@ func NewTrack(conn net.Conn, domain string) (p *Track) {
|
|||
|
||||
//Tracking --
|
||||
type Tracking struct {
|
||||
mutex sync.Mutex
|
||||
connections map[string]*Track
|
||||
register chan *Track
|
||||
unregister chan net.Conn
|
||||
|
@ -46,18 +50,22 @@ func (p *Tracking) Run(ctx context.Context) {
|
|||
return
|
||||
|
||||
case connection := <-p.register:
|
||||
p.mutex.Lock()
|
||||
key := connection.conn.RemoteAddr().String()
|
||||
loginfo.Println("register fired", key)
|
||||
p.connections[key] = connection
|
||||
p.list()
|
||||
p.mutex.Unlock()
|
||||
|
||||
case connection := <-p.unregister:
|
||||
p.mutex.Lock()
|
||||
key := connection.RemoteAddr().String()
|
||||
loginfo.Println("unregister fired", key)
|
||||
if _, ok := p.connections[key]; ok {
|
||||
delete(p.connections, key)
|
||||
}
|
||||
p.list()
|
||||
p.mutex.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -70,12 +78,12 @@ func (p *Tracking) list() {
|
|||
|
||||
//Lookup --
|
||||
// - 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 {
|
||||
c = p.connections[key]
|
||||
} else {
|
||||
err = fmt.Errorf("Lookup failed for %s", key)
|
||||
c = nil
|
||||
return p.connections[key], nil
|
||||
}
|
||||
return
|
||||
return nil, fmt.Errorf("Lookup failed for %s", key)
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package genericlistener
|
||||
package server
|
||||
|
||||
import (
|
||||
"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
|
||||
func (w *WedgeConn) Discard(n int) (discarded int, err error) {
|
||||
func (w *WedgeConn) Discard(n int) (int, error) {
|
||||
return w.reader.Discard(n)
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,7 @@ func (w *WedgeConn) ReadByte() (byte, error) {
|
|||
|
||||
//Read -- A normal reader.
|
||||
func (w *WedgeConn) Read(p []byte) (int, error) {
|
||||
cnt, err := w.reader.Read(p)
|
||||
return cnt, err
|
||||
return w.reader.Read(p)
|
||||
}
|
||||
|
||||
//Buffered --
|
||||
|
@ -56,13 +55,12 @@ func (w *WedgeConn) Buffered() int {
|
|||
//PeekAll --
|
||||
// - get all the chars available
|
||||
// - pass then back
|
||||
func (w *WedgeConn) PeekAll() (buf []byte, err error) {
|
||||
|
||||
_, err = w.Peek(1)
|
||||
if err != nil {
|
||||
func (w *WedgeConn) PeekAll() ([]byte, error) {
|
||||
// We first peek with 1 so that if there is no buffered data the reader will
|
||||
// fill the buffer before we read how much data is buffered.
|
||||
if _, err := w.Peek(1); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf, err = w.Peek(w.Buffered())
|
||||
return
|
||||
return w.Peek(w.Buffered())
|
||||
}
|
|
@ -1,30 +1,20 @@
|
|||
package genericlistener
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||
|
||||
"sync"
|
||||
|
||||
"io"
|
||||
|
||||
"context"
|
||||
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 4096,
|
||||
WriteBufferSize: 4096,
|
||||
}
|
||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||
)
|
||||
|
||||
// Connection track websocket and faciliates in and out data
|
||||
type Connection struct {
|
||||
mutex *sync.Mutex
|
||||
mutex sync.Mutex
|
||||
|
||||
// The main connection table (should be just one of these created at startup)
|
||||
connectionTable *Table
|
||||
|
@ -44,12 +34,21 @@ type Connection struct {
|
|||
// Address of the Remote End Point
|
||||
source string
|
||||
|
||||
// serverName -- Name of the server, at this point 1st domain registered. Will likely change with JWT
|
||||
serverName string
|
||||
|
||||
// bytes in
|
||||
bytesIn int64
|
||||
|
||||
// bytes out
|
||||
bytesOut int64
|
||||
|
||||
// requests
|
||||
requests int64
|
||||
|
||||
// response
|
||||
responses int64
|
||||
|
||||
// Connect Time
|
||||
connectTime time.Time
|
||||
|
||||
|
@ -63,28 +62,38 @@ type Connection struct {
|
|||
|
||||
///wssState tracks a highlevel status of the connection, false means do nothing.
|
||||
wssState bool
|
||||
|
||||
//connectionID
|
||||
connectionID int64
|
||||
}
|
||||
|
||||
//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.mutex = &sync.Mutex{}
|
||||
p.connectionTable = connectionTable
|
||||
p.conn = conn
|
||||
p.source = remoteAddress
|
||||
p.serverName = serverName
|
||||
p.bytesIn = 0
|
||||
p.bytesOut = 0
|
||||
p.requests = 0
|
||||
p.responses = 0
|
||||
p.send = make(chan *SendTrack)
|
||||
p.connectTime = time.Now()
|
||||
p.initialDomains = initialDomains
|
||||
p.connectionTrack = connectionTrack
|
||||
p.DomainTrack = make(map[string]*DomainTrack)
|
||||
p.lastUpdate = time.Now()
|
||||
|
||||
for _, domain := range initialDomains {
|
||||
p.AddTrackedDomain(string(domain.(string)))
|
||||
}
|
||||
|
||||
p.State(true)
|
||||
p.SetState(true)
|
||||
p.connectionID = connectionID
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -95,28 +104,34 @@ func (c *Connection) AddTrackedDomain(domain string) {
|
|||
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
|
||||
func (c *Connection) InitialDomains() (i []interface{}) {
|
||||
i = c.initialDomains
|
||||
return
|
||||
func (c *Connection) InitialDomains() []interface{} {
|
||||
return c.initialDomains
|
||||
}
|
||||
|
||||
//ConnectTime -- Property
|
||||
func (c *Connection) ConnectTime() (t time.Time) {
|
||||
t = c.connectTime
|
||||
return
|
||||
func (c *Connection) ConnectTime() time.Time {
|
||||
return c.connectTime
|
||||
}
|
||||
|
||||
//BytesIn -- Property
|
||||
func (c *Connection) BytesIn() (b int64) {
|
||||
b = c.bytesIn
|
||||
return
|
||||
func (c *Connection) BytesIn() int64 {
|
||||
return c.bytesIn
|
||||
}
|
||||
|
||||
//BytesOut -- Property
|
||||
func (c *Connection) BytesOut() (b int64) {
|
||||
b = c.bytesOut
|
||||
return
|
||||
func (c *Connection) BytesOut() int64 {
|
||||
return c.bytesOut
|
||||
}
|
||||
|
||||
//SendCh -- property to sending channel
|
||||
|
@ -124,6 +139,11 @@ func (c *Connection) SendCh() chan *SendTrack {
|
|||
return c.send
|
||||
}
|
||||
|
||||
//Source --
|
||||
func (c *Connection) Source() string {
|
||||
return c.source
|
||||
}
|
||||
|
||||
func (c *Connection) addIn(num int64) {
|
||||
c.bytesIn = c.bytesIn + num
|
||||
}
|
||||
|
@ -132,59 +152,74 @@ func (c *Connection) addOut(num int64) {
|
|||
c.bytesOut = c.bytesOut + num
|
||||
}
|
||||
|
||||
//ConnectionTable -- property
|
||||
func (c *Connection) ConnectionTable() (table *Table) {
|
||||
table = c.connectionTable
|
||||
return
|
||||
func (c *Connection) addRequests() {
|
||||
c.requests = c.requests + 1
|
||||
}
|
||||
|
||||
//GetState -- Get state of Socket...this is a high level state.
|
||||
func (c *Connection) GetState() bool {
|
||||
defer func() {
|
||||
c.mutex.Unlock()
|
||||
}()
|
||||
func (c *Connection) addResponse() {
|
||||
c.responses = c.responses + 1
|
||||
}
|
||||
|
||||
//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()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.wssState
|
||||
}
|
||||
|
||||
//State -- Set the set of the high level connection
|
||||
func (c *Connection) State(state bool) {
|
||||
defer func() {
|
||||
c.mutex.Unlock()
|
||||
}()
|
||||
|
||||
//SetState -- Set the set of the high level connection
|
||||
func (c *Connection) SetState(state bool) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
c.wssState = state
|
||||
}
|
||||
|
||||
//Update -- updates the lastUpdate property tracking idle time
|
||||
func (c *Connection) Update() {
|
||||
defer func() {
|
||||
c.mutex.Unlock()
|
||||
}()
|
||||
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
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
|
||||
//The libary failes if client abends during write-cycle. a fast moving write is not caught before socket state bubbles up
|
||||
//A synchronised state is maintained
|
||||
func (c Connection) NextWriter(wssMessageType int) (w io.WriteCloser, err error) {
|
||||
if c.GetState() == true {
|
||||
w, err = c.conn.NextWriter(wssMessageType)
|
||||
} else {
|
||||
loginfo.Println("NextWriter aborted, state is not true")
|
||||
func (c *Connection) NextWriter(wssMessageType int) (io.WriteCloser, error) {
|
||||
if c.State() {
|
||||
return c.conn.NextWriter(wssMessageType)
|
||||
}
|
||||
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.
|
||||
func (c *Connection) Write(w io.WriteCloser, message []byte) (cnt int, err error) {
|
||||
if c.GetState() == true {
|
||||
cnt, err = w.Write(message)
|
||||
func (c *Connection) Write(w io.WriteCloser, message []byte) (int, error) {
|
||||
if c.State() {
|
||||
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
|
||||
|
@ -199,17 +234,17 @@ func (c *Connection) Reader(ctx context.Context) {
|
|||
|
||||
loginfo.Println("Reader Start ", c)
|
||||
|
||||
c.conn.SetReadLimit(65535)
|
||||
//c.conn.SetReadLimit(65535)
|
||||
for {
|
||||
msgType, message, err := c.conn.ReadMessage()
|
||||
_, message, err := c.conn.ReadMessage()
|
||||
|
||||
loginfo.Println("ReadMessage", msgType, err)
|
||||
//loginfo.Println("ReadMessage", msgType, err)
|
||||
|
||||
c.Update()
|
||||
|
||||
if err != nil {
|
||||
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
|
||||
c.State(false)
|
||||
c.SetState(false)
|
||||
loginfo.Printf("error: %v", err)
|
||||
}
|
||||
break
|
||||
|
@ -220,31 +255,31 @@ func (c *Connection) Reader(ctx context.Context) {
|
|||
key := p.Header.Address().String() + ":" + strconv.Itoa(p.Header.Port)
|
||||
track, err := connectionTrack.Lookup(key)
|
||||
|
||||
loginfo.Println(hex.Dump(p.Data.Data()))
|
||||
//loginfo.Println(hex.Dump(p.Data.Data()))
|
||||
|
||||
if err != nil {
|
||||
loginfo.Println("Unable to locate Tracking for ", key)
|
||||
//loginfo.Println("Unable to locate Tracking for ", key)
|
||||
continue
|
||||
}
|
||||
|
||||
//Support for tracking outbound traffic based on domain.
|
||||
if domainTrack, ok := c.DomainTrack[track.domain]; ok {
|
||||
//if ok then add to structure, else warn there is something wrong
|
||||
domainTrack.AddIn(int64(len(message)))
|
||||
domainTrack.AddOut(int64(len(message)))
|
||||
domainTrack.AddResponses()
|
||||
}
|
||||
|
||||
track.conn.Write(p.Data.Data())
|
||||
|
||||
c.addIn(int64(len(message)))
|
||||
loginfo.Println("end of read")
|
||||
c.addResponse()
|
||||
//loginfo.Println("end of read")
|
||||
}
|
||||
}
|
||||
|
||||
//Writer -- expoer the writer function
|
||||
func (c *Connection) Writer() {
|
||||
defer func() {
|
||||
c.conn.Close()
|
||||
}()
|
||||
defer c.conn.Close()
|
||||
|
||||
loginfo.Println("Writer Start ", c)
|
||||
|
||||
|
@ -255,6 +290,7 @@ func (c *Connection) Writer() {
|
|||
w, err := c.NextWriter(websocket.BinaryMessage)
|
||||
loginfo.Println("next writer ", w)
|
||||
if err != nil {
|
||||
c.SetState(false)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -269,11 +305,13 @@ func (c *Connection) Writer() {
|
|||
messageLen := int64(len(message.data))
|
||||
|
||||
c.addOut(messageLen)
|
||||
c.addRequests()
|
||||
|
||||
//Support for tracking outbound traffic based on domain.
|
||||
if domainTrack, ok := c.DomainTrack[message.domain]; ok {
|
||||
//if ok then add to structure, else warn there is something wrong
|
||||
domainTrack.AddOut(messageLen)
|
||||
domainTrack.AddIn(messageLen)
|
||||
domainTrack.AddRequests()
|
||||
loginfo.Println("adding ", messageLen, " to ", message.domain)
|
||||
} else {
|
||||
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
|
||||
//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
|
||||
source string
|
||||
|
||||
// serverName
|
||||
serverName string
|
||||
|
||||
// communications channel between go routines
|
||||
commCh chan bool
|
||||
|
||||
|
@ -22,10 +27,11 @@ type Registration struct {
|
|||
}
|
||||
|
||||
//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.conn = conn
|
||||
p.source = remoteAddress
|
||||
p.serverName = serverName
|
||||
p.commCh = make(chan bool)
|
||||
p.initialDomains = initialDomains
|
||||
p.connectionTrack = connectionTrack
|
|
@ -1,8 +1,10 @@
|
|||
package genericlistener
|
||||
package server
|
||||
|
||||
import "fmt"
|
||||
import "time"
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
initialDomains = 0
|
||||
|
@ -12,7 +14,7 @@ const (
|
|||
//Table maintains the set of connections
|
||||
type Table struct {
|
||||
connections map[*Connection][]string
|
||||
domains map[string]*Connection
|
||||
domains map[string]*DomainLoadBalance
|
||||
register chan *Registration
|
||||
unregister chan *Connection
|
||||
domainAnnounce chan *DomainMapping
|
||||
|
@ -22,10 +24,10 @@ type Table struct {
|
|||
}
|
||||
|
||||
//NewTable -- consructor
|
||||
func NewTable(dwell int, idle int) (p *Table) {
|
||||
func NewTable(dwell, idle int) (p *Table) {
|
||||
p = new(Table)
|
||||
p.connections = make(map[*Connection][]string)
|
||||
p.domains = make(map[string]*Connection)
|
||||
p.domains = make(map[string]*DomainLoadBalance)
|
||||
p.register = make(chan *Registration)
|
||||
p.unregister = make(chan *Connection)
|
||||
p.domainAnnounce = make(chan *DomainMapping)
|
||||
|
@ -36,15 +38,23 @@ func NewTable(dwell int, idle int) (p *Table) {
|
|||
}
|
||||
|
||||
//Connections Property
|
||||
func (c *Table) Connections() (table map[*Connection][]string) {
|
||||
table = c.connections
|
||||
return
|
||||
func (c *Table) Connections() map[*Connection][]string {
|
||||
return c.connections
|
||||
}
|
||||
|
||||
//ConnByDomain -- Obtains a connection from a domain announcement.
|
||||
func (c *Table) ConnByDomain(domain string) (conn *Connection, ok bool) {
|
||||
conn, ok = c.domains[domain]
|
||||
return
|
||||
//ConnByDomain -- Obtains a connection from a domain announcement. A domain may be announced more than once
|
||||
//if that is the case the system stores these connections and then sends traffic back round-robin
|
||||
//back to the WSS connections
|
||||
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 --
|
||||
|
@ -56,7 +66,7 @@ func (c *Table) reaper(delay int, idle int) {
|
|||
|
||||
loginfo.Println("Running scanning ", len(c.connections))
|
||||
for d := range c.connections {
|
||||
if d.GetState() == false {
|
||||
if !d.State() {
|
||||
if time.Since(d.lastUpdate).Seconds() > float64(idle) {
|
||||
loginfo.Println("reaper removing ", d.lastUpdate, time.Since(d.lastUpdate).Seconds())
|
||||
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
|
||||
func (c *Table) Run(ctx context.Context) {
|
||||
func (c *Table) Run(ctx context.Context, defaultMethod string) {
|
||||
loginfo.Println("ConnectionTable starting")
|
||||
|
||||
go c.reaper(c.dwell, c.idle)
|
||||
|
@ -82,7 +103,8 @@ func (c *Table) Run(ctx context.Context) {
|
|||
case registration := <-c.register:
|
||||
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)
|
||||
registration.commCh <- true
|
||||
|
||||
|
@ -92,7 +114,17 @@ func (c *Table) Run(ctx context.Context) {
|
|||
|
||||
newDomain := string(domain.(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
|
||||
s := c.connections[connection]
|
||||
|
@ -103,13 +135,28 @@ func (c *Table) Run(ctx context.Context) {
|
|||
|
||||
case connection := <-c.unregister:
|
||||
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 {
|
||||
|
||||
//iterate over the connections for the domain
|
||||
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 {
|
||||
|
||||
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.connections, connection)
|
||||
//close(connection.send)
|
||||
|
@ -129,7 +176,6 @@ func (c *Table) Run(ctx context.Context) {
|
|||
}
|
||||
|
||||
//Register -- Property
|
||||
func (c *Table) Register() (r chan *Registration) {
|
||||
r = c.register
|
||||
return
|
||||
func (c *Table) Register() chan *Registration {
|
||||
return c.register
|
||||
}
|
|
@ -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 --
|
||||
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 (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"net/http"
|
||||
|
||||
"bufio"
|
||||
|
||||
"git.daplie.com/Daplie/go-rvpn-server/rvpn/packer"
|
||||
)
|
||||
|
||||
|
@ -29,13 +25,17 @@ type contextKey string
|
|||
//CtxConnectionTrack
|
||||
const (
|
||||
ctxSecretKey contextKey = "secretKey"
|
||||
ctxConnectionTable contextKey = "connectionTable"
|
||||
ctxServerStatus contextKey = "serverstatus"
|
||||
|
||||
//ctxConnectionTable contextKey = "connectionTable"
|
||||
|
||||
ctxConfig contextKey = "config"
|
||||
ctxListenerRegistration contextKey = "listenerRegistration"
|
||||
ctxConnectionTrack contextKey = "connectionTrack"
|
||||
ctxWssHostName contextKey = "wsshostname"
|
||||
ctxAdminHostName contextKey = "adminHostName"
|
||||
ctxCancelCheck contextKey = "cancelcheck"
|
||||
ctxLoadbalanceDefaultMethod contextKey = "lbdefaultmethod"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -271,17 +271,17 @@ func handleStream(ctx context.Context, wConn *WedgeConn) {
|
|||
|
||||
//handleExternalHTTPRequest -
|
||||
// - get a wConn and start processing requests
|
||||
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname string, service string) {
|
||||
connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||
func handleExternalHTTPRequest(ctx context.Context, extConn *WedgeConn, hostname, service string) {
|
||||
//connectionTracking := ctx.Value(ctxConnectionTrack).(*Tracking)
|
||||
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
||||
|
||||
defer func() {
|
||||
connectionTracking.unregister <- extConn
|
||||
serverStatus.ExtConnectionUnregister(extConn)
|
||||
extConn.Close()
|
||||
}()
|
||||
|
||||
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||
//find the connection by domain name
|
||||
conn, ok := connectionTable.ConnByDomain(hostname)
|
||||
conn, ok := serverStatus.ConnectionTable.ConnByDomain(hostname)
|
||||
if !ok {
|
||||
//matching connection can not be found based on ConnByDomain
|
||||
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)
|
||||
connectionTracking.register <- track
|
||||
serverStatus.ExtConnectionRegister(track)
|
||||
|
||||
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])
|
||||
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)
|
||||
conn.SendCh() <- sendTrack
|
||||
serverStatus.SendExtRequest(conn, sendTrack)
|
||||
|
||||
_, err = extConn.Discard(cnt)
|
||||
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 -
|
||||
// - expecting an existing oneConnListener with a qualified wss client connected.
|
||||
// - auth will happen again since we were just peeking at the token.
|
||||
func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
||||
secretKey := ctx.Value(ctxSecretKey).(string)
|
||||
connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||
serverStatus := ctx.Value(ctxServerStatus).(*Status)
|
||||
|
||||
//connectionTable := ctx.Value(ctxConnectionTable).(*Table)
|
||||
|
||||
router := mux.NewRouter().StrictSlash(true)
|
||||
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{})
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
ReadBufferSize: 65535,
|
||||
WriteBufferSize: 65535,
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
|
@ -438,11 +382,11 @@ func handleWssClient(ctx context.Context, oneConn *oneConnListener) {
|
|||
|
||||
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()
|
||||
if !ok {
|
||||
loginfo.Println("connection registration failed ", newRegistration)
|
|
@ -1,4 +1,4 @@
|
|||
package genericlistener
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
@ -43,8 +43,8 @@ func NewListenerRegistration(port int) (p *ListenerRegistration) {
|
|||
return
|
||||
}
|
||||
|
||||
//GenericListeners -
|
||||
type GenericListeners struct {
|
||||
//servers -
|
||||
type servers struct {
|
||||
listeners map[*net.Listener]int
|
||||
ctx context.Context
|
||||
connnectionTable *Table
|
||||
|
@ -52,38 +52,41 @@ type GenericListeners struct {
|
|||
secretKey string
|
||||
certbundle tls.Certificate
|
||||
register chan *ListenerRegistration
|
||||
genericListeners *GenericListeners
|
||||
servers *servers
|
||||
wssHostName string
|
||||
adminHostName string
|
||||
cancelCheck int
|
||||
lbDefaultMethod string
|
||||
serverStatus *Status
|
||||
}
|
||||
|
||||
//NewGenerListeners --
|
||||
func NewGenerListeners(ctx context.Context, connectionTable *Table, connectionTrack *Tracking, secretKey string, certbundle tls.Certificate, wssHostName string, adminHostName string, cancelCheck int) (p *GenericListeners) {
|
||||
p = new(GenericListeners)
|
||||
func NewGenerListeners(ctx context.Context, secretKey string, certbundle tls.Certificate, serverStatus *Status) (p *servers) {
|
||||
p = new(servers)
|
||||
p.listeners = make(map[*net.Listener]int)
|
||||
p.ctx = ctx
|
||||
p.connnectionTable = connectionTable
|
||||
p.connectionTracking = connectionTrack
|
||||
p.connnectionTable = serverStatus.ConnectionTable
|
||||
p.connectionTracking = serverStatus.ConnectionTracking
|
||||
p.secretKey = secretKey
|
||||
p.certbundle = certbundle
|
||||
p.register = make(chan *ListenerRegistration)
|
||||
p.wssHostName = wssHostName
|
||||
p.adminHostName = adminHostName
|
||||
p.cancelCheck = cancelCheck
|
||||
p.wssHostName = serverStatus.WssDomain
|
||||
p.adminHostName = serverStatus.AdminDomain
|
||||
p.cancelCheck = serverStatus.DeadTime.cancelcheck
|
||||
p.lbDefaultMethod = serverStatus.LoadbalanceDefaultMethod
|
||||
p.serverStatus = serverStatus
|
||||
return
|
||||
}
|
||||
|
||||
//Run -- Execute
|
||||
// - execute the GenericLister
|
||||
// - 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")
|
||||
|
||||
config := &tls.Config{Certificates: []tls.Certificate{gl.certbundle}}
|
||||
|
||||
ctx = context.WithValue(ctx, ctxSecretKey, gl.secretKey)
|
||||
ctx = context.WithValue(ctx, ctxConnectionTable, gl.connnectionTable)
|
||||
|
||||
loginfo.Println(gl.connectionTracking)
|
||||
|
||||
|
@ -93,6 +96,8 @@ func (gl *GenericListeners) Run(ctx context.Context, initialPort int) {
|
|||
ctx = context.WithValue(ctx, ctxWssHostName, gl.wssHostName)
|
||||
ctx = context.WithValue(ctx, ctxAdminHostName, gl.adminHostName)
|
||||
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) {
|
||||
for {
|
|
@ -1,4 +1,4 @@
|
|||
package genericlistener
|
||||
package server
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
@ -9,18 +9,16 @@ type oneConnListener struct {
|
|||
conn net.Conn
|
||||
}
|
||||
|
||||
func (l *oneConnListener) Accept() (c net.Conn, err error) {
|
||||
c = l.conn
|
||||
|
||||
if c == nil {
|
||||
err = io.EOF
|
||||
loginfo.Println("Accept")
|
||||
return
|
||||
func (l *oneConnListener) Accept() (net.Conn, error) {
|
||||
if l.conn == nil {
|
||||
loginfo.Println("Accept EOF")
|
||||
return nil, io.EOF
|
||||
}
|
||||
err = nil
|
||||
|
||||
c := l.conn
|
||||
l.conn = nil
|
||||
loginfo.Println("Accept", c.LocalAddr().String(), c.RemoteAddr().String())
|
||||
return
|
||||
return c, nil
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
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