mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-20 11:58:39 +00:00
all: declare & plumb IPv6 masquerade address for peer
This PR plumbs through awareness of an IPv6 SNAT/masquerade address from the wire protocol through to the low-level (tstun / wgengine). This PR is the first in two PRs for implementing IPv6 NAT support to/from peers. A subsequent PR will implement the data-plane changes to implement IPv6 NAT - this is just plumbing. Signed-off-by: Tom DNetto <tom@tailscale.com> Updates ENG-991
This commit is contained in:
parent
d9ae7d670e
commit
c08cf2a9c6
@ -703,6 +703,14 @@ func peerChangeDiff(was tailcfg.NodeView, n *tailcfg.Node) (_ *tailcfg.PeerChang
|
|||||||
if va == nil || vb == nil || *va != *vb {
|
if va == nil || vb == nil || *va != *vb {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
case "SelfNodeV6MasqAddrForThisPeer":
|
||||||
|
va, vb := was.SelfNodeV6MasqAddrForThisPeer(), n.SelfNodeV6MasqAddrForThisPeer
|
||||||
|
if va == nil && vb == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if va == nil || vb == nil || *va != *vb {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
case "ExitNodeDNSResolvers":
|
case "ExitNodeDNSResolvers":
|
||||||
va, vb := was.ExitNodeDNSResolvers(), views.SliceOfViews(n.ExitNodeDNSResolvers)
|
va, vb := was.ExitNodeDNSResolvers(), views.SliceOfViews(n.ExitNodeDNSResolvers)
|
||||||
|
|
||||||
|
@ -736,6 +736,18 @@ func TestPeerChangeDiff(t *testing.T) {
|
|||||||
a: &tailcfg.Node{ID: 1, User: 1},
|
a: &tailcfg.Node{ID: 1, User: 1},
|
||||||
b: &tailcfg.Node{ID: 1, User: 2},
|
b: &tailcfg.Node{ID: 1, User: 2},
|
||||||
want: nil,
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miss-change-masq-v4",
|
||||||
|
a: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
|
||||||
|
b: &tailcfg.Node{ID: 1, SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.2"))},
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "miss-change-masq-v6",
|
||||||
|
a: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
|
||||||
|
b: &tailcfg.Node{ID: 1, SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3006"))},
|
||||||
|
want: nil,
|
||||||
}}
|
}}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
@ -612,6 +612,9 @@ func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool {
|
|||||||
if v := h.peerNode.SelfNodeV4MasqAddrForThisPeer(); v != nil {
|
if v := h.peerNode.SelfNodeV4MasqAddrForThisPeer(); v != nil {
|
||||||
return *v == addr
|
return *v == addr
|
||||||
}
|
}
|
||||||
|
if v := h.peerNode.SelfNodeV6MasqAddrForThisPeer(); v != nil {
|
||||||
|
return *v == addr
|
||||||
|
}
|
||||||
pfx := netip.PrefixFrom(addr, addr.BitLen())
|
pfx := netip.PrefixFrom(addr, addr.BitLen())
|
||||||
return views.SliceContains(h.selfNode.Addresses(), pfx)
|
return views.SliceContains(h.selfNode.Addresses(), pfx)
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ type Wrapper struct {
|
|||||||
// timeNow, if non-nil, will be used to obtain the current time.
|
// timeNow, if non-nil, will be used to obtain the current time.
|
||||||
timeNow func() time.Time
|
timeNow func() time.Time
|
||||||
|
|
||||||
// natV4Config stores the current NAT configuration.
|
// natV4Config stores the current IPv4 NAT configuration.
|
||||||
natV4Config atomic.Pointer[natV4Config]
|
natV4Config atomic.Pointer[natV4Config]
|
||||||
|
|
||||||
// vectorBuffer stores the oldest unconsumed packet vector from tdev. It is
|
// vectorBuffer stores the oldest unconsumed packet vector from tdev. It is
|
||||||
@ -577,9 +577,9 @@ func (c *natV4Config) selectSrcIP(oldSrc, dst netip.Addr) netip.Addr {
|
|||||||
return oldSrc
|
return oldSrc
|
||||||
}
|
}
|
||||||
|
|
||||||
// natConfigFromWireGuardConfig generates a natV4Config from nm.
|
// natV4ConfigFromWGConfig generates a natV4Config from nm.
|
||||||
// If v4 NAT is not required, it returns nil.
|
// If v4 NAT is not required, it returns nil.
|
||||||
func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
func natV4ConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
||||||
if wcfg == nil {
|
if wcfg == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -632,7 +632,7 @@ func natConfigFromWGConfig(wcfg *wgcfg.Config) *natV4Config {
|
|||||||
// SetNetMap is called when a new NetworkMap is received.
|
// SetNetMap is called when a new NetworkMap is received.
|
||||||
// It currently (2023-03-01) only updates the IPv4 NAT configuration.
|
// It currently (2023-03-01) only updates the IPv4 NAT configuration.
|
||||||
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
|
func (t *Wrapper) SetWGConfig(wcfg *wgcfg.Config) {
|
||||||
cfg := natConfigFromWGConfig(wcfg)
|
cfg := natV4ConfigFromWGConfig(wcfg)
|
||||||
old := t.natV4Config.Swap(cfg)
|
old := t.natV4Config.Swap(cfg)
|
||||||
if !reflect.DeepEqual(old, cfg) {
|
if !reflect.DeepEqual(old, cfg) {
|
||||||
t.logf("nat config: %+v", cfg)
|
t.logf("nat config: %+v", cfg)
|
||||||
|
@ -780,7 +780,7 @@ func TestNATCfg(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
ncfg := natConfigFromWGConfig(tc.wcfg)
|
ncfg := natV4ConfigFromWGConfig(tc.wcfg)
|
||||||
for peer, want := range tc.snatMap {
|
for peer, want := range tc.snatMap {
|
||||||
if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want {
|
if got := ncfg.selectSrcIP(selfNativeIP, peer); got != want {
|
||||||
t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want)
|
t.Errorf("selectSrcIP[%v]: got %v; want %v", peer, got, want)
|
||||||
|
@ -374,6 +374,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.
|
// order to be reachable.
|
||||||
@ -1940,6 +1955,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,9 @@ func (src *Node) Clone() *Node {
|
|||||||
if dst.SelfNodeV4MasqAddrForThisPeer != nil {
|
if dst.SelfNodeV4MasqAddrForThisPeer != nil {
|
||||||
dst.SelfNodeV4MasqAddrForThisPeer = ptr.To(*src.SelfNodeV4MasqAddrForThisPeer)
|
dst.SelfNodeV4MasqAddrForThisPeer = ptr.To(*src.SelfNodeV4MasqAddrForThisPeer)
|
||||||
}
|
}
|
||||||
|
if dst.SelfNodeV6MasqAddrForThisPeer != nil {
|
||||||
|
dst.SelfNodeV6MasqAddrForThisPeer = ptr.To(*src.SelfNodeV6MasqAddrForThisPeer)
|
||||||
|
}
|
||||||
if src.ExitNodeDNSResolvers != nil {
|
if src.ExitNodeDNSResolvers != nil {
|
||||||
dst.ExitNodeDNSResolvers = make([]*dnstype.Resolver, len(src.ExitNodeDNSResolvers))
|
dst.ExitNodeDNSResolvers = make([]*dnstype.Resolver, len(src.ExitNodeDNSResolvers))
|
||||||
for i := range dst.ExitNodeDNSResolvers {
|
for i := range dst.ExitNodeDNSResolvers {
|
||||||
@ -113,6 +116,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
|
||||||
ExitNodeDNSResolvers []*dnstype.Resolver
|
ExitNodeDNSResolvers []*dnstype.Resolver
|
||||||
}{})
|
}{})
|
||||||
|
@ -350,7 +350,7 @@ func TestNodeEqual(t *testing.T) {
|
|||||||
"UnsignedPeerAPIOnly",
|
"UnsignedPeerAPIOnly",
|
||||||
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
||||||
"DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
|
"DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer",
|
||||||
"IsWireGuardOnly", "ExitNodeDNSResolvers",
|
"SelfNodeV6MasqAddrForThisPeer", "IsWireGuardOnly", "ExitNodeDNSResolvers",
|
||||||
}
|
}
|
||||||
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",
|
||||||
@ -545,6 +545,16 @@ func TestNodeEqual(t *testing.T) {
|
|||||||
&Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
|
&Node{SelfNodeV4MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("100.64.0.1"))},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
&Node{},
|
||||||
|
&Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
|
||||||
|
&Node{SelfNodeV6MasqAddrForThisPeer: ptr.To(netip.MustParseAddr("2001::3456"))},
|
||||||
|
true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
&Node{
|
&Node{
|
||||||
CapMap: NodeCapMap{
|
CapMap: NodeCapMap{
|
||||||
|
@ -186,6 +186,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) ExitNodeDNSResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
|
func (v NodeView) ExitNodeDNSResolvers() views.SliceView[*dnstype.Resolver, dnstype.ResolverView] {
|
||||||
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.ExitNodeDNSResolvers)
|
return views.SliceOfViews[*dnstype.Resolver, dnstype.ResolverView](v.ж.ExitNodeDNSResolvers)
|
||||||
@ -225,6 +233,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
|
||||||
ExitNodeDNSResolvers []*dnstype.Resolver
|
ExitNodeDNSResolvers []*dnstype.Resolver
|
||||||
}{})
|
}{})
|
||||||
|
@ -66,7 +66,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
|
||||||
|
|
||||||
// suppressAutoMapResponses is the set of nodes that should not be sent
|
// suppressAutoMapResponses is the set of nodes that should not be sent
|
||||||
// automatic map responses from serveMap. (They should only get manually sent ones)
|
// automatic map responses from serveMap. (They should only get manually sent ones)
|
||||||
@ -330,7 +330,7 @@ func (s *Server) serveMachine(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Node masquerades as for the Peer.
|
// Node masquerades as for the Peer.
|
||||||
//
|
//
|
||||||
// Setting this will have future MapResponses for Node to have
|
// Setting this will have future MapResponses for Node to have
|
||||||
// Peer.SelfNodeV4MasqAddrForThisPeer set to NodeMasqueradesAs.
|
// Peer.SelfNodeV{4,6}MasqAddrForThisPeer set to NodeMasqueradesAs.
|
||||||
// MapResponses for the Peer will now see Node.Addresses as
|
// MapResponses for the Peer will now see Node.Addresses as
|
||||||
// NodeMasqueradesAs.
|
// NodeMasqueradesAs.
|
||||||
type MasqueradePair struct {
|
type MasqueradePair struct {
|
||||||
@ -889,7 +889,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.Is6() {
|
||||||
|
p.SelfNodeV6MasqAddrForThisPeer = ptr.To(masqIP)
|
||||||
|
} else {
|
||||||
|
p.SelfNodeV4MasqAddrForThisPeer = ptr.To(masqIP)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
|
@ -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.
|
||||||
|
@ -99,6 +99,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 i := range peer.AllowedIPs().LenIter() {
|
for i := range peer.AllowedIPs().LenIter() {
|
||||||
allowedIP := peer.AllowedIPs().At(i)
|
allowedIP := peer.AllowedIPs().At(i)
|
||||||
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
|
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
|
||||||
|
@ -60,6 +60,9 @@ func (src *Peer) Clone() *Peer {
|
|||||||
if dst.V4MasqAddr != nil {
|
if dst.V4MasqAddr != nil {
|
||||||
dst.V4MasqAddr = ptr.To(*src.V4MasqAddr)
|
dst.V4MasqAddr = ptr.To(*src.V4MasqAddr)
|
||||||
}
|
}
|
||||||
|
if dst.V6MasqAddr != nil {
|
||||||
|
dst.V6MasqAddr = ptr.To(*src.V6MasqAddr)
|
||||||
|
}
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,6 +72,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…
x
Reference in New Issue
Block a user