tun2: make Server better
This commit is contained in:
parent
96e2e399bc
commit
e8acea0351
|
@ -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"`
|
||||||
|
|
Loading…
Reference in New Issue