summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarwan Sulaiman <marwan@tailscale.com>2023-08-24 00:00:56 +0100
committerMarwan Sulaiman <marwan@tailscale.com>2023-08-24 15:38:54 +0100
commitdf49cb24d549393e75e95065c0fc865e2108eb7f (patch)
treed4ab6b4c2fe9dd07d47fbbac9be3fb8a51bdcae6
parent9c07f4f512f71799a796cb3cdb5532657df8444f (diff)
downloadtailscale-marwan/scmem.tar.xz
tailscale-marwan/scmem.zip
ipn, ipn/ipnlocal: add an in memory serve configmarwan/scmem
This PR adds a parallel in-memory ServeConfig so that foreground funnels are guaranteed to go away in case of unexpected shutdown Updates #8489 Signed-off-by: Marwan Sulaiman <marwan@tailscale.com>
-rw-r--r--client/tailscale/localclient.go11
-rw-r--r--cmd/tailscale/cli/funnel.go3
-rw-r--r--cmd/tailscale/cli/serve.go12
-rw-r--r--ipn/ipn_clone.go1
-rw-r--r--ipn/ipn_view.go3
-rw-r--r--ipn/ipnlocal/local.go76
-rw-r--r--ipn/ipnlocal/serve.go285
-rw-r--r--ipn/localapi/localapi.go2
-rw-r--r--ipn/serve.go5
9 files changed, 236 insertions, 162 deletions
diff --git a/client/tailscale/localclient.go b/client/tailscale/localclient.go
index c57b58895..692d1c92a 100644
--- a/client/tailscale/localclient.go
+++ b/client/tailscale/localclient.go
@@ -1091,6 +1091,17 @@ func (lc *LocalClient) GetServeConfig(ctx context.Context) (*ipn.ServeConfig, er
return getServeConfigFromJSON(body)
}
+// GetMemoryServeConfig return the current serve config.
+//
+// If the serve config is empty, it returns (nil, nil).
+func (lc *LocalClient) GetMemoryServeConfig(ctx context.Context) (*ipn.ServeConfig, error) {
+ body, err := lc.send(ctx, "GET", "/localapi/v0/serve-config?memory=true", 200, nil)
+ if err != nil {
+ return nil, fmt.Errorf("getting serve config: %w", err)
+ }
+ return getServeConfigFromJSON(body)
+}
+
func getServeConfigFromJSON(body []byte) (sc *ipn.ServeConfig, err error) {
if err := json.Unmarshal(body, &sc); err != nil {
return nil, err
diff --git a/cmd/tailscale/cli/funnel.go b/cmd/tailscale/cli/funnel.go
index ff66adc4d..8ce146669 100644
--- a/cmd/tailscale/cli/funnel.go
+++ b/cmd/tailscale/cli/funnel.go
@@ -44,7 +44,7 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
ShortHelp: "Turn on/off Funnel service",
ShortUsage: strings.Join([]string{
"funnel <serve-port> {on|off}",
- "funnel status [--json]",
+ "funnel status [--json] [--memory]",
}, "\n "),
LongHelp: strings.Join([]string{
"Funnel allows you to publish a 'tailscale serve'",
@@ -62,6 +62,7 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
ShortHelp: "show current serve/funnel status",
FlagSet: e.newFlags("funnel-status", func(fs *flag.FlagSet) {
fs.BoolVar(&e.json, "json", false, "output JSON")
+ fs.BoolVar(&e.memory, "memory", false, "in memory config")
}),
UsageFunc: usageFunc,
},
diff --git a/cmd/tailscale/cli/serve.go b/cmd/tailscale/cli/serve.go
index 7b4d38691..8c466471a 100644
--- a/cmd/tailscale/cli/serve.go
+++ b/cmd/tailscale/cli/serve.go
@@ -131,6 +131,7 @@ func (e *serveEnv) newFlags(name string, setup func(fs *flag.FlagSet)) *flag.Fla
type localServeClient interface {
StatusWithoutPeers(context.Context) (*ipnstate.Status, error)
GetServeConfig(context.Context) (*ipn.ServeConfig, error)
+ GetMemoryServeConfig(context.Context) (*ipn.ServeConfig, error)
SetServeConfig(context.Context, *ipn.ServeConfig) error
QueryFeature(ctx context.Context, feature string) (*tailcfg.QueryFeatureResponse, error)
WatchIPNBus(ctx context.Context, mask ipn.NotifyWatchOpt) (*tailscale.IPNBusWatcher, error)
@@ -146,7 +147,8 @@ type localServeClient interface {
// It also contains the flags, as registered with newServeCommand.
type serveEnv struct {
// flags
- json bool // output JSON (status only for now)
+ json bool // output JSON (status only for now)
+ memory bool // output memory (status only for now)
lc localServeClient // localClient interface, specific to serve
@@ -626,7 +628,13 @@ func (e *serveEnv) handleTCPServeRemove(ctx context.Context, src uint16) error {
// - tailscale status
// - tailscale status --json
func (e *serveEnv) runServeStatus(ctx context.Context, args []string) error {
- sc, err := e.lc.GetServeConfig(ctx)
+ var sc *ipn.ServeConfig
+ var err error
+ if e.memory {
+ sc, err = e.lc.GetMemoryServeConfig(ctx)
+ } else {
+ sc, err = e.lc.GetServeConfig(ctx)
+ }
if err != nil {
return err
}
diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go
index 5377705bb..a09e2e511 100644
--- a/ipn/ipn_clone.go
+++ b/ipn/ipn_clone.go
@@ -80,6 +80,7 @@ func (src *ServeConfig) Clone() *ServeConfig {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct {
+ InMemory bool
TCP map[uint16]*TCPPortHandler
Web map[HostPort]*WebServerConfig
AllowFunnel map[HostPort]bool
diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go
index 8a04c2c32..9cb66edcf 100644
--- a/ipn/ipn_view.go
+++ b/ipn/ipn_view.go
@@ -159,6 +159,8 @@ func (v *ServeConfigView) UnmarshalJSON(b []byte) error {
return nil
}
+func (v ServeConfigView) InMemory() bool { return v.ж.InMemory }
+
func (v ServeConfigView) TCP() views.MapFn[uint16, *TCPPortHandler, TCPPortHandlerView] {
return views.MapFnOf(v.ж.TCP, func(t *TCPPortHandler) TCPPortHandlerView {
return t.View()
@@ -177,6 +179,7 @@ func (v ServeConfigView) AllowFunnel() views.Map[HostPort, bool] {
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
var _ServeConfigViewNeedsRegeneration = ServeConfig(struct {
+ InMemory bool
TCP map[uint16]*TCPPortHandler
Web map[HostPort]*WebServerConfig
AllowFunnel map[HostPort]bool
diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go
index caac3977c..cd57305f5 100644
--- a/ipn/ipnlocal/local.go
+++ b/ipn/ipnlocal/local.go
@@ -242,6 +242,7 @@ type LocalBackend struct {
// ServeConfig fields. (also guarded by mu)
lastServeConfJSON mem.RO // last JSON that was parsed into serveConfig
serveConfig ipn.ServeConfigView // or !Valid if none
+ memServeConfig ipn.ServeConfigView // or !Valid if none
serveListeners map[netip.AddrPort]*serveListener // addrPort => serveListener
serveProxyHandlers sync.Map // string (HTTPHandler.Proxy) => *httputil.ReverseProxy
@@ -2329,6 +2330,7 @@ func (b *LocalBackend) setAtomicValuesFromPrefsLocked(p ipn.PrefsView) {
b.setTCPPortsIntercepted(nil)
b.lastServeConfJSON = mem.B(nil)
b.serveConfig = ipn.ServeConfigView{}
+ b.memServeConfig = ipn.ServeConfigView{}
} else {
filtered := tsaddr.FilterPrefixesCopy(p.AdvertiseRoutes(), tsaddr.IsViaPrefix)
b.containsViaIPFuncAtomic.Store(tsaddr.NewContainsIPFunc(filtered))
@@ -2686,7 +2688,7 @@ func (b *LocalBackend) checkExitNodePrefsLocked(p *ipn.Prefs) error {
}
func (b *LocalBackend) checkFunnelEnabledLocked(p *ipn.Prefs) error {
- if p.ShieldsUp && b.serveConfig.IsFunnelOn() {
+ if p.ShieldsUp && (b.serveConfig.IsFunnelOn() || b.memServeConfig.IsFunnelOn()) {
return errors.New("Cannot enable shields-up when Funnel is enabled.")
}
return nil
@@ -2765,7 +2767,8 @@ func (b *LocalBackend) SetPrefs(newp *ipn.Prefs) {
// doesn't affect security or correctness. And we also don't expect people to
// modify their ServeConfig in raw mode.
func (b *LocalBackend) wantIngressLocked() bool {
- return b.serveConfig.Valid() && b.serveConfig.AllowFunnel().Len() > 0
+ return b.serveConfig.Valid() && (b.serveConfig.AllowFunnel().Len() > 0) ||
+ b.memServeConfig.Valid() && (b.memServeConfig.AllowFunnel().Len() > 0)
}
// setPrefsLockedOnEntry requires b.mu be held to call it, but it
@@ -4073,6 +4076,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
// Don't try to load the serve config.
b.lastServeConfJSON = mem.B(nil)
b.serveConfig = ipn.ServeConfigView{}
+ // b.memServeConfig = ipn.ServeConfigView{} should we do this?
return
}
confKey := ipn.ServeConfigKey(b.pm.CurrentProfile().ID)
@@ -4082,6 +4086,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
if err != nil {
b.lastServeConfJSON = mem.B(nil)
b.serveConfig = ipn.ServeConfigView{}
+ // b.memServeConfig = ipn.ServeConfigView{} should we do this?
return
}
if b.lastServeConfJSON.Equal(mem.B(confj)) {
@@ -4092,6 +4097,7 @@ func (b *LocalBackend) reloadServeConfigLocked(prefs ipn.PrefsView) {
if err := json.Unmarshal(confj, &conf); err != nil {
b.logf("invalid ServeConfig %q in StateStore: %v", confKey, err)
b.serveConfig = ipn.ServeConfigView{}
+ // b.memServeConfig = ipn.ServeConfigView{} should we do this?
return
}
b.serveConfig = conf.View()
@@ -4109,9 +4115,13 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
}
b.reloadServeConfigLocked(prefs)
- if b.serveConfig.Valid() {
+
+ setServeProxy := func(sc ipn.ServeConfigView) {
+ if !sc.Valid() {
+ return
+ }
servePorts := make([]uint16, 0, 3)
- b.serveConfig.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
+ sc.TCP().Range(func(port uint16, _ ipn.TCPPortHandlerView) bool {
if port > 0 {
servePorts = append(servePorts, uint16(port))
}
@@ -4126,6 +4136,9 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
b.updateServeTCPPortNetMapAddrListenersLocked(servePorts)
}
}
+ setServeProxy(b.serveConfig)
+ setServeProxy(b.memServeConfig)
+
// Kick off a Hostinfo update to control if WireIngress changed.
if wire := b.wantIngressLocked(); b.hostinfo != nil && b.hostinfo.WireIngress != wire {
b.logf("Hostinfo.WireIngress changed to %v", wire)
@@ -4140,35 +4153,39 @@ func (b *LocalBackend) setTCPPortsInterceptedFromNetmapAndPrefsLocked(prefs ipn.
// backend specified in serveConfig. It expects serveConfig to be valid and
// up-to-date, so should be called after reloadServeConfigLocked.
func (b *LocalBackend) setServeProxyHandlersLocked() {
- if !b.serveConfig.Valid() {
- return
- }
var backends map[string]bool
- b.serveConfig.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
- conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
- backend := h.Proxy()
- if backend == "" {
- // Only create proxy handlers for servers with a proxy backend.
- return true
- }
- mak.Set(&backends, backend, true)
- if _, ok := b.serveProxyHandlers.Load(backend); ok {
- return true
- }
+ f := func(sc ipn.ServeConfigView) {
+ if !sc.Valid() {
+ return
+ }
+ sc.Web().Range(func(_ ipn.HostPort, conf ipn.WebServerConfigView) (cont bool) {
+ conf.Handlers().Range(func(_ string, h ipn.HTTPHandlerView) (cont bool) {
+ backend := h.Proxy()
+ if backend == "" {
+ // Only create proxy handlers for servers with a proxy backend.
+ return true
+ }
+ mak.Set(&backends, backend, true)
+ if _, ok := b.serveProxyHandlers.Load(backend); ok {
+ return true
+ }
- b.logf("serve: creating a new proxy handler for %s", backend)
- p, err := b.proxyHandlerForBackend(backend)
- if err != nil {
- // The backend endpoint (h.Proxy) should have been validated by expandProxyTarget
- // in the CLI, so just log the error here.
- b.logf("[unexpected] could not create proxy for %v: %s", backend, err)
+ b.logf("serve: creating a new proxy handler for %s", backend)
+ p, err := b.proxyHandlerForBackend(backend)
+ if err != nil {
+ // The backend endpoint (h.Proxy) should have been validated by expandProxyTarget
+ // in the CLI, so just log the error here.
+ b.logf("[unexpected] could not create proxy for %v: %s", backend, err)
+ return true
+ }
+ b.serveProxyHandlers.Store(backend, p)
return true
- }
- b.serveProxyHandlers.Store(backend, p)
+ })
return true
})
- return true
- })
+ }
+ f(b.serveConfig)
+ f(b.memServeConfig)
// Clean up handlers for proxy backends that are no longer present
// in configuration.
@@ -4937,7 +4954,8 @@ func (b *LocalBackend) resetForProfileChangeLockedOnEntry() error {
}
b.lastServeConfJSON = mem.B(nil)
b.serveConfig = ipn.ServeConfigView{}
- b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
+ b.memServeConfig = ipn.ServeConfigView{} // is this needed?
+ b.enterStateLockedOnEntry(ipn.NoState) // Reset state.
health.SetLocalLogConfigHealth(nil)
return b.Start(ipn.Options{})
}
diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go
index de9de77ce..8a0972eaf 100644
--- a/ipn/ipnlocal/serve.go
+++ b/ipn/ipnlocal/serve.go
@@ -231,19 +231,24 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
if !nm.SelfNode.Valid() {
return errors.New("netMap SelfNode is nil")
}
- profileID := b.pm.CurrentProfile().ID
- confKey := ipn.ServeConfigKey(profileID)
- var bs []byte
- if config != nil {
- j, err := json.Marshal(config)
- if err != nil {
- return fmt.Errorf("encoding serve config: %w", err)
+ if !config.InMemory {
+ profileID := b.pm.CurrentProfile().ID
+ confKey := ipn.ServeConfigKey(profileID)
+
+ var bs []byte
+ if config != nil {
+ j, err := json.Marshal(config)
+ if err != nil {
+ return fmt.Errorf("encoding serve config: %w", err)
+ }
+ bs = j
}
- bs = j
- }
- if err := b.store.WriteState(confKey, bs); err != nil {
- return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
+ if err := b.store.WriteState(confKey, bs); err != nil {
+ return fmt.Errorf("writing ServeConfig to StateStore: %w", err)
+ }
+ } else {
+ b.memServeConfig = config.View()
}
b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs())
@@ -252,9 +257,12 @@ func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error {
// ServeConfig provides a view of the current serve mappings.
// If serving is not configured, the returned view is not Valid.
-func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
+func (b *LocalBackend) ServeConfig(inMemory bool) ipn.ServeConfigView {
b.mu.Lock()
defer b.mu.Unlock()
+ if inMemory {
+ return b.memServeConfig
+ }
return b.serveConfig
}
@@ -278,9 +286,9 @@ func (b *LocalBackend) StreamServe(ctx context.Context, w io.Writer, req ipn.Ser
}
// Turn on Funnel for the given HostPort.
- sc := b.ServeConfig().AsStruct()
+ sc := b.ServeConfig(true).AsStruct()
if sc == nil {
- sc = &ipn.ServeConfig{}
+ sc = &ipn.ServeConfig{InMemory: true}
}
setHandler(sc, req)
if err := b.SetServeConfig(sc); err != nil {
@@ -288,7 +296,7 @@ func (b *LocalBackend) StreamServe(ctx context.Context, w io.Writer, req ipn.Ser
}
// Defer turning off Funnel once stream ends.
defer func() {
- sc := b.ServeConfig().AsStruct()
+ sc := b.ServeConfig(true).AsStruct()
deleteHandler(sc, req, port)
err = errors.Join(err, b.SetServeConfig(sc))
}()
@@ -419,58 +427,63 @@ func (b *LocalBackend) maybeLogServeConnection(destPort uint16, srcAddr netip.Ad
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer tailcfg.NodeView, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
b.mu.Lock()
sc := b.serveConfig
+ msc := b.memServeConfig
b.mu.Unlock()
- if !sc.Valid() {
- b.logf("localbackend: got ingress conn w/o serveConfig; rejecting")
- sendRST()
- return
- }
+ f := func(sc ipn.ServeConfigView) {
+ if !sc.Valid() {
+ b.logf("localbackend: got ingress conn w/o serveConfig; rejecting")
+ sendRST()
+ return
+ }
- if !sc.AllowFunnel().Get(target) {
- b.logf("localbackend: got ingress conn for unconfigured %q; rejecting", target)
- sendRST()
- return
- }
+ if !sc.AllowFunnel().Get(target) {
+ b.logf("localbackend: got ingress conn for unconfigured %q; rejecting", target)
+ sendRST()
+ return
+ }
- _, port, err := net.SplitHostPort(string(target))
- if err != nil {
- b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
- sendRST()
- return
- }
- port16, err := strconv.ParseUint(port, 10, 16)
- if err != nil {
- b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
- sendRST()
- return
- }
- dport := uint16(port16)
- if b.getTCPHandlerForFunnelFlow != nil {
- handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
- if handler != nil {
- c, ok := getConnOrReset()
- if !ok {
- b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
+ _, port, err := net.SplitHostPort(string(target))
+ if err != nil {
+ b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
+ sendRST()
+ return
+ }
+ port16, err := strconv.ParseUint(port, 10, 16)
+ if err != nil {
+ b.logf("localbackend: got ingress conn for bad target %q; rejecting", target)
+ sendRST()
+ return
+ }
+ dport := uint16(port16)
+ if b.getTCPHandlerForFunnelFlow != nil {
+ handler := b.getTCPHandlerForFunnelFlow(srcAddr, dport)
+ if handler != nil {
+ c, ok := getConnOrReset()
+ if !ok {
+ b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
+ return
+ }
+ handler(c)
return
}
- handler(c)
+ }
+ // TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
+ // extend serveHTTPContext or similar.
+ handler := b.tcpHandlerForServe(dport, srcAddr)
+ if handler == nil {
+ sendRST()
return
}
+ c, ok := getConnOrReset()
+ if !ok {
+ b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
+ return
+ }
+ handler(c)
}
- // TODO(bradfitz): pass ingressPeer etc in context to tcpHandlerForServe,
- // extend serveHTTPContext or similar.
- handler := b.tcpHandlerForServe(dport, srcAddr)
- if handler == nil {
- sendRST()
- return
- }
- c, ok := getConnOrReset()
- if !ok {
- b.logf("localbackend: getConn didn't complete from %v to port %v", srcAddr, dport)
- return
- }
- handler(c)
+ f(sc)
+ f(msc)
}
// tcpHandlerForServe returns a handler for a TCP connection to be served via
@@ -478,90 +491,100 @@ func (b *LocalBackend) HandleIngressTCPConn(ingressPeer tailcfg.NodeView, target
func (b *LocalBackend) tcpHandlerForServe(dport uint16, srcAddr netip.AddrPort) (handler func(net.Conn) error) {
b.mu.Lock()
sc := b.serveConfig
+ msc := b.memServeConfig
b.mu.Unlock()
- if !sc.Valid() {
- b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
- return nil
- }
-
- tcph, ok := sc.TCP().GetOk(dport)
- if !ok {
- b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
- return nil
- }
+ f := func(sc ipn.ServeConfigView) (handler func(net.Conn) error) {
+ if !sc.Valid() {
+ // TODO: should log only if both configs are invalid
+ b.logf("[unexpected] localbackend: got TCP conn w/o serveConfig; from %v to port %v", srcAddr, dport)
+ return nil
+ }
- if tcph.HTTPS() || tcph.HTTP() {
- hs := &http.Server{
- Handler: http.HandlerFunc(b.serveWebHandler),
- BaseContext: func(_ net.Listener) context.Context {
- return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
- SrcAddr: srcAddr,
- DestPort: dport,
- })
- },
+ tcph, ok := sc.TCP().GetOk(dport)
+ if !ok {
+ // TODO: should log only if both configs are not ok
+ b.logf("[unexpected] localbackend: got TCP conn without TCP config for port %v; from %v", dport, srcAddr)
+ return nil
}
- if tcph.HTTPS() {
- hs.TLSConfig = &tls.Config{
- GetCertificate: b.getTLSServeCertForPort(dport),
+
+ if tcph.HTTPS() || tcph.HTTP() {
+ hs := &http.Server{
+ Handler: http.HandlerFunc(b.serveWebHandler),
+ BaseContext: func(_ net.Listener) context.Context {
+ return context.WithValue(context.Background(), serveHTTPContextKey{}, &serveHTTPContext{
+ SrcAddr: srcAddr,
+ DestPort: dport,
+ })
+ },
+ }
+ if tcph.HTTPS() {
+ hs.TLSConfig = &tls.Config{
+ GetCertificate: b.getTLSServeCertForPort(dport),
+ }
+ return func(c net.Conn) error {
+ return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
+ }
}
+
return func(c net.Conn) error {
- return hs.ServeTLS(netutil.NewOneConnListener(c, nil), "", "")
+ return hs.Serve(netutil.NewOneConnListener(c, nil))
}
}
- return func(c net.Conn) error {
- return hs.Serve(netutil.NewOneConnListener(c, nil))
- }
- }
+ if backDst := tcph.TCPForward(); backDst != "" {
+ return func(conn net.Conn) error {
+ defer conn.Close()
+ b.maybeLogServeConnection(dport, srcAddr)
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
+ cancel()
+ if err != nil {
+ b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
+ return nil
+ }
+ defer backConn.Close()
+ if sni := tcph.TerminateTLS(); sni != "" {
+ conn = tls.Server(conn, &tls.Config{
+ GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
+ ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
+ defer cancel()
+ pair, err := b.GetCertPEM(ctx, sni, false)
+ if err != nil {
+ return nil, err
+ }
+ cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
+ if err != nil {
+ return nil, err
+ }
+ return &cert, nil
+ },
+ })
+ }
- if backDst := tcph.TCPForward(); backDst != "" {
- return func(conn net.Conn) error {
- defer conn.Close()
- b.maybeLogServeConnection(dport, srcAddr)
- ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
- backConn, err := b.dialer.SystemDial(ctx, "tcp", backDst)
- cancel()
- if err != nil {
- b.logf("localbackend: failed to TCP proxy port %v (from %v) to %s: %v", dport, srcAddr, backDst, err)
- return nil
+ // TODO(bradfitz): do the RegisterIPPortIdentity and
+ // UnregisterIPPortIdentity stuff that netstack does
+ errc := make(chan error, 1)
+ go func() {
+ _, err := io.Copy(backConn, conn)
+ errc <- err
+ }()
+ go func() {
+ _, err := io.Copy(conn, backConn)
+ errc <- err
+ }()
+ return <-errc
}
- defer backConn.Close()
- if sni := tcph.TerminateTLS(); sni != "" {
- conn = tls.Server(conn, &tls.Config{
- GetCertificate: func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
- ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
- defer cancel()
- pair, err := b.GetCertPEM(ctx, sni, false)
- if err != nil {
- return nil, err
- }
- cert, err := tls.X509KeyPair(pair.CertPEM, pair.KeyPEM)
- if err != nil {
- return nil, err
- }
- return &cert, nil
- },
- })
- }
-
- // TODO(bradfitz): do the RegisterIPPortIdentity and
- // UnregisterIPPortIdentity stuff that netstack does
- errc := make(chan error, 1)
- go func() {
- _, err := io.Copy(backConn, conn)
- errc <- err
- }()
- go func() {
- _, err := io.Copy(conn, backConn)
- errc <- err
- }()
- return <-errc
}
+
+ b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
+ return nil
}
- b.logf("closing TCP conn to port %v (from %v) with actionless TCPPortHandler", dport, srcAddr)
- return nil
+ if h := f(sc); h != nil {
+ return h
+ }
+ return f(msc)
}
func getServeHTTPContext(r *http.Request) (c *serveHTTPContext, ok bool) {
@@ -825,7 +848,11 @@ func (b *LocalBackend) webServerConfig(hostname string, port uint16) (c ipn.WebS
if !b.serveConfig.Valid() {
return c, false
}
- return b.serveConfig.Web().GetOk(key)
+ wc, ok := b.serveConfig.Web().GetOk(key)
+ if ok {
+ return wc, ok
+ }
+ return b.memServeConfig.Web().GetOk(key)
}
func (b *LocalBackend) getTLSServeCertForPort(port uint16) func(hi *tls.ClientHelloInfo) (*tls.Certificate, error) {
diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go
index 44ec4dc43..2dcb9df88 100644
--- a/ipn/localapi/localapi.go
+++ b/ipn/localapi/localapi.go
@@ -835,7 +835,7 @@ func (h *Handler) serveServeConfig(w http.ResponseWriter, r *http.Request) {
return
}
w.Header().Set("Content-Type", "application/json")
- config := h.b.ServeConfig()
+ config := h.b.ServeConfig(r.FormValue("memory") == "true")
json.NewEncoder(w).Encode(config)
case "POST":
if !h.PermitWrite {
diff --git a/ipn/serve.go b/ipn/serve.go
index 3b6034fa9..98f232b49 100644
--- a/ipn/serve.go
+++ b/ipn/serve.go
@@ -26,6 +26,11 @@ func ServeConfigKey(profileID ProfileID) StateKey {
// ServeConfig is the JSON type stored in the StateStore for
// StateKey "_serve/$PROFILE_ID" as returned by ServeConfigKey.
type ServeConfig struct {
+ // InMemory indicates whether this config
+ // is persisted in the local store or is
+ // an in memory config
+ InMemory bool
+
// TCP are the list of TCP port numbers that tailscaled should handle for
// the Tailscale IP addresses. (not subnet routers, etc)
TCP map[uint16]*TCPPortHandler `json:",omitempty"`