package server import ( "crypto/tls" "log" "net" "strings" "sync" "time" ) // Relay is the public TCP listener. It reads only the TLS ClientHello: SNI // equal to the domain is terminated with the server's certificate (the HTTP // API and bridge), tunnel hostnames are passed — still encrypted — to the // bridge that claimed the route. type Relay struct { Domain string Store *Store Hub *Hub Server *tls.Certificate // API domain certificate APIConns chan net.Conn // terminated API connections, served by http.Server } // Serve accepts on ln until it is closed. func (r *Relay) Serve(ln net.Listener) { for { conn, err := ln.Accept() if err != nil { return } go r.handle(conn) } } func (r *Relay) handle(conn net.Conn) { raw, sni, alpn, err := readClientHello(conn) if err != nil { log.Printf("relay: %s: %v", conn.RemoteAddr(), err) conn.Close() return } sni = strings.ToLower(sni) if sni == r.Domain { r.serveAPI(conn, raw) return } tunnelID, route, ok := routeForSNI(r.Domain, sni) if !ok { log.Printf("relay: %s: sni %q matches no tunnel hostname", conn.RemoteAddr(), sni) conn.Close() return } tunnel, ok := r.Store.get(tunnelID) if !ok || tunnel.Certificate == nil { log.Printf("relay: %s: tunnel %s unknown or certificate not ready", conn.RemoteAddr(), tunnelID) conn.Close() // unknown tunnel or certificate not ready return } bridge := r.Hub.bridgeFor(tunnelID, route) if bridge == nil { log.Printf("relay: %s: no bridge for route %q of tunnel %s", conn.RemoteAddr(), route, tunnelID) conn.Close() return } log.Printf("relay: %s: %s -> tunnel %s route %q", conn.RemoteAddr(), sni, tunnelID, route) bridge.openVisitor(conn, raw, conn.RemoteAddr().String(), sni, strings.Join(alpn, ",")) } // serveAPI replays the ClientHello into a TLS termination with the server's // certificate and hands the connection to the shared API http.Server. func (r *Relay) serveAPI(conn net.Conn, hello []byte) { tlsConn := tls.Server(&replayConn{Conn: conn, prefix: hello}, &tls.Config{ Certificates: []tls.Certificate{*r.Server}, NextProtos: []string{"http/1.1"}, }) conn.SetReadDeadline(time.Now().Add(clientHelloTimeout)) err := tlsConn.Handshake() conn.SetReadDeadline(time.Time{}) if err != nil { conn.Close() return } select { case r.APIConns <- tlsConn: default: log.Printf("API connection queue full; dropping %s", conn.RemoteAddr()) tlsConn.Close() } } // replayConn returns the already-read ClientHello bytes before reading from // the underlying connection again. type replayConn struct { net.Conn prefix []byte } func (c *replayConn) Read(p []byte) (int, error) { if len(c.prefix) > 0 { n := copy(p, c.prefix) c.prefix = c.prefix[n:] return n, nil } return c.Conn.Read(p) } // routeForSNI resolves a tunnel hostname to tunnel ID and route name: // . is route "@", .. is that route; anything // deeper, malformed, or outside the domain does not match. func routeForSNI(domain, sni string) (id, route string, ok bool) { sni = strings.ToLower(sni) suffix := "." + domain if sni == "" || sni == domain || !strings.HasSuffix(sni, suffix) { return "", "", false } labels := strings.Split(strings.TrimSuffix(sni, suffix), ".") switch len(labels) { case 1: if !routeLabel.MatchString(labels[0]) { return "", "", false } return labels[0], "@", true case 2: if !validRoute(labels[0]) || labels[0] == "@" || !routeLabel.MatchString(labels[1]) { return "", "", false } return labels[1], labels[0], true default: return "", "", false } } // chanListener turns a channel of connections into a net.Listener, so one // http.Server can serve the connections the relay terminates for the domain. type chanListener struct { conns chan net.Conn done chan struct{} once sync.Once addr net.Addr } func NewChanListener(capacity int, addr net.Addr) *chanListener { return &chanListener{ conns: make(chan net.Conn, capacity), done: make(chan struct{}), addr: addr, } } func (l *chanListener) Accept() (net.Conn, error) { select { case conn := <-l.conns: return conn, nil case <-l.done: return nil, errListenerClosed } } func (l *chanListener) Close() error { l.once.Do(func() { close(l.done) }) return nil } func (l *chanListener) Addr() net.Addr { return l.addr } // Conns returns the channel the listener serves connections from. func (l *chanListener) Conns() chan net.Conn { return l.conns } var errListenerClosed = &netError{} type netError struct{} func (e *netError) Error() string { return "listener closed" } func (e *netError) Timeout() bool { return false } func (e *netError) Temporary() bool { return false }