cmd/tailscale: combine foreground and background serve logic

Previously, foreground mode only worked in the simple case of `tailscale funnel <port>`.
This PR ensures that whatever you can do in the background can also be
done in the foreground such as setting mount paths or tcp forwarding.

Updates #8489

Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
pull/9309/head
Marwan Sulaiman 9 months ago committed by Marwan Sulaiman
parent 8452d273e3
commit a1d4144b18

@ -20,6 +20,7 @@ import (
"strings" "strings"
"github.com/peterbourgon/ff/v3/ffcli" "github.com/peterbourgon/ff/v3/ffcli"
"tailscale.com/client/tailscale"
"tailscale.com/ipn" "tailscale.com/ipn"
"tailscale.com/ipn/ipnstate" "tailscale.com/ipn/ipnstate"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
@ -188,99 +189,37 @@ func (e *serveEnv) runServeCombined(subcmd serveMode) execFunc {
mount = "/" mount = "/"
} }
if e.bg || turnOff || e.setPath != "" { if e.setPath != "" {
srvType, srvPort, err := srvTypeAndPortFromFlags(e) // TODO(marwan-at-work): either
if err != nil { // 1. Warn the user that this is a side effect.
fmt.Fprintf(os.Stderr, "error: %v\n\n", err) // 2. Force the user to pass --bg
return errHelp // 3. Allow set-path to be in the foreground.
} e.bg = true
}
if turnOff { srvType, srvPort, err := srvTypeAndPortFromFlags(e)
err := e.unsetServe(ctx, srvType, srvPort, mount) if err != nil {
if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n\n", err)
fmt.Fprintf(os.Stderr, "error: %v\n\n", err) return errHelp
return errHelp }
}
return nil
}
err = e.setServe(ctx, st, srvType, srvPort, mount, target, funnel) if turnOff {
err := e.unsetServe(ctx, srvType, srvPort, mount)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n\n", err) fmt.Fprintf(os.Stderr, "error: %v\n\n", err)
return errHelp return errHelp
} }
return nil return nil
} }
dnsName := strings.TrimSuffix(st.Self.DNSName, ".") err = e.setServe(ctx, st, srvType, srvPort, mount, target, funnel)
hp := ipn.HostPort(dnsName + ":443") // TODO(marwan-at-work): support the 2 other ports
// TODO(marwan-at-work): combine this with the above setServe code.
// Foreground and background should be the same, we just pass
// a foreground config instead of the top level background one.
return e.streamServe(ctx, ipn.ServeStreamRequest{
Funnel: funnel,
HostPort: hp,
Source: target,
MountPoint: mount,
})
}
}
func (e *serveEnv) streamServe(ctx context.Context, req ipn.ServeStreamRequest) error {
watcher, err := e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState)
if err != nil {
return err
}
defer watcher.Close()
n, err := watcher.Next()
if err != nil {
return err
}
sessionID := n.SessionID
if sessionID == "" {
return errors.New("missing SessionID")
}
sc, err := e.lc.GetServeConfig(ctx)
if err != nil {
return fmt.Errorf("error getting serve config: %w", err)
}
if sc == nil {
sc = &ipn.ServeConfig{}
}
setHandler(sc, req, sessionID)
err = e.lc.SetServeConfig(ctx, sc)
if err != nil {
return fmt.Errorf("error setting serve config: %w", err)
}
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")
for {
_, err = watcher.Next()
if err != nil { if err != nil {
if errors.Is(err, context.Canceled) { fmt.Fprintf(os.Stderr, "error: %v\n\n", err)
return nil return errHelp
}
return err
} }
}
}
// setHandler modifies sc to add a Foreground config (described by req) with the given sessionID. return nil
func setHandler(sc *ipn.ServeConfig, req ipn.ServeStreamRequest, sessionID string) { }
fconf := &ipn.ServeConfig{}
mak.Set(&sc.Foreground, sessionID, fconf)
mak.Set(&fconf.TCP, 443, &ipn.TCPPortHandler{HTTPS: true})
wsc := &ipn.WebServerConfig{}
mak.Set(&fconf.Web, req.HostPort, wsc)
mak.Set(&wsc.Handlers, req.MountPoint, &ipn.HTTPHandler{
Proxy: req.Source,
})
mak.Set(&fconf.AllowFunnel, req.HostPort, true)
} }
func (e *serveEnv) setServe(ctx context.Context, st *ipnstate.Status, srvType string, srvPort uint16, mount string, target string, allowFunnel bool) error { func (e *serveEnv) setServe(ctx context.Context, st *ipnstate.Status, srvType string, srvPort uint16, mount string, target string, allowFunnel bool) error {
@ -305,14 +244,42 @@ func (e *serveEnv) setServe(ctx context.Context, st *ipnstate.Status, srvType st
return err return err
} }
// nil if no config
if sc == nil {
sc = new(ipn.ServeConfig)
}
// set parent serve config to always be persisted
// at the top level, but a nested config might be
// the one that gets manipulated depending on
// foreground or background.
parentSC := sc
dnsName, err := e.getSelfDNSName(ctx) dnsName, err := e.getSelfDNSName(ctx)
if err != nil { if err != nil {
return err return err
} }
// nil if no config // if foreground mode, create a WatchIPNBus session
if sc == nil { // and use the nested config for all following operations
sc = new(ipn.ServeConfig) // TODO(marwan-at-work): nested-config validations should happen here or previous to this point.
var watcher *tailscale.IPNBusWatcher
if !e.bg {
watcher, err = e.lc.WatchIPNBus(ctx, ipn.NotifyInitialState)
if err != nil {
return err
}
defer watcher.Close()
n, err := watcher.Next()
if err != nil {
return err
}
if n.SessionID == "" {
return errors.New("missing SessionID")
}
fsc := &ipn.ServeConfig{}
mak.Set(&sc.Foreground, n.SessionID, fsc)
sc = fsc
} }
// update serve config based on the type // update serve config based on the type
@ -340,7 +307,7 @@ func (e *serveEnv) setServe(ctx context.Context, st *ipnstate.Status, srvType st
e.applyFunnel(sc, dnsName, srvPort, allowFunnel) e.applyFunnel(sc, dnsName, srvPort, allowFunnel)
// persist the serve config changes // persist the serve config changes
if err := e.lc.SetServeConfig(ctx, sc); err != nil { if err := e.lc.SetServeConfig(ctx, parentSC); err != nil {
return err return err
} }
@ -352,6 +319,18 @@ func (e *serveEnv) setServe(ctx context.Context, st *ipnstate.Status, srvType st
fmt.Fprintln(os.Stderr, m) fmt.Fprintln(os.Stderr, m)
if !e.bg {
for {
_, err = watcher.Next()
if err != nil {
if errors.Is(err, context.Canceled) {
return nil
}
return err
}
}
}
return nil return nil
} }
@ -423,8 +402,12 @@ func (e *serveEnv) messageForPort(ctx context.Context, sc *ipn.ServeConfig, st *
output.WriteString(fmt.Sprintf("|--> tcp://%s\n", h.TCPForward)) output.WriteString(fmt.Sprintf("|--> tcp://%s\n", h.TCPForward))
} }
output.WriteString("\nServe started and running in the background.\n") if e.bg {
output.WriteString(fmt.Sprintf("To disable the proxy, run: tailscale %s off", infoMap[e.subcmd].Name)) output.WriteString("\nServe started and running in the background.\n")
output.WriteString(fmt.Sprintf("To disable the proxy, run: tailscale %s off", infoMap[e.subcmd].Name))
} else {
// TODO(marwan-at-work): give the user more context on their foreground process.
}
return output.String(), nil return output.String(), nil
} }

Loading…
Cancel
Save