Compare commits

...

14 Commits

Author SHA1 Message Date
Kevin Liang
735838a0f1 Rate limiting the update of lastseen date for route. 2024-03-22 19:13:14 +00:00
Fran Bull
2dfe7593ed truncate last seen per hour 2024-03-22 09:52:42 -07:00
Kevin Liang
f8ce2cbb75 periodically remove dns looked up routes up on new look up results 2024-03-21 17:17:37 +00:00
Kevin Liang
4db1ea4729 make routes from corp presisting and update with the list of route change on corp 2024-03-20 17:08:21 +00:00
Kevin Liang
db01cc62e0 add the ability to remove routes associated with domain 2024-03-19 20:45:06 +00:00
Kevin Liang
26fba734a7 needs to be removed, just logging prefs 2024-03-19 16:55:57 +00:00
Kevin Liang
6b304b5c10 add the ability to keep dns routes and corp routes advertised when set advertise-routes locally 2024-03-19 16:43:18 +00:00
Kevin Liang
93697e9363 add route storing for domain dns lookups, no domain delete yet 2024-03-18 21:51:00 +00:00
Kevin Liang
4d40443ccd promote local routes recording to be before editprefs to avoid calling it when we didn't want to. 2024-03-15 20:03:22 +00:00
Kevin Liang
34b49b5793 temp todos 2024-03-15 19:36:02 +00:00
Fran Bull
7fa9bf10dd update currentRoutes when set is used on tailscale cli 2024-03-15 11:39:10 -07:00
Fran Bull
28bba7b3db read routes when switching profiles 2024-03-15 10:36:07 -07:00
Kevin Liang
9f0bb29912 a prototype for writing and reading the route info for current profile
Signed-off-by: Kevin Liang <kevinliang@tailscale.com>
2024-03-14 20:26:41 +00:00
Fran Bull
d0dd8fd38d add write to state store 2024-03-14 12:27:33 -07:00
7 changed files with 457 additions and 4 deletions

View File

