2017-03-26 19:50:51 +00:00
package tun2
import (
2017-09-30 18:04:33 +00:00
"bytes"
2017-03-26 20:38:05 +00:00
"context"
2017-03-26 19:50:51 +00:00
"crypto/tls"
"encoding/json"
"errors"
2017-09-30 18:04:33 +00:00
"fmt"
"io/ioutil"
2017-03-26 19:50:51 +00:00
"math/rand"
"net"
"net/http"
2017-09-30 18:04:33 +00:00
"os"
2017-03-26 19:50:51 +00:00
"sync"
2017-03-26 22:14:13 +00:00
"time"
2017-03-26 19:50:51 +00:00
"github.com/Xe/ln"
2017-04-05 21:31:15 +00:00
failure "github.com/dgryski/go-failure"
2017-03-26 19:50:51 +00:00
"github.com/mtneug/pkg/ulid"
2017-03-27 04:56:54 +00:00
cmap "github.com/streamrail/concurrent-map"
2017-03-26 19:50:51 +00:00
kcp "github.com/xtaci/kcp-go"
"github.com/xtaci/smux"
)
2017-04-28 22:23:26 +00:00
// Error values
var (
ErrNoSuchBackend = errors . New ( "tun2: there is no such backend" )
ErrAuthMismatch = errors . New ( "tun2: authenication doesn't match database records" )
ErrCantRemoveWhatDoesntExist = errors . New ( "tun2: this connection does not exist, cannot remove it" )
)
// ServerConfig ...
2017-03-26 19:50:51 +00:00
type ServerConfig struct {
TCPAddr string
KCPAddr string
TLSConfig * tls . Config
SmuxConf * smux . Config
Storage Storage
}
2017-04-28 22:23:26 +00:00
// Storage is the minimal subset of features that tun2's Server needs out of a
// persistence layer.
2017-03-26 19:50:51 +00:00
type Storage interface {
2017-04-28 22:23:26 +00:00
HasToken ( token string ) ( user string , scopes [ ] string , err error )
HasRoute ( domain string ) ( user string , err error )
2017-03-26 19:50:51 +00:00
}
2017-04-28 22:23:26 +00:00
// Server routes frontend HTTP traffic to backend TCP traffic.
2017-03-26 19:50:51 +00:00
type Server struct {
cfg * ServerConfig
connlock sync . Mutex
conns map [ net . Conn ] * Connection
2017-03-27 04:56:54 +00:00
domains cmap . ConcurrentMap
2017-03-26 19:50:51 +00:00
}
2017-04-28 22:23:26 +00:00
// NewServer creates a new Server instance with a given config, acquiring all
// relevant resources.
2017-03-26 19:50:51 +00:00
func NewServer ( cfg * ServerConfig ) ( * Server , error ) {
if cfg == nil {
return nil , errors . New ( "tun2: config must be specified" )
}
if cfg . SmuxConf == nil {
cfg . SmuxConf = smux . DefaultConfig ( )
}
2017-04-05 22:54:58 +00:00
cfg . SmuxConf . KeepAliveInterval = time . Second
cfg . SmuxConf . KeepAliveTimeout = 15 * time . Second
2017-03-26 19:50:51 +00:00
server := & Server {
cfg : cfg ,
conns : map [ net . Conn ] * Connection { } ,
2017-03-27 04:56:54 +00:00
domains : cmap . New ( ) ,
2017-03-26 19:50:51 +00:00
}
return server , nil
}
2017-09-30 16:47:47 +00:00
type backendMatcher func ( * Connection ) bool
func ( s * Server ) getBackendsForMatcher ( bm backendMatcher ) [ ] Backend {
s . connlock . Lock ( )
defer s . connlock . Unlock ( )
var result [ ] Backend
for _ , c := range s . conns {
if ! bm ( c ) {
continue
}
protocol := "tcp"
if c . isKCP {
protocol = "kcp"
}
result = append ( result , Backend {
ID : c . id ,
Proto : protocol ,
User : c . user ,
Domain : c . domain ,
Phi : float32 ( c . detector . Phi ( time . Now ( ) ) ) ,
Host : c . conn . RemoteAddr ( ) . String ( ) ,
2017-09-30 17:33:19 +00:00
Usable : c . usable ,
2017-09-30 16:47:47 +00:00
} )
}
return result
}
func ( s * Server ) KillBackend ( id string ) error {
s . connlock . Lock ( )
defer s . connlock . Unlock ( )
for _ , c := range s . conns {
if c . id == id {
c . cancel ( )
return nil
}
}
return ErrNoSuchBackend
}
func ( s * Server ) GetBackendsForDomain ( domain string ) [ ] Backend {
return s . getBackendsForMatcher ( func ( c * Connection ) bool {
return c . domain == domain
} )
}
func ( s * Server ) GetBackendsForUser ( uname string ) [ ] Backend {
return s . getBackendsForMatcher ( func ( c * Connection ) bool {
return c . user == uname
} )
}
func ( s * Server ) GetAllBackends ( ) [ ] Backend {
return s . getBackendsForMatcher ( func ( * Connection ) bool { return true } )
}
2017-04-28 22:23:26 +00:00
// ListenAndServe starts the backend TCP/KCP listeners and relays backend
// traffic to and from them.
2017-03-26 19:50:51 +00:00
func ( s * Server ) ListenAndServe ( ) error {
2017-10-01 13:28:13 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
ln . Log ( ctx , ln . F {
2017-03-26 20:15:42 +00:00
"action" : "listen_and_serve_called" ,
} )
2017-03-26 19:50:51 +00:00
if s . cfg . TCPAddr != "" {
go func ( ) {
l , err := tls . Listen ( "tcp" , s . cfg . TCPAddr , s . cfg . TLSConfig )
if err != nil {
panic ( err )
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , ln . F {
2017-03-26 20:14:05 +00:00
"action" : "tcp+tls_listening" ,
"addr" : l . Addr ( ) ,
} )
2017-03-26 19:50:51 +00:00
for {
conn , err := l . Accept ( )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F { "kind" : "tcp" , "addr" : l . Addr ( ) . String ( ) } )
2017-03-26 19:50:51 +00:00
continue
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , ln . F {
2017-03-26 20:23:52 +00:00
"action" : "new_client" ,
"kcp" : false ,
"addr" : conn . RemoteAddr ( ) ,
} )
2017-03-26 19:50:51 +00:00
go s . HandleConn ( conn , false )
}
} ( )
}
if s . cfg . KCPAddr != "" {
go func ( ) {
l , err := kcp . Listen ( s . cfg . KCPAddr )
if err != nil {
panic ( err )
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , ln . F {
2017-03-26 20:14:05 +00:00
"action" : "kcp+tls_listening" ,
"addr" : l . Addr ( ) ,
} )
2017-03-26 19:50:51 +00:00
for {
conn , err := l . Accept ( )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F { "kind" : "kcp" , "addr" : l . Addr ( ) . String ( ) } )
2017-03-26 19:50:51 +00:00
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , ln . F {
2017-03-26 20:23:52 +00:00
"action" : "new_client" ,
"kcp" : true ,
"addr" : conn . RemoteAddr ( ) ,
} )
2017-03-26 19:50:51 +00:00
tc := tls . Server ( conn , s . cfg . TLSConfig )
go s . HandleConn ( tc , true )
}
} ( )
}
2017-04-28 22:23:26 +00:00
// XXX experimental, might get rid of this inside this process
2017-03-26 22:14:13 +00:00
go func ( ) {
for {
2017-04-05 22:26:44 +00:00
time . Sleep ( time . Second )
2017-04-05 21:31:15 +00:00
now := time . Now ( )
s . connlock . Lock ( )
for _ , c := range s . conns {
failureChance := c . detector . Phi ( now )
2017-09-30 15:39:30 +00:00
if failureChance > 0.8 {
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , c . F ( ) , ln . F {
2017-04-05 22:23:13 +00:00
"action" : "phi_failure_detection" ,
"value" : failureChance ,
} )
}
2017-04-05 21:31:15 +00:00
}
2017-04-05 21:33:54 +00:00
s . connlock . Unlock ( )
2017-03-27 05:19:43 +00:00
}
} ( )
2017-03-26 22:14:13 +00:00
2017-03-27 05:19:43 +00:00
return nil
}
2017-03-26 23:16:39 +00:00
2017-04-28 22:23:26 +00:00
// HandleConn starts up the needed mechanisms to relay HTTP traffic to/from
// the currently connected backend.
2017-03-26 19:50:51 +00:00
func ( s * Server ) HandleConn ( c net . Conn , isKCP bool ) {
2017-04-28 22:23:26 +00:00
// XXX TODO clean this up it's really ugly.
defer c . Close ( )
2017-03-26 20:38:05 +00:00
ctx , cancel := context . WithCancel ( context . Background ( ) )
defer cancel ( )
2017-03-26 19:50:51 +00:00
session , err := smux . Server ( c , s . cfg . SmuxConf )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F {
2017-03-26 19:50:51 +00:00
"action" : "session_failure" ,
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
c . Close ( )
return
}
2017-04-28 22:23:26 +00:00
defer session . Close ( )
2017-03-26 19:50:51 +00:00
controlStream , err := session . OpenStream ( )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F {
2017-03-26 22:19:33 +00:00
"action" : "control_stream_failure" ,
2017-03-26 19:50:51 +00:00
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
return
}
2017-04-28 22:23:26 +00:00
defer controlStream . Close ( )
2017-03-26 19:50:51 +00:00
csd := json . NewDecoder ( controlStream )
auth := & Auth { }
err = csd . Decode ( auth )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F {
2017-03-26 19:50:51 +00:00
"action" : "control_stream_auth_decoding_failure" ,
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
return
}
2017-04-28 22:23:26 +00:00
routeUser , err := s . cfg . Storage . HasRoute ( auth . Domain )
2017-03-26 19:50:51 +00:00
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F {
2017-03-26 19:50:51 +00:00
"action" : "nosuch_domain" ,
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
return
}
2017-04-28 22:23:26 +00:00
tokenUser , scopes , err := s . cfg . Storage . HasToken ( auth . Token )
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , ln . F {
2017-04-28 22:23:26 +00:00
"action" : "nosuch_token" ,
2017-03-26 19:50:51 +00:00
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
2017-04-28 22:23:26 +00:00
return
}
ok := false
for _ , sc := range scopes {
if sc == "connect" {
ok = true
break
}
}
2017-03-26 19:50:51 +00:00
2017-04-28 22:23:26 +00:00
if ! ok {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , ErrAuthMismatch , ln . F {
2017-04-28 22:23:26 +00:00
"action" : "token_not_authorized" ,
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
}
if routeUser != tokenUser {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , ErrAuthMismatch , ln . F {
2017-04-28 22:23:26 +00:00
"action" : "auth_mismatch" ,
"local" : c . LocalAddr ( ) . String ( ) ,
"remote" : c . RemoteAddr ( ) . String ( ) ,
} )
2017-03-26 19:50:51 +00:00
return
}
connection := & Connection {
2017-04-05 21:31:15 +00:00
id : ulid . New ( ) . String ( ) ,
conn : c ,
isKCP : isKCP ,
session : session ,
2017-04-28 22:23:26 +00:00
user : tokenUser ,
2017-04-05 21:31:15 +00:00
domain : auth . Domain ,
2017-09-30 17:33:19 +00:00
cf : cancel ,
2017-04-05 21:40:37 +00:00
detector : failure . New ( 15 , 1 ) ,
2017-04-06 04:44:12 +00:00
Auth : auth ,
2017-03-26 19:50:51 +00:00
}
2017-09-30 17:42:09 +00:00
defer func ( ) {
if r := recover ( ) ; r != nil {
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , connection , ln . F { "action" : "connection handler panic" , "err" : r } )
2017-09-30 17:42:09 +00:00
}
} ( )
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , ln . F {
2017-03-26 20:18:58 +00:00
"action" : "backend_connected" ,
2017-03-26 22:30:19 +00:00
} , connection . F ( ) )
2017-03-26 20:18:58 +00:00
2017-03-26 19:50:51 +00:00
s . connlock . Lock ( )
s . conns [ c ] = connection
s . connlock . Unlock ( )
2017-03-27 04:56:54 +00:00
var conns [ ] * Connection
val , ok := s . domains . Get ( auth . Domain )
if ok {
conns , ok = val . ( [ ] * Connection )
if ! ok {
conns = nil
s . domains . Remove ( auth . Domain )
}
}
conns = append ( conns , connection )
s . domains . Set ( auth . Domain , conns )
2017-09-30 17:33:19 +00:00
connection . usable = true
2017-03-26 20:38:05 +00:00
2017-04-05 21:31:15 +00:00
ticker := time . NewTicker ( 5 * time . Second )
defer ticker . Stop ( )
2017-04-05 21:48:08 +00:00
for {
select {
case <- ticker . C :
err := connection . Ping ( )
if err != nil {
2017-09-30 17:33:19 +00:00
connection . cancel ( )
2017-03-27 04:56:54 +00:00
}
2017-04-05 21:48:08 +00:00
case <- ctx . Done ( ) :
2017-10-01 13:28:13 +00:00
s . RemoveConn ( ctx , connection )
2017-04-05 22:04:02 +00:00
connection . Close ( )
2017-03-26 20:38:05 +00:00
2017-04-05 22:04:02 +00:00
return
}
}
}
2017-03-26 20:38:05 +00:00
2017-04-28 22:23:26 +00:00
// RemoveConn removes a connection.
2017-10-01 13:28:13 +00:00
func ( s * Server ) RemoveConn ( ctx context . Context , connection * Connection ) {
2017-04-05 22:04:02 +00:00
s . connlock . Lock ( )
delete ( s . conns , connection . conn )
s . connlock . Unlock ( )
2017-03-26 20:38:05 +00:00
2017-04-06 04:44:12 +00:00
auth := connection . Auth
2017-04-05 22:04:02 +00:00
var conns [ ] * Connection
2017-03-26 20:38:05 +00:00
2017-04-05 22:04:02 +00:00
val , ok := s . domains . Get ( auth . Domain )
if ok {
conns , ok = val . ( [ ] * Connection )
if ! ok {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , ErrCantRemoveWhatDoesntExist , connection . F ( ) , ln . F {
2017-04-05 22:04:02 +00:00
"action" : "looking_up_for_disconnect_removal" ,
2017-04-05 21:48:08 +00:00
} )
return
}
2017-03-26 20:38:05 +00:00
}
2017-04-05 22:04:02 +00:00
for i , cntn := range conns {
if cntn . id == connection . id {
conns [ i ] = conns [ len ( conns ) - 1 ]
conns = conns [ : len ( conns ) - 1 ]
}
}
if len ( conns ) != 0 {
s . domains . Set ( auth . Domain , conns )
} else {
s . domains . Remove ( auth . Domain )
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , connection . F ( ) , ln . F {
2017-04-05 22:04:02 +00:00
"action" : "client_disconnecting" ,
} )
2017-03-26 19:50:51 +00:00
}
2017-09-30 18:08:41 +00:00
func gen502Page ( req * http . Request ) * http . Response {
template := ` <html><head><title>no backends connected</title></head><body><h1>no backends connected</h1><p>Please ensure a backend is running for $ { HOST}. This is request ID $ { REQ_ID}.</p></body></html> `
resbody := [ ] byte ( os . Expand ( template , func ( in string ) string {
switch in {
case "HOST" :
return req . Host
case "REQ_ID" :
return req . Header . Get ( "X-Request-Id" )
}
return "<unknown>"
} ) )
reshdr := req . Header
reshdr . Set ( "Content-Type" , "text/html; charset=utf-8" )
resp := & http . Response {
Status : fmt . Sprintf ( "%d Bad Gateway" , http . StatusBadGateway ) ,
StatusCode : http . StatusBadGateway ,
Body : ioutil . NopCloser ( bytes . NewBuffer ( resbody ) ) ,
Proto : req . Proto ,
ProtoMajor : req . ProtoMajor ,
ProtoMinor : req . ProtoMinor ,
Header : reshdr ,
ContentLength : int64 ( len ( resbody ) ) ,
Close : true ,
Request : req ,
}
return resp
}
2017-04-28 22:23:26 +00:00
// RoundTrip sends a HTTP request to a backend and then returns its response.
2017-03-26 19:50:51 +00:00
func ( s * Server ) RoundTrip ( req * http . Request ) ( * http . Response , error ) {
2017-03-27 04:56:54 +00:00
var conns [ ] * Connection
2017-10-01 13:28:13 +00:00
ctx := req . Context ( )
2017-03-27 04:56:54 +00:00
val , ok := s . domains . Get ( req . Host )
if ok {
conns , ok = val . ( [ ] * Connection )
2017-03-27 05:03:06 +00:00
if ! ok {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , ErrNoSuchBackend , ln . F {
2017-03-27 04:56:54 +00:00
"action" : "no_backend_connected" ,
"remote" : req . RemoteAddr ,
"host" : req . Host ,
"uri" : req . RequestURI ,
} )
2017-09-30 18:08:41 +00:00
return gen502Page ( req ) , nil
2017-03-27 04:56:54 +00:00
}
2017-03-26 19:50:51 +00:00
}
2017-09-30 17:33:19 +00:00
var goodConns [ ] * Connection
for _ , conn := range conns {
if conn . usable {
goodConns = append ( goodConns , conn )
}
}
if len ( goodConns ) == 0 {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , ErrNoSuchBackend , ln . F {
2017-03-27 05:50:45 +00:00
"action" : "no_backend_connected" ,
"remote" : req . RemoteAddr ,
"host" : req . Host ,
"uri" : req . RequestURI ,
} )
2017-09-30 18:08:41 +00:00
return gen502Page ( req ) , nil
2017-03-27 05:03:06 +00:00
}
2017-09-30 17:33:19 +00:00
c := goodConns [ rand . Intn ( len ( goodConns ) ) ]
2017-03-26 19:50:51 +00:00
2017-04-06 04:44:12 +00:00
resp , err := c . RoundTrip ( req )
2017-03-26 19:50:51 +00:00
if err != nil {
2017-10-01 13:28:13 +00:00
ln . Error ( ctx , err , c , ln . F {
2017-04-06 04:44:12 +00:00
"action" : "connection_roundtrip" ,
} )
2017-03-26 20:47:42 +00:00
2017-04-06 04:44:12 +00:00
defer c . cancel ( )
2017-03-26 19:50:51 +00:00
return nil , err
}
2017-10-01 13:28:13 +00:00
ln . Log ( ctx , c , ln . F {
2017-10-01 15:49:59 +00:00
"action" : "http traffic" ,
"remote_addr" : req . RemoteAddr ,
"host" : req . Host ,
"uri" : req . URL . Path ,
"status" : resp . Status ,
"status_code" : resp . StatusCode ,
"content_length" : resp . ContentLength ,
2017-03-26 22:36:58 +00:00
} )
2017-03-26 19:50:51 +00:00
return resp , nil
}
2017-04-28 22:23:26 +00:00
// Auth is the authentication info the client passes to the server.
2017-03-26 19:50:51 +00:00
type Auth struct {
Token string ` json:"token" `
Domain string ` json:"domain" `
}
const defaultUser = "Cadey"