add tun2: a tunnel rewrite using KCP/TLS and smux
This commit is contained in:
parent
dd0856cfa5
commit
ede0affbdc
|
@ -0,0 +1,126 @@
|
|||
package tun2
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
cfg *ClientConfig
|
||||
}
|
||||
|
||||
type ClientConfig struct {
|
||||
TLSConfig *tls.Config
|
||||
ConnType string
|
||||
ServerAddr string
|
||||
Token string
|
||||
Domain string
|
||||
BackendURL string
|
||||
}
|
||||
|
||||
func NewClient(cfg *ClientConfig) (*Client, error) {
|
||||
if cfg == nil {
|
||||
return nil, errors.New("tun2: client config needed")
|
||||
}
|
||||
|
||||
c := &Client{
|
||||
cfg: cfg,
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Client) connect(serverAddr string) error {
|
||||
target, err := url.Parse(c.cfg.BackendURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s := &http.Server{
|
||||
Handler: httputil.NewSingleHostReverseProxy(target),
|
||||
}
|
||||
|
||||
var conn net.Conn
|
||||
|
||||
switch c.cfg.ConnType {
|
||||
case "tcp":
|
||||
conn, err = tls.Dial("tcp", serverAddr, c.cfg.TLSConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
case "kcp":
|
||||
kc, err := kcp.Dial(serverAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer kc.Close()
|
||||
|
||||
serverHost, _, _ := net.SplitHostPort(serverAddr)
|
||||
|
||||
tc := c.cfg.TLSConfig.Clone()
|
||||
tc.ServerName = serverHost
|
||||
conn = tls.Client(kc, tc)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
session, err := smux.Client(conn, smux.DefaultConfig())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
controlStream, err := session.AcceptStream()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authData, err := json.Marshal(&Auth{
|
||||
Token: c.cfg.Token,
|
||||
Domain: c.cfg.Domain,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = controlStream.Write(authData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.Serve(&smuxListener{
|
||||
conn: conn,
|
||||
session: session,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type smuxListener struct {
|
||||
conn net.Conn
|
||||
session *smux.Session
|
||||
}
|
||||
|
||||
func (sl *smuxListener) Accept() (net.Conn, error) {
|
||||
return sl.session.AcceptStream()
|
||||
}
|
||||
|
||||
func (sl *smuxListener) Addr() net.Addr {
|
||||
return sl.conn.LocalAddr()
|
||||
}
|
||||
|
||||
func (sl *smuxListener) Close() error {
|
||||
return sl.session.Close()
|
||||
}
|
|
@ -0,0 +1,271 @@
|
|||
package tun2
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"git.xeserv.us/xena/route/database"
|
||||
"github.com/Xe/ln"
|
||||
"github.com/mtneug/pkg/ulid"
|
||||
kcp "github.com/xtaci/kcp-go"
|
||||
"github.com/xtaci/smux"
|
||||
)
|
||||
|
||||
type ServerConfig struct {
|
||||
TCPAddr string
|
||||
KCPAddr string
|
||||
TLSConfig *tls.Config
|
||||
|
||||
SmuxConf *smux.Config
|
||||
Storage Storage
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
GetRouteForHost(name string) (*database.Route, error)
|
||||
//ValidateToken(token string) (username string, ok bool, err error) // XXX RIP implement when users are implemented
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
cfg *ServerConfig
|
||||
|
||||
connlock sync.Mutex
|
||||
conns map[net.Conn]*Connection
|
||||
|
||||
domainlock sync.Mutex
|
||||
domains map[string][]*Connection
|
||||
}
|
||||
|
||||
type Connection struct {
|
||||
id string
|
||||
conn net.Conn
|
||||
isKCP bool
|
||||
session *smux.Session
|
||||
controlStream *smux.Stream
|
||||
user string
|
||||
domain string
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
cfg: cfg,
|
||||
|
||||
conns: map[net.Conn]*Connection{},
|
||||
domains: map[string][]*Connection{},
|
||||
}
|
||||
|
||||
return server, nil
|
||||
}
|
||||
|
||||
func (s *Server) ListenAndServe() error {
|
||||
if s.cfg.TCPAddr != "" {
|
||||
go func() {
|
||||
l, err := tls.Listen("tcp", s.cfg.TCPAddr, s.cfg.TLSConfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{"kind": "tcp", "addr": l.Addr().String()})
|
||||
continue
|
||||
}
|
||||
|
||||
go s.HandleConn(conn, false)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
if s.cfg.KCPAddr != "" {
|
||||
go func() {
|
||||
l, err := kcp.Listen(s.cfg.KCPAddr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{"kind": "kcp", "addr": l.Addr().String()})
|
||||
}
|
||||
|
||||
tc := tls.Server(conn, s.cfg.TLSConfig)
|
||||
|
||||
go s.HandleConn(tc, true)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) HandleConn(c net.Conn, isKCP bool) {
|
||||
session, err := smux.Server(c, s.cfg.SmuxConf)
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "session_failure",
|
||||
"local": c.LocalAddr().String(),
|
||||
"remote": c.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
controlStream, err := session.OpenStream()
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "control_stream_failure",
|
||||
"local": c.LocalAddr().String(),
|
||||
"remote": c.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
session.Close()
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
csd := json.NewDecoder(controlStream)
|
||||
auth := &Auth{}
|
||||
err = csd.Decode(auth)
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "control_stream_auth_decoding_failure",
|
||||
"local": c.LocalAddr().String(),
|
||||
"remote": c.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
controlStream.Close()
|
||||
session.Close()
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
route, err := s.cfg.Storage.GetRouteForHost(auth.Domain)
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "nosuch_domain",
|
||||
"local": c.LocalAddr().String(),
|
||||
"remote": c.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
controlStream.Close()
|
||||
session.Close()
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if route.Token != auth.Token {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "bad_token",
|
||||
"local": c.LocalAddr().String(),
|
||||
"remote": c.RemoteAddr().String(),
|
||||
})
|
||||
|
||||
fmt.Fprintln(controlStream, "bad token")
|
||||
|
||||
controlStream.Close()
|
||||
session.Close()
|
||||
c.Close()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
connection := &Connection{
|
||||
id: ulid.New().String(),
|
||||
conn: c,
|
||||
isKCP: isKCP,
|
||||
session: session,
|
||||
user: defaultUser, // XXX RIP replace this with the actual token user once users are implemented
|
||||
domain: auth.Domain,
|
||||
}
|
||||
|
||||
s.connlock.Lock()
|
||||
s.conns[c] = connection
|
||||
s.connlock.Unlock()
|
||||
|
||||
s.domainlock.Lock()
|
||||
s.domains[auth.Domain] = append(s.domains[auth.Domain], connection)
|
||||
s.domainlock.Unlock()
|
||||
}
|
||||
|
||||
func (s *Server) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
s.domainlock.Lock()
|
||||
conns, ok := s.domains[req.Host]
|
||||
s.domainlock.Unlock()
|
||||
if !ok {
|
||||
return nil, errors.New("domain not found")
|
||||
}
|
||||
|
||||
c := conns[rand.Intn(len(conns))]
|
||||
|
||||
stream, err := c.session.OpenStream()
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "opening_session_stream",
|
||||
"backend": c.conn.RemoteAddr().String(),
|
||||
"remote_addr": req.RemoteAddr,
|
||||
"host": req.Host,
|
||||
"uri": req.RequestURI,
|
||||
})
|
||||
|
||||
return nil, err
|
||||
}
|
||||
defer stream.Close()
|
||||
|
||||
err = req.Write(stream)
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "request_writing",
|
||||
"backend": c.conn.RemoteAddr().String(),
|
||||
"remote_addr": req.RemoteAddr,
|
||||
"host": req.Host,
|
||||
"uri": req.RequestURI,
|
||||
})
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buf := bufio.NewReader(stream)
|
||||
|
||||
resp, err := http.ReadResponse(buf, req)
|
||||
if err != nil {
|
||||
ln.Error(err, ln.F{
|
||||
"action": "response_reading",
|
||||
"backend": c.conn.RemoteAddr().String(),
|
||||
"remote_addr": req.RemoteAddr,
|
||||
"host": req.Host,
|
||||
"uri": req.RequestURI,
|
||||
})
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Token string `json:"token"`
|
||||
Domain string `json:"domain"`
|
||||
}
|
||||
|
||||
const defaultUser = "Cadey"
|
Loading…
Reference in New Issue