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
10 changed files with 36 additions and 161 deletions
Showing only changes of commit 4660a961eb - Show all commits
-1
View File
@@ -928,7 +928,6 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
golang.org/x/crypto/argon2 from tailscale.com/tka
-1
View File
@@ -200,7 +200,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
-1
View File
@@ -221,7 +221,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
golang.org/x/crypto/blake2b from golang.org/x/crypto/nacl/box
-1
View File
@@ -521,7 +521,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
tailscale.com/wgengine/router/osrouter from tailscale.com/feature/condregister
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
W 💣 tailscale.com/wgengine/winnet from tailscale.com/wgengine/router/osrouter
-1
View File
@@ -326,7 +326,6 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
golang.org/x/crypto/argon2 from tailscale.com/tka
+13 -9
View File
@@ -107,7 +107,6 @@ import (
"tailscale.com/wgengine/magicsock"
"tailscale.com/wgengine/router"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
)
var controlDebugFlags = getControlDebugFlags()
@@ -2502,8 +2501,8 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
needsAuthReconfig = needsAuthReconfig || peersUpsertedOrRemoved
if needsAuthReconfig {
if peersUpsertedOrRemoved {
if nm := cn.netMapWithPeers(); nm != nil {
b.e.SetSelfNode(nm.SelfNode)
if self := cn.Self(); self.Valid() {
b.e.SetSelfNode(self)
}
}
b.authReconfigLocked()
@@ -6054,7 +6053,10 @@ func (b *LocalBackend) authReconfigLocked() {
cn := b.currentNode()
nm := cn.netMapWithPeers()
// Note this netmap does not have its Peers populated. Nothing
// below needs them; per-peer work rides the incremental route
// manager and engine paths instead.
nm := cn.NetMap()
if nm == nil {
b.logf("[v1] authReconfig: netmap not yet valid. Skipping.")
return
@@ -6100,10 +6102,12 @@ func (b *LocalBackend) authReconfigLocked() {
priv = key.NodePrivate{}
}
cfg, err := nmcfg.WGCfg(priv, nm, b.logf, flags, prefs.ExitNodeID())
if err != nil {
b.logf("wgcfg: %v", err)
return
// The config carries no peers; wireguard-go gets those from the
// live per-peer config source installed via
// [wgengine.Engine.SetPeerConfigFunc], fed by the route manager.
cfg := &wgcfg.Config{
PrivateKey: priv,
Addresses: nm.GetAddresses().AsSlice(),
}
// Note: b.goos (set only by tests) speaks runtime.GOOS while
@@ -6146,7 +6150,7 @@ func (b *LocalBackend) authReconfigLocked() {
// the new config never see a stale peer table.
b.setDataPlanePeerRoutes()
err = b.e.Reconfig(cfg, rcfg, dcfg)
err := b.e.Reconfig(cfg, rcfg, dcfg)
if err == wgengine.ErrNoChanges {
return
}
+6 -8
View File
@@ -397,12 +397,11 @@ func (m *Mutation) UpsertPeer(n tailcfg.NodeView) {
// peerViewOf reduces a tailcfg.NodeView to the routing-relevant
// peerView.
//
// It mirrors the peer and prefix filtering in nmcfg.WGCfg: peers we
// cannot communicate with (expired, or predating both DERP and disco)
// contribute no prefixes. They remain tracked by ID and key so that a
// later update can make them routable again. AllowedIPs is the sole
// source of prefixes; an address absent from AllowedIPs is not
// routable. The self-vs-route split mirrors nmcfg's cidrIsSubnet:
// Peers we cannot communicate with (expired, or predating both DERP
// and disco) contribute no prefixes. They remain tracked by ID and key
// so that a later update can make them routable again. AllowedIPs is
// the sole source of prefixes; an address absent from AllowedIPs is
// not routable. For the self-vs-route split,
// single Tailscale IPs are never subnets, so a VIP service address
// hosted by the peer lands in SelfAddrs and stays routable without
// Prefs.RouteAll.
@@ -662,8 +661,7 @@ func normalizePrefix(p netip.Prefix) netip.Prefix {
// non-address bits set, mirroring the defensive check in
// ipnlocal.peerRoutes. It includes the peer's extra allowed IPs,
// except for peers that contribute no addresses or routes of their
// own (expired or otherwise non-communicable peers, which mirrors
// nmcfg.WGCfg dropping such peers entirely).
// own (expired or otherwise non-communicable peers).
func (rm *RouteManager) contribs(p peerView) map[netip.Prefix]contribKind {
c := make(map[netip.Prefix]contribKind, len(p.SelfAddrs)+len(p.Routes))
add := func(pfx netip.Prefix, kind contribKind) {
-1
View File
@@ -321,7 +321,6 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
tailscale.com/wgengine/router from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
💣 tailscale.com/wgengine/wgint from tailscale.com/wgengine+
tailscale.com/wgengine/wglog from tailscale.com/wgengine
golang.org/x/crypto/argon2 from tailscale.com/tka
+17 -21
View File
@@ -81,7 +81,6 @@ import (
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/filter/filtertype"
"tailscale.com/wgengine/wgcfg"
"tailscale.com/wgengine/wgcfg/nmcfg"
"tailscale.com/wgengine/wglog"
)
@@ -246,6 +245,17 @@ func newMagicStackWithKey(t testing.TB, logf logger.Logf, ln nettype.PacketListe
}
}
// wgCfgOf builds a test node's wgcfg.Config from its netmap the same
// way ipnlocal.authReconfigLocked does in production: just the private
// key and self addresses. Peers ride the per-peer config source
// instead; see [magicStack.Reconfig].
func wgCfgOf(pk key.NodePrivate, nm *netmap.NetworkMap) *wgcfg.Config {
return &wgcfg.Config{
PrivateKey: pk,
Addresses: nm.GetAddresses().AsSlice(),
}
}
// Reconfig applies cfg and peers to the stack's WireGuard device and
// tun-layer data plane. In production these flow from LocalBackend
// (via [tailscale.com/wgengine.Engine.Reconfig] and the live per-peer
@@ -259,8 +269,8 @@ func (s *magicStack) Reconfig(cfg *wgcfg.Config, peers []tailcfg.NodeView) error
rm := routemanager.New(nil)
mut := rm.Begin()
// Mirror the netmap.AllowSubnetRoutes flag the tests pass to
// nmcfg.WGCfg.
// Tests want subnet routes accepted, matching what production
// gets from Prefs.RouteAll.
mut.SetPrefs(routemanager.Prefs{RouteAll: true})
// idByKey stands in for nodeBackend's public-key-to-node-ID
// index, which LocalBackend uses to serve the engine's per-peer
@@ -412,12 +422,7 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM
for i, m := range ms {
nm := buildNetmapLocked(i)
m.conn.SetNetworkMap(nm.SelfNode, nm.Peers)
wg, err := nmcfg.WGCfg(ms[i].privateKey, nm, logf, 0, "")
if err != nil {
// We're too far from the *testing.T to be graceful,
// blow up. Shouldn't happen anyway.
panic(fmt.Sprintf("failed to construct wgcfg from netmap: %v", err))
}
wg := wgCfgOf(ms[i].privateKey, nm)
if err := m.Reconfig(wg, nm.Peers); err != nil {
if ctx.Err() != nil || errors.Is(err, errConnClosed) {
// shutdown race, don't care.
@@ -2568,10 +2573,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
}
m.conn.SetNetworkMap(nm.SelfNode, nm.Peers)
cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "")
if err != nil {
t.Fatal(err)
}
cfg := wgCfgOf(m.privateKey, nm)
m.Reconfig(cfg, nm.Peers)
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
@@ -2629,10 +2631,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
}
m.conn.SetNetworkMap(nm.SelfNode, nm.Peers)
cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "")
if err != nil {
t.Fatal(err)
}
cfg := wgCfgOf(m.privateKey, nm)
m.Reconfig(cfg, nm.Peers)
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
@@ -2669,10 +2668,7 @@ func applyNetworkMap(t *testing.T, m *magicStack, nm *netmap.NetworkMap) {
m.conn.noV6.Store(true)
// Turn the network map into a wireguard config (for the tailscale internal wireguard device).
cfg, err := nmcfg.WGCfg(m.privateKey, nm, t.Logf, netmap.AllowSubnetRoutes, "")
if err != nil {
t.Fatal(err)
}
cfg := wgCfgOf(m.privateKey, nm)
// Apply the wireguard config to the tailscale internal wireguard device.
if err := m.Reconfig(cfg, nm.Peers); err != nil {
t.Fatal(err)
-117
View File
@@ -1,117 +0,0 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package nmcfg converts a controlclient.NetMap into a wgcfg config.
package nmcfg
import (
"bufio"
"cmp"
"fmt"
"net/netip"
"strings"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netmap"
"tailscale.com/wgengine/wgcfg"
)
func nodeDebugName(n tailcfg.NodeView) string {
name, _, _ := strings.Cut(cmp.Or(n.Name(), n.Hostinfo().Hostname()), ".")
return name
}
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node tailcfg.NodeView, cidr netip.Prefix) bool {
if cidr.Bits() == 0 {
return false
}
if !cidr.IsSingleIP() {
return true
}
if tsaddr.IsTailscaleIP(cidr.Addr()) {
return false
}
for _, selfCIDR := range node.Addresses().All() {
if cidr == selfCIDR {
return false
}
}
return true
}
// WGCfg returns the NetworkMaps's WireGuard configuration.
//
// The config does not include peers; wireguard-go gets those from the
// live per-peer config source installed via
// [tailscale.com/wgengine.Engine.SetPeerConfigFunc], fed by the route
// manager. WGCfg still walks the peers to log which ones are not
// routable and why, mirroring the route manager's filtering.
func WGCfg(pk key.NodePrivate, nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags, exitNode tailcfg.StableNodeID) (*wgcfg.Config, error) {
cfg := &wgcfg.Config{
PrivateKey: pk,
Addresses: nm.GetAddresses().AsSlice(),
}
var skippedExitNode, skippedSubnetRouter, skippedExpired []tailcfg.NodeView
for _, peer := range nm.Peers {
if peer.DiscoKey().IsZero() && peer.HomeDERP() == 0 && !peer.IsWireGuardOnly() {
// Peer predates both DERP and active discovery, we cannot
// communicate with it.
logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key().ShortString())
continue
}
// Skip expired peers; we'll end up failing to connect to them
// anyway, since control intentionally breaks node keys for
// expired peers so that we can't discover endpoints via DERP.
if peer.Expired() {
skippedExpired = append(skippedExpired, peer)
continue
}
didExitNodeLog := false
for _, allowedIP := range peer.AllowedIPs().All() {
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
if didExitNodeLog {
// Don't log about both the IPv4 /0 and IPv6 /0.
continue
}
didExitNodeLog = true
skippedExitNode = append(skippedExitNode, peer)
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & netmap.AllowSubnetRoutes) == 0 {
skippedSubnetRouter = append(skippedSubnetRouter, peer)
}
}
}
}
logList := func(title string, nodes []tailcfg.NodeView) {
if len(nodes) == 0 {
return
}
logf("[v1] wgcfg: %s from %d nodes: %s", title, len(nodes), logger.ArgWriter(func(bw *bufio.Writer) {
const max = 5
for i, n := range nodes {
if i == max {
fmt.Fprintf(bw, "... +%d", len(nodes)-max)
return
}
if i > 0 {
bw.WriteString(", ")
}
fmt.Fprintf(bw, "%s (%s)", nodeDebugName(n), n.StableID())
}
}))
}
logList("skipped unselected exit nodes", skippedExitNode)
logList("did not accept subnet routes", skippedSubnetRouter)
logList("skipped expired peers", skippedExpired)
return cfg, nil
}