mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
appc: add RouteInfo struct and persist it to StateStore
Lays the groundwork for the ability to persist app connectors discovered routes, which will allow us to stop advertising routes for a domain if the app connector no longer monitors that domain. Updates #11008 Signed-off-by: Fran Bull <fran@tailscale.com>
This commit is contained in:
parent
b2b49cb3d5
commit
79836e7bfd
@ -36,6 +36,19 @@ type RouteAdvertiser interface {
|
||||
UnadvertiseRoute(...netip.Prefix) error
|
||||
}
|
||||
|
||||
// RouteInfo is a data structure used to persist the in memory state of an AppConnector
|
||||
// so that we can know, even after a restart, which routes came from ACLs and which were
|
||||
// learned from domains.
|
||||
type RouteInfo struct {
|
||||
// Control is the routes from the 'routes' section of an app connector acl.
|
||||
Control []netip.Prefix `json:",omitempty"`
|
||||
// Domains are the routes discovered by observing DNS lookups for configured domains.
|
||||
Domains map[string][]netip.Addr `json:",omitempty"`
|
||||
// Wildcards are the configured DNS lookup domains to observe. When a DNS query matches Wildcards,
|
||||
// its result is added to Domains.
|
||||
Wildcards []string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// AppConnector is an implementation of an AppConnector that performs
|
||||
// its function as a subsystem inside of a tailscale node. At the control plane
|
||||
// side App Connector routing is configured in terms of domains rather than IP
|
||||
|
@ -6210,6 +6210,43 @@ 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"
|
||||
|
||||
func (b *LocalBackend) storeRouteInfo(ri *appc.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)
|
||||
}
|
||||
|
||||
func (b *LocalBackend) readRouteInfoLocked() (*appc.RouteInfo, error) {
|
||||
if b.pm.CurrentProfile().ID == "" {
|
||||
return &appc.RouteInfo{}, nil
|
||||
}
|
||||
key := namespaceKeyForCurrentProfile(b.pm, routeInfoStateStoreKey)
|
||||
bs, err := b.pm.Store().ReadState(key)
|
||||
ri := &appc.RouteInfo{}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
|
@ -3454,3 +3454,66 @@ func TestEnableAutoUpdates(t *testing.T) {
|
||||
t.Fatalf("disabling auto-updates: got error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadWriteRouteInfo(t *testing.T) {
|
||||
// set up a backend with more than one profile
|
||||
b := newTestBackend(t)
|
||||
prof1 := ipn.LoginProfile{ID: "id1", Key: "key1"}
|
||||
prof2 := ipn.LoginProfile{ID: "id2", Key: "key2"}
|
||||
b.pm.knownProfiles["id1"] = &prof1
|
||||
b.pm.knownProfiles["id2"] = &prof2
|
||||
b.pm.currentProfile = &prof1
|
||||
|
||||
// set up routeInfo
|
||||
ri1 := &appc.RouteInfo{}
|
||||
ri1.Wildcards = []string{"1"}
|
||||
|
||||
ri2 := &appc.RouteInfo{}
|
||||
ri2.Wildcards = []string{"2"}
|
||||
|
||||
// read before write
|
||||
readRi, err := b.readRouteInfoLocked()
|
||||
if readRi != nil {
|
||||
t.Fatalf("read before writing: want nil, got %v", readRi)
|
||||
}
|
||||
if err != ipn.ErrStateNotExist {
|
||||
t.Fatalf("read before writing: want %v, got %v", ipn.ErrStateNotExist, err)
|
||||
}
|
||||
|
||||
// write the first routeInfo
|
||||
if err := b.storeRouteInfo(ri1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// write the other routeInfo as the other profile
|
||||
if err := b.pm.SwitchProfile("id2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := b.storeRouteInfo(ri2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// read the routeInfo of the first profile
|
||||
if err := b.pm.SwitchProfile("id1"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
readRi, err = b.readRouteInfoLocked()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Equal(readRi.Wildcards, ri1.Wildcards) {
|
||||
t.Fatalf("read prof1 routeInfo wildcards: want %v, got %v", ri1.Wildcards, readRi.Wildcards)
|
||||
}
|
||||
|
||||
// read the routeInfo of the second profile
|
||||
if err := b.pm.SwitchProfile("id2"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
readRi, err = b.readRouteInfoLocked()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !slices.Equal(readRi.Wildcards, ri2.Wildcards) {
|
||||
t.Fatalf("read prof2 routeInfo wildcards: want %v, got %v", ri2.Wildcards, readRi.Wildcards)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user