diff --git a/appc/appconnector.go b/appc/appconnector.go index 0b7fba2de..6e37f0501 100644 --- a/appc/appconnector.go +++ b/appc/appconnector.go @@ -110,10 +110,10 @@ func (e *AppConnector) updateDomains(domains []string) { defer e.mu.Unlock() var oldDomains map[string][]netip.Addr - var oldDiscovered map[string]ipn.DatedRoute + var oldDiscovered map[string]*ipn.DatedRoute routeInfo := e.routeAdvertiser.ReadRouteInfoFromStore() - oldDiscovered, routeInfo.Discovered = routeInfo.Discovered, make(map[string]ipn.DatedRoute, len(domains)) + oldDiscovered, routeInfo.Discovered = routeInfo.Discovered, make(map[string]*ipn.DatedRoute, len(domains)) oldDomains, e.domains = e.domains, make(map[string][]netip.Addr, len(domains)) e.wildcards = e.wildcards[:0] for _, d := range domains { @@ -335,7 +335,6 @@ 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 - // Kevin comment: Will this update be too frequent? var toUpdateDate []netip.Prefix for _, addr := range addrs { if !e.isAddrKnownLocked(domain, addr) { @@ -352,12 +351,14 @@ func (e *AppConnector) ObserveDNSResponse(res []byte) { routesToUpdate := append(toAdvertise, toUpdateDate...) routeInfo := e.routeAdvertiser.ReadRouteInfoFromStore() if routeInfo.Discovered == nil { - routeInfo.Discovered = make(map[string]ipn.DatedRoute) + routeInfo.Discovered = make(map[string]*ipn.DatedRoute) } routeInfo.UpdateRoutesInDiscoveredForDomain(domain, routesToUpdate) - + outDated := routeInfo.OutDatedRoutesInDiscoveredForDomain(domain) e.routeAdvertiser.UpdateRoutesInfoToStore(routeInfo) + // fmt.Println("Route infos after dns update: ", e.routeAdvertiser.ReadRouteInfoFromStore()) e.scheduleAdvertisement(domain, toAdvertise...) + e.scheduleUndvertisement(domain, outDated...) } } @@ -435,6 +436,27 @@ func (e *AppConnector) scheduleAdvertisement(domain string, routes ...netip.Pref }) } +func (e *AppConnector) scheduleUndvertisement(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 { @@ -449,6 +471,15 @@ func (e *AppConnector) addDomainAddrLocked(domain string, addr netip.Addr) { slices.SortFunc(e.domains[domain], compareAddr) } +func (e *AppConnector) deleteDomainAddrLocked(domain string, addr netip.Addr) { + ind, ok := slices.BinarySearchFunc(e.domains[domain], addr, compareAddr) + if !ok { + return + } + e.domains[domain] = slices.Delete(e.domains[domain], ind, ind+1) + slices.SortFunc(e.domains[domain], compareAddr) +} + func compareAddr(l, r netip.Addr) int { return l.Compare(r) } diff --git a/client/tailscale/localclient.go b/client/tailscale/localclient.go index c93490c39..649c04c1c 100644 --- a/client/tailscale/localclient.go +++ b/client/tailscale/localclient.go @@ -727,7 +727,7 @@ func (lc *LocalClient) EditPrefs(ctx context.Context, mp *ipn.MaskedPrefs) (*ipn if err != nil { return nil, err } - fmt.Println(decodeJSON[*ipn.Prefs](body)) // Kevin debug + // fmt.Println(decodeJSON[*ipn.Prefs](body)) // Kevin debug return decodeJSON[*ipn.Prefs](body) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 8a73a1209..8497866fb 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -355,7 +355,6 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo return nil, err } } - // Kevin comment: no need to do this since tailscaled startup will not affect routes. envknob.LogCurrent(logf) if dialer == nil { @@ -3142,6 +3141,7 @@ func (b *LocalBackend) PatchPrefsHandler(mp *ipn.MaskedPrefs) (ipn.PrefsView, er // up or set is used on the tailscale cli _not_ when we calculate the new advertisedRoutes field. routeInfo := b.pm.CurrentRoutes() curRoutes := routeInfo.CorpAndDiscoveredAsSlice() + // fmt.Println("Kevin cur route: ", curRoutes) if mp.AdvertiseRoutesSet { routeInfo.Local = mp.AdvertiseRoutes b.pm.SetCurrentRoutes(routeInfo) @@ -3183,7 +3183,7 @@ func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) { // in setPrefsLocksOnEntry instead. // This should return the public prefs, not the private ones. - fmt.Println("Editpref in local", b.pm.CurrentRoutes()) + // fmt.Println("Editpref in local", b.pm.CurrentRoutes()) //Kevin debug return stripKeysFromPrefs(newPrefs), nil } @@ -3562,8 +3562,6 @@ func (b *LocalBackend) reconfigAppConnectorLocked(nm *netmap.NetworkMap, prefs i slices.SortFunc(routes, func(i, j netip.Prefix) int { return i.Addr().Compare(j.Addr()) }) domains = slices.Compact(domains) routes = slices.Compact(routes) - fmt.Println("Connector Kevin: ", domains) - fmt.Println("Connector Kevin: ", routes) b.appConnector.UpdateDomainsAndRoutes(domains, routes) } @@ -5988,7 +5986,7 @@ func coveredRouteRangeNoDefault(finalRoutes []netip.Prefix, ipp netip.Prefix) bo // 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 { - fmt.Println("We are unadvertising routes: ", toRemove) + // fmt.Println("We are unadvertising routes: ", toRemove) //Kevin debug currentRoutes := b.Prefs().AdvertiseRoutes().AsSlice() finalRoutes := currentRoutes[:0] @@ -5998,7 +5996,7 @@ func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error { } finalRoutes = append(finalRoutes, ipp) } - fmt.Println("We are advertising these routes in unadvertising routes: ", finalRoutes) + // fmt.Println("We are advertising these routes in unadvertising routes: ", finalRoutes) // Kevin debug _, err := b.EditPrefs(&ipn.MaskedPrefs{ Prefs: ipn.Prefs{ AdvertiseRoutes: finalRoutes, @@ -6009,7 +6007,9 @@ func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error { } func (b *LocalBackend) ReadRouteInfoFromStore() *ipn.RouteInfo { - b.pm.ReadRoutesForCurrentProfile() + if b.pm.CurrentRoutes() == nil { + b.pm.ReadRoutesForCurrentProfile() + } return b.pm.CurrentRoutes() } diff --git a/ipn/ipnlocal/profiles.go b/ipn/ipnlocal/profiles.go index f01c721d0..1ba48cdce 100644 --- a/ipn/ipnlocal/profiles.go +++ b/ipn/ipnlocal/profiles.go @@ -65,7 +65,6 @@ func (pm *profileManager) WriteRoutesForCurrentProfile() error { if err != nil { return err } - return pm.writeStateForCurrentProfile("_routes", routeInfoInBytes) } diff --git a/ipn/prefs.go b/ipn/prefs.go index 8bd667121..79c32b7ea 100644 --- a/ipn/prefs.go +++ b/ipn/prefs.go @@ -957,48 +957,67 @@ type LoginProfile struct { type RouteInfo struct { Local []netip.Prefix Corp []netip.Prefix - Discovered map[string]DatedRoute + Discovered map[string]*DatedRoute } func (r RouteInfo) UpdateRoutesInDiscoveredForDomain(domain string, addrs []netip.Prefix) { - newDatedRoutes := make(DatedRoute) - val, hasKey := r.Discovered[domain] - if !hasKey || val == nil { - r.Discovered[domain] = addAddrsToDatedRoute(newDatedRoutes, addrs) + dr, hasKey := r.Discovered[domain] + if !hasKey || dr == nil || dr.Routes == nil { + newDatedRoutes := &DatedRoute{make(map[netip.Prefix]time.Time), time.Now()} + newDatedRoutes.addAddrsToDatedRoute(addrs) + r.Discovered[domain] = newDatedRoutes return } // kevin comment: we won't see any existing routes here because know addrs are filtered. currentRoutes := r.Discovered[domain] - r.Discovered[domain] = addAddrsToDatedRoute(currentRoutes, addrs) + currentRoutes.addAddrsToDatedRoute(addrs) + r.Discovered[domain] = currentRoutes return } -func (r RouteInfo) CorpAndDiscoveredAsSlice() []netip.Prefix { - dr := r.Corp - for _, routes := range r.Discovered { - for k := range routes { - dr = append(dr, k) +func (r RouteInfo) OutDatedRoutesInDiscoveredForDomain(domain string) []netip.Prefix { + dr, hasKey := r.Discovered[domain] + var outdate []netip.Prefix + now := time.Now() + if !hasKey || dr == nil || dr.Routes == nil || now.Sub(dr.LastCleanUp) < 360 { + return nil + } + for addr, date := range dr.Routes { + if now.Sub(date).Hours() >= 360 { + // 15 days old when last seen + outdate = append(outdate, addr) + delete(dr.Routes, addr) } } - return dr + r.Discovered[domain] = dr + return outdate } -type DatedRoute = map[netip.Prefix]time.Time - -func addAddrsToDatedRoute(curDatedRoute DatedRoute, addrs []netip.Prefix) DatedRoute { - time := time.Now() - ret := curDatedRoute - for _, addr := range addrs { - ret[addr] = time +func (r RouteInfo) CorpAndDiscoveredAsSlice() []netip.Prefix { + ret := r.Corp + for _, dr := range r.Discovered { + if dr != nil && dr.Routes != nil { + for k := range dr.Routes { + ret = append(ret, k) + } + } } return ret } -// func datedRouteToSlice(curDatedRoute DatedRoute) []netip.Prefix { -// r := make([]netip.Prefix, 0, len(curDatedRoute)) -// for k := range curDatedRoute { -// r = append(r, k) -// } -// return r -// } +type DatedRoute struct { + Routes map[netip.Prefix]time.Time + LastCleanUp time.Time +} + +func (d *DatedRoute) String() string { + return fmt.Sprintf("routes: %s, lastCleanUp: %v", d.Routes, d.LastCleanUp) +} + +func (d *DatedRoute) addAddrsToDatedRoute(addrs []netip.Prefix) { + time := time.Now() + for _, addr := range addrs { + d.Routes[addr] = time + } +}