package server import ( "crypto/tls" "errors" "net" "net/http" "net/http/httputil" "time" "git.xeserv.us/xena/route/internal/database" "git.xeserv.us/xena/route/internal/tun2" proto "git.xeserv.us/xena/route/proto" "github.com/Xe/ln" "github.com/mtneug/pkg/ulid" kcp "github.com/xtaci/kcp-go" "golang.org/x/crypto/acme/autocert" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" ) // RPC constants const ( RPCPort uint16 = 39453 ) // Server is the main server type type Server struct { cfg *Config db database.Storage ts *tun2.Server *autocert.Manager } // Config configures Server type Config struct { BoltDBPath string `env:"BOLTDB_PATH,required"` WebAddr string `env:"WEB_ADDR,required"` SSLAddr string `env:"SSL_ADDR,required"` BackendTCPAddr string `env:"BACKEND_TCP_ADDR,required"` BackendKCPAddr string `env:"BACKEND_KCP_ADDR,required"` GRPCAddr string `env:"GRPC_ADDR,required"` DomainSuffix string `env:"DOMAIN_SUFFIX,required"` ACMEEmail string `env:"ACME_EMAIL,required"` CertKey *[32]byte } func (s *Server) listenTCP(ctx context.Context, addr string, tcfg *tls.Config) { l, err := tls.Listen("tcp", addr, tcfg) if err != nil { panic(err) } ln.Log(ctx, ln.Action("tcp+tls listening"), ln.F{"addr": l.Addr()}) for { conn, err := l.Accept() if err != nil { ln.Error(ctx, err, ln.Action("accept backend client socket")) } ln.Log(ctx, ln.F{ "action": "new backend client", "addr": conn.RemoteAddr(), "network": conn.RemoteAddr().Network(), }) go s.ts.HandleConn(conn, false) } } func (s *Server) listenKCP(ctx context.Context, addr string, tcfg *tls.Config) { l, err := kcp.Listen(addr) if err != nil { panic(err) } ln.Log(ctx, ln.Action("kcp+tls listening"), ln.F{"addr": l.Addr()}) for { conn, err := l.Accept() if err != nil { ln.Error(ctx, err, ln.F{"kind": "kcp", "addr": l.Addr().String()}) } ln.Log(ctx, ln.F{ "action": "new_client", "network": conn.RemoteAddr().Network(), "addr": conn.RemoteAddr(), }) tc := tls.Server(conn, tcfg) go s.ts.HandleConn(tc, true) } } // New creates a new Server func New(cfg Config) (*Server, error) { if cfg.CertKey == nil { return nil, errors.New("no cert decryption key, can't do anything") } db, err := database.NewBoltStorage(cfg.BoltDBPath, cfg.CertKey) if err != nil { return nil, err } m := &autocert.Manager{ Prompt: autocert.AcceptTOS, Cache: database.Cache(db), HostPolicy: nil, Email: cfg.ACMEEmail, } tcfg := &tun2.ServerConfig{ Storage: &storageWrapper{ Storage: db, }, } ts, err := tun2.NewServer(tcfg) if err != nil { return nil, err } s := &Server{ cfg: &cfg, db: db, ts: ts, Manager: m, } go s.listenKCP(context.Background(), cfg.BackendKCPAddr, &tls.Config{ GetCertificate: m.GetCertificate, }) go s.listenTCP(context.Background(), cfg.BackendTCPAddr, &tls.Config{ GetCertificate: m.GetCertificate, }) gs := grpc.NewServer(grpc.Creds(credentials.NewTLS(&tls.Config{ GetCertificate: m.GetCertificate, InsecureSkipVerify: true, }))) proto.RegisterBackendsServer(gs, &Backend{Server: s}) proto.RegisterRoutesServer(gs, &Route{Server: s}) proto.RegisterTokensServer(gs, &Token{Server: s}) l, err := net.Listen("tcp", cfg.GRPCAddr) if err != nil { return nil, err } go gs.Serve(l) return s, nil } // Director removes headers that are typically stripped off at request ingress. func (s *Server) Director(r *http.Request) { r.Header.Del("X-Forwarded-For") r.Header.Del("X-Client-Ip") } func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Tor2web") != "" { http.Error(w, "tor2web proxy use is not allowed", 400) return } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { http.Error(w, err.Error(), 500) return } r.Header.Set("X-Remote-Ip", host) r.Header.Set("X-Request-Ingress", time.Now().Format(time.RFC3339)) rid := ulid.New().String() r.Header.Set("X-Request-Id", rid) w.Header().Set("X-Request-Id", rid) // http://www.gnuterrypratchett.com/ w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn") rp := &httputil.ReverseProxy{ Director: s.Director, Transport: s.ts, FlushInterval: 1 * time.Second, //BufferPool: bpool.NewBytePool(256, 4096), } rp.ServeHTTP(w, r) }