@@ -15,9 +15,11 @@ import (
"slices"
"strings"
"sync"
"time"
xmaps "golang.org/x/exp/maps"
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/ipn"
"tailscale.com/types/logger"
"tailscale.com/types/views"
"tailscale.com/util/dnsname"
@@ -34,6 +36,10 @@ type RouteAdvertiser interface {
// UnadvertiseRoute removes any matching route advertisements.
UnadvertiseRoute(...netip.Prefix) error
ReadRouteInfoFromStore() *ipn.RouteInfo
UpdateRoutesInfoToStore(*ipn.RouteInfo) error
}
// AppConnector is an implementation of an AppConnector that performs
@@ -64,6 +70,9 @@ type AppConnector struct {
// queue provides ordering for update operations
queue execqueue.ExecQueue
discoveredToUpdateDate map[string][]netip.Prefix
discoveredLastUpdate time.Time
}
// NewAppConnector creates a new AppConnector.
@@ -105,6 +114,10 @@ func (e *AppConnector) updateDomains(domains []string) {
defer e.mu.Unlock()
var oldDomains map[string][]netip.Addr
var oldDiscovered map[string]*ipn.DatedRoute
routeInfo := e.routeAdvertiser.ReadRouteInfoFromStore()
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 {
@@ -117,7 +130,9 @@ func (e *AppConnector) updateDomains(domains []string) {
continue
}
e.domains[d] = oldDomains[d]
routeInfo.Discovered[d] = oldDiscovered[d]
delete(oldDomains, d)
delete(oldDiscovered, d)
}
// Ensure that still-live wildcards addresses are preserved as well.
@@ -129,6 +144,17 @@ func (e *AppConnector) updateDomains(domains []string) {
}
}
}
for d, dr := range oldDiscovered {
for _, wc := range e.wildcards {
if dnsname.HasSuffix(d, wc) {
routeInfo.Discovered[d] = dr
break
}
}
}
e.routeAdvertiser.UpdateRoutesInfoToStore(routeInfo)
e.logf("handling domains: %v and wildcards: %v", xmaps.Keys(e.domains), e.wildcards)
}
@@ -145,12 +171,26 @@ func (e *AppConnector) updateRoutes(routes []netip.Prefix) {
return
}
routeInfo := e.routeAdvertiser.ReadRouteInfoFromStore()
oldCorp := routeInfo.Corp
var toRemove []netip.Prefix
for _, ipp := range oldCorp {
if slices.Contains(routes, ipp) {
continue
}
toRemove = append(toRemove, ipp)
}
if err := e.routeAdvertiser.UnadvertiseRoute(toRemove...); err != nil {
e.logf("failed to unadvertise old routes: %v: %v", routes, err)
}
routeInfo.Corp = routes
if err := e.routeAdvertiser.AdvertiseRoute(routes...); err != nil {
e.logf("failed to advertise routes: %v: %v", routes, err)
return
}
var toRemove []netip.Prefix
toRemove = toRemove[:0]
nextRoute:
for _, r := range routes {
@@ -170,6 +210,7 @@ nextRoute:
}
e.controlRoutes = routes
e.routeAdvertiser.UpdateRoutesInfoToStore(routeInfo)
}
// Domains returns the currently configured domain list.
@@ -298,14 +339,54 @@ 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 toUpdateDate []netip.Prefix
for _, addr := range addrs {
if !e.isAddrKnownLocked(domain, addr) {
toAdvertise = append(toAdvertise, netip.PrefixFrom(addr, addr.BitLen()))
} else {
if e.discoveredToUpdateDate == nil {
e.discoveredToUpdateDate = make(map[string][]netip.Prefix)
}
e.discoveredToUpdateDate[domain] = append(e.discoveredToUpdateDate[domain], netip.PrefixFrom(addr, addr.BitLen()))
slices.SortFunc(e.discoveredToUpdateDate[domain], func(i, j netip.Prefix) int { return i.Addr().Compare(j.Addr()) })
slices.Compact(e.discoveredToUpdateDate[domain])
}
}
e.logf("[v2] observed new routes for %s: %s", domain, toAdvertise)
// update the pm.currentroutes, then advertise those routes.
// we might also able to do this in local backend's advertiseRoute method, doing the update one domain by one domain.
// currentRouteInfo := nil
updateStore := false
routeInfo := e.routeAdvertiser.ReadRouteInfoFromStore()
if len(toAdvertise) != 0 {
routeInfo.AddRoutesInDiscoveredForDomain(domain, toAdvertise)
updateStore = true
}
now := time.Now()
if len(e.discoveredToUpdateDate) > 0 && now.Sub(e.discoveredLastUpdate) > 5*time.Minute {
// fmt.Println("We did a date update to the existing routes") // Kevin debug
// fmt.Println("The time now is: ", now, " The last update was; ", e.discoveredLastUpdate) // Kevin debug
routeInfo.UpdateDatesForRoutesInDiscovered(e.discoveredToUpdateDate)
e.discoveredToUpdateDate = make(map[string][]netip.Prefix)
e.discoveredLastUpdate = now
updateStore = true
}
// fmt.Println("The time I updated the date was: ", now) // Kevin debug
// fmt.Println("Next time we update the date will be: ", now.Add(1*time.Minute)) // Kevin debug
// fmt.Println("The last update was; ", e.discoveredLastUpdate)// Kevin debug
// fmt.Println("discoveredToUpdate: ", e.discoveredToUpdateDate)// Kevin debug
outDated := routeInfo.OutDatedRoutesInDiscoveredForDomain(domain)
if len(outDated) != 0 {
updateStore = true
}
if updateStore {
e.routeAdvertiser.UpdateRoutesInfoToStore(routeInfo)
}
// fmt.Println("Route infos after dns update: ", e.routeAdvertiser.ReadRouteInfoFromStore()) // Kevin debug
e.scheduleAdvertisement(domain, toAdvertise...)
e.scheduleUndvertisement(domain, outDated...)
}
}
@@ -383,6 +464,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 {
@@ -397,6 +499,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)
}

View File

@@ -727,6 +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
return decodeJSON[*ipn.Prefs](body)
}

View File

