diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 8e10a8262..0e1b9d05b 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -95,7 +95,8 @@ type CapabilityVersion int // - 56: 2023-01-24: Client understands CapabilityDebugTSDNSResolution // - 57: 2023-01-25: Client understands CapabilityBindToInterfaceByRoute // - 58: 2023-03-10: Client retries lite map updates before restarting map poll. -const CurrentCapabilityVersion CapabilityVersion = 58 +// - 59: 2023-03-16: Client understands Peers[].SelfNodeV4MasqAddrForThisPeer +const CurrentCapabilityVersion CapabilityVersion = 59 type StableID string @@ -273,6 +274,21 @@ type Node struct { // the client, this is calculated client-side based on a timestamp sent // from control, to avoid clock skew issues. Expired bool `json:",omitempty"` + + // SelfNodeV4MasqAddrForThisPeer is the IPv4 that this peer knows the current node as. + // It may be empty if the peer knows the current node by its native + // IPv4 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). + SelfNodeV4MasqAddrForThisPeer netip.Addr `json:",omitempty"` } // DisplayName returns the user-facing name for a node which should @@ -1698,7 +1714,8 @@ func (n *Node) Equal(n2 *Node) bool { n.computedHostIfDifferent == n2.computedHostIfDifferent && n.ComputedNameWithHost == n2.ComputedNameWithHost && eqStrings(n.Tags, n2.Tags) && - n.Expired == n2.Expired + n.Expired == n2.Expired && + n.SelfNodeV4MasqAddrForThisPeer == n2.SelfNodeV4MasqAddrForThisPeer } func eqBoolPtr(a, b *bool) bool { diff --git a/tailcfg/tailcfg_clone.go b/tailcfg/tailcfg_clone.go index 17557643a..746cf95b7 100644 --- a/tailcfg/tailcfg_clone.go +++ b/tailcfg/tailcfg_clone.go @@ -68,36 +68,37 @@ func (src *Node) Clone() *Node { // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _NodeCloneNeedsRegeneration = Node(struct { - ID NodeID - StableID StableNodeID - Name string - User UserID - Sharer UserID - Key key.NodePublic - KeyExpiry time.Time - KeySignature tkatype.MarshaledSignature - Machine key.MachinePublic - DiscoKey key.DiscoPublic - Addresses []netip.Prefix - AllowedIPs []netip.Prefix - Endpoints []string - DERP string - Hostinfo HostinfoView - Created time.Time - Cap CapabilityVersion - Tags []string - PrimaryRoutes []netip.Prefix - LastSeen *time.Time - Online *bool - KeepAlive bool - MachineAuthorized bool - Capabilities []string - UnsignedPeerAPIOnly bool - ComputedName string - computedHostIfDifferent string - ComputedNameWithHost string - DataPlaneAuditLogID string - Expired bool + ID NodeID + StableID StableNodeID + Name string + User UserID + Sharer UserID + Key key.NodePublic + KeyExpiry time.Time + KeySignature tkatype.MarshaledSignature + Machine key.MachinePublic + DiscoKey key.DiscoPublic + Addresses []netip.Prefix + AllowedIPs []netip.Prefix + Endpoints []string + DERP string + Hostinfo HostinfoView + Created time.Time + Cap CapabilityVersion + Tags []string + PrimaryRoutes []netip.Prefix + LastSeen *time.Time + Online *bool + KeepAlive bool + MachineAuthorized bool + Capabilities []string + UnsignedPeerAPIOnly bool + ComputedName string + computedHostIfDifferent string + ComputedNameWithHost string + DataPlaneAuditLogID string + Expired bool + SelfNodeV4MasqAddrForThisPeer netip.Addr }{}) // Clone makes a deep copy of Hostinfo. diff --git a/tailcfg/tailcfg_test.go b/tailcfg/tailcfg_test.go index 43c1a4bc3..d5f5402aa 100644 --- a/tailcfg/tailcfg_test.go +++ b/tailcfg/tailcfg_test.go @@ -349,7 +349,7 @@ func TestNodeEqual(t *testing.T) { "Capabilities", "UnsignedPeerAPIOnly", "ComputedName", "computedHostIfDifferent", "ComputedNameWithHost", - "DataPlaneAuditLogID", "Expired", + "DataPlaneAuditLogID", "Expired", "SelfNodeV4MasqAddrForThisPeer", } 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", @@ -534,6 +534,16 @@ func TestNodeEqual(t *testing.T) { &Node{}, false, }, + { + &Node{}, + &Node{SelfNodeV4MasqAddrForThisPeer: netip.MustParseAddr("100.64.0.1")}, + false, + }, + { + &Node{SelfNodeV4MasqAddrForThisPeer: netip.MustParseAddr("100.64.0.1")}, + &Node{SelfNodeV4MasqAddrForThisPeer: netip.MustParseAddr("100.64.0.1")}, + true, + }, } for i, tt := range tests { got := tt.a.Equal(tt.b) diff --git a/tailcfg/tailcfg_view.go b/tailcfg/tailcfg_view.go index 0e8676141..2d038108a 100644 --- a/tailcfg/tailcfg_view.go +++ b/tailcfg/tailcfg_view.go @@ -176,40 +176,44 @@ func (v NodeView) ComputedName() string { return v.ж.ComputedName func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost } func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID } func (v NodeView) Expired() bool { return v.ж.Expired } -func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) } +func (v NodeView) SelfNodeV4MasqAddrForThisPeer() netip.Addr { + return v.ж.SelfNodeV4MasqAddrForThisPeer +} +func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) } // A compilation failure here means this code must be regenerated, with the command at the top of this file. var _NodeViewNeedsRegeneration = Node(struct { - ID NodeID - StableID StableNodeID - Name string - User UserID - Sharer UserID - Key key.NodePublic - KeyExpiry time.Time - KeySignature tkatype.MarshaledSignature - Machine key.MachinePublic - DiscoKey key.DiscoPublic - Addresses []netip.Prefix - AllowedIPs []netip.Prefix - Endpoints []string - DERP string - Hostinfo HostinfoView - Created time.Time - Cap CapabilityVersion - Tags []string - PrimaryRoutes []netip.Prefix - LastSeen *time.Time - Online *bool - KeepAlive bool - MachineAuthorized bool - Capabilities []string - UnsignedPeerAPIOnly bool - ComputedName string - computedHostIfDifferent string - ComputedNameWithHost string - DataPlaneAuditLogID string - Expired bool + ID NodeID + StableID StableNodeID + Name string + User UserID + Sharer UserID + Key key.NodePublic + KeyExpiry time.Time + KeySignature tkatype.MarshaledSignature + Machine key.MachinePublic + DiscoKey key.DiscoPublic + Addresses []netip.Prefix + AllowedIPs []netip.Prefix + Endpoints []string + DERP string + Hostinfo HostinfoView + Created time.Time + Cap CapabilityVersion + Tags []string + PrimaryRoutes []netip.Prefix + LastSeen *time.Time + Online *bool + KeepAlive bool + MachineAuthorized bool + Capabilities []string + UnsignedPeerAPIOnly bool + ComputedName string + computedHostIfDifferent string + ComputedNameWithHost string + DataPlaneAuditLogID string + Expired bool + SelfNodeV4MasqAddrForThisPeer netip.Addr }{}) // View returns a readonly view of Hostinfo. diff --git a/util/deephash/deephash_test.go b/util/deephash/deephash_test.go index 159cc2a5c..6ad65e314 100644 --- a/util/deephash/deephash_test.go +++ b/util/deephash/deephash_test.go @@ -575,8 +575,8 @@ func TestGetTypeHasher(t *testing.T) { { name: "tailcfg.Node", val: &tailcfg.Node{}, - out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", - out32: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", + out32: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", }, } for _, tt := range tests {