From b683921b874f34c929a5514534522ed59007fd8b Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Wed, 9 Nov 2022 15:38:09 -0800 Subject: [PATCH] ipn/ipnlocal: add start of handling TCP proxying Updates tailscale/corp#7515 Change-Id: I82d19b5864674b2169f25ec8e429f60a543e0c57 Signed-off-by: Brad Fitzpatrick --- ipn/ipnlocal/serve.go | 67 +++++++++++++++++++++++++++++++---- wgengine/netstack/netstack.go | 13 ++++--- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 430ffb716..d0475e9e7 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -11,20 +11,73 @@ import ( "io" "net" "net/http" + "net/netip" pathpkg "path" "time" - "tailscale.com/envknob" "tailscale.com/ipn" "tailscale.com/net/netutil" ) -var runDevWebServer = envknob.RegisterBool("TS_DEV_WEBSERVER") +func (b *LocalBackend) HandleInterceptedTCPConn(dport uint16, srcAddr netip.AddrPort, getConn func() (net.Conn, bool), sendRST func()) { + b.mu.Lock() + sc := b.serveConfig + b.mu.Unlock() -func (b *LocalBackend) HandleInterceptedTCPConn(c net.Conn) { - if !runDevWebServer() { - b.logf("localbackend: closing TCP conn from %v to %v", c.RemoteAddr(), c.LocalAddr()) - c.Close() + if !sc.Valid() { + b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport) + sendRST() + return + } + + tcph, ok := sc.TCP().GetOk(int(dport)) + if !ok { + b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr) + sendRST() + return + } + + if backDst := tcph.TCPForward(); backDst != "" { + if tcph.TerminateTLS() { + b.logf("TODO(bradfitz): finish") + sendRST() + return + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst) + cancel() + if err != nil { + b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err) + sendRST() + return + } + conn, ok := getConn() + if !ok { + b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport) + backConn.Close() + return + } + defer conn.Close() + defer backConn.Close() + + // TODO(bradfitz): do the RegisterIPPortIdentity and + // UnregisterIPPortIdentity stuff that netstack does + + errc := make(chan error, 1) + go func() { + _, err := io.Copy(backConn, conn) + errc <- err + }() + go func() { + _, err := io.Copy(conn, backConn) + errc <- err + }() + <-errc + return + } + + conn, ok := getConn() + if !ok { return } @@ -35,7 +88,7 @@ func (b *LocalBackend) HandleInterceptedTCPConn(c net.Conn) { }, Handler: http.HandlerFunc(b.serveWebHandler), } - hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "") + hs.ServeTLS(netutil.NewOneConnListener(conn, nil), "", "") } func (b *LocalBackend) getServeHandler(r *http.Request) (_ ipn.HTTPHandlerView, ok bool) { diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 7c430585a..347f49d9e 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -784,6 +784,8 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) { r.Complete(true) // sends a RST return } + clientRemotePort := reqDetails.RemotePort + clientRemoteAddrPort := netip.AddrPortFrom(clientRemoteIP, clientRemotePort) dialIP := netaddrIPFromNetstackIP(reqDetails.LocalAddress) isTailscaleIP := tsaddr.IsTailscaleIP(dialIP) @@ -894,11 +896,14 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) { return } if ns.lb.ShouldInterceptTCPPort(reqDetails.LocalPort) && ns.isLocalIP(dialIP) { - c := createConn() - if c == nil { - return + getTCPConn := func() (_ net.Conn, ok bool) { + c := createConn() + return c, c != nil + } + sendRST := func() { + r.Complete(true) } - ns.lb.HandleInterceptedTCPConn(c) + ns.lb.HandleInterceptedTCPConn(reqDetails.LocalPort, clientRemoteAddrPort, getTCPConn, sendRST) return } }