Compare commits
14 Commits
knyar/inte
...
fran/appc-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
735838a0f1 | ||
|
|
2dfe7593ed | ||
|
|
f8ce2cbb75 | ||
|
|
4db1ea4729 | ||
|
|
db01cc62e0 | ||
|
|
26fba734a7 | ||
|
|
6b304b5c10 | ||
|
|
93697e9363 | ||
|
|
4d40443ccd | ||
|
|
34b49b5793 | ||
|
|
7fa9bf10dd | ||
|
|
28bba7b3db | ||
|
|
9f0bb29912 | ||
|
|
d0dd8fd38d |
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
78
ipn/prefs.go
78
ipn/prefs.go
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user