diff --git a/cmd/tailscale/cli/serve_legacy.go b/cmd/tailscale/cli/serve_legacy.go index c4bbc542f..21caa7f9a 100644 --- a/cmd/tailscale/cli/serve_legacy.go +++ b/cmd/tailscale/cli/serve_legacy.go @@ -358,7 +358,7 @@ func (e *serveEnv) handleWebServe(ctx context.Context, srvPort uint16, useTLS bo if err != nil { return err } - if sc.IsTCPForwardingOnPort(dnsName, srvPort) { + if sc.IsTCPForwardingOnPort(srvPort, "") { fmt.Fprintf(Stderr, "error: cannot serve web; already serving TCP\n") return errHelp } @@ -415,7 +415,7 @@ func (e *serveEnv) handleWebServeRemove(ctx context.Context, srvPort uint16, mou if err != nil { return err } - if sc.IsTCPForwardingOnPort(dnsName, srvPort) { + if sc.IsTCPForwardingOnPort(srvPort, "") { return errors.New("cannot remove web handler; currently serving TCP") } hp := ipn.HostPort(net.JoinHostPort(dnsName, strconv.Itoa(int(srvPort)))) @@ -559,7 +559,7 @@ func (e *serveEnv) handleTCPServe(ctx context.Context, srcType string, srcPort u return err } - if sc.IsServingWeb(srcPort, dnsName) { + if sc.IsServingWeb(srcPort, "") { return fmt.Errorf("cannot serve TCP; already serving web on %d", srcPort) } @@ -589,7 +589,7 @@ func (e *serveEnv) handleTCPServeRemove(ctx context.Context, src uint16) error { if err != nil { return err } - if sc.IsServingWeb(src, dnsName) { + if sc.IsServingWeb(src, "") { return fmt.Errorf("unable to remove; serving web, not TCP forwarding on serve port %d", src) } if ph := sc.GetTCPPortHandler(src, dnsName); ph != nil { @@ -690,7 +690,7 @@ func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) erro } scheme := "https" - if sc.IsServingHTTP(port, host) { + if sc.IsServingHTTP(port, "") { scheme = "http" } diff --git a/cmd/tailscale/cli/serve_v2.go b/cmd/tailscale/cli/serve_v2.go index 64f8e7131..35fefe1ab 100644 --- a/cmd/tailscale/cli/serve_v2.go +++ b/cmd/tailscale/cli/serve_v2.go @@ -557,7 +557,7 @@ func (e *serveEnv) messageForPort(sc *ipn.ServeConfig, st *ipnstate.Status, dnsN hp = ipn.HostPort(net.JoinHostPort(host, strconv.Itoa(int(srvPort)))) scheme := "https" - if sc.IsServingHTTP(srvPort, dnsName) { + if sc.IsServingHTTP(srvPort, svcName) { scheme = "http" } @@ -699,7 +699,7 @@ func (e *serveEnv) applyWebServe(sc *ipn.ServeConfig, st *ipnstate.Status, dnsNa } // TODO: validation needs to check nested foreground configs - if sc.IsTCPForwardingOnPort(dnsName, srvPort) { + if sc.IsTCPForwardingOnPort(srvPort, tailcfg.ServiceName(dnsName)) { return errors.New("cannot serve web; already serving TCP") } @@ -730,7 +730,7 @@ func (e *serveEnv) applyTCPServe(sc *ipn.ServeConfig, dnsName string, srcType se } // TODO: needs to account for multiple configs from foreground mode - if sc.IsServingWeb(srcPort, dnsName) { + if sc.IsServingWeb(srcPort, tailcfg.ServiceName(dnsName)) { return fmt.Errorf("cannot serve TCP; already serving web on %d for %s", srcPort, dnsName) } @@ -939,7 +939,7 @@ func (e *serveEnv) removeWebServe(sc *ipn.ServeConfig, st *ipnstate.Status, dnsN hp := ipn.HostPort(net.JoinHostPort(hostName, portStr)) - if sc.IsTCPForwardingOnPort(dnsName, srvPort) { + if sc.IsTCPForwardingOnPort(srvPort, tailcfg.ServiceName(dnsName)) { return errors.New("cannot remove web handler; currently serving TCP") } var targetExists bool @@ -986,7 +986,7 @@ func (e *serveEnv) removeTCPServe(sc *ipn.ServeConfig, dnsName string, src uint1 if sc.GetTCPPortHandler(src, dnsName) == nil { return errors.New("serve config does not exist") } - if sc.IsServingWeb(src, dnsName) { + if sc.IsServingWeb(src, tailcfg.ServiceName(dnsName)) { return fmt.Errorf("unable to remove; serving web, not TCP forwarding on serve port %d", src) } sc.RemoveTCPForwarding(dnsName, src) diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 90166d0f8..273960cc5 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -260,7 +260,7 @@ func printFunnelStatus(ctx context.Context) { } sni, portStr, _ := net.SplitHostPort(string(hp)) p, _ := strconv.ParseUint(portStr, 10, 16) - isTCP := sc.IsTCPForwardingOnPort(sni, uint16(p)) + isTCP := sc.IsTCPForwardingOnPort(uint16(p), "") url := "https://" if isTCP { url = "tcp://" diff --git a/ipn/serve.go b/ipn/serve.go index c8e32b87d..ce1452336 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -245,14 +245,15 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool { } // IsTCPForwardingOnPort reports whether ServeConfig is currently forwarding -// in TCPForward mode on the given port for a DNSName. DNSName will be either node's DNSName, or a -// serviceName for service hosted on node. This is exclusive of Web/HTTPS serving. -func (sc *ServeConfig) IsTCPForwardingOnPort(dnsName string, port uint16) bool { +// in TCPForward mode on the given port for local or a service. svcName will +// either be empty string for local serve or a serviceName for service hosted +// on node. Notice TCPForwarding is exclusive with Web/HTTPS serving. +func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16, svcName tailcfg.ServiceName) bool { if sc == nil { return false } - if svcName, ok := tailcfg.AsServiceName(dnsName); ok { + if err := svcName.Validate(); err == nil { svc, ok := sc.Services[svcName] if !ok || svc == nil { return false @@ -263,25 +264,26 @@ func (sc *ServeConfig) IsTCPForwardingOnPort(dnsName string, port uint16) bool { } else if sc.TCP[port] == nil { return false } - return !sc.IsServingWeb(port, dnsName) + return !sc.IsServingWeb(port, svcName) } -// IsServingWeb reports whether ServeConfig is currently serving Web -// (HTTP/HTTPS) on the given port for a DNSName. DNSName will be either node's DNSName, or a -// serviceName for service hosted on node. This is exclusive of TCPForwarding. -func (sc *ServeConfig) IsServingWeb(port uint16, dnsName string) bool { - return sc.IsServingHTTP(port, dnsName) || sc.IsServingHTTPS(port, dnsName) +// IsServingWeb reports whether ServeConfig is currently serving Web (HTTP/HTTPS) +// on the given port for local or a service. DNSName will be either node's DNSName, or a +// serviceName for service hosted on node. This is exclusive with TCPForwarding. +func (sc *ServeConfig) IsServingWeb(port uint16, svcName tailcfg.ServiceName) bool { + return sc.IsServingHTTP(port, svcName) || sc.IsServingHTTPS(port, svcName) } // IsServingHTTPS reports whether ServeConfig is currently serving HTTPS on -// the given port for a DNSName. DNSName will be either node's DNSName, or a -// serviceName for service hosted on node. This is exclusive of HTTP and TCPForwarding. -func (sc *ServeConfig) IsServingHTTPS(port uint16, dnsName string) bool { +// the given port for local or a service. svcName will be either empty string +// for local serve, or a serviceName for service hosted on node. This is exclusive +// with HTTP and TCPForwarding. +func (sc *ServeConfig) IsServingHTTPS(port uint16, svcName tailcfg.ServiceName) bool { if sc == nil { return false } var tcpHandlers map[uint16]*TCPPortHandler - if svcName, ok := tailcfg.AsServiceName(dnsName); ok { + if err := svcName.Validate(); err == nil { if svc := sc.Services[svcName]; svc != nil { tcpHandlers = svc.TCP } @@ -297,14 +299,15 @@ func (sc *ServeConfig) IsServingHTTPS(port uint16, dnsName string) bool { } // IsServingHTTP reports whether ServeConfig is currently serving HTTP on the -// given port for a DNSName. DNSName will be either node's DNSName, or a -// serviceName for service hosted on node. This is exclusive of HTTPS and TCPForwarding. -func (sc *ServeConfig) IsServingHTTP(port uint16, dnsName string) bool { +// given port for local or a service. svcName will be either empty string for +// local serve, or a serviceName for service hosted on node. This is exclusive +// with HTTPS and TCPForwarding. +func (sc *ServeConfig) IsServingHTTP(port uint16, svcName tailcfg.ServiceName) bool { if sc == nil { return false } - if svcName, ok := tailcfg.AsServiceName(dnsName); ok { - if svc, ok := sc.Services[svcName]; ok && svc != nil { + if err := svcName.Validate(); err == nil { + if svc := sc.Services[svcName]; svc != nil { if svc.TCP[port] != nil { return svc.TCP[port].HTTP } diff --git a/ipn/serve_test.go b/ipn/serve_test.go index 8b66a1c69..e8a646b26 100644 --- a/ipn/serve_test.go +++ b/ipn/serve_test.go @@ -130,45 +130,45 @@ func TestHasPathHandler(t *testing.T) { func TestIsTCPForwardingOnPort(t *testing.T) { tests := []struct { - name string - cfg ServeConfig - dns string - port uint16 - want bool + name string + cfg ServeConfig + svcName tailcfg.ServiceName + port uint16 + want bool }{ { - name: "empty-config", - cfg: ServeConfig{}, - dns: "foo.test.ts.net", - port: 80, - want: false, + name: "empty-config", + cfg: ServeConfig{}, + svcName: "foo.test.ts.net", + port: 80, + want: false, }, { name: "node-tcp-config-match", cfg: ServeConfig{ TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}}, }, - dns: "foo.test.ts.net", - port: 80, - want: true, + svcName: "foo.test.ts.net", + port: 80, + want: true, }, { name: "node-tcp-config-no-match", cfg: ServeConfig{ TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}}, }, - dns: "foo.test.ts.net", - port: 443, - want: false, + svcName: "foo.test.ts.net", + port: 443, + want: false, }, { name: "node-tcp-config-no-match-with-service", cfg: ServeConfig{ TCP: map[uint16]*TCPPortHandler{80: {TCPForward: "10.0.0.123:3000"}}, }, - dns: "svc:bar", - port: 80, - want: false, + svcName: "svc:bar", + port: 80, + want: false, }, { name: "node-web-config-no-match", @@ -182,9 +182,9 @@ func TestIsTCPForwardingOnPort(t *testing.T) { }, }, }, - dns: "foo.test.ts.net", - port: 80, - want: false, + svcName: "foo.test.ts.net", + port: 80, + want: false, }, { name: "service-tcp-config-match", @@ -195,9 +195,9 @@ func TestIsTCPForwardingOnPort(t *testing.T) { }, }, }, - dns: "svc:foo", - port: 80, - want: true, + svcName: "svc:foo", + port: 80, + want: true, }, { name: "service-tcp-config-no-match", @@ -208,9 +208,9 @@ func TestIsTCPForwardingOnPort(t *testing.T) { }, }, }, - dns: "svc:bar", - port: 80, - want: false, + svcName: "svc:bar", + port: 80, + want: false, }, { name: "service-web-config-no-match", @@ -228,14 +228,14 @@ func TestIsTCPForwardingOnPort(t *testing.T) { }, }, }, - dns: "svc:foo", - port: 80, - want: false, + svcName: "svc:foo", + port: 80, + want: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := tt.cfg.IsTCPForwardingOnPort(tt.dns, tt.port) + got := tt.cfg.IsTCPForwardingOnPort(tt.port, tt.svcName) if tt.want != got { t.Errorf("IsTCPForwardingOnPort() = %v, want %v", got, tt.want) }