From 480ee9fec05a60d00e5b744434243270c8ac60ad Mon Sep 17 00:00:00 2001 From: Naman Sood Date: Wed, 7 Jan 2026 09:31:46 -0500 Subject: [PATCH] ipn,cmd/tailscale/cli: set correct SNI name for TLS-terminated TCP Services (#17752) Fixes #17749. Signed-off-by: Naman Sood --- cmd/tailscale/cli/serve_v2.go | 17 +++++++++++--- cmd/tailscale/cli/serve_v2_test.go | 8 ++++--- ipn/serve.go | 37 ++++++++++++++++++++---------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 6e0408195..6a2907481 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -923,7 +923,7 @@ func (e *serveEnv) setServe(sc *ipn.ServeConfig, dnsName string, srvType serveTy if e.setPath != "" { return fmt.Errorf("cannot mount a path for TCP serve") } - err := e.applyTCPServe(sc, dnsName, srvType, srvPort, target, proxyProtocol) + err := e.applyTCPServe(sc, dnsName, srvType, srvPort, target, mds, proxyProtocol) if err != nil { return fmt.Errorf("failed to apply TCP serve: %w", err) } @@ -1203,7 +1203,7 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, dnsName string, srvPort ui return nil } -func (e *serveEnv) applyTCPServe(sc *ipn.ServeConfig, dnsName string, srcType serveType, srcPort uint16, target string, proxyProtocol int) error { +func (e *serveEnv) applyTCPServe(sc *ipn.ServeConfig, dnsName string, srcType serveType, srcPort uint16, target string, mds string, proxyProtocol int) error { var terminateTLS bool switch srcType { case serveTypeTCP: @@ -1226,11 +1226,22 @@ func (e *serveEnv) applyTCPServe(sc *ipn.ServeConfig, dnsName string, srcType se return fmt.Errorf("invalid TCP target %q: %v", target, err) } - // TODO: needs to account for multiple configs from foreground mode if sc.IsServingWeb(srcPort, svcName) { return fmt.Errorf("cannot serve TCP; already serving web on %d for %s", srcPort, dnsName) } + // TODO: needs to account for multiple configs from foreground mode + if svcName := tailcfg.AsServiceName(dnsName); svcName != "" { + sc.SetTCPForwardingForService(srcPort, dstURL.Host, terminateTLS, svcName, proxyProtocol, mds) + return nil + } + + // TODO: needs to account for multiple configs from foreground mode + if svcName != "" { + sc.SetTCPForwardingForService(srcPort, dstURL.Host, terminateTLS, svcName, proxyProtocol, mds) + return nil + } + sc.SetTCPForwarding(srcPort, dstURL.Host, terminateTLS, proxyProtocol, dnsName) return nil } diff --git a/cmd/tailscale/cli/serve_v2_test.go b/cmd/tailscale/cli/serve_v2_test.go index b3ebb32a2..a56fece3e 100644 --- a/cmd/tailscale/cli/serve_v2_test.go +++ b/cmd/tailscale/cli/serve_v2_test.go @@ -2077,9 +2077,11 @@ func TestSetServe(t *testing.T) { if err == nil && tt.expectErr { t.Fatalf("got no error; expected error.") } - if !tt.expectErr && !reflect.DeepEqual(tt.cfg, tt.expected) { - svcName := tailcfg.ServiceName(tt.dnsName) - t.Fatalf("got: %v; expected: %v", tt.cfg.Services[svcName], tt.expected.Services[svcName]) + if !tt.expectErr { + if diff := cmp.Diff(tt.expected, tt.cfg); diff != "" { + // svcName := tailcfg.ServiceName(tt.dnsName) + t.Fatalf("got diff:\n%s", diff) + } } }) } diff --git a/ipn/serve.go b/ipn/serve.go index 76823a846..240308f29 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -433,24 +433,37 @@ func (sc *ServeConfig) SetTCPForwarding(port uint16, fwdAddr string, terminateTL if sc == nil { sc = new(ServeConfig) } - tcpPortHandler := &sc.TCP - if svcName := tailcfg.AsServiceName(host); svcName != "" { - svcConfig, ok := sc.Services[svcName] - if !ok { - svcConfig = new(ServiceConfig) - mak.Set(&sc.Services, svcName, svcConfig) - } - tcpPortHandler = &svcConfig.TCP + mak.Set(&sc.TCP, port, &TCPPortHandler{ + TCPForward: fwdAddr, + ProxyProtocol: proxyProtocol, // can be 0 + }) + + if terminateTLS { + sc.TCP[port].TerminateTLS = host } +} - handler := &TCPPortHandler{ +// SetTCPForwardingForService sets the fwdAddr (IP:port form) to which to +// forward connections from the given port on the service. If terminateTLS +// is true, TLS connections are terminated, with only the FQDN that corresponds +// to the given service being permitted, before passing them to the fwdAddr. +func (sc *ServeConfig) SetTCPForwardingForService(port uint16, fwdAddr string, terminateTLS bool, svcName tailcfg.ServiceName, proxyProtocol int, magicDNSSuffix string) { + if sc == nil { + sc = new(ServeConfig) + } + svcConfig, ok := sc.Services[svcName] + if !ok { + svcConfig = new(ServiceConfig) + mak.Set(&sc.Services, svcName, svcConfig) + } + mak.Set(&svcConfig.TCP, port, &TCPPortHandler{ TCPForward: fwdAddr, ProxyProtocol: proxyProtocol, // can be 0 - } + }) + if terminateTLS { - handler.TerminateTLS = host + svcConfig.TCP[port].TerminateTLS = fmt.Sprintf("%s.%s", svcName.WithoutPrefix(), magicDNSSuffix) } - mak.Set(tcpPortHandler, port, handler) } // SetFunnel sets the sc.AllowFunnel value for the given host and port.