diff --git a/appc/appconnector.go b/appc/appconnector.go index cd45f7c86..6ea75ce8e 100644 --- a/appc/appconnector.go +++ b/appc/appconnector.go @@ -30,12 +30,7 @@ import ( // RouteAdvertiser is an interface that allows the AppConnector to advertise // newly discovered routes that need to be served through the AppConnector. type RouteAdvertiser interface { - // AdvertiseRoute adds one or more route advertisements skipping any that - // are already advertised. - AdvertiseRoute(...netip.Prefix) error - - // UnadvertiseRoute removes any matching route advertisements. - UnadvertiseRoute(...netip.Prefix) error + AdvertiseRouteInfo(*routeinfo.RouteInfo) // Store/ReadRouteInfo persists and retreives RouteInfo to stable storage StoreRouteInfo(*routeinfo.RouteInfo) error @@ -60,13 +55,13 @@ type AppConnector struct { // domains is a map of lower case domain names with no trailing dot, to an // ordered list of resolved IP addresses. - domains map[string][]netip.Addr + // domains map[string][]netip.Addr // controlRoutes is the list of routes that were last supplied by control. - controlRoutes []netip.Prefix + // controlRoutes []netip.Prefix // wildcards is the list of domain strings that match subdomains. - wildcards []string + // wildcards []string // the in memory copy of all the routes that's advertised routeInfo *routeinfo.RouteInfo @@ -121,12 +116,11 @@ func (e *AppConnector) RouteInfo() *routeinfo.RouteInfo { func (e *AppConnector) RecreateRouteInfoFromStore(localRoutes []netip.Prefix) { e.queue.Add(func() { ri := e.RouteInfo() - ri.Local = localRoutes err := e.routeAdvertiser.StoreRouteInfo(ri) if err != nil { e.logf("Appc recreate routeInfo: Error updating routeInfo in store: ", err) } - err = e.routeAdvertiser.AdvertiseRoute(ri.Routes(false, true, true)...) + e.routeAdvertiser.AdvertiseRouteInfo(ri) if err != nil { e.logf("Appc recreate routeInfo: Error advertise routes: ", err) } @@ -146,10 +140,7 @@ func (e *AppConnector) UpdateRouteInfo(ri *routeinfo.RouteInfo) { func (e *AppConnector) UnadvertiseRemoteRoutes() { e.queue.Add(func() { - toRemove := e.RouteInfo().Routes(false, true, true) - if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil { - e.logf("failed to unadvertise routes %v: %v", toRemove, err) - } + e.routeAdvertiser.AdvertiseRouteInfo(nil) }) } @@ -173,76 +164,45 @@ func (e *AppConnector) updateDomains(domains []string) { e.mu.Lock() defer e.mu.Unlock() - var oldDomains map[string][]netip.Addr - oldDomains, e.domains = e.domains, make(map[string][]netip.Addr, len(domains)) var oldDiscovered map[string]*routeinfo.DatedRoutes var routeInfo *routeinfo.RouteInfo shouldStoreRoutes := e.ShouldStoreRoutes - if shouldStoreRoutes { - routeInfo = e.RouteInfo() - oldDiscovered, routeInfo.Discovered = routeInfo.Discovered, make(map[string]*routeinfo.DatedRoutes, len(domains)) - } - e.wildcards = e.wildcards[:0] + + routeInfo = e.RouteInfo() + oldDiscovered, routeInfo.Discovered = routeInfo.Discovered, make(map[string]*routeinfo.DatedRoutes, len(domains)) + + routeInfo.Wildcards = routeInfo.Wildcards[:0] for _, d := range domains { d = strings.ToLower(d) if len(d) == 0 { continue } if strings.HasPrefix(d, "*.") { - e.wildcards = append(e.wildcards, d[2:]) + routeInfo.Wildcards = append(routeInfo.Wildcards, d[2:]) continue } - e.domains[d] = oldDomains[d] - delete(oldDomains, d) - if shouldStoreRoutes { - routeInfo.Discovered[d] = oldDiscovered[d] - delete(oldDiscovered, d) - } + routeInfo.Discovered[d] = oldDiscovered[d] + delete(oldDiscovered, d) } - // Ensure that still-live wildcards addresses are preserved as well. - for d, addrs := range oldDomains { - for _, wc := range e.wildcards { + for d, dr := range oldDiscovered { + for _, wc := range routeInfo.Wildcards { if dnsname.HasSuffix(d, wc) { - e.domains[d] = addrs + routeInfo.Discovered[d] = dr + delete(oldDiscovered, d) break } } } - if shouldStoreRoutes { - for d, dr := range oldDiscovered { - for _, wc := range e.wildcards { - if dnsname.HasSuffix(d, wc) { - routeInfo.Discovered[d] = dr - delete(oldDiscovered, d) - break - } - } - } - } if shouldStoreRoutes { e.UpdateRouteInfo(routeInfo) - // every domain left in oldDiscovered won't be in e.domains - // routes can be unadvertised if it's not in local, control, or new discovered - currentRoutes := routeInfo.Routes(true, true, true) - slices.SortFunc(currentRoutes, comparePrefix) - currentRoutes = slices.Compact(currentRoutes) - for domainName, domainsRoutes := range oldDiscovered { - if domainsRoutes != nil { - toRemove := []netip.Prefix{} - for _, route := range domainsRoutes.RoutesSlice() { - _, ok := slices.BinarySearchFunc(currentRoutes, route, comparePrefix) - if !ok { - toRemove = append(toRemove, route) - } - } - e.logf("unadvertising %d routes for domain: %s", len(toRemove), domainName) - e.scheduleUnadvertisement(domainName, toRemove...) - } - } + } else { + e.routeInfo = routeInfo } - e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.domains), e.wildcards) + e.scheduleAdvertiseRouteInfo(e.RouteInfo()) + + e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.RouteInfo().Discovered), routeInfo.Wildcards) } // updateRoutes merges the supplied routes into the currently configured routes. The routes supplied @@ -254,64 +214,26 @@ func (e *AppConnector) updateRoutes(routes []netip.Prefix) { defer e.mu.Unlock() // If there was no change since the last update, no work to do. - if slices.Equal(e.controlRoutes, routes) { + if slices.Equal(e.RouteInfo().Control, routes) { return } - var toRemove []netip.Prefix var routeInfo *routeinfo.RouteInfo var err error + e.routeInfo.Control = routes if e.ShouldStoreRoutes { - routeInfo, err = e.routeAdvertiser.ReadRouteInfo() + routeInfo = e.RouteInfo() if err != nil { if err != ipn.ErrStateNotExist { e.logf("Appc: Unsuccessful Read RouteInfo: ", err) } routeInfo = routeinfo.NewRouteInfo() } - oldControl := routeInfo.Control routeInfo.Control = routes - e.routeInfo = routeInfo e.routeAdvertiser.StoreRouteInfo(e.routeInfo) - oldOtherRoutes := routeInfo.Routes(true, false, true) - for _, ipp := range oldControl { - if slices.Contains(routes, ipp) { - continue - } - // unadvertise the prefix if the prefix is not recorded from other source. - if !slices.Contains(oldOtherRoutes, ipp) { - toRemove = append(toRemove, ipp) - } - } - - if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil { - e.logf("failed to unadvertise old routes: %v: %v", routes, err) - } - } - if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil { - e.logf("failed to advertise routes: %v: %v", routes, err) - return } - toRemove = toRemove[:0] - -nextRoute: - for _, r := range routes { - for _, addr := range e.domains { - for _, a := range addr { - if r.Contains(a) && netip.PrefixFrom(a, a.BitLen()) != r { - pfx := netip.PrefixFrom(a, a.BitLen()) - toRemove = append(toRemove, pfx) - continue nextRoute - } - } - } - } - - if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil { - e.logf("failed to unadvertise routes: %v: %v", toRemove, err) - } - e.controlRoutes = routes + e.routeAdvertiser.AdvertiseRouteInfo(e.routeInfo) } // Domains returns the currently configured domain list. @@ -319,7 +241,7 @@ func (e *AppConnector) Domains() views.Slice[string] { e.mu.Lock() defer e.mu.Unlock() - return views.SliceOf(xmaps.Keys(e.domains)) + return views.SliceOf(xmaps.Keys(e.RouteInfo().Discovered)) } // DomainRoutes returns a map of domains to resolved IP @@ -328,12 +250,7 @@ func (e *AppConnector) DomainRoutes() map[string][]netip.Addr { e.mu.Lock() defer e.mu.Unlock() - drCopy := make(map[string][]netip.Addr) - for k, v := range e.domains { - drCopy[k] = append(drCopy[k], v...) - } - - return drCopy + return e.routeInfo.DomainRoutes() } // ObserveDNSResponse is a callback invoked by the DNS resolver when a DNS @@ -429,6 +346,7 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) { e.mu.Lock() defer e.mu.Unlock() + routeInfo := e.RouteInfo() for domain, addrs := range addressRecords { domain, isRouted := e.findRoutedDomainLocked(domain, cnameChain) @@ -439,21 +357,18 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) { // advertise each address we have learned for the routed domain, that // was not already known. - var toAdvertise []netip.Prefix + var domainPrefixs []netip.Prefix for _, addr := range addrs { - if !e.isAddrKnownLocked(domain, addr) { - toAdvertise = append(toAdvertise, netip.PrefixFrom(addr, addr.BitLen())) - } + domainPrefixs = append(domainPrefixs, netip.PrefixFrom(addr, addr.BitLen())) } - e.logf("[v2] observed new routes for %s: %s", domain, toAdvertise) - if e.ShouldStoreRoutes && len(toAdvertise) != 0 { - routeInfo := e.RouteInfo() - routeInfo.AddRoutesInDiscoveredForDomain(domain, toAdvertise) + e.logf("[v2] observed new routes for %s: %s", domain, domainPrefixs) + routeInfo.AddRoutesInDiscoveredForDomain(domain, domainPrefixs) + if e.ShouldStoreRoutes { e.UpdateRouteInfo(routeInfo) } - e.scheduleAdvertisement(domain, toAdvertise...) } + e.scheduleAdvertiseRouteInfo(e.RouteInfo()) } // starting from the given domain that resolved to an address, find it, or any @@ -464,15 +379,15 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) { func (e *AppConnector) findRoutedDomainLocked(domain string, cnameChain map[string]string) (string, bool) { var isRouted bool for { - _, isRouted = e.domains[domain] + _, isRouted = e.RouteInfo().Discovered[domain] if isRouted { break } // match wildcard domains - for _, wc := range e.wildcards { + for _, wc := range e.RouteInfo().Wildcards { if dnsname.HasSuffix(domain, wc) { - e.domains[domain] = nil + e.routeInfo.Discovered[domain] = nil isRouted = true break } @@ -487,88 +402,53 @@ func (e *AppConnector) findRoutedDomainLocked(domain string, cnameChain map[stri return domain, isRouted } -// isAddrKnownLocked returns true if the address is known to be associated with -// the given domain. Known domain tables are updated for covered routes to speed -// up future matches. -// e.mu must be held. -func (e *AppConnector) isAddrKnownLocked(domain string, addr netip.Addr) bool { - if e.hasDomainAddrLocked(domain, addr) { - return true - } - for _, route := range e.controlRoutes { - if route.Contains(addr) { - // record the new address associated with the domain for faster matching in subsequent - // requests and for diagnostic records. - e.addDomainAddrLocked(domain, addr) - return true - } - } - return false -} +// // scheduleAdvertisement schedules an advertisement of the given address +// // associated with the given domain. +// func (e *AppConnector) scheduleAdvertisement(domain string, routes ...netip.Prefix) { +// e.queue.Add(func() { +// if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil { +// e.logf("failed to advertise routes for %s: %v: %v", domain, routes, err) +// return +// } +// e.mu.Lock() +// defer e.mu.Unlock() -// scheduleAdvertisement schedules an advertisement of the given address -// associated with the given domain. -func (e *AppConnector) scheduleAdvertisement(domain string, routes ...netip.Prefix) { +// for _, route := range routes { +// if !route.IsSingleIP() { +// continue +// } +// addr := route.Addr() +// if !e.hasDomainAddrLocked(domain, addr) { +// e.addDomainAddrLocked(domain, addr) +// e.logf("[v2] advertised route for %v: %v", domain, addr) +// } +// } +// }) +// } + +// func (e *AppConnector) scheduleUnadvertisement(domain string, routes ...netip.Prefix) { +// e.queue.Add(func() { +// if err := e.routeAdvertiser.UnadvertiseRoute(routes...); err != nil { +// e.logf("failed to unadvertise routes for %s: %v: %v", domain, routes, err) +// return +// } +// e.mu.Lock() +// defer e.mu.Unlock() + +// for _, route := range routes { +// if !route.IsSingleIP() { +// continue +// } +// addr := route.Addr() + +// // e.deleteDomainAddrLocked(domain, addr) +// e.logf("[v2] unadvertised route for %v: %v", domain, addr) +// } +// }) +// } + +func (e *AppConnector) scheduleAdvertiseRouteInfo(ri *routeinfo.RouteInfo) { e.queue.Add(func() { - if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil { - e.logf("failed to advertise routes for %s: %v: %v", domain, routes, err) - return - } - e.mu.Lock() - defer e.mu.Unlock() - - for _, route := range routes { - if !route.IsSingleIP() { - continue - } - addr := route.Addr() - if !e.hasDomainAddrLocked(domain, addr) { - e.addDomainAddrLocked(domain, addr) - e.logf("[v2] advertised route for %v: %v", domain, addr) - } - } + e.routeAdvertiser.AdvertiseRouteInfo(ri) }) } - -func (e *AppConnector) scheduleUnadvertisement(domain string, routes ...netip.Prefix) { - e.queue.Add(func() { - if err := e.routeAdvertiser.UnadvertiseRoute(routes...); err != nil { - e.logf("failed to unadvertise routes for %s: %v: %v", domain, routes, err) - return - } - e.mu.Lock() - defer e.mu.Unlock() - - for _, route := range routes { - if !route.IsSingleIP() { - continue - } - addr := route.Addr() - - //e.deleteDomainAddrLocked(domain, addr) - e.logf("[v2] unadvertised route for %v: %v", domain, addr) - } - }) -} - -// hasDomainAddrLocked returns true if the address has been observed in a -// resolution of domain. -func (e *AppConnector) hasDomainAddrLocked(domain string, addr netip.Addr) bool { - _, ok := slices.BinarySearchFunc(e.domains[domain], addr, compareAddr) - return ok -} - -// addDomainAddrLocked adds the address to the list of addresses resolved for -// domain and ensures the list remains sorted. Does not attempt to deduplicate. -func (e *AppConnector) addDomainAddrLocked(domain string, addr netip.Addr) { - e.domains[domain] = append(e.domains[domain], addr) - slices.SortFunc(e.domains[domain], compareAddr) -} - -func compareAddr(l, r netip.Addr) int { - return l.Compare(r) -} - -func comparePrefix(i, j netip.Prefix) int { - return i.Addr().Compare(j.Addr()) -} diff --git a/appc/routeinfo/routeinfo.go b/appc/routeinfo/routeinfo.go index d0a5641de..acc9441ec 100644 --- a/appc/routeinfo/routeinfo.go +++ b/appc/routeinfo/routeinfo.go @@ -9,29 +9,28 @@ import ( ) type RouteInfo struct { - // routes set with --advertise-routes - Local []netip.Prefix // routes from the 'routes' section of an app connector acl Control []netip.Prefix // routes discovered by observing dns lookups for configured domains Discovered map[string]*DatedRoutes + + Wildcards []string } func NewRouteInfo() *RouteInfo { discovered := make(map[string]*DatedRoutes) return &RouteInfo{ - Local: []netip.Prefix{}, Control: []netip.Prefix{}, Discovered: discovered, } } // RouteInfo.Routes returns a slice containing all the routes stored from the wanted resources. -func (ri *RouteInfo) Routes(local, control, discovered bool) []netip.Prefix { - var ret []netip.Prefix - if local { - ret = ri.Local +func (ri *RouteInfo) Routes(control, discovered bool) []netip.Prefix { + if ri == nil { + return []netip.Prefix{} } + var ret []netip.Prefix if control && len(ret) == 0 { ret = ri.Control } else if control { @@ -48,6 +47,14 @@ func (ri *RouteInfo) Routes(local, control, discovered bool) []netip.Prefix { return ret } +func (ri *RouteInfo) DomainRoutes() map[string][]netip.Addr { + drCopy := make(map[string][]netip.Addr) + for k, v := range ri.Discovered { + drCopy[k] = append(drCopy[k], v.AddrsSlice()...) + } + return drCopy +} + type DatedRoutes struct { // routes discovered for a domain, and when they were last seen in a dns query Routes map[netip.Prefix]time.Time @@ -63,6 +70,16 @@ func (dr *DatedRoutes) RoutesSlice() []netip.Prefix { return routes } +func (dr *DatedRoutes) AddrsSlice() []netip.Addr { + var routes []netip.Addr + for k := range dr.Routes { + if k.IsSingleIP() { + routes = append(routes, k.Addr()) + } + } + return routes +} + func (r *RouteInfo) AddRoutesInDiscoveredForDomain(domain string, addrs []netip.Prefix) { dr, hasKey := r.Discovered[domain] if !hasKey || dr == nil || dr.Routes == nil { diff --git a/cmd/tailscaled/__debug_bin b/cmd/tailscaled/__debug_bin new file mode 100755 index 000000000..d2e685fc7 Binary files /dev/null and b/cmd/tailscaled/__debug_bin differ diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4c79174ff..7ad632d93 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -3189,29 +3189,6 @@ func (b *LocalBackend) SetUseExitNodeEnabled(v bool) (ipn.PrefsView, error) { return b.editPrefsLockedOnEntry(mp, unlock) } -func (b *LocalBackend) PatchPrefsHandler(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) { - // we believe that for the purpose of figuring out advertisedRoutes setPrefsLockedOnEntry is _only_ called when - // up or set is used on the tailscale cli _not_ when we calculate the new advertisedRoutes field. - if b.appConnector != nil && b.appConnector.ShouldStoreRoutes && mp.AdvertiseRoutesSet { - routeInfo := b.appConnector.RouteInfo() - curRoutes := routeInfo.Routes(false, true, true) - routeInfo.Local = mp.AdvertiseRoutes - b.appConnector.UpdateRouteInfo(routeInfo) - // When b.appConnector != nil, AppConnectorSet = true means - // The appConnector is turned off, in this case we should not - // append the remote routes to mp.AdvertiseRoutes. Appc will be - // set to nil first and unadvertise remote routes, but these remote routes - // will then be advertised again when the prefs are sent. - if !mp.AppConnectorSet { - curRoutes = append(curRoutes, mp.AdvertiseRoutes...) - slices.SortFunc(curRoutes, func(i, j netip.Prefix) int { return i.Addr().Compare(j.Addr()) }) - curRoutes = slices.Compact(curRoutes) - mp.AdvertiseRoutes = curRoutes - } - } - return b.EditPrefs(mp) -} - func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) { if mp.SetsInternal() { return ipn.PrefsView{}, errors.New("can't set Internal fields") @@ -3653,7 +3630,8 @@ func (b *LocalBackend) authReconfig() { // If the current node is an app connector, ensure the app connector machine is started b.reconfigAppConnectorLocked(nm, prefs) b.mu.Unlock() - + fmt.Println("kevin -- try lock1", b.mu.TryLock()) + b.mu.Unlock() if blocked { b.logf("[v1] authReconfig: blocked, skipping.") return @@ -3666,6 +3644,8 @@ func (b *LocalBackend) authReconfig() { b.logf("[v1] authReconfig: skipping because !WantRunning.") return } + fmt.Println("kevin -- try lock2", b.mu.TryLock()) + b.mu.Unlock() var flags netmap.WGConfigFlags if prefs.RouteAll() { @@ -3680,7 +3660,8 @@ func (b *LocalBackend) authReconfig() { flags &^= netmap.AllowSubnetRoutes } } - + fmt.Println("kevin -- try lock3", b.mu.TryLock()) + b.mu.Unlock() // Keep the dialer updated about whether we're supposed to use // an exit node's DNS server (so SOCKS5/HTTP outgoing dials // can use it for name resolution) @@ -3695,8 +3676,11 @@ func (b *LocalBackend) authReconfig() { b.logf("wgcfg: %v", err) return } - + fmt.Println("kevin -- try lock 4", b.mu.TryLock()) + b.mu.Unlock() oneCGNATRoute := shouldUseOneCGNATRoute(b.logf, b.sys.ControlKnobs(), version.OS()) + fmt.Println("kevin -- try lock 5", b.mu.TryLock()) + b.mu.Unlock() rcfg := b.routerConfig(cfg, prefs, oneCGNATRoute) err = b.e.Reconfig(cfg, rcfg, dcfg) @@ -4188,11 +4172,14 @@ func peerRoutes(logf logger.Logf, peers []wgcfg.Peer, cgnatThreshold int) (route // routerConfig produces a router.Config from a wireguard config and IPN prefs. func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs ipn.PrefsView, oneCGNATRoute bool) *router.Config { + fmt.Println("kevin -- try lock 6", b.mu.TryLock()) + b.mu.Unlock() singleRouteThreshold := 10_000 if oneCGNATRoute { singleRouteThreshold = 1 } - + fmt.Println("kevin -- try lock 7", b.mu.TryLock()) + b.mu.Unlock() b.mu.Lock() netfilterKind := b.capForcedNetfilter // protected by b.mu b.mu.Unlock() @@ -4204,9 +4191,11 @@ func (b *LocalBackend) routerConfig(cfg *wgcfg.Config, prefs ipn.PrefsView, oneC netfilterKind = prefs.NetfilterKind() } + toAdvertise := b.appConnector.RouteInfo().Routes(true, true) + toAdvertise = append(toAdvertise, prefs.AdvertiseRoutes().AsSlice()...) rs := &router.Config{ LocalAddrs: unmapIPPrefixes(cfg.Addresses), - SubnetRoutes: unmapIPPrefixes(prefs.AdvertiseRoutes().AsSlice()), + SubnetRoutes: unmapIPPrefixes(toAdvertise), SNATSubnetRoutes: !prefs.NoSNAT(), NetfilterMode: prefs.NetfilterMode(), Routes: peerRoutes(b.logf, cfg.Peers, singleRouteThreshold), @@ -4290,7 +4279,13 @@ func (b *LocalBackend) applyPrefsToHostinfoLocked(hi *tailcfg.Hostinfo, prefs ip if h := prefs.Hostname(); h != "" { hi.Hostname = h } - hi.RoutableIPs = prefs.AdvertiseRoutes().AsSlice() + + var routableIPs []netip.Prefix + if b.appConnector != nil { + routableIPs = append(routableIPs, b.appConnector.RouteInfo().Routes(true, true)...) + } + routableIPs = append(routableIPs, prefs.AdvertiseRoutes().AsSlice()...) + hi.RoutableIPs = routableIPs hi.RequestTags = prefs.AdvertiseTags().AsSlice() hi.ShieldsUp = prefs.ShieldsUp() hi.AllowsUpdate = envknob.AllowsRemoteUpdate() || prefs.AutoUpdate().Apply.EqualBool(true) @@ -6212,38 +6207,50 @@ var ErrDisallowedAutoRoute = errors.New("route is not allowed") // AdvertiseRoute implements the appc.RouteAdvertiser interface. It sets a new // route advertisement if one is not already present in the existing routes. // If the route is disallowed, ErrDisallowedAutoRoute is returned. -func (b *LocalBackend) AdvertiseRoute(ipps ...netip.Prefix) error { - finalRoutes := b.Prefs().AdvertiseRoutes().AsSlice() - newRoutes := false +func (b *LocalBackend) AdvertiseRouteInfo(ri *routeinfo.RouteInfo) { + b.mu.Lock() + defer b.mu.Unlock() + pref := b.pm.CurrentPrefs() + newRoutes := pref.AdvertiseRoutes().AsSlice() + oldHi := b.hostinfo + oldRoutes := oldHi.RoutableIPs + newHi := oldHi.Clone() + if newHi == nil { + newHi = new(tailcfg.Hostinfo) + } + routeInfoRoutes := ri.Routes(true, true) - for _, ipp := range ipps { + for _, ipp := range routeInfoRoutes { if !allowedAutoRoute(ipp) { continue } - if slices.Contains(finalRoutes, ipp) { + if slices.Contains(newRoutes, ipp) { continue } // If the new prefix is already contained by existing routes, skip it. - if coveredRouteRangeNoDefault(finalRoutes, ipp) { + if coveredRouteRangeNoDefault(newRoutes, ipp) { continue } - finalRoutes = append(finalRoutes, ipp) - newRoutes = true + newRoutes = append(newRoutes, ipp) } - if !newRoutes { - return nil + slices.SortFunc(oldRoutes, comparePrefix) + slices.SortFunc(newRoutes, comparePrefix) + + if slices.CompareFunc(oldRoutes, newRoutes, comparePrefix) != 0 { + return } - _, err := b.EditPrefs(&ipn.MaskedPrefs{ - Prefs: ipn.Prefs{ - AdvertiseRoutes: finalRoutes, - }, - AdvertiseRoutesSet: true, - }) - return err + newHi.RoutableIPs = newRoutes + b.hostinfo = newHi + + if !oldHi.Equal(newHi) { + b.doSetHostinfoFilterServices() + } + + b.authReconfig() } // coveredRouteRangeNoDefault checks if a route is already included in a slice of @@ -6266,28 +6273,6 @@ func coveredRouteRangeNoDefault(finalRoutes []netip.Prefix, ipp netip.Prefix) bo return false } -// UnadvertiseRoute implements the appc.RouteAdvertiser interface. It removes -// a route advertisement if one is present in the existing routes. -func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error { - currentRoutes := b.Prefs().AdvertiseRoutes().AsSlice() - finalRoutes := currentRoutes[:0] - - for _, ipp := range currentRoutes { - if slices.Contains(toRemove, ipp) { - continue - } - finalRoutes = append(finalRoutes, ipp) - } - - _, err := b.EditPrefs(&ipn.MaskedPrefs{ - Prefs: ipn.Prefs{ - AdvertiseRoutes: finalRoutes, - }, - AdvertiseRoutesSet: true, - }) - return err -} - // namespace a key with the profile manager's current profile key, if any func namespaceKeyForCurrentProfile(pm *profileManager, key ipn.StateKey) ipn.StateKey { return pm.CurrentProfile().Key + "||" + key @@ -6314,8 +6299,8 @@ func (b *LocalBackend) StoreRouteInfo(ri *routeinfo.RouteInfo) error { // ReadRouteInfo implements the appc.RouteAdvertiser interface. It reads // RouteInfo from StateStore per profile. func (b *LocalBackend) ReadRouteInfo() (*routeinfo.RouteInfo, error) { - b.mu.Lock() - defer b.mu.Unlock() + // b.mu.Lock() + // defer b.mu.Unlock() if b.pm.CurrentProfile().ID == "" { return &routeinfo.RouteInfo{}, nil } @@ -6376,3 +6361,7 @@ func mayDeref[T any](p *T) (v T) { } return *p } + +func comparePrefix(i, j netip.Prefix) int { + return i.Addr().Compare(j.Addr()) +} diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index d1cd6405f..2d3ca70db 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -1368,7 +1368,7 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) { return } var err error - prefs, err = h.b.PatchPrefsHandler(mp) + prefs, err = h.b.EditPrefs(mp) if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 04cd89672..4a0c61959 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -2232,13 +2232,10 @@ const ( // NodeAttrDisableWebClient disables using the web client. NodeAttrDisableWebClient NodeCapability = "disable-web-client" -<<<<<<< HEAD // NodeAttrExitDstNetworkFlowLog enables exit node destinations in network flow logs. NodeAttrExitDstNetworkFlowLog NodeCapability = "exit-dst-network-flow-log" -======= // NodeAttrStoreAppCRoutes enables storing app connector routes persistently. NodeAttrStoreAppCRoutes NodeCapability = "store-appc-routes" ->>>>>>> 61f7b83bd (Add a control knob to toggle writing RouteInfo to StateStore) ) // SetDNSRequest is a request to add a DNS record.