wgengine: support per-peer WireGuard PSKs

Replace the allowed-IP-only peer callback result with wgcfg.PeerConfig.
It carries allowed IPs and an optional pre-shared key through lazy peer
creation and active peer synchronization.

Update wireguard-go for the new peer PSK APIs.

Updates tailscale/tailcat#84

Change-Id: Iacd9d2c74b0b64d690f3cbdf93918686ac6076d7
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick authored and Brad Fitzpatrick committed 2026-09-03 20:04:09 -07:00
1 parent 9ea7cba445
commit 31d8badb3b
21 files changed
+86 -54

No files matched your search

+1
View File
@@ -120,6 +120,7 @@ tailscale.com/cmd/k8s-operator dependencies: (generated by github.com/tailscale/
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
github.com/x448/float16 from github.com/fxamacker/cbor/v2
go.opentelemetry.io/otel/attribute from go.opentelemetry.io/otel/trace+
+1
View File
@@ -34,6 +34,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
💣 go4.org/mem from tailscale.com/control/controlbase+
go4.org/netipx from tailscale.com/ipn/ipnlocal+
+1
View File
@@ -39,6 +39,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
💣 go4.org/mem from tailscale.com/control/controlbase+
go4.org/netipx from tailscale.com/ipn/ipnlocal+
+1
View File
@@ -201,6 +201,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
github.com/tailscale/xnet/webdav from tailscale.com/drive/driveimpl+
github.com/tailscale/xnet/webdav/internal/xml from github.com/tailscale/xnet/webdav
+1
View File
@@ -71,6 +71,7 @@ tailscale.com/cmd/tsidp dependencies: (generated by github.com/tailscale/depawar
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/local+
+1 -1
View File
@@ -173,4 +173,4 @@
});
};
}
# nix-direnv cache busting line: sha256-h3KPqXE1Wvl1nfUmus0zWowaiqBIxz6eJT/+dIBEkOM=
# nix-direnv cache busting line: sha256-w7VuEoXsnL7wmHbKlwd6qgUqyRj09JAqHw073Zgnidc=
+2 -2
View File
@@ -4,7 +4,7 @@
"sri": "sha256-+cTlDLusJthxTayVx6yGoqZHRN4ylvs/9Qpy4P2dlDI="
},
"vendor": {
"goModSum": "sha256-MRFSK8wD2bSzZ/Tnhx2Z+EuF4jqj6D91chMIaB6hnzQ=",
"sri": "sha256-h3KPqXE1Wvl1nfUmus0zWowaiqBIxz6eJT/+dIBEkOM="
"goModSum": "sha256-aYPi3BUkgTUZ3ESpK33W2CtHruPSgb7gMJlAP/tUL0I=",
"sri": "sha256-w7VuEoXsnL7wmHbKlwd6qgUqyRj09JAqHw073Zgnidc="
}
}
+1 -1
View File
@@ -126,7 +126,7 @@ require (
github.com/tailscale/ts-gokrazy v0.0.0-20260630224145-b83088f2e52e
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6
github.com/tailscale/wireguard-go v0.0.0-20260821191448-23d18d66172c
github.com/tailscale/wireguard-go v0.0.0-20260904023712-e855235c55a2
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e
github.com/tc-hib/winres v0.2.1
github.com/tcnksm/go-httpstat v0.2.0
+2 -2
View File
@@ -1217,8 +1217,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
github.com/tailscale/wireguard-go v0.0.0-20260821191448-23d18d66172c h1:rjqTtbumXpM5B1UR7u+dsOaI9wRetOfIDan5kJJrgn8=
github.com/tailscale/wireguard-go v0.0.0-20260821191448-23d18d66172c/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI=
github.com/tailscale/wireguard-go v0.0.0-20260904023712-e855235c55a2 h1:nRCnARSGqRvSAK9S+vbH87dMykfv2vWwx6kb8rw9Ayk=
github.com/tailscale/wireguard-go v0.0.0-20260904023712-e855235c55a2/go.mod h1:rUelGmuK4UnSJYM5gl5Mknp6YbwwcL8+VAPMhNYe+jg=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
+1 -1
View File
@@ -619,7 +619,7 @@ func NewLocalBackend(logf logger.Logf, logID logid.PublicID, sys *tsd.System, lo
nb.ready()
e.SetPeerByIPPacketFunc(b.lookupPeerByIP)
e.SetPeerConfigFunc(b.peerAllowedIPs)
e.SetPeerConfigFunc(b.peerConfig)
e.SetPeerForIPFunc(b.PeerForIP)
e.SetPeerSessionStateFunc(b.onPeerWireGuardState)
e.SetNetLogSource(netLogNodeSource{b})
+6 -5
View File
@@ -14,6 +14,7 @@ import (
"tailscale.com/types/key"
"tailscale.com/util/dnsname"
"tailscale.com/wgengine"
"tailscale.com/wgengine/wgcfg"
)
// lookupPeerByIP returns the node public key for the peer that should
@@ -40,14 +41,14 @@ func (b *LocalBackend) lookupPeerByIP(ip netip.Addr) (key.NodePublic, bool) {
return key.NodePublic{}, false
}
// peerAllowedIPs returns the prefixes from which the peer with the
// given public key is currently allowed to originate traffic, or
// ok=false if the peer is unknown (or currently routable via no
// peerConfig returns the WireGuard configuration for the peer with the given
// public key, or ok=false if the peer is unknown (or currently routable via no
// prefix at all). It is installed as the
// [wgengine.Engine.SetPeerConfigFunc] callback, backing wireguard-go's
// lazy peer creation and per-delta peer sync.
func (b *LocalBackend) peerAllowedIPs(k key.NodePublic) (_ []netip.Prefix, ok bool) {
return b.currentNode().PeerAllowedIPs(k)
func (b *LocalBackend) peerConfig(k key.NodePublic) (_ wgcfg.PeerConfig, ok bool) {
ips, ok := b.currentNode().PeerAllowedIPs(k)
return wgcfg.PeerConfig{AllowedIPs: ips}, ok
}
// resolveMagicDNS resolves a MagicDNS hostname to the owning node's IP
+6 -5
View File
@@ -1884,7 +1884,7 @@ type mockEngine struct {
filter, jailedFilter *filter.Filter
peerConfigFn func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)
peerConfigFn func(key.NodePublic) (config wgcfg.PeerConfig, ok bool)
statusCb wgengine.StatusCallback
}
@@ -1991,7 +1991,7 @@ func (e *mockEngine) InstallCaptureHook(packet.CaptureCallback) {}
func (e *mockEngine) SetPeerByIPPacketFunc(func(netip.Addr) (_ key.NodePublic, ok bool)) {}
func (e *mockEngine) SetPeerForIPFunc(func(netip.Addr) (_ wgengine.PeerForIP, ok bool)) {}
func (e *mockEngine) SetPeerConfigFunc(fn func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)) {
func (e *mockEngine) SetPeerConfigFunc(fn func(key.NodePublic) (config wgcfg.PeerConfig, ok bool)) {
e.mu.Lock()
defer e.mu.Unlock()
e.peerConfigFn = fn
@@ -2006,14 +2006,15 @@ func (e *mockEngine) PeerAllowedIPs(k key.NodePublic) (_ []netip.Prefix, ok bool
if fn == nil {
return nil, false
}
return fn(k)
conf, ok := fn(k)
return conf.AllowedIPs, ok
}
func (e *mockEngine) SyncDevicePeer(key.NodePublic) {}
func (e *mockEngine) SyncDevicePeer(key.NodePublic) {}
func (e *mockEngine) MarkDevicePeerForHandshake(key.NodePublic) {}
func (e *mockEngine) SetPeerSessionStateFunc(func(key.NodePublic, wgengine.PeerWireGuardState)) {
}
func (e *mockEngine) SetNetLogSource(wgengine.NetLogSource) {}
func (e *mockEngine) SetNetLogSource(wgengine.NetLogSource) {}
func (e *mockEngine) SetPeerPriorityMessageOnEstablishmentFunc(fn func(key.NodePublic) (msg []byte)) {
}
func (e *mockEngine) SetWGPeerLookup(func(wgString string) (tsString string, ok bool)) {}
+1 -1
View File
@@ -16,4 +16,4 @@
) {
src = ./.;
}).shellNix
# nix-direnv cache busting line: sha256-h3KPqXE1Wvl1nfUmus0zWowaiqBIxz6eJT/+dIBEkOM=
# nix-direnv cache busting line: sha256-w7VuEoXsnL7wmHbKlwd6qgUqyRj09JAqHw073Zgnidc=
+1
View File
@@ -71,6 +71,7 @@ tailscale.com/tsnet dependencies: (generated by github.com/tailscale/depaware)
github.com/tailscale/wireguard-go/replay from github.com/tailscale/wireguard-go/device
github.com/tailscale/wireguard-go/rwcancel from github.com/tailscale/wireguard-go/device+
github.com/tailscale/wireguard-go/tai64n from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tsasm/amd64/chacha20poly1305 from github.com/tailscale/wireguard-go/device
💣 github.com/tailscale/wireguard-go/tun from github.com/tailscale/wireguard-go/device+
github.com/x448/float16 from github.com/fxamacker/cbor/v2
💣 go4.org/mem from tailscale.com/client/local+
+6 -6
View File
@@ -92,17 +92,17 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
e2.SetPeerByIPPacketFunc(func(dst netip.Addr) (_ key.NodePublic, ok bool) {
return k1pub, a1.Contains(dst)
})
e1.SetPeerConfigFunc(func(pubk key.NodePublic) (_ []netip.Prefix, ok bool) {
e1.SetPeerConfigFunc(func(pubk key.NodePublic) (_ wgcfg.PeerConfig, ok bool) {
if pubk == k2pub {
return []netip.Prefix{a2}, true
return wgcfg.PeerConfig{AllowedIPs: []netip.Prefix{a2}}, true
}
return nil, false
return wgcfg.PeerConfig{}, false
})
e2.SetPeerConfigFunc(func(pubk key.NodePublic) (_ []netip.Prefix, ok bool) {
e2.SetPeerConfigFunc(func(pubk key.NodePublic) (_ wgcfg.PeerConfig, ok bool) {
if pubk == k1pub {
return []netip.Prefix{a1}, true
return wgcfg.PeerConfig{AllowedIPs: []netip.Prefix{a1}}, true
}
return nil, false
return wgcfg.PeerConfig{}, false
})
var wait sync.WaitGroup
+4 -3
View File
@@ -305,10 +305,11 @@ func (s *magicStack) Reconfig(cfg *wgcfg.Config, peers []tailcfg.NodeView) error
})
// The live per-peer config source backing lazy peer creation, as
// LocalBackend's peerAllowedIPs does via PeerAllowedIPs, and the
// LocalBackend's peerConfig does via PeerAllowedIPs, and the
// per-peer device convergence that Engine.SyncDevicePeer does.
s.dev.SetPeerLookupFunc(wgcfg.NewPeerLookupFunc(s.conn.Bind(), s.conn.logf, func(pubk device.NoisePublicKey) ([]netip.Prefix, bool) {
return peerAllowedIPs(key.NodePublicFromRaw32(mem.B(pubk[:])))
s.dev.SetPeerLookupFunc(wgcfg.NewPeerLookupFunc(s.conn.Bind(), s.conn.logf, func(pubk device.NoisePublicKey) (wgcfg.PeerConfig, bool) {
ips, ok := peerAllowedIPs(key.NodePublicFromRaw32(mem.B(pubk[:])))
return wgcfg.PeerConfig{AllowedIPs: ips}, ok
}))
s.dev.SetPrivateKey(key.NodePrivateAs[device.NoisePrivateKey](cfg.PrivateKey))
s.dev.RemoveMatchingPeers(func(pk device.NoisePublicKey) bool {
+8 -7
View File
@@ -109,11 +109,11 @@ type userspaceEngine struct {
// for the cold-path control lookups (Ping, TSMP, pendopen, etc).
peerForIPFn atomic.Pointer[func(netip.Addr) (_ PeerForIP, ok bool)]
// peerConfigFn, if non-nil, is the live per-peer allowed-IPs
// peerConfigFn, if non-nil, is the live per-peer WireGuard config
// source installed via [userspaceEngine.SetPeerConfigFunc]. When
// set, wgdev's PeerLookupFunc queries it directly, so reconfigs
// no longer install per-config lookup closures.
peerConfigFn atomic.Pointer[func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)]
peerConfigFn atomic.Pointer[func(key.NodePublic) (config wgcfg.PeerConfig, ok bool)]
lastCfg wgcfg.Config
lastRouter *router.Config
@@ -672,14 +672,14 @@ func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper)
// SetPeerConfigFunc implements [Engine.SetPeerConfigFunc]. It stores
// fn and installs a single wgdev PeerLookupFunc wrapping it, so
// lazily-created peers always get current allowed IPs and the lookup
// lazily-created peers always get their current config and the lookup
// func never needs to be reinstalled as the peer set changes.
func (e *userspaceEngine) SetPeerConfigFunc(fn func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool)) {
func (e *userspaceEngine) SetPeerConfigFunc(fn func(key.NodePublic) (config wgcfg.PeerConfig, ok bool)) {
if fn == nil {
panic("SetPeerConfigFunc: nil fn")
}
e.peerConfigFn.Store(&fn)
e.wgdev.SetPeerLookupFunc(wgcfg.NewPeerLookupFunc(e.wgdev.Bind(), e.logf, func(pubk device.NoisePublicKey) ([]netip.Prefix, bool) {
e.wgdev.SetPeerLookupFunc(wgcfg.NewPeerLookupFunc(e.wgdev.Bind(), e.logf, func(pubk device.NoisePublicKey) (wgcfg.PeerConfig, bool) {
return fn(key.NodePublicFromRaw32(mem.B(pubk[:])))
}))
}
@@ -695,13 +695,14 @@ func (e *userspaceEngine) SyncDevicePeer(k key.NodePublic) {
// The peer set may be about to change; drop the wgLogger's cached
// peer-string rewrites so the next log line re-resolves them.
e.wgLogger.Invalidate()
allowedIPs, ok := (*fn)(k)
conf, ok := (*fn)(k)
if !ok {
e.wgdev.RemovePeer(k.Raw32())
return
}
if peer, ok := e.wgdev.LookupActivePeer(k.Raw32()); ok {
peer.SetAllowedIPs(allowedIPs)
peer.SetPresharedKey(conf.PresharedKey)
peer.SetAllowedIPs(conf.AllowedIPs)
}
}
+12 -1
View File
@@ -8,6 +8,7 @@ import (
"net/netip"
"slices"
"github.com/tailscale/wireguard-go/device"
"tailscale.com/types/key"
)
@@ -17,13 +18,23 @@ import (
// It only supports the set of things Tailscale uses.
//
// Peers are not part of the config: wireguard-go learns the peer set
// and each peer's allowed IPs from the live per-peer config source
// and each peer's configuration from the live per-peer config source
// installed via [tailscale.com/wgengine.Engine.SetPeerConfigFunc].
type Config struct {
PrivateKey key.NodePrivate
Addresses []netip.Prefix
}
// PeerConfig is the WireGuard configuration for one peer.
type PeerConfig struct {
// AllowedIPs is the set of prefixes the peer may originate traffic from.
AllowedIPs []netip.Prefix
// PresharedKey is the optional WireGuard pre-shared key. The zero value
// disables the pre-shared-key layer.
PresharedKey device.NoisePresharedKey
}
func (c *Config) Equal(o *Config) bool {
if c == nil || o == nil {
return c == o
+8 -7
View File
@@ -5,7 +5,6 @@ package wgcfg
import (
"fmt"
"net/netip"
"github.com/tailscale/wireguard-go/conn"
"github.com/tailscale/wireguard-go/device"
@@ -19,11 +18,12 @@ func NewDevice(tunDev tun.Device, bind conn.Bind, logger *device.Logger) *device
}
// NewPeerLookupFunc returns a [device.PeerLookupFunc] that lazily
// creates peers using allowedIPs as the source of each peer's allowed
// IPs. The peer's endpoint is derived from its public key via bind.
func NewPeerLookupFunc(bind conn.Bind, logf logger.Logf, allowedIPs func(device.NoisePublicKey) ([]netip.Prefix, bool)) device.PeerLookupFunc {
// creates peers using peerConfig as the source of each peer's allowed IPs and
// optional pre-shared key. The peer's endpoint is derived from its public key
// via bind.
func NewPeerLookupFunc(bind conn.Bind, logf logger.Logf, peerConfig func(device.NoisePublicKey) (PeerConfig, bool)) device.PeerLookupFunc {
return func(pubk device.NoisePublicKey) (_ *device.NewPeerConfig, ok bool) {
ips, ok := allowedIPs(pubk)
conf, ok := peerConfig(pubk)
if !ok {
return nil, false
}
@@ -33,8 +33,9 @@ func NewPeerLookupFunc(bind conn.Bind, logf logger.Logf, allowedIPs func(device.
return nil, false
}
return &device.NewPeerConfig{
AllowedIPs: ips,
Endpoint: ep,
AllowedIPs: conf.AllowedIPs,
PresharedKey: conf.PresharedKey,
Endpoint: ep,
}, true
}
}
+14 -5
View File
@@ -7,6 +7,7 @@ import (
"io"
"net/netip"
"os"
"strings"
"testing"
"github.com/tailscale/wireguard-go/conn"
@@ -28,12 +29,13 @@ func TestNewPeerLookupFunc(t *testing.T) {
// peers is the live per-peer config source, standing in for what
// LocalBackend provides via wgengine.Engine.SetPeerConfigFunc.
peers := map[device.NoisePublicKey][]netip.Prefix{
k2.Raw32(): {ip2},
psk2 := device.NoisePresharedKey{1, 2, 3}
peers := map[device.NoisePublicKey]PeerConfig{
k2.Raw32(): {AllowedIPs: []netip.Prefix{ip2}, PresharedKey: psk2},
}
dev.SetPeerLookupFunc(NewPeerLookupFunc(dev.Bind(), t.Logf, func(pubk device.NoisePublicKey) ([]netip.Prefix, bool) {
ips, ok := peers[pubk]
return ips, ok
dev.SetPeerLookupFunc(NewPeerLookupFunc(dev.Bind(), t.Logf, func(pubk device.NoisePublicKey) (PeerConfig, bool) {
conf, ok := peers[pubk]
return conf, ok
}))
t.Run("lazy-creation", func(t *testing.T) {
@@ -42,6 +44,13 @@ func TestNewPeerLookupFunc(t *testing.T) {
if p := dev.LookupPeer(k2.Raw32()); p == nil {
t.Fatal("expected peer k2 to exist via LookupPeer")
}
got, err := dev.IpcGet()
if err != nil {
t.Fatal(err)
}
if want := "preshared_key=0102030000000000000000000000000000000000000000000000000000000000"; !strings.Contains(got, want) {
t.Fatalf("device config does not contain %q:\n%s", want, got)
}
// An unknown peer should not be found.
if p := dev.LookupPeer(k3.Raw32()); p != nil {
t.Fatal("expected unknown peer k3 to not exist")
+8 -7
View File
@@ -185,10 +185,10 @@ type Engine interface {
SetPeerByIPPacketFunc(func(netip.Addr) (_ key.NodePublic, ok bool))
// SetPeerConfigFunc installs the live source of per-peer WireGuard
// configuration: given a peer's public key, fn returns the prefixes
// the peer is currently allowed to originate traffic from, or
// ok=false if the peer is unknown (in which case it must not exist
// in the WireGuard device). The engine installs a single
// configuration: given a peer's public key, fn returns its current
// WireGuard configuration, including the prefixes it may originate and
// its optional pre-shared key. It returns ok=false if the peer is unknown,
// in which case it must not exist in the WireGuard device. The engine installs a single
// [device.PeerLookupFunc] wrapping fn, so lazily-created peers
// always see current state and the lookup func never needs to be
// reinstalled as peers come and go.
@@ -197,14 +197,15 @@ type Engine interface {
// before the first [Engine.Reconfig]. fn is called rarely (when
// wireguard-go first hears from a peer it doesn't have) and may
// acquire locks.
SetPeerConfigFunc(fn func(key.NodePublic) (allowedIPs []netip.Prefix, ok bool))
SetPeerConfigFunc(fn func(key.NodePublic) (config wgcfg.PeerConfig, ok bool))
// SyncDevicePeer synchronizes the WireGuard device's state for a
// single peer with the config source installed via
// [Engine.SetPeerConfigFunc]: if the source no longer knows the
// peer, it is removed from the device; if the peer is active in the
// device, its allowed IPs are updated. It does O(1) work (plus the
// config source lookup) and is intended to be called for each peer
// device, its allowed IPs and pre-shared key are updated. It does O(1)
// work (plus the config source lookup) and is intended to be called for
// each peer
// added, updated, or removed by an incremental netmap delta,
// avoiding a full [Engine.Reconfig].
//