diff --git a/ipn/ipn_clone.go b/ipn/ipn_clone.go index 90718fb8d..5f30caa92 100644 --- a/ipn/ipn_clone.go +++ b/ipn/ipn_clone.go @@ -76,6 +76,12 @@ func (src *ServeConfig) Clone() *ServeConfig { } } dst.AllowFunnel = maps.Clone(src.AllowFunnel) + if dst.Foreground != nil { + dst.Foreground = map[string]*ServeConfig{} + for k, v := range src.Foreground { + dst.Foreground[k] = v.Clone() + } + } return dst } @@ -84,6 +90,7 @@ var _ServeConfigCloneNeedsRegeneration = ServeConfig(struct { TCP map[uint16]*TCPPortHandler Web map[HostPort]*WebServerConfig AllowFunnel map[HostPort]bool + Foreground map[string]*ServeConfig }{}) // Clone makes a deep copy of TCPPortHandler. diff --git a/ipn/ipn_view.go b/ipn/ipn_view.go index 0e22544dd..cd89fa151 100644 --- a/ipn/ipn_view.go +++ b/ipn/ipn_view.go @@ -177,11 +177,18 @@ func (v ServeConfigView) AllowFunnel() views.Map[HostPort, bool] { return views.MapOf(v.ж.AllowFunnel) } +func (v ServeConfigView) Foreground() views.MapFn[string, *ServeConfig, ServeConfigView] { + return views.MapFnOf(v.ж.Foreground, func(t *ServeConfig) ServeConfigView { + return t.View() + }) +} + // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _ServeConfigViewNeedsRegeneration = ServeConfig(struct { TCP map[uint16]*TCPPortHandler Web map[HostPort]*WebServerConfig AllowFunnel map[HostPort]bool + Foreground map[string]*ServeConfig }{}) // View returns a readonly view of TCPPortHandler. diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 94cfe1e42..56a73ef29 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -2014,6 +2014,8 @@ func (b *LocalBackend) WatchNotifications(ctx context.Context, mask ipn.NotifyWa go b.pollRequestEngineStatus(ctx) } + defer b.DeleteForegroundSession(sessionID) // TODO(marwan-at-work): check err + for { select { case <-ctx.Done(): diff --git a/ipn/ipnlocal/serve.go b/ipn/ipnlocal/serve.go index 8778548c1..4fd04e03d 100644 --- a/ipn/ipnlocal/serve.go +++ b/ipn/ipnlocal/serve.go @@ -218,7 +218,10 @@ func (b *LocalBackend) updateServeTCPPortNetMapAddrListenersLocked(ports []uint1 func (b *LocalBackend) SetServeConfig(config *ipn.ServeConfig) error { b.mu.Lock() defer b.mu.Unlock() + return b.setServeConfigLocked(config) +} +func (b *LocalBackend) setServeConfigLocked(config *ipn.ServeConfig) error { prefs := b.pm.CurrentPrefs() if config.IsFunnelOn() && prefs.ShieldsUp() { return errors.New("Unable to turn on Funnel while shields-up is enabled") @@ -258,6 +261,20 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView { return b.serveConfig } +// DeleteForegroundSession deletes a ServeConfig's foreground session +// in the LocalBackend if it exists. It also ensures check, delete, and +// set operations happen within the same mutex lock to avoid any races. +func (b *LocalBackend) DeleteForegroundSession(sessionID string) error { + b.mu.Lock() + defer b.mu.Unlock() + if !b.serveConfig.Valid() || !b.serveConfig.Foreground().Has(sessionID) { + return nil + } + sc := b.serveConfig.AsStruct() + delete(sc.Foreground, sessionID) + return b.setServeConfigLocked(sc) +} + // StreamServe opens a stream to write any incoming connections made // to the given HostPort out to the listening io.Writer. // diff --git a/ipn/serve.go b/ipn/serve.go index 11df99726..e6a79b5b6 100644 --- a/ipn/serve.go +++ b/ipn/serve.go @@ -37,6 +37,14 @@ type ServeConfig struct { // AllowFunnel is the set of SNI:port values for which funnel // traffic is allowed, from trusted ingress peers. AllowFunnel map[HostPort]bool `json:",omitempty"` + + // Foreground is a map of an IPN Bus session id to a + // foreground serve config. Note that only TCP and Web + // are used inside the Foreground map. + // + // TODO(marwan-at-work): this is not currently + // used. Remove the TODO in the follow up PR. + Foreground map[string]*ServeConfig `json:",omitempty"` } // HostPort is an SNI name and port number, joined by a colon.