This commit is contained in:
Fran Bull 2024-03-26 13:59:00 -07:00 committed by Kevin Liang
parent 9258bcc360
commit 43fbc0d588
5 changed files with 131 additions and 0 deletions

View File

@ -18,6 +18,7 @@ import (
xmaps "golang.org/x/exp/maps"
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/appc/routeinfo"
"tailscale.com/types/logger"
"tailscale.com/types/views"
"tailscale.com/util/dnsname"
@ -34,6 +35,10 @@ type RouteAdvertiser interface {
// UnadvertiseRoute removes any matching route advertisements.
UnadvertiseRoute(...netip.Prefix) error
// Store/ReadRouteInfo persists and retreives RouteInfo to stable storage
StoreRouteInfo(*routeinfo.RouteInfo) error
ReadRouteInfo() (*routeinfo.RouteInfo, error)
}
// AppConnector is an implementation of an AppConnector that performs

View File

@ -6,6 +6,8 @@ package appctest
import (
"net/netip"
"slices"
"tailscale.com/appc/routeinfo"
)
// RouteCollector is a test helper that collects the list of routes advertised
@ -32,6 +34,14 @@ func (rc *RouteCollector) UnadvertiseRoute(toRemove ...netip.Prefix) error {
return nil
}
func (rc *RouteCollector) StoreRouteInfo(ri *routeinfo.RouteInfo) error {
return nil
}
func (rc *RouteCollector) ReadRouteInfo() (*routeinfo.RouteInfo, error) {
return nil, nil
}
// RemovedRoutes returns the list of routes that were removed.
func (rc *RouteCollector) RemovedRoutes() []netip.Prefix {
return rc.removedRoutes

View File

@ -0,0 +1,25 @@
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package routeinfo
import (
"net/netip"
"time"
)
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
}
type DatedRoutes struct {
// routes discovered for a domain, and when they were last seen in a dns query
Routes map[netip.Prefix]time.Time
// the time at which we last expired old routes
LastCleanup time.Time
}

View File

@ -35,6 +35,7 @@ import (
xmaps "golang.org/x/exp/maps"
"gvisor.dev/gvisor/pkg/tcpip"
"tailscale.com/appc"
"tailscale.com/appc/routeinfo"
"tailscale.com/client/tailscale/apitype"
"tailscale.com/clientupdate"
"tailscale.com/control/controlclient"
@ -6250,6 +6251,49 @@ func (b *LocalBackend) UnadvertiseRoute(toRemove ...netip.Prefix) error {
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
}
const routeInfoStateStoreKey ipn.StateKey = "_routeInfo"
// StoreRouteInfo implements the appc.RouteAdvertiser interface. It stores
// RouteInfo to StateStore per profile.
func (b *LocalBackend) StoreRouteInfo(ri *routeinfo.RouteInfo) error {
b.mu.Lock()
defer b.mu.Unlock()
if b.pm.CurrentProfile().ID == "" {
return nil
}
key := namespaceKeyForCurrentProfile(b.pm, routeInfoStateStoreKey)
bs, err := json.Marshal(ri)
if err != nil {
return err
}
return b.pm.WriteState(key, bs)
}
// 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()
if b.pm.CurrentProfile().ID == "" {
return &routeinfo.RouteInfo{}, nil
}
key := namespaceKeyForCurrentProfile(b.pm, routeInfoStateStoreKey)
bs, err := b.pm.Store().ReadState(key)
if err != nil {
return nil, err
}
ri := &routeinfo.RouteInfo{}
if err := json.Unmarshal(bs, ri); err != nil {
return nil, err
}
return ri, 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

@ -23,6 +23,7 @@ import (
"golang.org/x/net/dns/dnsmessage"
"tailscale.com/appc"
"tailscale.com/appc/appctest"
"tailscale.com/appc/routeinfo"
"tailscale.com/control/controlclient"
"tailscale.com/drive"
"tailscale.com/drive/driveimpl"
@ -2634,3 +2635,49 @@ func (b *LocalBackend) SetPrefsForTest(newp *ipn.Prefs) {
defer unlock()
b.setPrefsLockedOnEntry(newp, unlock)
}
func TestReadWriteRouteInfo(t *testing.T) {
// test can read what's written
prefix1 := netip.MustParsePrefix("1.2.3.4/32")
prefix2 := netip.MustParsePrefix("1.2.3.5/32")
prefix3 := netip.MustParsePrefix("1.2.3.6/32")
now := time.Now()
discovered := make(map[string]*routeinfo.DatedRoutes)
routes := make(map[netip.Prefix]time.Time)
routes[prefix3] = now
discovered["example.com"] = &routeinfo.DatedRoutes{
LastCleanup: now,
Routes: routes,
}
b := newTestBackend(t)
ri := routeinfo.RouteInfo{
Local: []netip.Prefix{prefix1},
Control: []netip.Prefix{prefix2},
Discovered: discovered,
}
if err := b.StoreRouteInfo(&ri); err != nil {
t.Fatal(err)
}
readRi, err := b.ReadRouteInfo()
if err != nil {
t.Fatal(err)
}
if len(readRi.Local) != 1 || len(readRi.Control) != 1 || len(readRi.Discovered) != 1 {
t.Fatal("read Ri expected to be same shape as ri")
}
if readRi.Local[0] != ri.Local[0] {
t.Fatalf("wanted %v, got %v", ri.Local[0], readRi.Local[0])
}
if readRi.Control[0] != ri.Control[0] {
t.Fatalf("wanted %v, got %v", ri.Control[0], readRi.Control[0])
}
dr := readRi.Discovered["example.com"]
if dr.LastCleanup.Compare(now) != 0 {
t.Fatalf("wanted %v, got %v", now, dr.LastCleanup)
}
if len(dr.Routes) != 1 {
t.Fatalf("read Ri expected to be same shape as ri")
}
if dr.Routes[prefix3].Compare(routes[prefix3]) != 0 {
t.Fatalf("wanted %v, got %v", routes[prefix3], dr.Routes[prefix3])
}
}