mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
net/tstun,wgengine,ipn/ipnlocal: make tstun's peerConfigTable use RouteManager table
Previously tstun.Wrapper.SetWGConfig walked wgcfg.Config.Peers on every netmap to rebuild its own IP-to-peer table for masquerade NAT rewrites and jailed-peer classification. Now the tun layer instead consumes the route manager's shared immutable outbound snapshot directly, via a new Engine.SetPeerRoutes method: LocalBackend pushes the snapshot (plus this node's native Tailscale addresses) after every route manager commit that can change it, and per-packet lookups read the interned PeerRoute attributes from that table. When no current peer is jailed or masqueraded, LocalBackend installs a nil table (gated on RouteManager.HasDataPlaneAttrs), preserving the per-packet nil-check fast path. The exitNodeRequiresMasq machinery is deleted: its purpose was populating the table with all peers so that more-specific entries shadow an exit node's /0, and the always-full route manager table gives that shadowing inherently. This is the last step before removing the Peers field from wgcfg.Config. Updates #12542 Change-Id: Ifce09ca929a3f2511303ca1d6efdd583739494ce Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
e4144230f4
commit
f0ce89b715
@ -811,7 +811,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
@ -109,7 +109,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
||||
|
||||
@ -126,7 +126,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/ping from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/portmapper/portmappertype from tailscale.com/net/netcheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
tailscale.com/net/stun from tailscale.com/net/netcheck+
|
||||
|
||||
@ -385,7 +385,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/net/proxymux from tailscale.com/cmd/tailscaled
|
||||
tailscale.com/net/routecheck from tailscale.com/feature/routecheck+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routetable from tailscale.com/doctor/routetable
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock+
|
||||
tailscale.com/net/socks5 from tailscale.com/cmd/tailscaled
|
||||
|
||||
@ -210,7 +210,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
@ -31,6 +31,7 @@
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"go4.org/mem"
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
@ -62,6 +63,7 @@
|
||||
"tailscale.com/net/netns"
|
||||
"tailscale.com/net/netutil"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/net/traffic"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tsdial"
|
||||
@ -2516,6 +2518,7 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
for k := range deltaRes.ChangedAllowedIPs {
|
||||
b.e.SyncDevicePeer(k)
|
||||
}
|
||||
b.setDataPlanePeerRoutes()
|
||||
|
||||
// Reset the WireGuard session for peers whose disco key changed in
|
||||
// a way that indicates a restart, flushing their dead session keys;
|
||||
@ -2530,11 +2533,9 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
||||
// and its lazy peer creation reads live state via
|
||||
// [wgengine.Engine.SetPeerConfigFunc]. What still rides
|
||||
// authReconfig is everything else derived from the full peer set:
|
||||
// OS routes (router.Config), the quad-100 resolver's MagicDNS
|
||||
// hosts map (dnsConfigForNetmap), and tstun's per-peer config
|
||||
// (masquerade addresses and jailed peers, via SetWGConfig). Once
|
||||
// those become delta-aware too, this can be gated on the route
|
||||
// manager's OS-routes changes and the tstun-relevant fields
|
||||
// OS routes (router.Config) and the quad-100 resolver's MagicDNS
|
||||
// hosts map (dnsConfigForNetmap). Once those become delta-aware
|
||||
// too, this can be gated on the route manager's OS-routes changes
|
||||
// instead of firing on every peer change.
|
||||
needsAuthReconfig = needsAuthReconfig || peersUpsertedOrRemoved
|
||||
if needsAuthReconfig {
|
||||
@ -6183,6 +6184,13 @@ func (b *LocalBackend) authReconfigLocked() {
|
||||
cfg.Peers[i].AllowedIPs = extras.AppendTo(cfg.Peers[i].AllowedIPs)
|
||||
}
|
||||
}
|
||||
// The prefs and extras commits above can both change the outbound
|
||||
// table (such as installing the selected exit node's /0 routes),
|
||||
// so refresh the data plane's view of it. This must stay after
|
||||
// updateRouteManagerExtras, so the pushed snapshot includes its
|
||||
// commit, and before the Reconfig below, so packets flowing under
|
||||
// the new config never see a stale peer table.
|
||||
b.setDataPlanePeerRoutes()
|
||||
|
||||
err = b.e.Reconfig(cfg, rcfg, dcfg)
|
||||
if err == wgengine.ErrNoChanges {
|
||||
@ -6196,6 +6204,29 @@ func (b *LocalBackend) authReconfigLocked() {
|
||||
}
|
||||
}
|
||||
|
||||
// setDataPlanePeerRoutes pushes the route manager's outbound table and
|
||||
// this node's native Tailscale addresses into the engine's tun-layer
|
||||
// data plane, which uses them for per-packet NAT rewrites and
|
||||
// jailed-filter selection. It must be called after every route manager
|
||||
// commit that can change the outbound table or the set of peers with
|
||||
// data-plane attributes: netmap updates (full or delta), prefs changes,
|
||||
// and extra-allowed-IP changes.
|
||||
//
|
||||
// When no current peer is jailed or masqueraded, it installs nil, which
|
||||
// keeps the data plane's per-packet fast path to a nil check.
|
||||
func (b *LocalBackend) setDataPlanePeerRoutes() {
|
||||
nb := b.currentNode()
|
||||
var native4, native6 netip.Addr
|
||||
var routes *bart.Table[*routemanager.PeerRoute]
|
||||
if nb.routeMgr.HasDataPlaneAttrs() {
|
||||
routes = nb.routeMgr.Outbound()
|
||||
if nm := nb.NetMap(); nm != nil {
|
||||
native4, native6 = tsaddr.FirstTailscaleAddrs(nm.GetAddresses().All())
|
||||
}
|
||||
}
|
||||
b.e.SetPeerRoutes(native4, native6, routes)
|
||||
}
|
||||
|
||||
// shouldUseOneCGNATRoute reports whether we should prefer to make one big
|
||||
// CGNAT /10 route rather than a /32 per peer.
|
||||
//
|
||||
@ -7312,6 +7343,7 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
|
||||
login = cmp.Or(profileFromView(nm.UserProfiles[nm.User()]).LoginName, "<missing-profile>")
|
||||
}
|
||||
discoChanged := b.currentNode().SetNetMap(nm)
|
||||
b.setDataPlanePeerRoutes()
|
||||
if ms, ok := b.sys.MagicSock.GetOK(); ok {
|
||||
if nm != nil {
|
||||
if nm.Cached {
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
"time"
|
||||
|
||||
qt "github.com/frankban/quicktest"
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
|
||||
@ -29,6 +30,7 @@
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tsd"
|
||||
@ -1961,6 +1963,9 @@ func (e *mockEngine) SetJailedFilter(f *filter.Filter) {
|
||||
e.mu.Unlock()
|
||||
}
|
||||
|
||||
func (e *mockEngine) SetPeerRoutes(native4, native6 netip.Addr, routes *bart.Table[*routemanager.PeerRoute]) {
|
||||
}
|
||||
|
||||
func (e *mockEngine) SetStatusCallback(cb wgengine.StatusCallback) {
|
||||
e.mu.Lock()
|
||||
e.statusCb = cb
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"iter"
|
||||
"net/netip"
|
||||
"slices"
|
||||
"sync"
|
||||
@ -317,3 +318,24 @@ func MapVia(siteID uint32, v4 netip.Prefix) (via netip.Prefix, err error) {
|
||||
copy(a[12:], ip4a[:])
|
||||
return netip.PrefixFrom(netip.AddrFrom16(a), v4.Bits()+64+32), nil
|
||||
}
|
||||
|
||||
// FirstTailscaleAddrs returns the first Tailscale IPv4 address and
|
||||
// the first Tailscale IPv6 address among the addresses of addrs'
|
||||
// prefixes, if any. The addrs sequence is typically a node's own
|
||||
// address list, either a slice (via [slices.All]) or a view (via
|
||||
// [views.Slice.All]).
|
||||
func FirstTailscaleAddrs(addrs iter.Seq2[int, netip.Prefix]) (a4, a6 netip.Addr) {
|
||||
for _, pfx := range addrs {
|
||||
a := pfx.Addr()
|
||||
switch {
|
||||
case a.Is4() && !a4.IsValid() && IsTailscaleIP(a):
|
||||
a4 = a
|
||||
case a.Is6() && !a6.IsValid() && IsTailscaleIP(a):
|
||||
a6 = a
|
||||
}
|
||||
if a4.IsValid() && a6.IsValid() {
|
||||
break
|
||||
}
|
||||
}
|
||||
return a4, a6
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
@ -290,3 +291,27 @@ func TestIsTailscaleIP(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFirstTailscaleAddrs(t *testing.T) {
|
||||
pfx := func(s string) netip.Prefix { return netip.MustParsePrefix(s) }
|
||||
addr := func(s string) netip.Addr { return netip.MustParseAddr(s) }
|
||||
tests := []struct {
|
||||
name string
|
||||
in []netip.Prefix
|
||||
want4 netip.Addr
|
||||
want6 netip.Addr
|
||||
}{
|
||||
{"empty", nil, netip.Addr{}, netip.Addr{}},
|
||||
{"both", []netip.Prefix{pfx("100.64.0.1/32"), pfx("fd7a:115c:a1e0::1/128")}, addr("100.64.0.1"), addr("fd7a:115c:a1e0::1")},
|
||||
{"first-of-each", []netip.Prefix{pfx("100.64.0.1/32"), pfx("100.64.0.2/32"), pfx("fd7a:115c:a1e0::1/128"), pfx("fd7a:115c:a1e0::2/128")}, addr("100.64.0.1"), addr("fd7a:115c:a1e0::1")},
|
||||
{"non-tailscale-skipped", []netip.Prefix{pfx("192.168.1.1/32"), pfx("2001:db8::1/128"), pfx("100.64.0.9/32")}, addr("100.64.0.9"), netip.Addr{}},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got4, got6 := FirstTailscaleAddrs(slices.All(tt.in))
|
||||
if got4 != tt.want4 || got6 != tt.want6 {
|
||||
t.Errorf("FirstTailscaleAddrs = %v, %v; want %v, %v", got4, got6, tt.want4, tt.want6)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
"io"
|
||||
"net/netip"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
@ -28,6 +27,7 @@
|
||||
"tailscale.com/feature/buildfeatures"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/packet/checksum"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/syncs"
|
||||
"tailscale.com/tstime/mono"
|
||||
@ -565,31 +565,9 @@ func (pc *peerConfigTable) dnat(p *packet.Parsed) {
|
||||
}
|
||||
}
|
||||
|
||||
// findV4 returns the first Tailscale IPv4 address in addrs.
|
||||
func findV4(addrs []netip.Prefix) netip.Addr {
|
||||
for _, ap := range addrs {
|
||||
a := ap.Addr()
|
||||
if a.Is4() && tsaddr.IsTailscaleIP(a) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// findV6 returns the first Tailscale IPv6 address in addrs.
|
||||
func findV6(addrs []netip.Prefix) netip.Addr {
|
||||
for _, ap := range addrs {
|
||||
a := ap.Addr()
|
||||
if a.Is6() && tsaddr.IsTailscaleIP(a) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
return netip.Addr{}
|
||||
}
|
||||
|
||||
// peerConfigTable contains configuration for individual peers and related
|
||||
// information necessary to perform peer-specific operations. It should be
|
||||
// treated as immutable.
|
||||
// peerConfigTable contains the per-peer route attributes and related
|
||||
// information necessary to perform peer-specific operations. It should
|
||||
// be treated as immutable.
|
||||
//
|
||||
// The nil value is a valid configuration.
|
||||
type peerConfigTable struct {
|
||||
@ -602,57 +580,17 @@ type peerConfigTable struct {
|
||||
// inbound packet is IPv6.
|
||||
nativeAddr4, nativeAddr6 netip.Addr
|
||||
|
||||
// byIP contains configuration for each peer, indexed by a peer's IP
|
||||
// address(es).
|
||||
byIP bart.Table[*peerConfig]
|
||||
|
||||
// masqAddrCounts is a count of peers by MasqueradeAsIP.
|
||||
// TODO? for logging
|
||||
masqAddrCounts map[netip.Addr]int
|
||||
}
|
||||
|
||||
// peerConfig is the configuration for a single peer.
|
||||
type peerConfig struct {
|
||||
// dstMasqAddr{4,6} are the addresses that should be used as the
|
||||
// source address when masquerading packets to this peer (i.e.
|
||||
// SNAT). If an address is not valid, the packet should not be
|
||||
// masqueraded for that address family.
|
||||
dstMasqAddr4 netip.Addr
|
||||
dstMasqAddr6 netip.Addr
|
||||
|
||||
// jailed is whether this peer is "jailed" (i.e. is restricted from being
|
||||
// able to initiate connections to this node). This is the case for shared
|
||||
// nodes.
|
||||
jailed bool
|
||||
// byIP maps a peer's IP addresses and routed prefixes to the
|
||||
// peer's route attributes. It is a shared immutable snapshot
|
||||
// from [routemanager.RouteManager.Outbound].
|
||||
byIP *bart.Table[*routemanager.PeerRoute]
|
||||
}
|
||||
|
||||
func (c *peerConfigTable) String() string {
|
||||
if c == nil {
|
||||
return "peerConfigTable(nil)"
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("peerConfigTable{")
|
||||
fmt.Fprintf(&b, "nativeAddr4: %v, ", c.nativeAddr4)
|
||||
fmt.Fprintf(&b, "nativeAddr6: %v, ", c.nativeAddr6)
|
||||
|
||||
// TODO: figure out how to iterate/debug/print c.byIP
|
||||
|
||||
b.WriteString("}")
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *peerConfig) String() string {
|
||||
if c == nil {
|
||||
return "peerConfig(nil)"
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString("peerConfig{")
|
||||
fmt.Fprintf(&b, "dstMasqAddr4: %v, ", c.dstMasqAddr4)
|
||||
fmt.Fprintf(&b, "dstMasqAddr6: %v, ", c.dstMasqAddr6)
|
||||
fmt.Fprintf(&b, "jailed: %v}", c.jailed)
|
||||
|
||||
return b.String()
|
||||
return fmt.Sprintf("peerConfigTable{nativeAddr4: %v, nativeAddr6: %v}", c.nativeAddr4, c.nativeAddr6)
|
||||
}
|
||||
|
||||
// mapDstIP returns the destination IP to use for a packet to dst.
|
||||
@ -675,10 +613,10 @@ func (pc *peerConfigTable) mapDstIP(src, oldDst netip.Addr) netip.Addr {
|
||||
return oldDst
|
||||
}
|
||||
|
||||
if oldDst.Is4() && pc.nativeAddr4.IsValid() && c.dstMasqAddr4 == oldDst {
|
||||
if oldDst.Is4() && pc.nativeAddr4.IsValid() && c.MasqAddr4 == oldDst {
|
||||
return pc.nativeAddr4
|
||||
}
|
||||
if oldDst.Is6() && pc.nativeAddr6.IsValid() && c.dstMasqAddr6 == oldDst {
|
||||
if oldDst.Is6() && pc.nativeAddr6.IsValid() && c.MasqAddr6 == oldDst {
|
||||
return pc.nativeAddr6
|
||||
}
|
||||
return oldDst
|
||||
@ -708,105 +646,17 @@ func (pc *peerConfigTable) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr {
|
||||
|
||||
// Perform SNAT based on the address family and whether we have a valid
|
||||
// addr.
|
||||
if oldSrc.Is4() && c.dstMasqAddr4.IsValid() {
|
||||
return c.dstMasqAddr4
|
||||
if oldSrc.Is4() && c.MasqAddr4.IsValid() {
|
||||
return c.MasqAddr4
|
||||
}
|
||||
if oldSrc.Is6() && c.dstMasqAddr6.IsValid() {
|
||||
return c.dstMasqAddr6
|
||||
if oldSrc.Is6() && c.MasqAddr6.IsValid() {
|
||||
return c.MasqAddr6
|
||||
}
|
||||
|
||||
// No SNAT; use old src
|
||||
return oldSrc
|
||||
}
|
||||
|
||||
// peerConfigTableFromWGConfig generates a peerConfigTable from nm. If NAT is
|
||||
// not required, and no additional configuration is present, it returns nil.
|
||||
func peerConfigTableFromWGConfig(wcfg *wgcfg.Config) *peerConfigTable {
|
||||
if wcfg == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
nativeAddr4 := findV4(wcfg.Addresses)
|
||||
nativeAddr6 := findV6(wcfg.Addresses)
|
||||
if !nativeAddr4.IsValid() && !nativeAddr6.IsValid() {
|
||||
return nil
|
||||
}
|
||||
|
||||
ret := &peerConfigTable{
|
||||
nativeAddr4: nativeAddr4,
|
||||
nativeAddr6: nativeAddr6,
|
||||
masqAddrCounts: make(map[netip.Addr]int),
|
||||
}
|
||||
|
||||
// When using an exit node that requires masquerading, we need to
|
||||
// fill out the routing table with all peers not just the ones that
|
||||
// require masquerading.
|
||||
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
|
||||
for _, p := range wcfg.Peers {
|
||||
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
|
||||
if isExitNode {
|
||||
hasMasqAddr := false ||
|
||||
(p.V4MasqAddr != nil && p.V4MasqAddr.IsValid()) ||
|
||||
(p.V6MasqAddr != nil && p.V6MasqAddr.IsValid())
|
||||
if hasMasqAddr {
|
||||
exitNodeRequiresMasq = true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
byIPSize := 0
|
||||
for i := range wcfg.Peers {
|
||||
p := &wcfg.Peers[i]
|
||||
|
||||
// Build a routing table that configures DNAT (i.e. changing
|
||||
// the V4MasqAddr/V6MasqAddr for a given peer to the current
|
||||
// peer's v4/v6 IP).
|
||||
var addrToUse4, addrToUse6 netip.Addr
|
||||
if p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
||||
addrToUse4 = *p.V4MasqAddr
|
||||
ret.masqAddrCounts[addrToUse4]++
|
||||
}
|
||||
if p.V6MasqAddr != nil && p.V6MasqAddr.IsValid() {
|
||||
addrToUse6 = *p.V6MasqAddr
|
||||
ret.masqAddrCounts[addrToUse6]++
|
||||
}
|
||||
|
||||
// If the exit node requires masquerading, set the masquerade
|
||||
// addresses to our native addresses.
|
||||
if exitNodeRequiresMasq {
|
||||
if !addrToUse4.IsValid() && nativeAddr4.IsValid() {
|
||||
addrToUse4 = nativeAddr4
|
||||
}
|
||||
if !addrToUse6.IsValid() && nativeAddr6.IsValid() {
|
||||
addrToUse6 = nativeAddr6
|
||||
}
|
||||
}
|
||||
|
||||
if !addrToUse4.IsValid() && !addrToUse6.IsValid() && !p.IsJailed {
|
||||
// NAT not required for this peer.
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the same peer configuration for each address of the peer.
|
||||
pc := &peerConfig{
|
||||
dstMasqAddr4: addrToUse4,
|
||||
dstMasqAddr6: addrToUse6,
|
||||
jailed: p.IsJailed,
|
||||
}
|
||||
|
||||
// Insert an entry into our routing table for each allowed IP.
|
||||
for _, ip := range p.AllowedIPs {
|
||||
ret.byIP.Insert(ip, pc)
|
||||
byIPSize++
|
||||
}
|
||||
}
|
||||
if byIPSize == 0 && len(ret.masqAddrCounts) == 0 {
|
||||
return nil
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (pc *peerConfigTable) inboundPacketIsJailed(p *packet.Parsed) bool {
|
||||
if pc == nil {
|
||||
return false
|
||||
@ -815,7 +665,7 @@ func (pc *peerConfigTable) inboundPacketIsJailed(p *packet.Parsed) bool {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.jailed
|
||||
return c.Jailed
|
||||
}
|
||||
|
||||
func (pc *peerConfigTable) outboundPacketIsJailed(p *packet.Parsed) bool {
|
||||
@ -826,7 +676,7 @@ func (pc *peerConfigTable) outboundPacketIsJailed(p *packet.Parsed) bool {
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.jailed
|
||||
return c.Jailed
|
||||
}
|
||||
|
||||
// SetIPer is the interface expected to be implemented by the TAP implementation
|
||||
@ -836,16 +686,53 @@ type SetIPer interface {
|
||||
SetIP(ipV4, ipV6 netip.Addr) error
|
||||
}
|
||||
|
||||
// SetWGConfig is called when a new NetworkMap is received.
|
||||
// SetWGConfig is called when a new NetworkMap is received. Its only
|
||||
// remaining job is updating the TAP device's IP addresses; the
|
||||
// per-peer route attributes arrive via [Wrapper.SetPeerRoutes].
|
||||
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
|
||||
if t.isTAP {
|
||||
if sip, ok := t.tdev.(SetIPer); ok {
|
||||
sip.SetIP(findV4(wcfg.Addresses), findV6(wcfg.Addresses))
|
||||
sip.SetIP(tsaddr.FirstTailscaleAddrs(slices.All(wcfg.Addresses)))
|
||||
}
|
||||
}
|
||||
cfg := peerConfigTableFromWGConfig(wcfg)
|
||||
}
|
||||
|
||||
// SetPeerRoutes is called whenever this node's Tailscale addresses or
|
||||
// the route manager's outbound table change. native4 and native6 are
|
||||
// this node's own Tailscale addresses, and routes maps each peer's
|
||||
// addresses and routed prefixes to its route attributes.
|
||||
//
|
||||
// A nil routes table disables all per-packet peer processing (NAT
|
||||
// rewrites and jailed-filter selection); callers pass nil when no
|
||||
// current peer has any such attributes, which keeps the common
|
||||
// per-packet path to a nil check.
|
||||
//
|
||||
// Unchanged values are a cheap no-op, so callers can call it
|
||||
// unconditionally whenever the inputs might have changed: the routes
|
||||
// table is an immutable snapshot whose pointer identity means its
|
||||
// contents are unchanged.
|
||||
func (t *Wrapper) SetPeerRoutes(native4, native6 netip.Addr, routes *bart.Table[*routemanager.PeerRoute]) {
|
||||
var cfg *peerConfigTable
|
||||
if routes != nil && (native4.IsValid() || native6.IsValid()) {
|
||||
if old := t.peerConfig.Load(); old != nil &&
|
||||
old.byIP == routes && old.nativeAddr4 == native4 && old.nativeAddr6 == native6 {
|
||||
return
|
||||
}
|
||||
cfg = &peerConfigTable{
|
||||
nativeAddr4: native4,
|
||||
nativeAddr6: native6,
|
||||
byIP: routes,
|
||||
}
|
||||
} else if t.peerConfig.Load() == nil {
|
||||
// Uninstalling (cfg stays nil) over an already-nil config;
|
||||
// skip the Swap and its transition logging below.
|
||||
return
|
||||
}
|
||||
old := t.peerConfig.Swap(cfg)
|
||||
if !reflect.DeepEqual(old, cfg) {
|
||||
// Log only on nil-ness or native address transitions; the routes
|
||||
// table changes with every routing update and is too chatty to log.
|
||||
if (old == nil) != (cfg == nil) ||
|
||||
(old != nil && (old.nativeAddr4 != cfg.nativeAddr4 || old.nativeAddr6 != cfg.nativeAddr6)) {
|
||||
t.logf("peer config: %v", cfg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"unicode"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/tailscale/wireguard-go/tun/tuntest"
|
||||
@ -29,6 +30,7 @@
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/packet/checksum"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstime/mono"
|
||||
"tailscale.com/types/ipproto"
|
||||
@ -42,7 +44,6 @@
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/netstack/gro"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
)
|
||||
|
||||
func udp4(src, dst string, sport, dport uint16) []byte {
|
||||
@ -710,22 +711,43 @@ func TestFilterDiscoLoop(t *testing.T) {
|
||||
}
|
||||
|
||||
// TODO(andrew-d): refactor this test to no longer use addrFam, after #11945
|
||||
// removed it in peerConfigFromWGConfig
|
||||
func TestPeerCfg_NAT(t *testing.T) {
|
||||
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||
p := wgcfg.Peer{
|
||||
PublicKey: key.NewNode().Public(),
|
||||
AllowedIPs: []netip.Prefix{
|
||||
netip.PrefixFrom(ip, ip.BitLen()),
|
||||
},
|
||||
type testPeer struct {
|
||||
pr *routemanager.PeerRoute
|
||||
pfxs []netip.Prefix
|
||||
}
|
||||
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) testPeer {
|
||||
pr := &routemanager.PeerRoute{Key: key.NewNode().Public()}
|
||||
switch {
|
||||
case masqIP.Is4():
|
||||
pr.MasqAddr4 = masqIP
|
||||
case masqIP.Is6():
|
||||
pr.MasqAddr6 = masqIP
|
||||
}
|
||||
if masqIP.Is4() {
|
||||
p.V4MasqAddr = new(masqIP)
|
||||
return testPeer{
|
||||
pr: pr,
|
||||
pfxs: append([]netip.Prefix{netip.PrefixFrom(ip, ip.BitLen())}, otherAllowedIPs...),
|
||||
}
|
||||
}
|
||||
// peerCfgTable builds the peerConfigTable under test from peers,
|
||||
// as SetPeerRoutes would from the route manager's outbound table.
|
||||
// A nil peers means no table, matching an uninstalled config.
|
||||
peerCfgTable := func(selfNativeIP netip.Addr, peers []testPeer) *peerConfigTable {
|
||||
if peers == nil {
|
||||
return nil
|
||||
}
|
||||
pcfg := &peerConfigTable{byIP: &bart.Table[*routemanager.PeerRoute]{}}
|
||||
if selfNativeIP.Is4() {
|
||||
pcfg.nativeAddr4 = selfNativeIP
|
||||
} else {
|
||||
p.V6MasqAddr = new(masqIP)
|
||||
pcfg.nativeAddr6 = selfNativeIP
|
||||
}
|
||||
p.AllowedIPs = append(p.AllowedIPs, otherAllowedIPs...)
|
||||
return p
|
||||
for _, p := range peers {
|
||||
for _, pfx := range p.pfxs {
|
||||
pcfg.byIP.Insert(pfx, p.pr)
|
||||
}
|
||||
}
|
||||
return pcfg
|
||||
}
|
||||
test := func(addrFam ipproto.Version) {
|
||||
var (
|
||||
@ -734,7 +756,6 @@ func TestPeerCfg_NAT(t *testing.T) {
|
||||
selfNativeIP = netip.MustParseAddr("100.64.0.1")
|
||||
selfEIP1 = netip.MustParseAddr("100.64.1.1")
|
||||
selfEIP2 = netip.MustParseAddr("100.64.1.2")
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
|
||||
peer1IP = netip.MustParseAddr("100.64.0.2")
|
||||
peer2IP = netip.MustParseAddr("100.64.0.3")
|
||||
@ -749,7 +770,6 @@ func TestPeerCfg_NAT(t *testing.T) {
|
||||
selfNativeIP = netip.MustParseAddr("fd7a:115c:a1e0::a")
|
||||
selfEIP1 = netip.MustParseAddr("fd7a:115c:a1e0::1a")
|
||||
selfEIP2 = netip.MustParseAddr("fd7a:115c:a1e0::1b")
|
||||
selfAddrs = []netip.Prefix{netip.PrefixFrom(selfNativeIP, selfNativeIP.BitLen())}
|
||||
|
||||
peer1IP = netip.MustParseAddr("fd7a:115c:a1e0::b")
|
||||
peer2IP = netip.MustParseAddr("fd7a:115c:a1e0::c")
|
||||
@ -769,13 +789,13 @@ type dnatTest struct {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
wcfg *wgcfg.Config
|
||||
peers []testPeer // nil means no config at all
|
||||
snatMap map[netip.Addr]netip.Addr // dst -> src
|
||||
dnat []dnatTest
|
||||
}{
|
||||
{
|
||||
name: "no-cfg",
|
||||
wcfg: nil,
|
||||
name: "no-cfg",
|
||||
peers: nil,
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
peer2IP: selfNativeIP,
|
||||
@ -789,12 +809,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "single-peer-requires-nat",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
@ -810,12 +827,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "multiple-peers-require-nat",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
@ -831,12 +845,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "multiple-peers-require-nat-with-subnet",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, subnet),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, subnet),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
@ -852,12 +863,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "multiple-peers-require-nat-with-default-route",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, selfEIP1),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfEIP1,
|
||||
@ -873,12 +881,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "no-nat",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, noIP),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, noIP),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
@ -894,12 +899,9 @@ type dnatTest struct {
|
||||
},
|
||||
{
|
||||
name: "exit-node-require-nat-peer-doesnt",
|
||||
wcfg: &wgcfg.Config{
|
||||
Addresses: selfAddrs,
|
||||
Peers: []wgcfg.Peer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
peers: []testPeer{
|
||||
node(peer1IP, noIP),
|
||||
node(peer2IP, selfEIP2, exitRoute),
|
||||
},
|
||||
snatMap: map[netip.Addr]netip.Addr{
|
||||
peer1IP: selfNativeIP,
|
||||
@ -916,7 +918,7 @@ type dnatTest struct {
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(fmt.Sprintf("%v/%v", addrFam, tc.name), func(t *testing.T) {
|
||||
pcfg := peerConfigTableFromWGConfig(tc.wcfg)
|
||||
pcfg := peerCfgTable(selfNativeIP, tc.peers)
|
||||
for peer, want := range tc.snatMap {
|
||||
if got := pcfg.selectSrcIP(selfNativeIP, peer); got != want {
|
||||
t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want)
|
||||
@ -1154,3 +1156,60 @@ func TestInjectedReadCallsAppConnectorHook(t *testing.T) {
|
||||
t.Errorf("packet mismatch\nwant:\t% x\ngot:\t% x", wantPkt, gotPkt)
|
||||
}
|
||||
}
|
||||
|
||||
// Tests SetPeerRoutes's fast path: unchanged inputs (including the
|
||||
// nil-table case) must not replace the stored config, relying on the
|
||||
// routes table's immutable-snapshot pointer identity, so callers can
|
||||
// call it unconditionally on every routing update.
|
||||
func TestSetPeerRoutesFastPath(t *testing.T) {
|
||||
bus := eventbustest.NewBus(t)
|
||||
_, tun := newFakeTUN(t.Logf, bus, false)
|
||||
defer tun.Close()
|
||||
|
||||
// Installing nil over nil is a no-op.
|
||||
tun.SetPeerRoutes(netip.Addr{}, netip.Addr{}, nil)
|
||||
if got := tun.peerConfig.Load(); got != nil {
|
||||
t.Fatalf("peerConfig after nil install = %v; want nil", got)
|
||||
}
|
||||
|
||||
native4 := netip.MustParseAddr("100.64.0.1")
|
||||
native6 := netip.MustParseAddr("fd7a:115c:a1e0::1")
|
||||
routes := &bart.Table[*routemanager.PeerRoute]{}
|
||||
routes.Insert(netip.MustParsePrefix("100.64.0.2/32"), &routemanager.PeerRoute{Jailed: true})
|
||||
|
||||
tun.SetPeerRoutes(native4, native6, routes)
|
||||
installed := tun.peerConfig.Load()
|
||||
if installed == nil {
|
||||
t.Fatal("peerConfig = nil; want non-nil")
|
||||
}
|
||||
|
||||
// Same inputs: the stored config must be untouched.
|
||||
tun.SetPeerRoutes(native4, native6, routes)
|
||||
if got := tun.peerConfig.Load(); got != installed {
|
||||
t.Errorf("peerConfig replaced on unchanged inputs")
|
||||
}
|
||||
|
||||
// A new snapshot pointer installs a new config.
|
||||
routes2 := routes.InsertPersist(netip.MustParsePrefix("100.64.0.3/32"), &routemanager.PeerRoute{Jailed: true})
|
||||
tun.SetPeerRoutes(native4, native6, routes2)
|
||||
second := tun.peerConfig.Load()
|
||||
if second == installed {
|
||||
t.Error("peerConfig not replaced on changed routes table")
|
||||
}
|
||||
|
||||
// Changed native address installs a new config too.
|
||||
tun.SetPeerRoutes(netip.MustParseAddr("100.64.0.9"), native6, routes2)
|
||||
if got := tun.peerConfig.Load(); got == second {
|
||||
t.Error("peerConfig not replaced on changed native address")
|
||||
}
|
||||
|
||||
// Dropping back to nil works and is then a stable no-op.
|
||||
tun.SetPeerRoutes(netip.Addr{}, netip.Addr{}, nil)
|
||||
if got := tun.peerConfig.Load(); got != nil {
|
||||
t.Fatalf("peerConfig after uninstall = %v; want nil", got)
|
||||
}
|
||||
tun.SetPeerRoutes(netip.Addr{}, netip.Addr{}, nil)
|
||||
if got := tun.peerConfig.Load(); got != nil {
|
||||
t.Fatalf("peerConfig after second uninstall = %v; want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,7 +206,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
|
||||
tailscale.com/net/proxymux from tailscale.com/tsnet
|
||||
tailscale.com/net/routecheck from tailscale.com/client/local+
|
||||
tailscale.com/net/routecheck/peernode from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/net/routemanager from tailscale.com/ipn/ipnlocal+
|
||||
💣 tailscale.com/net/sockopts from tailscale.com/wgengine/magicsock
|
||||
tailscale.com/net/socks5 from tailscale.com/tsnet
|
||||
tailscale.com/net/sockstats from tailscale.com/control/controlclient+
|
||||
|
||||
@ -54,8 +54,10 @@
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/ping"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/net/stun"
|
||||
"tailscale.com/net/stun/stuntest"
|
||||
"tailscale.com/net/tsaddr"
|
||||
"tailscale.com/net/tstun"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/tstest"
|
||||
@ -244,9 +246,31 @@ func newMagicStackWithKey(t testing.TB, logf logger.Logf, ln nettype.PacketListe
|
||||
}
|
||||
}
|
||||
|
||||
func (s *magicStack) Reconfig(cfg *wgcfg.Config) error {
|
||||
// Reconfig applies cfg to the stack's device and tun layer. peers,
|
||||
// if non-nil, are the tailcfg nodes that cfg was derived from; the
|
||||
// tun layer's per-peer data-plane attributes (masquerade addresses,
|
||||
// jailed classification) are derived from them with a real
|
||||
// [routemanager.RouteManager], exactly as LocalBackend does in
|
||||
// production, so this helper cannot drift from the production
|
||||
// derivation. Tests whose hand-built configs carry no such
|
||||
// attributes may pass nil.
|
||||
func (s *magicStack) Reconfig(cfg *wgcfg.Config, peers []tailcfg.NodeView) error {
|
||||
s.tsTun.SetWGConfig(cfg)
|
||||
|
||||
if peers != nil {
|
||||
rm := routemanager.New(nil)
|
||||
mut := rm.Begin()
|
||||
// Mirror the netmap.AllowSubnetRoutes flag the tests pass to
|
||||
// nmcfg.WGCfg.
|
||||
mut.SetPrefs(routemanager.Prefs{RouteAll: true})
|
||||
for _, n := range peers {
|
||||
mut.UpsertPeer(n)
|
||||
}
|
||||
mut.Commit()
|
||||
native4, native6 := tsaddr.FirstTailscaleAddrs(slices.All(cfg.Addresses))
|
||||
s.tsTun.SetPeerRoutes(native4, native6, rm.Outbound())
|
||||
}
|
||||
|
||||
// In production, LocalBackend installs a PeerByIPPacketFunc via
|
||||
// Engine.SetPeerByIPPacketFunc. Tests that bypass LocalBackend need
|
||||
// to install one here for outbound packet routing.
|
||||
@ -373,7 +397,7 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM
|
||||
// blow up. Shouldn't happen anyway.
|
||||
panic(fmt.Sprintf("failed to construct wgcfg from netmap: %v", err))
|
||||
}
|
||||
if err := m.Reconfig(wg); err != nil {
|
||||
if err := m.Reconfig(wg, nm.Peers); err != nil {
|
||||
if ctx.Err() != nil || errors.Is(err, errConnClosed) {
|
||||
// shutdown race, don't care.
|
||||
return
|
||||
@ -1199,10 +1223,10 @@ func testTwoDevicePing(t *testing.T, d *devices) {
|
||||
},
|
||||
}
|
||||
|
||||
if err := m1.Reconfig(m1cfg); err != nil {
|
||||
if err := m1.Reconfig(m1cfg, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := m2.Reconfig(m2cfg); err != nil {
|
||||
if err := m2.Reconfig(m2cfg, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@ -1326,7 +1350,7 @@ func testTwoDevicePing(t *testing.T, d *devices) {
|
||||
t.Run("no-op-dev1-reconfig", func(t *testing.T) {
|
||||
setT(t)
|
||||
defer setT(outerT)
|
||||
if err := m1.Reconfig(m1cfg); err != nil {
|
||||
if err := m1.Reconfig(m1cfg, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ping1(t)
|
||||
@ -2525,7 +2549,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.Reconfig(cfg)
|
||||
m.Reconfig(cfg, nm.Peers)
|
||||
|
||||
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- pbuf
|
||||
@ -2586,7 +2610,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.Reconfig(cfg)
|
||||
m.Reconfig(cfg, nm.Peers)
|
||||
|
||||
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||
m.tun.Outbound <- pbuf
|
||||
@ -2627,7 +2651,7 @@ func applyNetworkMap(t *testing.T, m *magicStack, nm *netmap.NetworkMap) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// Apply the wireguard config to the tailscale internal wireguard device.
|
||||
if err := m.Reconfig(cfg); err != nil {
|
||||
if err := m.Reconfig(cfg, nm.Peers); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"github.com/tailscale/wireguard-go/device"
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"go4.org/mem"
|
||||
@ -33,6 +34,7 @@
|
||||
"tailscale.com/net/ipset"
|
||||
"tailscale.com/net/netmon"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/net/sockstats"
|
||||
"tailscale.com/net/tsdial"
|
||||
"tailscale.com/net/tstun"
|
||||
@ -1006,6 +1008,10 @@ func (e *userspaceEngine) SetJailedFilter(filt *filter.Filter) {
|
||||
e.tundev.SetJailedFilter(filt)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) SetPeerRoutes(native4, native6 netip.Addr, routes *bart.Table[*routemanager.PeerRoute]) {
|
||||
e.tundev.SetPeerRoutes(native4, native6, routes)
|
||||
}
|
||||
|
||||
func (e *userspaceEngine) SetStatusCallback(cb StatusCallback) {
|
||||
e.mu.Lock()
|
||||
defer e.mu.Unlock()
|
||||
|
||||
@ -9,9 +9,11 @@
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/gaissmai/bart"
|
||||
"tailscale.com/ipn/ipnstate"
|
||||
"tailscale.com/net/dns"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/net/routemanager"
|
||||
"tailscale.com/tailcfg"
|
||||
"tailscale.com/types/key"
|
||||
"tailscale.com/types/netmap"
|
||||
@ -125,6 +127,17 @@ type Engine interface {
|
||||
// SetJailedFilter updates the packet filter for jailed nodes.
|
||||
SetJailedFilter(*filter.Filter)
|
||||
|
||||
// SetPeerRoutes updates the per-peer route attributes used by the
|
||||
// tun-layer data plane for per-packet NAT rewrites and
|
||||
// jailed-filter selection. native4 and native6 are this node's own
|
||||
// Tailscale addresses, and routes maps each peer's addresses and
|
||||
// routed prefixes to its attributes; it is a shared immutable
|
||||
// snapshot from [routemanager.RouteManager.Outbound].
|
||||
//
|
||||
// A nil routes table disables all per-packet peer processing;
|
||||
// callers pass nil when no current peer has any such attributes.
|
||||
SetPeerRoutes(native4, native6 netip.Addr, routes *bart.Table[*routemanager.PeerRoute])
|
||||
|
||||
// SetStatusCallback sets the function to call when the
|
||||
// WireGuard status changes.
|
||||
SetStatusCallback(StatusCallback)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user