summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--cmd/tailscale/cli/cli.go2
-rw-r--r--cmd/tailscale/cli/funnel_dev.go66
-rw-r--r--cmd/tailscale/cli/serve.go11
-rw-r--r--cmd/tailscale/cli/serve_dev.go114
-rw-r--r--ipn/ipnlocal/serve.go8
-rw-r--r--ipn/serve.go4
6 files changed, 135 insertions, 70 deletions
diff --git a/cmd/tailscale/cli/cli.go b/cmd/tailscale/cli/cli.go
index 7c6ab488b..74d02800b 100644
--- a/cmd/tailscale/cli/cli.go
+++ b/cmd/tailscale/cli/cli.go
@@ -121,7 +121,7 @@ change in the future.
ncCmd,
sshCmd,
funnelCmd(),
- serveCmd,
+ serveCmd(),
versionCmd,
webCmd,
fileCmd,
diff --git a/cmd/tailscale/cli/funnel_dev.go b/cmd/tailscale/cli/funnel_dev.go
index 3aecabb83..84af6775e 100644
--- a/cmd/tailscale/cli/funnel_dev.go
+++ b/cmd/tailscale/cli/funnel_dev.go
@@ -4,16 +4,10 @@
package cli
import (
- "context"
"flag"
- "fmt"
- "io"
- "os"
- "strconv"
"strings"
"github.com/peterbourgon/ff/v3/ffcli"
- "tailscale.com/ipn"
)
// newFunnelDevCommand returns a new "funnel" subcommand using e as its environment.
@@ -37,7 +31,7 @@ func newFunnelDevCommand(e *serveEnv) *ffcli.Command {
"Note that it only supports https servers at this point.",
"This command is in development and is unsupported",
}, "\n"),
- Exec: e.runFunnelDev,
+ Exec: e.runServeDev(true),
UsageFunc: usageFunc,
Subcommands: []*ffcli.Command{
{
@@ -52,61 +46,3 @@ func newFunnelDevCommand(e *serveEnv) *ffcli.Command {
},
}
}
-
-// runFunnelDev is the entry point for the "tailscale funnel" subcommand and
-// manages turning on/off Funnel. Funnel is off by default.
-//
-// Note: funnel is only supported on single DNS name for now. (2023-08-18)
-func (e *serveEnv) runFunnelDev(ctx context.Context, args []string) error {
- if len(args) != 1 {
- return flag.ErrHelp
- }
- var source string
- port64, err := strconv.ParseUint(args[0], 10, 16)
- if err == nil {
- source = fmt.Sprintf("http://127.0.0.1:%d", port64)
- } else {
- source, err = expandProxyTarget(args[0])
- }
- if err != nil {
- return err
- }
-
- st, err := e.getLocalClientStatusWithoutPeers(ctx)
- if err != nil {
- return fmt.Errorf("getting client status: %w", err)
- }
-
- if err := e.verifyFunnelEnabled(ctx, st, 443); err != nil {
- return err
- }
-
- dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
- hp := ipn.HostPort(dnsName + ":443") // TODO(marwan-at-work): support the 2 other ports
-
- // In the streaming case, the process stays running in the
- // foreground and prints out connections to the HostPort.
- //
- // The local backend handles updating the ServeConfig as
- // necessary, then restores it to its original state once
- // the process's context is closed or the client turns off
- // Tailscale.
- return e.streamServe(ctx, ipn.ServeStreamRequest{
- HostPort: hp,
- Source: source,
- MountPoint: "/", // TODO(marwan-at-work): support multiple mount points
- })
-}
-
-func (e *serveEnv) streamServe(ctx context.Context, req ipn.ServeStreamRequest) error {
- stream, err := e.lc.StreamServe(ctx, req)
- if err != nil {
- return err
- }
- defer stream.Close()
-
- fmt.Fprintf(os.Stderr, "Funnel started on \"https://%s\".\n", strings.TrimSuffix(string(req.HostPort), ":443"))
- fmt.Fprintf(os.Stderr, "Press Ctrl-C to stop Funnel.\n\n")
- _, err = io.Copy(os.Stdout, stream)
- return err
-}
diff --git a/cmd/tailscale/cli/serve.go b/cmd/tailscale/cli/serve.go
index 7b4d38691..0f154ed71 100644
--- a/cmd/tailscale/cli/serve.go
+++ b/cmd/tailscale/cli/serve.go
@@ -32,7 +32,16 @@ import (
"tailscale.com/version"
)
-var serveCmd = newServeCommand(&serveEnv{lc: &localClient})
+var serveCmd = func() *ffcli.Command {
+ se := &serveEnv{lc: &localClient}
+ // This flag is used to switch to an in-development
+ // implementation of the tailscale funnel command.
+ // See https://github.com/tailscale/tailscale/issues/7844
+ if os.Getenv("TAILSCALE_FUNNEL_DEV") == "on" {
+ return newServeDevCommand(se)
+ }
+ return newServeCommand(se)
+}
// newServeCommand returns a new "serve" subcommand using e as its environment.
func newServeCommand(e *serveEnv) *ffcli.Command {
diff --git a/cmd/tailscale/cli/serve_dev.go b/cmd/tailscale/cli/serve_dev.go
new file mode 100644
index 000000000..4906e93e9
--- /dev/null
+++ b/cmd/tailscale/cli/serve_dev.go
@@ -0,0 +1,114 @@
+// Copyright (c) Tailscale Inc & AUTHORS
+// SPDX-License-Identifier: BSD-3-Clause
+
+package cli
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "os/signal"
+ "strconv"
+ "strings"
+
+ "github.com/peterbourgon/ff/v3/ffcli"
+ "tailscale.com/ipn"
+)
+
+type execFunc func(ctx context.Context, args []string) error
+
+// newServeDevCommand returns a new "serve" subcommand using e as its environment.
+func newServeDevCommand(e *serveEnv) *ffcli.Command {
+ return &ffcli.Command{
+ Name: "serve",
+ ShortHelp: "Serve content and local servers on your tailnet",
+ ShortUsage: strings.Join([]string{
+ "serve <port>",
+ "serve status [--json]",
+ }, "\n "),
+ LongHelp: strings.TrimSpace(`
+The 'tailscale serve' set of commands allows you to serve
+content and local servers from your Tailscale node to
+your tailnet.
+`),
+ Exec: e.runServeDev(false),
+ UsageFunc: usageFunc,
+ Subcommands: []*ffcli.Command{
+ {
+ Name: "status",
+ Exec: e.runServeStatus,
+ ShortHelp: "show current serve/Funnel status",
+ FlagSet: e.newFlags("funnel-status", func(fs *flag.FlagSet) {
+ fs.BoolVar(&e.json, "json", false, "output JSON")
+ }),
+ UsageFunc: usageFunc,
+ },
+ },
+ }
+}
+
+// runServeDev is the entry point for the "tailscale serve|funnel" subcommand.
+//
+// Note: funnel is only supported on single DNS name for now. (2023-08-18)
+func (e *serveEnv) runServeDev(funnel bool) execFunc {
+ return func(ctx context.Context, args []string) error {
+ ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
+ defer cancel()
+ if len(args) != 1 {
+ return flag.ErrHelp
+ }
+ var source string
+ port64, err := strconv.ParseUint(args[0], 10, 16)
+ if err == nil {
+ source = fmt.Sprintf("http://127.0.0.1:%d", port64)
+ } else {
+ source, err = expandProxyTarget(args[0])
+ }
+ if err != nil {
+ return err
+ }
+
+ st, err := e.getLocalClientStatusWithoutPeers(ctx)
+ if err != nil {
+ return fmt.Errorf("getting client status: %w", err)
+ }
+
+ if funnel {
+ if err := e.verifyFunnelEnabled(ctx, st, 443); err != nil {
+ return err
+ }
+ }
+
+ dnsName := strings.TrimSuffix(st.Self.DNSName, ".")
+ hp := ipn.HostPort(dnsName + ":443") // TODO(marwan-at-work): support the 2 other ports
+
+ // In the streaming case, the process stays running in the
+ // foreground and prints out connections to the HostPort.
+ //
+ // The local backend handles updating the ServeConfig as
+ // necessary, then restores it to its original state once
+ // the process's context is closed or the client turns off
+ // Tailscale.
+ return e.streamServe(ctx, ipn.ServeStreamRequest{
+ Funnel: funnel,
+ HostPort: hp,
+ Source: source,
+ MountPoint: "/", // TODO(marwan-at-work): support multiple mount points
+ })
+ }
+}
+
+func (e *serveEnv) streamServe(ctx context.Context, req ipn.ServeStreamRequest) error {
+ stream, err := e.lc.StreamServe(ctx, req)
+ if err != nil {
+ return err
+ }
+ defer stream.Close()
+
+ fmt.Fprintf(os.Stderr, "Serve started on \"https://%s\".\n", strings.TrimSuffix(string(req.HostPort), ":443"))
+ fmt.Fprintf(os.Stderr, "Press Ctrl-C to stop.\n\n")
+ _, err = io.Copy(os.Stdout, stream)
+ return err
+}
diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go
index de9de77ce..8778548c1 100644
--- a/ipn/ipnlocal/serve.go
+++ b/ipn/ipnlocal/serve.go
@@ -356,10 +356,12 @@ func setHandler(sc *ipn.ServeConfig, req ipn.ServeStreamRequest) {
wsc.Handlers[req.MountPoint] = &ipn.HTTPHandler{
Proxy: req.Source,
}
- if sc.AllowFunnel == nil {
- sc.AllowFunnel = make(map[ipn.HostPort]bool)
+ if req.Funnel {
+ if sc.AllowFunnel == nil {
+ sc.AllowFunnel = make(map[ipn.HostPort]bool)
+ }
+ sc.AllowFunnel[req.HostPort] = true
}
- sc.AllowFunnel[req.HostPort] = true
}
func deleteHandler(sc *ipn.ServeConfig, req ipn.ServeStreamRequest, port uint16) {
diff --git a/ipn/serve.go b/ipn/serve.go
index 3b6034fa9..11df99726 100644
--- a/ipn/serve.go
+++ b/ipn/serve.go
@@ -93,6 +93,10 @@ type ServeStreamRequest struct {
// MountPoint is the path prefix for
// the given HostPort.
MountPoint string `json:",omitempty"`
+
+ // Funnel indicates whether the request
+ // is a serve request or a funnel one.
+ Funnel bool `json:",omitempty"`
}
// FunnelRequestLog is the JSON type written out to io.Writers