Compare commits
1 Commits
main
...
raggi/v6ma
Author | SHA1 | Date |
---|---|---|
![]() |
31a5843f79 |
|
@ -611,6 +611,9 @@ func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool {
|
||||||
if h.peerNode.SelfNodeV4MasqAddrForThisPeer != nil {
|
if h.peerNode.SelfNodeV4MasqAddrForThisPeer != nil {
|
||||||
return *h.peerNode.SelfNodeV4MasqAddrForThisPeer == addr
|
return *h.peerNode.SelfNodeV4MasqAddrForThisPeer == addr
|
||||||
}
|
}
|
||||||
|
if h.peerNode.SelfNodeV6MasqAddrForThisPeer != nil {
|
||||||
|
return *h.peerNode.SelfNodeV6MasqAddrForThisPeer == addr
|
||||||
|
}
|
||||||
pfx := netip.PrefixFrom(addr, addr.BitLen())
|
pfx := netip.PrefixFrom(addr, addr.BitLen())
|
||||||
return slices.Contains(h.selfNode.Addresses, pfx)
|
return slices.Contains(h.selfNode.Addresses, pfx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -598,7 +598,7 @@ func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
||||||
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
|
exitNodeRequiresMasq := false // true if using an exit node and it requires masquerading
|
||||||
for _, p := range wcfg.Peers {
|
for _, p := range wcfg.Peers {
|
||||||
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
|
isExitNode := slices.Contains(p.AllowedIPs, tsaddr.AllIPv4()) || slices.Contains(p.AllowedIPs, tsaddr.AllIPv6())
|
||||||
if isExitNode && p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() {
|
if isExitNode && (p.V4MasqAddr != nil && p.V4MasqAddr.IsValid() || p.V6MasqAddr != nil && p.V6MasqAddr.IsValid()) {
|
||||||
exitNodeRequiresMasq = true
|
exitNodeRequiresMasq = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -602,6 +602,7 @@ func TestFilterDiscoLoop(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNATCfg(t *testing.T) {
|
func TestNATCfg(t *testing.T) {
|
||||||
|
t.Error("Missing case for IPv6")
|
||||||
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
node := func(ip, masqIP netip.Addr, otherAllowedIPs ...netip.Prefix) wgcfg.Peer {
|
||||||
p := wgcfg.Peer{
|
p := wgcfg.Peer{
|
||||||
PublicKey: key.NewNode().Public(),
|
PublicKey: key.NewNode().Public(),
|
||||||
|
|
|
@ -294,6 +294,21 @@ type Node struct {
|
||||||
// not be masqueraded (e.g. in case of --snat-subnet-routes).
|
// not be masqueraded (e.g. in case of --snat-subnet-routes).
|
||||||
SelfNodeV4MasqAddrForThisPeer *netip.Addr `json:",omitempty"`
|
SelfNodeV4MasqAddrForThisPeer *netip.Addr `json:",omitempty"`
|
||||||
|
|
||||||
|
// SelfNodeV6MasqAddrForThisPeer is the IPv6 that this peer knows the current node as.
|
||||||
|
// It may be empty if the peer knows the current node by its native
|
||||||
|
// IPv6 address.
|
||||||
|
// This field is only populated in a MapResponse for peers and not
|
||||||
|
// for the current node.
|
||||||
|
//
|
||||||
|
// If set, it should be used to masquerade traffic originating from the
|
||||||
|
// current node to this peer. The masquerade address is only relevant
|
||||||
|
// for this peer and not for other peers.
|
||||||
|
//
|
||||||
|
// This only applies to traffic originating from the current node to the
|
||||||
|
// peer or any of its subnets. Traffic originating from subnet routes will
|
||||||
|
// not be masqueraded (e.g. in case of --snat-subnet-routes).
|
||||||
|
SelfNodeV6MasqAddrForThisPeer *netip.Addr `json:",omitempty"`
|
||||||
|
|
||||||
// IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it
|
// IsWireGuardOnly indicates that this is a non-Tailscale WireGuard peer, it
|
||||||
// is not expected to speak Disco or DERP, and it must have Endpoints in
|
// is not expected to speak Disco or DERP, and it must have Endpoints in
|
||||||
// order to be reachable. TODO(#7826): 2023-04-06: only the first parseable
|
// order to be reachable. TODO(#7826): 2023-04-06: only the first parseable
|
||||||
|
@ -1726,6 +1741,7 @@ func (n *Node) Equal(n2 *Node) bool {
|
||||||
eqStrings(n.Tags, n2.Tags) &&
|
eqStrings(n.Tags, n2.Tags) &&
|
||||||
n.Expired == n2.Expired &&
|
n.Expired == n2.Expired &&
|
||||||
eqPtr(n.SelfNodeV4MasqAddrForThisPeer, n2.SelfNodeV4MasqAddrForThisPeer) &&
|
eqPtr(n.SelfNodeV4MasqAddrForThisPeer, n2.SelfNodeV4MasqAddrForThisPeer) &&
|
||||||
|
eqPtr(n.SelfNodeV6MasqAddrForThisPeer, n2.SelfNodeV6MasqAddrForThisPeer) &&
|
||||||
n.IsWireGuardOnly == n2.IsWireGuardOnly
|
n.IsWireGuardOnly == n2.IsWireGuardOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,10 @@ func (src *Node) Clone() *Node {
|
||||||
dst.SelfNodeV4MasqAddrForThisPeer = new(netip.Addr)
|
dst.SelfNodeV4MasqAddrForThisPeer = new(netip.Addr)
|
||||||
*dst.SelfNodeV4MasqAddrForThisPeer = *src.SelfNodeV4MasqAddrForThisPeer
|
*dst.SelfNodeV4MasqAddrForThisPeer = *src.SelfNodeV4MasqAddrForThisPeer
|
||||||
}
|
}
|
||||||
|
if dst.SelfNodeV6MasqAddrForThisPeer != nil {
|
||||||
|
dst.SelfNodeV6MasqAddrForThisPeer = new(netip.Addr)
|
||||||
|
*dst.SelfNodeV6MasqAddrForThisPeer = *src.SelfNodeV6MasqAddrForThisPeer
|
||||||
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,6 +107,7 @@ var _NodeCloneNeedsRegeneration = Node(struct {
|
||||||
DataPlaneAuditLogID string
|
DataPlaneAuditLogID string
|
||||||
Expired bool
|
Expired bool
|
||||||
SelfNodeV4MasqAddrForThisPeer *netip.Addr
|
SelfNodeV4MasqAddrForThisPeer *netip.Addr
|
||||||
|
SelfNodeV6MasqAddrForThisPeer *netip.Addr
|
||||||
IsWireGuardOnly bool
|
IsWireGuardOnly bool
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
|
|
|
@ -351,7 +351,7 @@ func TestNodeEqual(t *testing.T) {
|
||||||
"UnsignedPeerAPIOnly",
|
"UnsignedPeerAPIOnly",
|
||||||
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
||||||
"DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
|
"DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
|
||||||
"IsWireGuardOnly",
|
"SelfNodeV6MasqAddrForThisPeer", "IsWireGuardOnly",
|
||||||
}
|
}
|
||||||
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
|
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
|
||||||
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||||
|
|
|
@ -184,6 +184,14 @@ func (v NodeView) SelfNodeV4MasqAddrForThisPeer() *netip.Addr {
|
||||||
return &x
|
return &x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v NodeView) SelfNodeV6MasqAddrForThisPeer() *netip.Addr {
|
||||||
|
if v.ж.SelfNodeV6MasqAddrForThisPeer == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
x := *v.ж.SelfNodeV6MasqAddrForThisPeer
|
||||||
|
return &x
|
||||||
|
}
|
||||||
|
|
||||||
func (v NodeView) IsWireGuardOnly() bool { return v.ж.IsWireGuardOnly }
|
func (v NodeView) IsWireGuardOnly() bool { return v.ж.IsWireGuardOnly }
|
||||||
func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
|
func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
|
||||||
|
|
||||||
|
@ -220,6 +228,7 @@ var _NodeViewNeedsRegeneration = Node(struct {
|
||||||
DataPlaneAuditLogID string
|
DataPlaneAuditLogID string
|
||||||
Expired bool
|
Expired bool
|
||||||
SelfNodeV4MasqAddrForThisPeer *netip.Addr
|
SelfNodeV4MasqAddrForThisPeer *netip.Addr
|
||||||
|
SelfNodeV6MasqAddrForThisPeer *netip.Addr
|
||||||
IsWireGuardOnly bool
|
IsWireGuardOnly bool
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ type Server struct {
|
||||||
// MapResponses sent to clients. It is keyed by the requesting nodes
|
// MapResponses sent to clients. It is keyed by the requesting nodes
|
||||||
// public key, and then the peer node's public key. The value is the
|
// public key, and then the peer node's public key. The value is the
|
||||||
// masquerade address to use for that peer.
|
// masquerade address to use for that peer.
|
||||||
masquerades map[key.NodePublic]map[key.NodePublic]netip.Addr // node => peer => SelfNodeV4MasqAddrForThisPeer IP
|
masquerades map[key.NodePublic]map[key.NodePublic]netip.Addr // node => peer => SelfNodeV{4,6}MasqAddrForThisPeer IP
|
||||||
|
|
||||||
noisePubKey key.MachinePublic
|
noisePubKey key.MachinePublic
|
||||||
noisePrivKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
|
noisePrivKey key.ControlPrivate // not strictly needed vs. MachinePrivate, but handy to test type interactions.
|
||||||
|
@ -844,7 +844,11 @@ func (s *Server) MapResponse(req *tailcfg.MapRequest) (res *tailcfg.MapResponse,
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if masqIP := nodeMasqs[p.Key]; masqIP.IsValid() {
|
if masqIP := nodeMasqs[p.Key]; masqIP.IsValid() {
|
||||||
p.SelfNodeV4MasqAddrForThisPeer = ptr.To(masqIP)
|
if masqIP.Is4() {
|
||||||
|
p.SelfNodeV4MasqAddrForThisPeer = ptr.To(masqIP)
|
||||||
|
} else {
|
||||||
|
p.SelfNodeV6MasqAddrForThisPeer = ptr.To(masqIP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
|
|
@ -2271,74 +2271,93 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
|
func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
|
||||||
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
|
check := func(t *testing.T, tsaip, wgaip, masqip netip.Prefix) {
|
||||||
defer cleanup()
|
tskey := key.NewNode()
|
||||||
|
wgkey := key.NewNode()
|
||||||
|
|
||||||
tskey := key.NewNode()
|
derpMap, cleanup := runDERPAndStun(t, t.Logf, localhostListener{}, netaddr.IPv4(127, 0, 0, 1))
|
||||||
tsaip := netip.MustParsePrefix("100.111.222.111/32")
|
defer cleanup()
|
||||||
|
|
||||||
wgkey := key.NewNode()
|
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
||||||
wgaip := netip.MustParsePrefix("10.64.0.1/32")
|
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), masqip.String())
|
||||||
|
wgdev, wgtun, port := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
||||||
|
defer wgdev.Close()
|
||||||
|
wgEp := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), port)
|
||||||
|
|
||||||
// the ip that the wireguard peer has in allowed ips and expects as a masq source
|
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
||||||
masqip := netip.MustParsePrefix("10.64.0.2/32")
|
defer m.Close()
|
||||||
|
|
||||||
uapi := fmt.Sprintf("private_key=%s\npublic_key=%s\nallowed_ip=%s\n\n",
|
nm := &netmap.NetworkMap{
|
||||||
wgkey.UntypedHexString(), tskey.Public().UntypedHexString(), masqip.String())
|
Name: "ts",
|
||||||
wgdev, wgtun, port := newWireguard(t, uapi, []netip.Prefix{wgaip})
|
PrivateKey: m.privateKey,
|
||||||
defer wgdev.Close()
|
NodeKey: m.privateKey.Public(),
|
||||||
wgEp := netip.AddrPortFrom(netip.MustParseAddr("127.0.0.1"), port)
|
Addresses: []netip.Prefix{tsaip},
|
||||||
|
Peers: []*tailcfg.Node{
|
||||||
m := newMagicStackWithKey(t, t.Logf, localhostListener{}, derpMap, tskey)
|
{
|
||||||
defer m.Close()
|
Key: wgkey.Public(),
|
||||||
|
Endpoints: []string{wgEp.String()},
|
||||||
nm := &netmap.NetworkMap{
|
IsWireGuardOnly: true,
|
||||||
Name: "ts",
|
Addresses: []netip.Prefix{wgaip},
|
||||||
PrivateKey: m.privateKey,
|
AllowedIPs: []netip.Prefix{wgaip},
|
||||||
NodeKey: m.privateKey.Public(),
|
},
|
||||||
Addresses: []netip.Prefix{tsaip},
|
|
||||||
Peers: []*tailcfg.Node{
|
|
||||||
{
|
|
||||||
Key: wgkey.Public(),
|
|
||||||
Endpoints: []string{wgEp.String()},
|
|
||||||
IsWireGuardOnly: true,
|
|
||||||
Addresses: []netip.Prefix{wgaip},
|
|
||||||
AllowedIPs: []netip.Prefix{wgaip},
|
|
||||||
SelfNodeV4MasqAddrForThisPeer: ptr.To(masqip.Addr()),
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
}
|
|
||||||
m.conn.SetNetworkMap(nm)
|
|
||||||
|
|
||||||
cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
m.Reconfig(cfg)
|
|
||||||
|
|
||||||
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
|
||||||
m.tun.Outbound <- pbuf
|
|
||||||
|
|
||||||
select {
|
|
||||||
case p := <-wgtun.Inbound:
|
|
||||||
|
|
||||||
// TODO(raggi): move to a bytes.Equal based test later, once
|
|
||||||
// tuntest.Ping produces correct checksums!
|
|
||||||
|
|
||||||
var pkt packet.Parsed
|
|
||||||
pkt.Decode(p)
|
|
||||||
if pkt.ICMP4Header().Type != packet.ICMP4EchoRequest {
|
|
||||||
t.Fatalf("unexpected packet: %x", p)
|
|
||||||
}
|
}
|
||||||
if pkt.Src.Addr() != masqip.Addr() {
|
if masqip.Addr().Is4() {
|
||||||
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
nm.Peers[0].SelfNodeV4MasqAddrForThisPeer = ptr.To(masqip.Addr())
|
||||||
|
} else {
|
||||||
|
nm.Peers[0].SelfNodeV6MasqAddrForThisPeer = ptr.To(masqip.Addr())
|
||||||
}
|
}
|
||||||
if pkt.Dst.Addr() != wgaip.Addr() {
|
m.conn.SetNetworkMap(nm)
|
||||||
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
|
||||||
|
cfg, err := nmcfg.WGCfg(nm, t.Logf, netmap.AllowSingleHosts|netmap.AllowSubnetRoutes, "")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
m.Reconfig(cfg)
|
||||||
|
|
||||||
|
pbuf := tuntest.Ping(wgaip.Addr(), tsaip.Addr())
|
||||||
|
m.tun.Outbound <- pbuf
|
||||||
|
|
||||||
|
select {
|
||||||
|
case p := <-wgtun.Inbound:
|
||||||
|
// TODO(raggi): move to a bytes.Equal based test later, once
|
||||||
|
// tuntest.Ping produces correct checksums!
|
||||||
|
|
||||||
|
var pkt packet.Parsed
|
||||||
|
pkt.Decode(p)
|
||||||
|
if masqip.Addr().Is4() {
|
||||||
|
if pkt.ICMP4Header().Type != packet.ICMP4EchoRequest {
|
||||||
|
t.Fatalf("unexpected packet: %x", p)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if pkt.ICMP6Header().Type != packet.ICMP6EchoRequest {
|
||||||
|
t.Fatalf("unexpected packet: %x", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pkt.Src.Addr() != masqip.Addr() {
|
||||||
|
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
||||||
|
}
|
||||||
|
if pkt.Dst.Addr() != wgaip.Addr() {
|
||||||
|
t.Fatalf("bad source IP, got %s, want %s", pkt.Src.Addr(), masqip.Addr())
|
||||||
|
}
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("no packet after 1s")
|
||||||
}
|
}
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatal("no packet after 1s")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("IPv4", func(t *testing.T) {
|
||||||
|
tailscaleIP := netip.MustParsePrefix("100.111.222.111/32")
|
||||||
|
wireguardIP := netip.MustParsePrefix("10.64.0.1/32")
|
||||||
|
masqueradeIP := netip.MustParsePrefix("10.64.0.2/32")
|
||||||
|
check(t, tailscaleIP, wireguardIP, masqueradeIP)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("IPv6", func(t *testing.T) {
|
||||||
|
tailscaleIP := netip.MustParsePrefix("100::111/128")
|
||||||
|
wireguardIP := netip.MustParsePrefix("fd7a:115c:a1e0:ab12:4848:cd2a:3a2c:baa1/128")
|
||||||
|
masqueradeIP := netip.MustParsePrefix("fd7a:115c:a1e0:ab12:4848:cd2a:3a2c:baa2/128")
|
||||||
|
check(t, tailscaleIP, wireguardIP, masqueradeIP)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEndpointTracker(t *testing.T) {
|
func TestEndpointTracker(t *testing.T) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ type Peer struct {
|
||||||
DiscoKey key.DiscoPublic // present only so we can handle restarts within wgengine, not passed to WireGuard
|
DiscoKey key.DiscoPublic // present only so we can handle restarts within wgengine, not passed to WireGuard
|
||||||
AllowedIPs []netip.Prefix
|
AllowedIPs []netip.Prefix
|
||||||
V4MasqAddr *netip.Addr // if non-nil, masquerade IPv4 traffic to this peer using this address
|
V4MasqAddr *netip.Addr // if non-nil, masquerade IPv4 traffic to this peer using this address
|
||||||
|
V6MasqAddr *netip.Addr // if non-nil, masquerade IPv6 traffic to this peer using this address
|
||||||
PersistentKeepalive uint16
|
PersistentKeepalive uint16
|
||||||
// wireguard-go's endpoint for this peer. It should always equal Peer.PublicKey.
|
// wireguard-go's endpoint for this peer. It should always equal Peer.PublicKey.
|
||||||
// We represent it explicitly so that we can detect if they diverge and recover.
|
// We represent it explicitly so that we can detect if they diverge and recover.
|
||||||
|
|
|
@ -102,6 +102,7 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
|
||||||
|
|
||||||
didExitNodeWarn := false
|
didExitNodeWarn := false
|
||||||
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer
|
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer
|
||||||
|
cpeer.V6MasqAddr = peer.SelfNodeV6MasqAddrForThisPeer
|
||||||
for _, allowedIP := range peer.AllowedIPs {
|
for _, allowedIP := range peer.AllowedIPs {
|
||||||
if allowedIP.Bits() == 0 && peer.StableID != exitNode {
|
if allowedIP.Bits() == 0 && peer.StableID != exitNode {
|
||||||
if didExitNodeWarn {
|
if didExitNodeWarn {
|
||||||
|
|
|
@ -58,6 +58,10 @@ func (src *Peer) Clone() *Peer {
|
||||||
dst.V4MasqAddr = new(netip.Addr)
|
dst.V4MasqAddr = new(netip.Addr)
|
||||||
*dst.V4MasqAddr = *src.V4MasqAddr
|
*dst.V4MasqAddr = *src.V4MasqAddr
|
||||||
}
|
}
|
||||||
|
if dst.V6MasqAddr != nil {
|
||||||
|
dst.V6MasqAddr = new(netip.Addr)
|
||||||
|
*dst.V6MasqAddr = *src.V6MasqAddr
|
||||||
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,6 +71,7 @@ var _PeerCloneNeedsRegeneration = Peer(struct {
|
||||||
DiscoKey key.DiscoPublic
|
DiscoKey key.DiscoPublic
|
||||||
AllowedIPs []netip.Prefix
|
AllowedIPs []netip.Prefix
|
||||||
V4MasqAddr *netip.Addr
|
V4MasqAddr *netip.Addr
|
||||||
|
V6MasqAddr *netip.Addr
|
||||||
PersistentKeepalive uint16
|
PersistentKeepalive uint16
|
||||||
WGEndpoint key.NodePublic
|
WGEndpoint key.NodePublic
|
||||||
}{})
|
}{})
|
||||||
|
|
Loading…
Reference in New Issue