WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
3 changed files with 91 additions and 2 deletions
Showing only changes of commit 6a635c4e55 - Show all commits
+15 -2
View File
@@ -6155,10 +6155,23 @@ func (b *LocalBackend) authReconfigLocked() {
}
rcfg := b.routerConfigLocked(cfg, prefs, nm)
// Add these extra Allowed IPs after router configuration, because the expected
// extension (features/conn25), does not want these routes installed on the OS.
// Push each peer's extra WireGuard-only allowed IPs (the conn25
// extension's Transit IPs) into the route manager, which feeds
// them to WireGuard via the outbound peer lookup and the per-peer
// allowed source prefixes (including for lazily created peers)
// while keeping them out of the OS route set, because the
// expected extension (features/conn25) does not want these routes
// installed on the OS. This runs after routerConfigLocked above
// for the same reason: rcfg is derived from cfg.Peers, which must
// not yet include the extras.
// See also [Hooks.ExtraWireGuardAllowedIPs].
if extraAllowedIPsFn, ok := b.extHost.hooks.ExtraWireGuardAllowedIPs.GetOk(); ok {
for k := range cn.updateRouteManagerExtras(extraAllowedIPsFn) {
b.e.SyncDevicePeer(k)
}
// Also append the extras to cfg.Peers so the full SyncPeers
// in Reconfig below doesn't strip them from active peers.
// This loop goes away when cfg.Peers does.
for i := range cfg.Peers {
extras := extraAllowedIPsFn(cfg.Peers[i].PublicKey)
cfg.Peers[i].AllowedIPs = extras.AppendTo(cfg.Peers[i].AllowedIPs)
+22
View File
@@ -938,6 +938,28 @@ func (nb *nodeBackend) updateRouteManagerPrefs(p routePrefs) (changedAllowedIPs
return res.AllowedIPs
}
// updateRouteManagerExtras pushes each peer's extra WireGuard-only
// allowed IPs into the route manager, obtained by calling fn (the
// [ipnext.Hooks.ExtraWireGuardAllowedIPs] hook) with each peer's
// public key.
//
// It returns the peers whose allowed source prefixes changed as a
// result, as described by [routemanager.Result.AllowedIPs].
func (nb *nodeBackend) updateRouteManagerExtras(fn func(key.NodePublic) views.Slice[netip.Prefix]) (changedAllowedIPs map[key.NodePublic][]netip.Prefix) {
nb.mu.Lock()
defer nb.mu.Unlock()
var extras map[tailcfg.NodeID][]netip.Prefix
for id, p := range nb.peers {
if pfxs := fn(p.Key()); pfxs.Len() > 0 {
mak.Set(&extras, id, pfxs.AsSlice())
}
}
rt := nb.routeMgr.Begin()
rt.SetExtraAllowedIPs(extras)
res := rt.Commit()
return res.AllowedIPs
}
// osRoutes returns the sorted set of prefixes that the route manager
// wants programmed into the OS routing table.
func (nb *nodeBackend) osRoutes() []netip.Prefix {
+54
View File
@@ -17,6 +17,7 @@ import (
"tailscale.com/tstest"
"tailscale.com/types/key"
"tailscale.com/types/netmap"
"tailscale.com/types/views"
"tailscale.com/util/eventbus"
"tailscale.com/util/mak"
"tailscale.com/util/set"
@@ -505,3 +506,56 @@ func TestNodeBackendDiscoChangedDelta(t *testing.T) {
t.Errorf("upsert(after TSMP entry GC) discoChanged = %v; want %v", got, nk2)
}
}
func TestNodeBackendRouteManagerExtras(t *testing.T) {
nb := newNodeBackend(t.Context(), tstest.WhileTestRunningLogger(t), eventbus.New())
n := &tailcfg.Node{
ID: 1,
Key: key.NewNode().Public(),
HomeDERP: 1,
Addresses: []netip.Prefix{
netip.MustParsePrefix("100.64.0.1/32"),
},
}
n.AllowedIPs = n.Addresses
p1 := n.View()
nb.SetNetMap(&netmap.NetworkMap{Peers: []tailcfg.NodeView{p1}})
transit := netip.MustParsePrefix("fe80::1234/128")
extrasFor := func(k key.NodePublic) views.Slice[netip.Prefix] {
if k == p1.Key() {
return views.SliceOf([]netip.Prefix{transit})
}
return views.Slice[netip.Prefix]{}
}
// Installing extras reports the peer's allowed prefixes as
// changed and adds the transit IP to the outbound table.
changed := nb.updateRouteManagerExtras(extrasFor)
if len(changed) != 1 || !slices.Contains(changed[p1.Key()], transit) {
t.Errorf("updateRouteManagerExtras changed = %v; want %v including %v", changed, p1.Key(), transit)
}
if pr, ok := nb.routeMgr.Outbound().Lookup(transit.Addr()); !ok || pr.Key != p1.Key() {
t.Errorf("Outbound lookup %v = %v, %v; want %v", transit.Addr(), pr, ok, p1.Key())
}
if nb.routeMgr.OSRoutes().Get(transit) {
t.Errorf("OSRoutes contains %v; extras must not reach the OS route set", transit)
}
// An unchanged hook result is a no-op.
if changed := nb.updateRouteManagerExtras(extrasFor); changed != nil {
t.Errorf("unchanged extras reported changes: %v", changed)
}
// A hook that no longer returns extras removes them.
changed = nb.updateRouteManagerExtras(func(key.NodePublic) views.Slice[netip.Prefix] {
return views.Slice[netip.Prefix]{}
})
if len(changed) != 1 {
t.Errorf("clearing extras changed = %v; want just %v", changed, p1.Key())
}
if _, ok := nb.routeMgr.Outbound().Lookup(transit.Addr()); ok {
t.Errorf("Outbound still routes %v after extras cleared", transit.Addr())
}
}