summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorKevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>2025-03-16 22:28:50 -0400
committerKevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>2025-03-17 09:24:08 -0400
commit1103609548e534e2999a86203a64ed323306ab9e (patch)
treed8240f8d88d8b57189e9a5d46df6517067f5f1ec
parent793ca2fb430214a4a3e852a393f879e8043a0449 (diff)
downloadtailscale-kevin/add_services_to_status_subcommand_for_serve.tar.xz
tailscale-kevin/add_services_to_status_subcommand_for_serve.zip
cmd/tailscale/cli: Add output for services to serve statuskevin/add_services_to_status_subcommand_for_serve
This commit rewrote a part of runServeStatus and added services info to status subcomand. Fixes tailscale/corp#22954 Signed-off-by: KevinLiang10 <37811973+KevinLiang10@users.noreply.github.com>
-rw-r--r--cmd/tailscale/cli/serve_legacy.go169
-rw-r--r--ipn/serve.go27
2 files changed, 135 insertions, 61 deletions
diff --git a/cmd/tailscale/cli/serve_legacy.go b/cmd/tailscale/cli/serve_legacy.go
index 3861883df..bebaf7313 100644
--- a/cmd/tailscale/cli/serve_legacy.go
+++ b/cmd/tailscale/cli/serve_legacy.go
@@ -12,6 +12,7 @@ import (
"io"
"log"
"net"
+ "net/netip"
"net/url"
"os"
"path"
@@ -625,7 +626,7 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
return nil
}
printFunnelStatus(ctx)
- if sc == nil || (len(sc.TCP) == 0 && len(sc.Web) == 0 && len(sc.AllowFunnel) == 0) {
+ if sc == nil || (len(sc.TCP) == 0 && len(sc.Web) == 0 && len(sc.AllowFunnel) == 0 && len(sc.Services) == 0) {
printf("No serve config\n")
return nil
}
@@ -633,26 +634,67 @@ func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
if err != nil {
return err
}
- if sc.IsTCPForwardingAny() {
- if err := printTCPStatusTree(ctx, sc, st); err != nil {
- return err
- }
+ nodeDNSName := strings.TrimSuffix(st.Self.DNSName, ".")
+ if sc.IsNodeTCPForwardingAny() {
+ printTCPStatusTree(sc.TCP, nodeDNSName, st.TailscaleIPs, sc.AllowFunnel)
printf("\n")
}
- for hp := range sc.Web {
- err := e.printWebStatusTree(sc, hp)
+ if sc.IsNodeServingWeb() {
+ err := printWebStatusTree(sc, nodeDNSName)
if err != nil {
return err
}
printf("\n")
}
+ if sc.HostingServices() {
+ if err := printServicesStatusTree(e, sc, st); err != nil {
+ return err
+ }
+ }
printFunnelWarning(sc)
return nil
}
-func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.Status) error {
- dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
- for p, h := range sc.TCP {
+func printServicesStatusTree(e *serveEnv, sc *ipn.ServeConfig, st *ipnstate.Status) error {
+ print("Services:\n")
+ if st.Self == nil || st.Self.CapMap == nil {
+ return nil
+ }
+ var svcIPMap tailcfg.ServiceIPMappings
+ serviceIPMaps, err := tailcfg.UnmarshalNodeCapJSON[tailcfg.ServiceIPMappings](st.Self.CapMap, tailcfg.NodeAttrServiceHost)
+ if len(serviceIPMaps) != 1 || err != nil {
+ if len(serviceIPMaps) != 0 {
+ fmt.Fprintln(e.stderr(), "error: unable to get service IP mappings")
+ }
+ svcIPMap = nil
+ print("No service have IPs assigned yet.\n")
+ print("They're either partially configured, not advertised or not approved.\n")
+ } else {
+ svcIPMap = serviceIPMaps[0]
+ }
+ print("\n")
+
+ for svcName, svcCfg := range sc.Services {
+ print(svcName.String() + ":\n")
+ host := svcName.WithoutPrefix() + "." + st.CurrentTailnet.MagicDNSSuffix
+ if ips, ok := svcIPMap[svcName]; ok {
+ printTCPStatusTree(svcCfg.TCP, host, ips, nil)
+ } else {
+ if svcIPMap != nil {
+ print("Service doesn't have IPs assigned yet.\n")
+ print("It's either partially configured, not advertised or not approved.\n")
+ }
+ printTCPStatusTree(svcCfg.TCP, host, nil, nil)
+ }
+ printWebStatusTree(sc, svcName.String())
+ print("\n")
+ }
+
+ return nil
+}
+
+func printTCPStatusTree(tcpMap map[uint16]*ipn.TCPPortHandler, dnsName string, ips []netip.Addr, allowFunnel map[ipn.HostPort]bool) {
+ for p, h := range tcpMap {
if h.TCPForward == "" {
continue
}
@@ -662,72 +704,81 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S
tlsStatus = "TLS terminated"
}
fStatus := "tailnet only"
- if sc.AllowFunnel[hp] {
+ if allowFunnel[hp] {
fStatus = "Funnel on"
}
printf("|-- tcp://%s (%s, %s)\n", hp, tlsStatus, fStatus)
- for _, a := range st.TailscaleIPs {
+ for _, a := range ips {
ipp := net.JoinHostPort(a.String(), strconv.Itoa(int(p)))
printf("|-- tcp://%s\n", ipp)
}
printf("|--> tcp://%s\n", h.TCPForward)
}
- return nil
}
-func (e *serveEnv) printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) error {
- // No-op if no serve config
- if sc == nil {
- return nil
- }
- fStatus := "tailnet only"
- if sc.AllowFunnel[hp] {
- fStatus = "Funnel on"
+func printWebStatusTree(sc *ipn.ServeConfig, dnsName string) error {
+ webMap := sc.Web
+ forService := ipn.IsServiceName(dnsName)
+ if forService {
+ svcName := tailcfg.ServiceName(dnsName)
+ if svc, ok := sc.Services[svcName]; ok && svc != nil {
+ webMap = svc.Web
+ } else {
+ // If the service is not found, just silently skip it.
+ return nil
+ }
}
- host, portStr, _ := net.SplitHostPort(string(hp))
+ for hp := range webMap {
+ fStatus := "tailnet only"
+ if !forService && sc.AllowFunnel[hp] {
+ fStatus = "Funnel on"
+ }
+ host, portStr, _ := net.SplitHostPort(string(hp))
- port, err := parseServePort(portStr)
- if err != nil {
- return fmt.Errorf("invalid port %q: %w", portStr, err)
- }
+ port, err := parseServePort(portStr)
+ if err != nil {
+ return fmt.Errorf("invalid port %q: %w", portStr, err)
+ }
- scheme := "https"
- if sc.IsServingHTTP(port, host) {
- scheme = "http"
- }
+ scheme := "https"
+ if sc.IsServingHTTP(port, dnsName) {
+ scheme = "http"
+ }
- portPart := ":" + portStr
- if scheme == "http" && portStr == "80" ||
- scheme == "https" && portStr == "443" {
- portPart = ""
- }
- if scheme == "http" {
- hostname, _, _ := strings.Cut(host, ".")
- printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
- }
- printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
- srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
- switch {
- case h.Path != "":
- return "path", h.Path
- case h.Proxy != "":
- return "proxy", h.Proxy
- case h.Text != "":
- return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\""
+ portPart := ":" + portStr
+ if scheme == "http" && portStr == "80" ||
+ scheme == "https" && portStr == "443" {
+ portPart = ""
+ }
+ if scheme == "http" {
+ hostname, _, _ := strings.Cut(host, ".")
+ printf("%s://%s%s (%s)\n", scheme, hostname, portPart, fStatus)
+ }
+ printf("%s://%s%s (%s)\n", scheme, host, portPart, fStatus)
+ srvTypeAndDesc := func(h *ipn.HTTPHandler) (string, string) {
+ switch {
+ case h.Path != "":
+ return "path", h.Path
+ case h.Proxy != "":
+ return "proxy", h.Proxy
+ case h.Text != "":
+ return "text", "\"" + elipticallyTruncate(h.Text, 20) + "\""
+ }
+ return "", ""
}
- return "", ""
- }
- mounts := slicesx.MapKeys(sc.Web[hp].Handlers)
- sort.Slice(mounts, func(i, j int) bool {
- return len(mounts[i]) < len(mounts[j])
- })
- maxLen := len(mounts[len(mounts)-1])
+ mounts := slicesx.MapKeys(webMap[hp].Handlers)
+ sort.Slice(mounts, func(i, j int) bool {
+ return len(mounts[i]) < len(mounts[j])
+ })
+ maxLen := len(mounts[len(mounts)-1])
- for _, m := range mounts {
- h := sc.Web[hp].Handlers[m]
- t, d := srvTypeAndDesc(h)
- printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
+ for _, m := range mounts {
+ h := webMap[hp].Handlers[m]
+ t, d := srvTypeAndDesc(h)
+ printf("%s %s%s %-5s %s\n", "|--", m, strings.Repeat(" ", maxLen-len(m)), t, d)
+ }
+ print("\n")
}
return nil
diff --git a/ipn/serve.go b/ipn/serve.go
index 2f912a03f..4bf8c0624 100644
--- a/ipn/serve.go
+++ b/ipn/serve.go
@@ -230,9 +230,9 @@ func (sc *ServeConfig) HasPathHandler() bool {
return false
}
-// IsTCPForwardingAny reports whether ServeConfig is currently forwarding in
+// IsNodeTCPForwardingAny reports whether ServeConfig is currently forwarding in
// TCPForward mode on any port. This is exclusive of Web/HTTPS serving.
-func (sc *ServeConfig) IsTCPForwardingAny() bool {
+func (sc *ServeConfig) IsNodeTCPForwardingAny() bool {
if sc == nil || len(sc.TCP) == 0 {
return false
}
@@ -244,6 +244,29 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool {
return false
}
+func (sc *ServeConfig) IsNodeServingWeb() bool {
+ if sc == nil || len(sc.Web) == 0 {
+ return false
+ }
+ for _, webServerConfig := range sc.Web {
+ if len(webServerConfig.Handlers) > 0 {
+ return true
+ }
+ }
+ return false
+}
+func (sc *ServeConfig) HostingServices() bool {
+ if sc == nil || len(sc.Services) == 0 {
+ return false
+ }
+ for _, svc := range sc.Services {
+ if len(svc.TCP) > 0 || len(svc.Web) > 0 || svc.Tun {
+ return true
+ }
+ }
+ return false
+}
+
// IsServiceName reports whether if the given string is a valid service name.
func IsServiceName(s string) bool {
return tailcfg.ServiceName(s).Validate() == nil