diff --git a/cmd/tailscale/cli/serve.go b/cmd/tailscale/cli/serve.go index 19309340a..dd5b8bc49 100644 --- a/cmd/tailscale/cli/serve.go +++ b/cmd/tailscale/cli/serve.go @@ -517,7 +517,7 @@ func printTCPStatusTree(ctx context.Context, sc *ipn.ServeConfig, st *ipnstate.S tlsStatus = "TLS terminated" } fStatus := "tailnet only" - if sc.IsFunnelOn(hp) { + if sc.AllowFunnel[hp] { fStatus = "Funnel on" } printf("|-- tcp://%s (%s, %s)\n", hp, tlsStatus, fStatus) @@ -535,7 +535,7 @@ func printWebStatusTree(sc *ipn.ServeConfig, hp ipn.HostPort) { return } fStatus := "tailnet only" - if sc.IsFunnelOn(hp) { + if sc.AllowFunnel[hp] { fStatus = "Funnel on" } host, portStr, _ := net.SplitHostPort(string(hp)) @@ -690,8 +690,7 @@ func (e *serveEnv) runServeFunnel(ctx context.Context, args []string) error { } dnsName := strings.TrimSuffix(st.Self.DNSName, ".") hp := ipn.HostPort(dnsName + ":" + srvPortStr) - isFun := sc.IsFunnelOn(hp) - if on && isFun || !on && !isFun { + if on == sc.AllowFunnel[hp] { // Nothing to do. return nil } diff --git a/cmd/tailscale/cli/status.go b/cmd/tailscale/cli/status.go index 1c28a62e6..c6e40ba84 100644 --- a/cmd/tailscale/cli/status.go +++ b/cmd/tailscale/cli/status.go @@ -15,6 +15,7 @@ import ( "net/http" "net/netip" "os" + "strconv" "strings" "github.com/peterbourgon/ff/v3/ffcli" @@ -222,9 +223,44 @@ func runStatus(ctx context.Context, args []string) error { outln() printHealth() } + printFunnelStatus(ctx) return nil } +// printFunnelStatus prints the status of the funnel, if it's running. +// It prints nothing if the funnel is not running. +func printFunnelStatus(ctx context.Context) { + sc, err := localClient.GetServeConfig(ctx) + if err != nil { + outln() + printf("# Funnel:\n") + printf("# - Unable to get Funnel status: %v\n", err) + return + } + if !sc.IsFunnelOn() { + return + } + outln() + printf("# Funnel on:\n") + for hp, on := range sc.AllowFunnel { + if !on { // if present, should be on + continue + } + sni, portStr, _ := net.SplitHostPort(string(hp)) + p, _ := strconv.ParseUint(portStr, 10, 16) + isTCP := sc.IsTCPForwardingOnPort(uint16(p)) + url := "https://" + if isTCP { + url = "tcp://" + } + url += sni + if isTCP || p != 443 { + url += ":" + portStr + } + printf("# - %s\n", url) + } +} + // isRunningOrStarting reports whether st is in state Running or Starting. // It also returns a description of the status suitable to display to a user. func isRunningOrStarting(st *ipnstate.Status) (description string, ok bool) { diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 7d1ecb383..e2d281be5 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -540,37 +540,32 @@ func (h *Handler) servePprof(w http.ResponseWriter, r *http.Request) { } func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) { - if !h.PermitWrite { - http.Error(w, "serve config denied", http.StatusForbidden) - return - } - - w.Header().Set("Content-Type", "application/json") - switch r.Method { case "GET": + if !h.PermitRead { + http.Error(w, "serve config denied", http.StatusForbidden) + return + } + w.Header().Set("Content-Type", "application/json") config := h.b.ServeConfig() json.NewEncoder(w).Encode(config) case "POST": + if !h.PermitWrite { + http.Error(w, "serve config denied", http.StatusForbidden) + return + } configIn := new(ipn.ServeConfig) if err := json.NewDecoder(r.Body).Decode(configIn); err != nil { - json.NewEncoder(w).Encode(struct { - Error error - }{ - Error: fmt.Errorf("decoding config: %w", err), - }) + writeErrorJSON(w, fmt.Errorf("decoding config: %w", err)) return } - err := h.b.SetServeConfig(configIn) - if err != nil { - json.NewEncoder(w).Encode(struct { - Error error - }{ - Error: fmt.Errorf("updating config: %w", err), - }) + if err := h.b.SetServeConfig(configIn); err != nil { + writeErrorJSON(w, fmt.Errorf("updating config: %w", err)) return } w.WriteHeader(http.StatusOK) + default: + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } diff --git a/ipn/serve.go b/ipn/serve.go index 93d6b077e..a0c6849a1 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -81,15 +81,18 @@ func (sc *ServeConfig) WebHandlerExists(hp HostPort, mount string) bool { // GetWebHandler returns the HTTPHandler for the given host:port and mount point. // Returns nil if the handler does not exist. func (sc *ServeConfig) GetWebHandler(hp HostPort, mount string) *HTTPHandler { - if sc.Web[hp] != nil { - return sc.Web[hp].Handlers[mount] + if sc == nil || sc.Web[hp] == nil { + return nil } - return nil + return sc.Web[hp].Handlers[mount] } // GetTCPPortHandler returns the TCPPortHandler for the given port. // If the port is not configured, nil is returned. func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler { + if sc == nil { + return nil + } return sc.TCP[port] } @@ -97,7 +100,7 @@ func (sc *ServeConfig) GetTCPPortHandler(port uint16) *TCPPortHandler { // in TCPForward mode on any port. // This is exclusive of Web/HTTPS serving. func (sc *ServeConfig) IsTCPForwardingAny() bool { - if len(sc.TCP) == 0 { + if sc == nil || len(sc.TCP) == 0 { return false } for _, h := range sc.TCP { @@ -112,7 +115,7 @@ func (sc *ServeConfig) IsTCPForwardingAny() bool { // in TCPForward mode on the given port. // This is exclusive of Web/HTTPS serving. func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool { - if sc.TCP[port] == nil { + if sc == nil || sc.TCP[port] == nil { return false } return !sc.TCP[port].HTTPS @@ -122,14 +125,22 @@ func (sc *ServeConfig) IsTCPForwardingOnPort(port uint16) bool { // Web/HTTPS on the given port. // This is exclusive of TCPForwarding. func (sc *ServeConfig) IsServingWeb(port uint16) bool { - if sc.TCP[port] == nil { + if sc == nil || sc.TCP[port] == nil { return false } return sc.TCP[port].HTTPS } // IsFunnelOn checks if ServeConfig is currently allowing -// funnel traffic on for the given host:port. -func (sc *ServeConfig) IsFunnelOn(hp HostPort) bool { - return sc.AllowFunnel[hp] +// funnel traffic for any host:port. +func (sc *ServeConfig) IsFunnelOn() bool { + if sc == nil { + return false + } + for _, b := range sc.AllowFunnel { + if b { + return true + } + } + return false }