tun2: make Server better

This commit is contained in:
Cadey Ratio 2017-04-28 15:23:26 -07:00
parent 96e2e399bc
commit e8acea0351
1 changed files with 62 additions and 29 deletions

View File

@ -5,14 +5,12 @@ import (
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"math/rand" "math/rand"
"net" "net"
"net/http" "net/http"
"sync" "sync"
"time" "time"
"git.xeserv.us/xena/route/database"
"github.com/Xe/ln" "github.com/Xe/ln"
failure "github.com/dgryski/go-failure" failure "github.com/dgryski/go-failure"
"github.com/mtneug/pkg/ulid" "github.com/mtneug/pkg/ulid"
@ -21,6 +19,14 @@ import (
"github.com/xtaci/smux" "github.com/xtaci/smux"
) )
// 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 ...
type ServerConfig struct { type ServerConfig struct {
TCPAddr string TCPAddr string
KCPAddr string KCPAddr string
@ -30,11 +36,14 @@ type ServerConfig struct {
Storage Storage Storage Storage
} }
// Storage is the minimal subset of features that tun2's Server needs out of a
// persistence layer.
type Storage interface { type Storage interface {
GetRouteForHost(name string) (*database.Route, error) HasToken(token string) (user string, scopes []string, err error)
//ValidateToken(token string) (username string, ok bool, err error) // XXX RIP implement when users are implemented HasRoute(domain string) (user string, err error)
} }
// Server routes frontend HTTP traffic to backend TCP traffic.
type Server struct { type Server struct {
cfg *ServerConfig cfg *ServerConfig
@ -44,6 +53,8 @@ type Server struct {
domains cmap.ConcurrentMap domains cmap.ConcurrentMap
} }
// NewServer creates a new Server instance with a given config, acquiring all
// relevant resources.
func NewServer(cfg *ServerConfig) (*Server, error) { func NewServer(cfg *ServerConfig) (*Server, error) {
if cfg == nil { if cfg == nil {
return nil, errors.New("tun2: config must be specified") return nil, errors.New("tun2: config must be specified")
@ -66,6 +77,8 @@ func NewServer(cfg *ServerConfig) (*Server, error) {
return server, nil return server, nil
} }
// ListenAndServe starts the backend TCP/KCP listeners and relays backend
// traffic to and from them.
func (s *Server) ListenAndServe() error { func (s *Server) ListenAndServe() error {
ln.Log(ln.F{ ln.Log(ln.F{
"action": "listen_and_serve_called", "action": "listen_and_serve_called",
@ -132,6 +145,7 @@ func (s *Server) ListenAndServe() error {
}() }()
} }
// XXX experimental, might get rid of this inside this process
go func() { go func() {
for { for {
time.Sleep(time.Second) time.Sleep(time.Second)
@ -156,7 +170,12 @@ func (s *Server) ListenAndServe() error {
return nil return nil
} }
// HandleConn starts up the needed mechanisms to relay HTTP traffic to/from
// the currently connected backend.
func (s *Server) HandleConn(c net.Conn, isKCP bool) { func (s *Server) HandleConn(c net.Conn, isKCP bool) {
// XXX TODO clean this up it's really ugly.
defer c.Close()
ctx, cancel := context.WithCancel(context.Background()) ctx, cancel := context.WithCancel(context.Background())
defer cancel() defer cancel()
@ -172,6 +191,7 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
return return
} }
defer session.Close()
controlStream, err := session.OpenStream() controlStream, err := session.OpenStream()
if err != nil { if err != nil {
@ -181,11 +201,9 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
"remote": c.RemoteAddr().String(), "remote": c.RemoteAddr().String(),
}) })
session.Close()
c.Close()
return return
} }
defer controlStream.Close()
csd := json.NewDecoder(controlStream) csd := json.NewDecoder(controlStream)
auth := &Auth{} auth := &Auth{}
@ -197,14 +215,10 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
"remote": c.RemoteAddr().String(), "remote": c.RemoteAddr().String(),
}) })
controlStream.Close()
session.Close()
c.Close()
return return
} }
route, err := s.cfg.Storage.GetRouteForHost(auth.Domain) routeUser, err := s.cfg.Storage.HasRoute(auth.Domain)
if err != nil { if err != nil {
ln.Error(err, ln.F{ ln.Error(err, ln.F{
"action": "nosuch_domain", "action": "nosuch_domain",
@ -212,25 +226,42 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
"remote": c.RemoteAddr().String(), "remote": c.RemoteAddr().String(),
}) })
controlStream.Close()
session.Close()
c.Close()
return return
} }
if route.Token != auth.Token { tokenUser, scopes, err := s.cfg.Storage.HasToken(auth.Token)
if err != nil {
ln.Error(err, ln.F{ ln.Error(err, ln.F{
"action": "bad_token", "action": "nosuch_token",
"local": c.LocalAddr().String(), "local": c.LocalAddr().String(),
"remote": c.RemoteAddr().String(), "remote": c.RemoteAddr().String(),
}) })
fmt.Fprintln(controlStream, "bad token") return
}
controlStream.Close() ok := false
session.Close() for _, sc := range scopes {
c.Close() if sc == "connect" {
ok = true
break
}
}
if !ok {
ln.Error(ErrAuthMismatch, ln.F{
"action": "token_not_authorized",
"local": c.LocalAddr().String(),
"remote": c.RemoteAddr().String(),
})
}
if routeUser != tokenUser {
ln.Error(ErrAuthMismatch, ln.F{
"action": "auth_mismatch",
"local": c.LocalAddr().String(),
"remote": c.RemoteAddr().String(),
})
return return
} }
@ -240,7 +271,7 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
conn: c, conn: c,
isKCP: isKCP, isKCP: isKCP,
session: session, session: session,
user: defaultUser, // XXX RIP replace this with the actual token user once users are implemented user: tokenUser,
domain: auth.Domain, domain: auth.Domain,
cancel: cancel, cancel: cancel,
detector: failure.New(15, 1), detector: failure.New(15, 1),
@ -290,7 +321,7 @@ func (s *Server) HandleConn(c net.Conn, isKCP bool) {
} }
} }
// RemoveConn removes a connection // RemoveConn removes a connection.
func (s *Server) RemoveConn(connection *Connection) { func (s *Server) RemoveConn(connection *Connection) {
s.connlock.Lock() s.connlock.Lock()
delete(s.conns, connection.conn) delete(s.conns, connection.conn)
@ -304,7 +335,7 @@ func (s *Server) RemoveConn(connection *Connection) {
if ok { if ok {
conns, ok = val.([]*Connection) conns, ok = val.([]*Connection)
if !ok { if !ok {
ln.Error(errors.New("fundamental assertion is not met"), connection.F(), ln.F{ ln.Error(ErrCantRemoveWhatDoesntExist, connection.F(), ln.F{
"action": "looking_up_for_disconnect_removal", "action": "looking_up_for_disconnect_removal",
}) })
return return
@ -329,6 +360,7 @@ func (s *Server) RemoveConn(connection *Connection) {
}) })
} }
// RoundTrip sends a HTTP request to a backend and then returns its response.
func (s *Server) RoundTrip(req *http.Request) (*http.Response, error) { func (s *Server) RoundTrip(req *http.Request) (*http.Response, error) {
var conns []*Connection var conns []*Connection
@ -336,26 +368,26 @@ func (s *Server) RoundTrip(req *http.Request) (*http.Response, error) {
if ok { if ok {
conns, ok = val.([]*Connection) conns, ok = val.([]*Connection)
if !ok { if !ok {
ln.Error(errors.New("no backend connected"), ln.F{ ln.Error(ErrNoSuchBackend, ln.F{
"action": "no_backend_connected", "action": "no_backend_connected",
"remote": req.RemoteAddr, "remote": req.RemoteAddr,
"host": req.Host, "host": req.Host,
"uri": req.RequestURI, "uri": req.RequestURI,
}) })
return nil, errors.New("no backend connected") return nil, ErrNoSuchBackend
} }
} }
if len(conns) == 0 { if len(conns) == 0 {
ln.Error(errors.New("no backend connected"), ln.F{ ln.Error(ErrNoSuchBackend, ln.F{
"action": "no_backend_connected", "action": "no_backend_connected",
"remote": req.RemoteAddr, "remote": req.RemoteAddr,
"host": req.Host, "host": req.Host,
"uri": req.RequestURI, "uri": req.RequestURI,
}) })
return nil, errors.New("no backend connected") return nil, ErrNoSuchBackend
} }
c := conns[rand.Intn(len(conns))] c := conns[rand.Intn(len(conns))]
@ -380,6 +412,7 @@ func (s *Server) RoundTrip(req *http.Request) (*http.Response, error) {
return resp, nil return resp, nil
} }
// Auth is the authentication info the client passes to the server.
type Auth struct { type Auth struct {
Token string `json:"token"` Token string `json:"token"`
Domain string `json:"domain"` Domain string `json:"domain"`