diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index b32d39a46..05d92db19 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -927,6 +927,9 @@ func (b *LocalBackend) SetIPServiceMappingsForTesting(m netmap.IPServiceMappings b.mu.Lock() defer b.mu.Unlock() b.ipVIPServiceMap = m + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(m) + } } // setConfigLocked uses the provided config to update the backend's prefs @@ -4509,6 +4512,12 @@ func (b *LocalBackend) onEditPrefsLocked(_ ipnauth.Actor, mp *ipn.MaskedPrefs, o } } + if mp.AdvertiseServicesSet { + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateActiveVIPServices(newPrefs.AdvertiseServices().AsSlice()) + } + } + // This is recorded here in the EditPrefs path, not the setPrefs path on purpose. // recordForEdit records metrics related to edits and changes, not the final state. // If, in the future, we want to record gauge-metrics related to the state of prefs, @@ -6238,7 +6247,12 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) { b.setTCPPortsInterceptedFromNetmapAndPrefsLocked(b.pm.CurrentPrefs()) if buildfeatures.HasServe { - b.ipVIPServiceMap = nm.GetIPVIPServiceMap() + ipsvMap := nm.GetIPVIPServiceMap() + b.ipVIPServiceMap = ipsvMap + if ns, ok := b.sys.Netstack.GetOK(); ok { + ns.UpdateIPServiceMappings(ipsvMap) + } + } if !oldSelf.Equal(nm.SelfNodeOrZero()) { diff --git a/tsd/tsd.go b/tsd/tsd.go index 8dc0c1427..d76dfdf04 100644 --- a/tsd/tsd.go +++ b/tsd/tsd.go @@ -111,6 +111,8 @@ type LocalBackend = any type NetstackImpl interface { Start(LocalBackend) error UpdateNetstackIPs(*netmap.NetworkMap) + UpdateIPServiceMappings(netmap.IPServiceMappings) + UpdateActiveVIPServices([]string) } // Set is a convenience method to set a subsystem value. diff --git a/wgengine/netstack/netstack.go b/wgengine/netstack/netstack.go index 4c432747a..1e4630751 100644 --- a/wgengine/netstack/netstack.go +++ b/wgengine/netstack/netstack.go @@ -209,6 +209,10 @@ type Impl struct { atomicIsVIPServiceIPFunc syncs.AtomicValue[func(netip.Addr) bool] + atomicIpVIPServiceMap syncs.AtomicValue[netmap.IPServiceMappings] + // make this a set of strings for faster lookup + atomicActiveVIPServices syncs.AtomicValue[set.Set[string]] + // forwardDialFunc, if non-nil, is the net.Dialer.DialContext-style // function that is used to make outgoing connections when forwarding a // TCP connection to another host (e.g. in subnet router mode). @@ -761,6 +765,22 @@ func (ns *Impl) UpdateNetstackIPs(nm *netmap.NetworkMap) { } } +// UpdateIPServiceMappings updates the IPServiceMappings when there is a change +// in this value in localbackend. This is usually triggered from a netmap update. +func (ns *Impl) UpdateIPServiceMappings(mappings netmap.IPServiceMappings) { + ns.mu.Lock() + defer ns.mu.Unlock() + ns.atomicIpVIPServiceMap.Store(mappings) +} + +// UpdateActiveVIPServices updates the set of active VIP services names. +func (ns *Impl) UpdateActiveVIPServices(activeServices []string) { + ns.mu.Lock() + defer ns.mu.Unlock() + activeServicesSet := set.SetOf(activeServices) + ns.atomicActiveVIPServices.Store(activeServicesSet) +} + func (ns *Impl) isLoopbackPort(port uint16) bool { if ns.loopbackPort != nil && int(port) == *ns.loopbackPort { return true @@ -780,9 +800,7 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro. // Determine if we care about this local packet. dst := p.Dst.Addr() var IPServiceMappings netmap.IPServiceMappings - if ns.lb != nil { - IPServiceMappings = ns.lb.IPServiceMappings() - } + IPServiceMappings = ns.atomicIpVIPServiceMap.Load() serviceName, isVIPServiceIP := IPServiceMappings[dst] switch { case dst == serviceIP || dst == serviceIPv6: @@ -804,19 +822,11 @@ func (ns *Impl) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper, gro *gro. if p.IPProto != ipproto.TCP { return filter.Accept, gro } - // returns all configured VIP services, since the IPServiceMappings contains - // inactive service IPs when node hosts the service, we need to check the - // service is active or not before dropping the packet. - VIPServices := ns.lb.VIPServices() - serviceActive := false - for _, svc := range VIPServices { - // Even though control only send service IP down when there is a config - // for the service, we want to still check that the config still exists - // before passing the packet to netstack. - if svc.Name == serviceName { - serviceActive = svc.Active - } - } + // returns all active VIP services in a set, since the IPServiceMappings + // contains inactive service IPs when node hosts the service, we need to + // check the service is active or not before dropping the packet. + activeServices := ns.atomicActiveVIPServices.Load() + _, serviceActive := activeServices[serviceName.String()] if !serviceActive { return filter.Accept, gro } diff --git a/wgengine/netstack/netstack_test.go b/wgengine/netstack/netstack_test.go index 88d0dfe3a..9302053d7 100644 --- a/wgengine/netstack/netstack_test.go +++ b/wgengine/netstack/netstack_test.go @@ -126,6 +126,7 @@ func makeNetstack(tb testing.TB, config func(*Impl)) *Impl { tb.Fatal(err) } tb.Cleanup(func() { ns.Close() }) + sys.Set(ns) lb, err := ipnlocal.NewLocalBackend(logf, logid.PublicID{}, sys, 0) if err != nil {