@@ -3136,6 +3136,21 @@ func (b *LocalBackend) checkFunnelEnabledLocked(p *ipn.Prefs) error {
return nil
}
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.
routeInfo := b.pm.CurrentRoutes()
curRoutes := routeInfo.CorpAndDiscoveredAsSlice()
// fmt.Println("Kevin cur route: ", curRoutes)
if mp.AdvertiseRoutesSet {
routeInfo.Local = mp.AdvertiseRoutes
b.pm.SetCurrentRoutes(routeInfo)
curRoutes := append(curRoutes, mp.AdvertiseRoutes...)
mp.AdvertiseRoutes = curRoutes
}
return b.EditPrefs(mp)
}
func (b *LocalBackend) EditPrefs(mp *ipn.MaskedPrefs) (ipn.PrefsView, error) {
b.mu.Lock()
if mp.EggSet {
@@ -3168,6 +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()) //Kevin debug
return stripKeysFromPrefs(newPrefs), nil
}
@@ -3223,6 +3239,7 @@ func (b *LocalBackend) setPrefsLockedOnEntry(caller string, newp *ipn.Prefs) ipn
if oldp.Valid() {
newp.Persist = oldp.Persist().AsStruct() // caller isn't allowed to override this
}
// setExitNodeID returns whether it updated b.prefs, but
// everything in this function treats b.prefs as completely new
// anyway. No-op if no exit node resolution is needed.
@@ -5969,6 +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) //Kevin debug
currentRoutes := b.Prefs().AdvertiseRoutes().AsSlice()
finalRoutes := currentRoutes[:0]
@@ -5978,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) // Kevin debug
_, err := b.EditPrefs(&ipn.MaskedPrefs{
Prefs: ipn.Prefs{
AdvertiseRoutes: finalRoutes,
@@ -5988,6 +6006,18 @@ func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error {
return err
}
func (b *LocalBackend) ReadRouteInfoFromStore() *ipn.RouteInfo {
if b.pm.CurrentRoutes() == nil {
b.pm.ReadRoutesForCurrentProfile()
}
return b.pm.CurrentRoutes()
}
func (b *LocalBackend) UpdateRoutesInfoToStore(newRouteInfo *ipn.RouteInfo) error {
b.pm.SetCurrentRoutes(newRouteInfo)
return nil
}
// seamlessRenewalEnabled reports whether seamless key renewals are enabled
// (i.e. we saw our self node with the SeamlessKeyRenewal attr in a netmap).
// This enables beta functionality of renewing node keys without breaking

View File

@@ -36,6 +36,8 @@ type profileManager struct {
knownProfiles map[ipn.ProfileID]*ipn.LoginProfile // always non-nil
currentProfile *ipn.LoginProfile // always non-nil
prefs ipn.PrefsView // always Valid.
currentRoutes *ipn.RouteInfo
}
func (pm *profileManager) dlogf(format string, args ...any) {
@@ -45,10 +47,68 @@ func (pm *profileManager) dlogf(format string, args ...any) {
pm.logf(format, args...)
}
func (pm *profileManager) CurrentRoutes() *ipn.RouteInfo {
return pm.currentRoutes
}
func (pm *profileManager) SetCurrentRoutes(in *ipn.RouteInfo) error {
pm.currentRoutes = in
if err := pm.WriteRoutesForCurrentProfile(); err != nil {
return err
}
return nil
}
func (pm *profileManager) WriteState(id ipn.StateKey, val []byte) error {
return ipn.WriteState(pm.store, id, val)
}
func (pm *profileManager) WriteRoutesForCurrentProfile() error {
routeInfoInBytes, err := json.Marshal(*pm.currentRoutes)
if err != nil {
return err
}
return pm.writeStateForCurrentProfile("_routes", routeInfoInBytes)
}
func (pm *profileManager) ReadRoutesForCurrentProfile() error {
// TODO(fran) key should be a const somewhere
routeInfoInBytes, err := pm.readStateForCurrentProfile("_routes")
if err == ipn.ErrStateNotExist || len(routeInfoInBytes) == 0 {
pm.currentRoutes = &ipn.RouteInfo{}
pm.WriteRoutesForCurrentProfile()
return nil
} else if err != nil {
return err
}
storedRoutes := &ipn.RouteInfo{}
if err := json.Unmarshal(routeInfoInBytes, storedRoutes); err != nil {
return err
}
pm.currentRoutes = storedRoutes
return nil
}
func (pm *profileManager) writeStateForCurrentProfile(id ipn.StateKey, val []byte) error {
var currentProfileKey ipn.StateKey
// TODO(fran) maybe we should error if the current profile is nil?
if pm.currentProfile != nil {
currentProfileKey = pm.currentProfile.Key
}
return ipn.WriteState(pm.store, currentProfileKey+"||"+id, val)
}
func (pm *profileManager) readStateForCurrentProfile(id ipn.StateKey) ([]byte, error) {
var currentProfileKey ipn.StateKey
// TODO(fran) maybe we should error if the current profile is nil?
if pm.currentProfile != nil {
currentProfileKey = pm.currentProfile.Key
}
fmt.Println(currentProfileKey)
return pm.store.ReadState(currentProfileKey + "||" + id)
}
// CurrentUserID returns the current user ID. It is only non-empty on
// Windows where we have a multi-user system.
func (pm *profileManager) CurrentUserID() ipn.WindowsUserID {
@@ -101,6 +161,10 @@ func (pm *profileManager) SetCurrentUserID(uid ipn.WindowsUserID) error {
}
pm.currentProfile = prof
pm.prefs = prefs
if err := pm.ReadRoutesForCurrentProfile(); err != nil {
return err
}
return nil
}
@@ -336,6 +400,9 @@ func (pm *profileManager) SwitchProfile(id ipn.ProfileID) error {
}
pm.prefs = prefs
pm.currentProfile = kp
if err := pm.ReadRoutesForCurrentProfile(); err != nil {
return err
}
return pm.setAsUserSelectedProfileLocked()
}
@@ -520,7 +587,6 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
if err != nil {
return nil, err
}
pm := &profileManager{
store: store,
knownProfiles: knownProfiles,
@@ -548,6 +614,9 @@ func newProfileManagerWithGOOS(store ipn.StateStore, logf logger.Logf, goos stri
if err := pm.setPrefsLocked(prefs); err != nil {
return nil, err
}
if err := pm.ReadRoutesForCurrentProfile(); err != nil {
return nil, err
}
// Most platform behavior is controlled by the goos parameter, however
// some behavior is implied by build tag and fails when run on Windows,
// so we explicitly avoid that behavior when running on Windows.

View File

@@ -5,9 +5,11 @@ package ipnlocal
import (
"fmt"
"net/netip"
"os/user"
"strconv"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
@@ -576,3 +578,165 @@ func TestProfileManagementWindows(t *testing.T) {
t.Fatalf("CurrentUserID = %q; want %q", pm.CurrentUserID(), uid)
}
}
func TestFran(t *testing.T) {
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
if err != nil {
t.Fatal(err)
}
err = pm.writeStateForCurrentProfile("x", []byte("hello"))
if err != nil {
t.Fatal(err)
}
bs, err := pm.readStateForCurrentProfile("x")
if err != nil {
t.Fatal(err)
}
s := string(bs)
fmt.Println(s)
if s != "hello" {
t.Fatal("oh")
}
var id int
newProfile := func(t *testing.T, loginName string) ipn.PrefsView {
id++
t.Helper()
pm.NewProfile()
p := pm.CurrentPrefs().AsStruct()
p.Persist = &persist.Persist{
NodeID: tailcfg.StableNodeID(fmt.Sprint(id)),
PrivateNodeKey: key.NewNode(),
UserProfile: tailcfg.UserProfile{
ID: tailcfg.UserID(id),
LoginName: loginName,
},
}
if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil {
t.Fatal(err)
}
return p.View()
}
pm.SetCurrentUserID("user1")
newProfile(t, "user1")
err = pm.writeStateForCurrentProfile("x", []byte("hello user1"))
if err != nil {
t.Fatal(err)
}
bs, err = pm.readStateForCurrentProfile("x")
if err != nil {
t.Fatal(err)
}
s = string(bs)
fmt.Println(s)
if s != "hello user1" {
t.Fatal("oh")
}
pm.SetCurrentUserID("")
bs, err = pm.readStateForCurrentProfile("x")
if err != nil {
t.Fatal(err)
}
s = string(bs)
fmt.Println(s)
if s != "hello" {
t.Fatal("oh")
}
}
func TestKevin(t *testing.T) {
store := new(mem.Store)
pm, err := newProfileManagerWithGOOS(store, logger.Discard, "linux")
if err != nil {
t.Fatal(err)
}
var id int
newProfile := func(t *testing.T, loginName string) ipn.PrefsView {
id++
t.Helper()
pm.NewProfile()
p := pm.CurrentPrefs().AsStruct()
p.Persist = &persist.Persist{
NodeID: tailcfg.StableNodeID(fmt.Sprint(id)),
PrivateNodeKey: key.NewNode(),
UserProfile: tailcfg.UserProfile{
ID: tailcfg.UserID(id),
LoginName: loginName,
},
}
if err := pm.SetPrefs(p.View(), ipn.NetworkProfile{}); err != nil {
t.Fatal(err)
}
return p.View()
}
pm.SetCurrentUserID("user1")
newProfile(t, "user1")
Prefix_1 := netip.MustParsePrefix("1.2.3.0/24")
Prefix_2 := netip.MustParsePrefix("2.3.4.5/32")
Prefix_3 := netip.MustParsePrefix("192.16.0.0/16")
fakeRouteInfo1 := ipn.RouteInfo{
Local: []netip.Prefix{Prefix_2},
Corp: []netip.Prefix{Prefix_1},
Discovered: map[string]ipn.DatedRoute{
"Home.dom": {Route: Prefix_3, LastSeen: time.Now()},
},
}
fakeRouteInfo2 := ipn.RouteInfo{
Local: []netip.Prefix{Prefix_1},
Corp: []netip.Prefix{Prefix_3},
Discovered: map[string]ipn.DatedRoute{
"Home.dom": {Route: Prefix_2, LastSeen: time.Now()},
},
}
pm.currentRoutes = &fakeRouteInfo1
if err := pm.WriteRoutesForCurrentProfile(); err != nil {
t.Fatal(err)
}
assertIsUser1Routes := func() {
if pm.currentRoutes.Corp[0] != Prefix_1 {
t.Fatal("That's not right")
}
if pm.currentRoutes.Discovered["Home.dom"].Route != Prefix_3 {
t.Fatal("That's not right: discovered")
}
}
assertIsUser2Routes := func() {
if pm.currentRoutes.Corp[0] != Prefix_3 {
t.Fatal("That's not right")
}
if pm.currentRoutes.Discovered["Home.dom"].Route != Prefix_2 {
t.Fatal("That's not right: discovered")
}
}
assertIsUser1Routes()
pm.SetCurrentUserID("user2")
newProfile(t, "user2")
pm.currentRoutes = &fakeRouteInfo2
if err := pm.WriteRoutesForCurrentProfile(); err != nil {
t.Fatal(err)
}
assertIsUser2Routes()
pm.SetCurrentUserID("user1")
// should have read out the other route info
assertIsUser1Routes()
pm.SetCurrentUserID("user2")
assertIsUser2Routes()
}

View File

@@ -1378,7 +1378,7 @@ func (h *Handler) servePrefs(w http.ResponseWriter, r *http.Request) {
return
}
var err error
prefs, err = h.b.EditPrefs(mp)
prefs, err = h.b.PatchPrefsHandler(mp)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)

View File

@@ -16,6 +16,7 @@ import (
"runtime"
"slices"
"strings"
"time"
"tailscale.com/atomicfile"
"tailscale.com/ipn/ipnstate"
@@ -952,3 +953,80 @@ type LoginProfile struct {
// into.
ControlURL string
}
type RouteInfo struct {
Local []netip.Prefix
Corp []netip.Prefix
Discovered map[string]*DatedRoute
}
func (r RouteInfo) AddRoutesInDiscoveredForDomain(domain string, addrs []netip.Prefix) {
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]
currentRoutes.addAddrsToDatedRoute(addrs)
r.Discovered[domain] = currentRoutes
return
}
func (r RouteInfo) UpdateDatesForRoutesInDiscovered(toUpdate map[string][]netip.Prefix) {
for domain, addrs := range toUpdate {
for _, addr := range addrs {
r.Discovered[domain].Routes[addr] = time.Now()
}
}
}
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*time.Hour {
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)
}
}
r.Discovered[domain] = dr
dr.LastCleanUp = time.Now()
return outdate
}
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
}
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
}
}