types/netmap, all: use read-only tailcfg.NodeView in NetworkMap

Updates #8948

Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
pull/8966/head
Brad Fitzpatrick 10 months ago committed by Brad Fitzpatrick
parent b040094b90
commit 58a4fd43d8

@ -257,21 +257,25 @@ func (i *jsIPN) run(jsCallbacks js.Value) {
},
MachineStatus: jsMachineStatus[nm.MachineStatus],
},
Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode {
name := p.Name
Peers: mapSlice(nm.Peers, func(p tailcfg.NodeView) jsNetMapPeerNode {
name := p.Name()
if name == "" {
// In practice this should only happen for Hello.
name = p.Hostinfo.Hostname()
name = p.Hostinfo().Hostname()
}
addrs := make([]string, p.Addresses().Len())
for i := range p.Addresses().LenIter() {
addrs[i] = p.Addresses().At(i).Addr().String()
}
return jsNetMapPeerNode{
jsNetMapNode: jsNetMapNode{
Name: name,
Addresses: mapSlice(p.Addresses, func(a netip.Prefix) string { return a.Addr().String() }),
MachineKey: p.Machine.String(),
NodeKey: p.Key.String(),
Addresses: addrs,
MachineKey: p.Machine().String(),
NodeKey: p.Key().String(),
},
Online: p.Online,
TailscaleSSHEnabled: p.Hostinfo.TailscaleSSHEnabled(),
Online: p.Online(),
TailscaleSSHEnabled: p.Hostinfo().TailscaleSSHEnabled(),
}
}),
LockedOut: nm.TKAEnabled && len(nm.SelfNode.KeySignature) == 0,

@ -144,11 +144,18 @@ func (ms *mapSession) netmapForResponse(resp *tailcfg.MapResponse) *netmap.Netwo
ms.lastTKAInfo = resp.TKAInfo
}
// TODO(bradfitz): now that this is a view, remove some of the defensive
// cloning elsewhere in mapSession.
peerViews := make([]tailcfg.NodeView, len(resp.Peers))
for i, n := range resp.Peers {
peerViews[i] = n.View()
}
nm := &netmap.NetworkMap{
NodeKey: ms.privateNodeKey.Public(),
PrivateKey: ms.privateNodeKey,
MachineKey: ms.machinePubKey,
Peers: resp.Peers,
Peers: peerViews,
UserProfiles: make(map[tailcfg.UserID]tailcfg.UserProfile),
Domain: ms.lastDomain,
DomainAuditLogID: ms.lastDomainAuditLogID,

@ -38,6 +38,14 @@ func ips(ss ...string) (ips []netip.Addr) {
return
}
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
nv := make([]tailcfg.NodeView, len(v))
for i, n := range v {
nv[i] = n.View()
}
return nv
}
func TestDNSConfigForNetmap(t *testing.T) {
tests := []struct {
name string
@ -62,7 +70,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
nm: &netmap.NetworkMap{
Name: "myname.net",
Addresses: ipps("100.101.101.101"),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Name: "peera.net",
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001", "fe75::1002"),
@ -75,7 +83,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
Name: "v6-only.net",
Addresses: ipps("fe75::3"), // no IPv4, so we don't ignore IPv6
},
},
}),
},
prefs: &ipn.Prefs{},
want: &dns.Config{
@ -96,7 +104,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
nm: &netmap.NetworkMap{
Name: "myname.net",
Addresses: ipps("fe75::1"),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Name: "peera.net",
Addresses: ipps("100.102.0.1", "100.102.0.2", "fe75::1001"),
@ -109,7 +117,7 @@ func TestDNSConfigForNetmap(t *testing.T) {
Name: "v6-only.net",
Addresses: ipps("fe75::3"), // no IPv4, so we don't ignore IPv6
},
},
}),
},
prefs: &ipn.Prefs{},
want: &dns.Config{

@ -87,24 +87,26 @@ func (em *expiryManager) flagExpiredPeers(netmap *netmap.NetworkMap, localNow ti
return
}
for _, peer := range netmap.Peers {
for i, peer := range netmap.Peers {
// Nodes that don't expire have KeyExpiry set to the zero time;
// skip those and peers that are already marked as expired
// (e.g. from control).
if peer.KeyExpiry.IsZero() || peer.KeyExpiry.After(controlNow) {
delete(em.previouslyExpired, peer.StableID)
if peer.KeyExpiry().IsZero() || peer.KeyExpiry().After(controlNow) {
delete(em.previouslyExpired, peer.StableID())
continue
} else if peer.Expired {
} else if peer.Expired() {
continue
}
if !em.previouslyExpired[peer.StableID] {
em.logf("[v1] netmap: flagExpiredPeers: clearing expired peer %v", peer.StableID)
em.previouslyExpired[peer.StableID] = true
if !em.previouslyExpired[peer.StableID()] {
em.logf("[v1] netmap: flagExpiredPeers: clearing expired peer %v", peer.StableID())
em.previouslyExpired[peer.StableID()] = true
}
mut := peer.AsStruct()
// Actually mark the node as expired
peer.Expired = true
mut.Expired = true
// Control clears the Endpoints and DERP fields of expired
// nodes; do so here as well. The Expired bool is the correct
@ -113,12 +115,14 @@ func (em *expiryManager) flagExpiredPeers(netmap *netmap.NetworkMap, localNow ti
// NOTE: this is insufficient to actually break connectivity,
// since we discover endpoints via DERP, and due to DERP return
// path optimization.
peer.Endpoints = nil
peer.DERP = ""
mut.Endpoints = nil
mut.DERP = ""
// Defense-in-depth: break the node's public key as well, in
// case something tries to communicate.
peer.Key = key.NodePublicWithBadOldPrefix(peer.Key)
mut.Key = key.NodePublicWithBadOldPrefix(peer.Key())
netmap.Peers[i] = mut.View()
}
}
@ -144,13 +148,13 @@ func (em *expiryManager) nextPeerExpiry(nm *netmap.NetworkMap, localNow time.Tim
var nextExpiry time.Time // zero if none
for _, peer := range nm.Peers {
if peer.KeyExpiry.IsZero() {
if peer.KeyExpiry().IsZero() {
continue // tagged node
} else if peer.Expired {
} else if peer.Expired() {
// Peer already expired; Expired is set by the
// flagExpiredPeers function, above.
continue
} else if peer.KeyExpiry.Before(controlNow) {
} else if peer.KeyExpiry().Before(controlNow) {
// This peer already expired, and peer.Expired
// isn't set for some reason. Skip this node.
continue
@ -160,8 +164,8 @@ func (em *expiryManager) nextPeerExpiry(nm *netmap.NetworkMap, localNow time.Tim
// an expiry; otherwise, only update if this node's expiry is
// sooner than the currently-stored one (since we want the
// soonest-occurring expiry time).
if nextExpiry.IsZero() || peer.KeyExpiry.Before(nextExpiry) {
nextExpiry = peer.KeyExpiry
if nextExpiry.IsZero() || peer.KeyExpiry().Before(nextExpiry) {
nextExpiry = peer.KeyExpiry()
}
}

@ -44,38 +44,38 @@ func TestFlagExpiredPeers(t *testing.T) {
name string
controlTime *time.Time
netmap *netmap.NetworkMap
want []*tailcfg.Node
want []tailcfg.NodeView
}{
{
name: "no_expiry",
controlTime: &now,
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeInFuture),
},
}),
},
want: []*tailcfg.Node{
want: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeInFuture),
},
}),
},
{
name: "expiry",
controlTime: &now,
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeInPast),
},
}),
},
want: []*tailcfg.Node{
want: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeInPast, func(n *tailcfg.Node) {
n.Expired = true
n.Key = expiredKey
}),
},
}),
},
{
name: "bad_ControlTime",
@ -83,29 +83,29 @@ func TestFlagExpiredPeers(t *testing.T) {
controlTime: &timeBeforeEpoch,
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // before ControlTime
},
}),
},
want: []*tailcfg.Node{
want: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeBeforeEpoch.Add(-1*time.Hour)), // should have expired, but ControlTime is before epoch
},
}),
},
{
name: "tagged_node",
controlTime: &now,
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", time.Time{}), // tagged node; zero expiry
},
}),
},
want: []*tailcfg.Node{
want: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", time.Time{}), // not expired
},
}),
},
}
for _, tt := range tests {
@ -147,10 +147,10 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "no_expiry",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", noExpiry),
n(2, "bar", noExpiry),
},
}),
SelfNode: n(3, "self", noExpiry),
},
want: noExpiry,
@ -158,10 +158,10 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "future_expiry_from_peer",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", noExpiry),
n(2, "bar", timeInFuture),
},
}),
SelfNode: n(3, "self", noExpiry),
},
want: timeInFuture,
@ -169,10 +169,10 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "future_expiry_from_self",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", noExpiry),
n(2, "bar", noExpiry),
},
}),
SelfNode: n(3, "self", timeInFuture),
},
want: timeInFuture,
@ -180,10 +180,10 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "future_expiry_from_multiple_peers",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
n(2, "bar", timeInMoreFuture),
},
}),
SelfNode: n(3, "self", noExpiry),
},
want: timeInFuture,
@ -191,9 +191,9 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "future_expiry_from_peer_and_self",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInMoreFuture),
},
}),
SelfNode: n(2, "self", timeInFuture),
},
want: timeInFuture,
@ -201,7 +201,7 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "only_self",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{},
Peers: nodeViews([]*tailcfg.Node{}),
SelfNode: n(1, "self", timeInFuture),
},
want: timeInFuture,
@ -209,9 +209,9 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "peer_already_expired",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInPast),
},
}),
SelfNode: n(2, "self", timeInFuture),
},
want: timeInFuture,
@ -219,9 +219,9 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "self_already_expired",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInFuture),
},
}),
SelfNode: n(2, "self", timeInPast),
},
want: timeInFuture,
@ -229,9 +229,9 @@ func TestNextPeerExpiry(t *testing.T) {
{
name: "all_nodes_already_expired",
netmap: &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInPast),
},
}),
SelfNode: n(2, "self", timeInPast),
},
want: noExpiry,
@ -263,9 +263,9 @@ func TestNextPeerExpiry(t *testing.T) {
// If we don't adjust for the local time, this would return a
// time in the past.
nm := &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
n(1, "foo", timeInPast),
},
}),
}
got := em.nextPeerExpiry(nm, now)
want := now.Add(30 * time.Second)
@ -275,24 +275,24 @@ func TestNextPeerExpiry(t *testing.T) {
})
}
func formatNodes(nodes []*tailcfg.Node) string {
func formatNodes(nodes []tailcfg.NodeView) string {
var sb strings.Builder
for i, n := range nodes {
if i > 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "(%d, %q", n.ID, n.Name)
fmt.Fprintf(&sb, "(%d, %q", n.ID(), n.Name())
if n.Online != nil {
fmt.Fprintf(&sb, ", online=%v", *n.Online)
if n.Online() != nil {
fmt.Fprintf(&sb, ", online=%v", *n.Online())
}
if n.LastSeen != nil {
fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen.Unix())
if n.LastSeen() != nil {
fmt.Fprintf(&sb, ", lastSeen=%v", n.LastSeen().Unix())
}
if n.Key != (key.NodePublic{}) {
fmt.Fprintf(&sb, ", key=%v", n.Key.String())
if n.Key() != (key.NodePublic{}) {
fmt.Fprintf(&sb, ", key=%v", n.Key().String())
}
if n.Expired {
if n.Expired() {
fmt.Fprintf(&sb, ", expired=true")
}
sb.WriteString(")")

@ -204,7 +204,7 @@ type LocalBackend struct {
// netMap is not mutated in-place once set.
netMap *netmap.NetworkMap
nmExpiryTimer tstime.TimerController // for updating netMap on node expiry; can be nil
nodeByAddr map[netip.Addr]*tailcfg.Node
nodeByAddr map[netip.Addr]tailcfg.NodeView
activeLogin string // last logged LoginName from netMap
engineStatus ipn.EngineStatus
endpoints []tailcfg.Endpoint
@ -684,13 +684,13 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
if !prefs.ExitNodeID().IsZero() {
if exitPeer, ok := b.netMap.PeerWithStableID(prefs.ExitNodeID()); ok {
var online = false
if exitPeer.Online != nil {
online = *exitPeer.Online
if v := exitPeer.Online(); v != nil {
online = *v
}
s.ExitNodeStatus = &ipnstate.ExitNodeStatus{
ID: prefs.ExitNodeID(),
Online: online,
TailscaleIPs: exitPeer.Addresses,
TailscaleIPs: exitPeer.Addresses().AsSlice(),
}
}
}
@ -705,7 +705,7 @@ func (b *LocalBackend) updateStatus(sb *ipnstate.StatusBuilder, extraLocked func
ss.DNSName = b.netMap.Name
ss.UserID = b.netMap.User
if sn := b.netMap.SelfNode; sn != nil {
peerStatusFromNode(ss, sn)
peerStatusFromNode(ss, sn.View())
if c := sn.Capabilities; len(c) > 0 {
ss.Capabilities = append([]string(nil), c...)
}
@ -735,28 +735,30 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
exitNodeID := b.pm.CurrentPrefs().ExitNodeID()
for _, p := range b.netMap.Peers {
var lastSeen time.Time
if p.LastSeen != nil {
lastSeen = *p.LastSeen
if p.LastSeen() != nil {
lastSeen = *p.LastSeen()
}
var tailscaleIPs = make([]netip.Addr, 0, len(p.Addresses))
for _, addr := range p.Addresses {
var tailscaleIPs = make([]netip.Addr, 0, p.Addresses().Len())
for i := range p.Addresses().LenIter() {
addr := p.Addresses().At(i)
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.Addr()) {
tailscaleIPs = append(tailscaleIPs, addr.Addr())
}
}
online := p.Online()
ps := &ipnstate.PeerStatus{
InNetworkMap: true,
UserID: p.User,
UserID: p.User(),
TailscaleIPs: tailscaleIPs,
HostName: p.Hostinfo.Hostname(),
DNSName: p.Name,
OS: p.Hostinfo.OS(),
HostName: p.Hostinfo().Hostname(),
DNSName: p.Name(),
OS: p.Hostinfo().OS(),
LastSeen: lastSeen,
Online: p.Online != nil && *p.Online,
ShareeNode: p.Hostinfo.ShareeNode(),
ExitNode: p.StableID != "" && p.StableID == exitNodeID,
SSH_HostKeys: p.Hostinfo.SSH_HostKeys().AsSlice(),
Location: p.Hostinfo.Location(),
Online: online != nil && *online,
ShareeNode: p.Hostinfo().ShareeNode(),
ExitNode: p.StableID() != "" && p.StableID() == exitNodeID,
SSH_HostKeys: p.Hostinfo().SSH_HostKeys().AsSlice(),
Location: p.Hostinfo().Location(),
}
peerStatusFromNode(ps, p)
@ -767,29 +769,29 @@ func (b *LocalBackend) populatePeerStatusLocked(sb *ipnstate.StatusBuilder) {
if u := peerAPIURL(nodeIP(p, netip.Addr.Is6), p6); u != "" {
ps.PeerAPIURL = append(ps.PeerAPIURL, u)
}
sb.AddPeer(p.Key, ps)
sb.AddPeer(p.Key(), ps)
}
}
// peerStatusFromNode copies fields that exist in the Node struct for
// current node and peers into the provided PeerStatus.
func peerStatusFromNode(ps *ipnstate.PeerStatus, n *tailcfg.Node) {
ps.ID = n.StableID
ps.Created = n.Created
ps.ExitNodeOption = tsaddr.ContainsExitRoutes(views.SliceOf(n.AllowedIPs))
if n.Tags != nil {
v := views.SliceOf(n.Tags)
func peerStatusFromNode(ps *ipnstate.PeerStatus, n tailcfg.NodeView) {
ps.ID = n.StableID()
ps.Created = n.Created()
ps.ExitNodeOption = tsaddr.ContainsExitRoutes(n.AllowedIPs())
if n.Tags().Len() != 0 {
v := n.Tags()
ps.Tags = &v
}
if n.PrimaryRoutes != nil {
v := views.SliceOf(n.PrimaryRoutes)
if n.PrimaryRoutes().Len() != 0 {
v := n.PrimaryRoutes()
ps.PrimaryRoutes = &v
}
if n.Expired {
if n.Expired() {
ps.Expired = true
}
if t := n.KeyExpiry; !t.IsZero() {
if t := n.KeyExpiry(); !t.IsZero() {
t = t.Round(time.Second)
ps.KeyExpiry = &t
}
@ -798,7 +800,8 @@ func peerStatusFromNode(ps *ipnstate.PeerStatus, n *tailcfg.Node) {
// WhoIs reports the node and user who owns the node with the given IP:port.
// If the IP address is a Tailscale IP, the provided port may be 0.
// If ok == true, n and u are valid.
func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) {
var zero tailcfg.NodeView
b.mu.Lock()
defer b.mu.Unlock()
n, ok = b.nodeByAddr[ipp.Addr()]
@ -808,16 +811,16 @@ func (b *LocalBackend) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.Use
ip, ok = b.e.WhoIsIPPort(ipp)
}
if !ok {
return nil, u, false
return zero, u, false
}
n, ok = b.nodeByAddr[ip]
if !ok {
return nil, u, false
return zero, u, false
}
}
u, ok = b.netMap.UserProfiles[n.User]
u, ok = b.netMap.UserProfiles[n.User()]
if !ok {
return nil, u, false
return zero, u, false
}
return n, u, true
}
@ -1114,13 +1117,14 @@ func setExitNodeID(prefs *ipn.Prefs, nm *netmap.NetworkMap) (prefsChanged bool)
}
for _, peer := range nm.Peers {
for _, addr := range peer.Addresses {
for i := range peer.Addresses().LenIter() {
addr := peer.Addresses().At(i)
if !addr.IsSingleIP() || addr.Addr() != prefs.ExitNodeIP {
continue
}
// Found the node being referenced, upgrade prefs to
// reference it directly for next time.
prefs.ExitNodeID = peer.StableID
prefs.ExitNodeID = peer.StableID()
prefs.ExitNodeIP = netip.Addr{}
return true
}
@ -1597,16 +1601,16 @@ func (b *LocalBackend) updateFilterLocked(netMap *netmap.NetworkMap, prefs ipn.P
//
// If this reports true, the packet filter is invalid (the server is either broken
// or malicious) and should be ignored for safety.
func packetFilterPermitsUnlockedNodes(peers []*tailcfg.Node, packetFilter []filter.Match) bool {
func packetFilterPermitsUnlockedNodes(peers []tailcfg.NodeView, packetFilter []filter.Match) bool {
var b netipx.IPSetBuilder
var numUnlocked int
for _, p := range peers {
if !p.UnsignedPeerAPIOnly {
if !p.UnsignedPeerAPIOnly() {
continue
}
numUnlocked++
for _, a := range p.AllowedIPs { // not only addresses!
b.AddPrefix(a)
for i := range p.AllowedIPs().LenIter() { // not only addresses!
b.AddPrefix(p.AllowedIPs().At(i))
}
}
if numUnlocked == 0 {
@ -1764,11 +1768,11 @@ func shrinkDefaultRoute(route netip.Prefix, localInterfaceRoutes *netipx.IPSet,
// dnsCIDRsEqual determines whether two CIDR lists are equal
// for DNS map construction purposes (that is, only the first entry counts).
func dnsCIDRsEqual(newAddr, oldAddr []netip.Prefix) bool {
if len(newAddr) != len(oldAddr) {
func dnsCIDRsEqual(newAddr, oldAddr views.Slice[netip.Prefix]) bool {
if newAddr.Len() != oldAddr.Len() {
return false
}
if len(newAddr) == 0 || newAddr[0] == oldAddr[0] {
if newAddr.Len() == 0 || newAddr.At(0) == oldAddr.At(0) {
return true
}
return false
@ -1792,16 +1796,16 @@ func dnsMapsEqual(new, old *netmap.NetworkMap) bool {
if new.Name != old.Name {
return false
}
if !dnsCIDRsEqual(new.Addresses, old.Addresses) {
if !dnsCIDRsEqual(views.SliceOf(new.Addresses), views.SliceOf(old.Addresses)) {
return false
}
for i, newPeer := range new.Peers {
oldPeer := old.Peers[i]
if newPeer.Name != oldPeer.Name {
if newPeer.Name() != oldPeer.Name() {
return false
}
if !dnsCIDRsEqual(newPeer.Addresses, oldPeer.Addresses) {
if !dnsCIDRsEqual(newPeer.Addresses(), oldPeer.Addresses()) {
return false
}
}
@ -2418,8 +2422,8 @@ func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg
if err != nil {
pr.Err = err.Error()
}
if node != nil {
pr.NodeName = node.Name
if node.Valid() {
pr.NodeName = node.Name()
}
return pr, nil
}
@ -2438,36 +2442,37 @@ func (b *LocalBackend) Ping(ctx context.Context, ip netip.Addr, pingType tailcfg
}
}
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer *tailcfg.Node, peerBase string, err error) {
func (b *LocalBackend) pingPeerAPI(ctx context.Context, ip netip.Addr) (peer tailcfg.NodeView, peerBase string, err error) {
var zero tailcfg.NodeView
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
nm := b.NetMap()
if nm == nil {
return nil, "", errors.New("no netmap")
return zero, "", errors.New("no netmap")
}
peer, ok := nm.PeerByTailscaleIP(ip)
if !ok {
return nil, "", fmt.Errorf("no peer found with Tailscale IP %v", ip)
return zero, "", fmt.Errorf("no peer found with Tailscale IP %v", ip)
}
if peer.Expired {
return nil, "", errors.New("peer's node key has expired")
if peer.Expired() {
return zero, "", errors.New("peer's node key has expired")
}
base := peerAPIBase(nm, peer)
if base == "" {
return nil, "", fmt.Errorf("no PeerAPI base found for peer %v (%v)", peer.ID, ip)
return zero, "", fmt.Errorf("no PeerAPI base found for peer %v (%v)", peer.ID(), ip)
}
outReq, err := http.NewRequestWithContext(ctx, "HEAD", base, nil)
if err != nil {
return nil, "", err
return zero, "", err
}
tr := b.Dialer().PeerAPITransport()
res, err := tr.RoundTrip(outReq)
if err != nil {
return nil, "", err
return zero, "", err
}
defer res.Body.Close() // but unnecessary on HEAD responses
if res.StatusCode != http.StatusOK {
return nil, "", fmt.Errorf("HTTP status %v", res.Status)
return zero, "", fmt.Errorf("HTTP status %v", res.Status)
}
return peer, base, nil
}
@ -3098,17 +3103,24 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs ipn.PrefsView, logf logger.
// isn't configured to make MagicDNS resolution truly
// magic. Details in
// https://github.com/tailscale/tailscale/issues/1886.
set := func(name string, addrs []netip.Prefix) {
if len(addrs) == 0 || name == "" {
set := func(name string, addrs views.Slice[netip.Prefix]) {
if addrs.Len() == 0 || name == "" {
return
}
fqdn, err := dnsname.ToFQDN(name)
if err != nil {
return // TODO: propagate error?
}
have4 := slices.ContainsFunc(addrs, tsaddr.PrefixIs4)
var have4 bool
for i := range addrs.LenIter() {
if addrs.At(i).Addr().Is4() {
have4 = true
break
}
}
var ips []netip.Addr
for _, addr := range addrs {
for i := range addrs.LenIter() {
addr := addrs.At(i)
if selfV6Only {
if addr.Addr().Is6() {
ips = append(ips, addr.Addr())
@ -3130,9 +3142,9 @@ func dnsConfigForNetmap(nm *netmap.NetworkMap, prefs ipn.PrefsView, logf logger.
}
dcfg.Hosts[fqdn] = ips
}
set(nm.Name, nm.Addresses)
set(nm.Name, views.SliceOf(nm.Addresses))
for _, peer := range nm.Peers {
set(peer.Name, peer.Addresses)
set(peer.Name(), peer.Addresses())
}
for _, rec := range nm.DNS.ExtraRecords {
switch rec.Type {
@ -3995,28 +4007,28 @@ func (b *LocalBackend) setNetMapLocked(nm *netmap.NetworkMap) {
// Update the nodeByAddr index.
if b.nodeByAddr == nil {
b.nodeByAddr = map[netip.Addr]*tailcfg.Node{}
b.nodeByAddr = map[netip.Addr]tailcfg.NodeView{}
}
// First pass, mark everything unwanted.
for k := range b.nodeByAddr {
b.nodeByAddr[k] = nil
b.nodeByAddr[k] = tailcfg.NodeView{}
}
addNode := func(n *tailcfg.Node) {
for _, ipp := range n.Addresses {
if ipp.IsSingleIP() {
addNode := func(n tailcfg.NodeView) {
for i := range n.Addresses().LenIter() {
if ipp := n.Addresses().At(i); ipp.IsSingleIP() {
b.nodeByAddr[ipp.Addr()] = n
}
}
}
if nm.SelfNode != nil {
addNode(nm.SelfNode)
addNode(nm.SelfNode.View())
}
for _, p := range nm.Peers {
addNode(p)
}
// Third pass, actually delete the unwanted items.
for k, v := range b.nodeByAddr {
if v == nil {
if !v.Valid() {
delete(b.nodeByAddr, k)
}
}
@ -4293,7 +4305,7 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
continue
}
ret = append(ret, &apitype.FileTarget{
Node: p,
Node: p.AsStruct(),
PeerAPIURL: peerAPI,
})
}
@ -4306,15 +4318,15 @@ func (b *LocalBackend) FileTargets() ([]*apitype.FileTarget, error) {
// the netmap.
//
// b.mu must be locked.
func (b *LocalBackend) peerIsTaildropTargetLocked(p *tailcfg.Node) bool {
if b.netMap == nil || p == nil {
func (b *LocalBackend) peerIsTaildropTargetLocked(p tailcfg.NodeView) bool {
if b.netMap == nil || !p.Valid() {
return false
}
if b.netMap.User == p.User {
if b.netMap.User == p.User() {
return true
}
if len(p.Addresses) > 0 &&
b.peerHasCapLocked(p.Addresses[0].Addr(), tailcfg.PeerCapabilityFileSharingTarget) {
if p.Addresses().Len() > 0 &&
b.peerHasCapLocked(p.Addresses().At(0).Addr(), tailcfg.PeerCapabilityFileSharingTarget) {
// Explicitly noted in the netmap ACL caps as a target.
return true
}
@ -4374,9 +4386,9 @@ func (b *LocalBackend) registerIncomingFile(inf *incomingFile, active bool) {
}
}
func peerAPIPorts(peer *tailcfg.Node) (p4, p6 uint16) {
svcs := peer.Hostinfo.Services()
for i, n := 0, svcs.Len(); i < n; i++ {
func peerAPIPorts(peer tailcfg.NodeView) (p4, p6 uint16) {
svcs := peer.Hostinfo().Services()
for i := range svcs.LenIter() {
s := svcs.At(i)
switch s.Proto {
case tailcfg.PeerAPI4:
@ -4402,8 +4414,8 @@ func peerAPIURL(ip netip.Addr, port uint16) string {
// peerAPIBase returns the "http://ip:port" URL base to reach peer's peerAPI.
// It returns the empty string if the peer doesn't support the peerapi
// or there's no matching address family based on the netmap's own addresses.
func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
if nm == nil || peer == nil || !peer.Hostinfo.Valid() {
func peerAPIBase(nm *netmap.NetworkMap, peer tailcfg.NodeView) string {
if nm == nil || !peer.Valid() || !peer.Hostinfo().Valid() {
return ""
}
@ -4429,8 +4441,9 @@ func peerAPIBase(nm *netmap.NetworkMap, peer *tailcfg.Node) string {
return ""
}
func nodeIP(n *tailcfg.Node, pred func(netip.Addr) bool) netip.Addr {
for _, a := range n.Addresses {
func nodeIP(n tailcfg.NodeView, pred func(netip.Addr) bool) netip.Addr {
for i := range n.Addresses().LenIter() {
a := n.Addresses().At(i)
if a.IsSingleIP() && pred(a.Addr()) {
return a.Addr()
}
@ -4540,15 +4553,15 @@ func exitNodeCanProxyDNS(nm *netmap.NetworkMap, exitNodeID tailcfg.StableNodeID)
return "", false
}
for _, p := range nm.Peers {
if p.StableID == exitNodeID && peerCanProxyDNS(p) {
if p.StableID() == exitNodeID && peerCanProxyDNS(p) {
return peerAPIBase(nm, p) + "/dns-query", true
}
}
return "", false
}
func peerCanProxyDNS(p *tailcfg.Node) bool {
if p.Cap >= 26 {
func peerCanProxyDNS(p tailcfg.NodeView) bool {
if p.Cap() >= 26 {
// Actually added at 25
// (https://github.com/tailscale/tailscale/blob/3ae6f898cfdb58fd0e30937147dd6ce28c6808dd/tailcfg/tailcfg.go#L51)
// so anything >= 26 can do it.
@ -4556,10 +4569,9 @@ func peerCanProxyDNS(p *tailcfg.Node) bool {
}
// If p.Cap is not populated (e.g. older control server), then do the old
// thing of searching through services.
services := p.Hostinfo.Services()
for i, n := 0, services.Len(); i < n; i++ {
s := services.At(i)
if s.Proto == tailcfg.PeerAPIDNS && s.Port >= 1 {
services := p.Hostinfo().Services()
for i := range services.LenIter() {
if s := services.At(i); s.Proto == tailcfg.PeerAPIDNS && s.Port >= 1 {
return true
}
}

@ -87,46 +87,46 @@ func TestNetworkMapCompare(t *testing.T) {
},
{
"Peers identical",
&netmap.NetworkMap{Peers: []*tailcfg.Node{}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{})},
true,
},
{
"Peer list length",
// length of Peers list differs
&netmap.NetworkMap{Peers: []*tailcfg.Node{{}}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{}})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{})},
false,
},
{
"Node names identical",
&netmap.NetworkMap{Peers: []*tailcfg.Node{{Name: "A"}}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{{Name: "A"}}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{Name: "A"}})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{Name: "A"}})},
true,
},
{
"Node names differ",
&netmap.NetworkMap{Peers: []*tailcfg.Node{{Name: "A"}}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{{Name: "B"}}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{Name: "A"}})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{Name: "B"}})},
false,
},
{
"Node lists identical",
&netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{node1, node1})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{node1, node1})},
true,
},
{
"Node lists differ",
&netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node1}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{node1, node2}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{node1, node1})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{node1, node2})},
false,
},
{
"Node Users differ",
// User field is not checked.
&netmap.NetworkMap{Peers: []*tailcfg.Node{{User: 0}}},
&netmap.NetworkMap{Peers: []*tailcfg.Node{{User: 1}}},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{User: 0}})},
&netmap.NetworkMap{Peers: nodeViews([]*tailcfg.Node{{User: 1}})},
true,
},
}
@ -483,7 +483,7 @@ func TestPeerAPIBase(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := peerAPIBase(tt.nm, tt.peer)
got := peerAPIBase(tt.nm, tt.peer.View())
if got != tt.want {
t.Errorf("got %q; want %q", got, tt.want)
}
@ -758,7 +758,7 @@ func TestPacketFilterPermitsUnlockedNodes(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := packetFilterPermitsUnlockedNodes(tt.peers, tt.filter); got != tt.want {
if got := packetFilterPermitsUnlockedNodes(nodeViews(tt.peers), tt.filter); got != tt.want {
t.Errorf("got %v, want %v", got, tt.want)
}
})

@ -69,15 +69,16 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
var toDelete map[int]bool // peer index => true
for i, p := range nm.Peers {
if p.UnsignedPeerAPIOnly {
if p.UnsignedPeerAPIOnly() {
// Not subject to tailnet lock.
continue
}
if len(p.KeySignature) == 0 {
keySig := tkatype.MarshaledSignature(p.KeySignature().StringCopy()) // TODO(bradfitz,maisem): this is unfortunate. Change tkatype.MarshaledSignature to a string for viewer?
if len(keySig) == 0 {
b.logf("Network lock is dropping peer %v(%v) due to missing signature", p.ID, p.StableID)
mak.Set(&toDelete, i, true)
} else {
if err := b.tka.authority.NodeKeyAuthorized(p.Key, p.KeySignature); err != nil {
if err := b.tka.authority.NodeKeyAuthorized(p.Key(), keySig); err != nil {
b.logf("Network lock is dropping peer %v(%v) due to failed signature check: %v", p.ID, p.StableID, err)
mak.Set(&toDelete, i, true)
}
@ -86,7 +87,7 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
// nm.Peers is ordered, so deletion must be order-preserving.
if len(toDelete) > 0 {
peers := make([]*tailcfg.Node, 0, len(nm.Peers))
peers := make([]tailcfg.NodeView, 0, len(nm.Peers))
filtered := make([]ipnstate.TKAFilteredPeer, 0, len(toDelete))
for i, p := range nm.Peers {
if !toDelete[i] {
@ -94,13 +95,14 @@ func (b *LocalBackend) tkaFilterNetmapLocked(nm *netmap.NetworkMap) {
} else {
// Record information about the node we filtered out.
fp := ipnstate.TKAFilteredPeer{
Name: p.Name,
ID: p.ID,
StableID: p.StableID,
TailscaleIPs: make([]netip.Addr, len(p.Addresses)),
NodeKey: p.Key,
Name: p.Name(),
ID: p.ID(),
StableID: p.StableID(),
TailscaleIPs: make([]netip.Addr, p.Addresses().Len()),
NodeKey: p.Key(),
}
for i, addr := range p.Addresses {
for i := range p.Addresses().LenIter() {
addr := p.Addresses().At(i)
if addr.IsSingleIP() && tsaddr.IsTailscaleIP(addr.Addr()) {
fp.TailscaleIPs[i] = addr.Addr()
}

@ -558,26 +558,26 @@ func TestTKAFilterNetmap(t *testing.T) {
t.Fatal(err)
}
nm := netmap.NetworkMap{
Peers: []*tailcfg.Node{
nm := &netmap.NetworkMap{
Peers: nodeViews([]*tailcfg.Node{
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
{ID: 2, Key: n2.Public(), KeySignature: nil}, // missing sig
{ID: 3, Key: n3.Public(), KeySignature: n1GoodSig.Serialize()}, // someone elses sig
{ID: 4, Key: n4.Public(), KeySignature: n4Sig.Serialize()}, // messed-up signature
{ID: 5, Key: n5.Public(), KeySignature: n5GoodSig.Serialize()},
},
}),
}
b := &LocalBackend{
logf: t.Logf,
tka: &tkaState{authority: authority},
}
b.tkaFilterNetmapLocked(&nm)
b.tkaFilterNetmapLocked(nm)
want := []*tailcfg.Node{
want := nodeViews([]*tailcfg.Node{
{ID: 1, Key: n1.Public(), KeySignature: n1GoodSig.Serialize()},
{ID: 5, Key: n5.Public(), KeySignature: n5GoodSig.Serialize()},
}
})
nodePubComparer := cmp.Comparer(func(x, y key.NodePublic) bool {
return x.Raw32() == y.Raw32()
})

@ -576,7 +576,7 @@ func (pln *peerAPIListener) ServeConn(src netip.AddrPort, c net.Conn) {
}
h := &peerAPIHandler{
ps: pln.ps,
isSelf: nm.SelfNode.User == peerNode.User,
isSelf: nm.SelfNode.User == peerNode.User(),
remoteAddr: src,
selfNode: nm.SelfNode,
peerNode: peerNode,
@ -597,7 +597,7 @@ type peerAPIHandler struct {
remoteAddr netip.AddrPort
isSelf bool // whether peerNode is owned by same user as this node
selfNode *tailcfg.Node // this node; always non-nil
peerNode *tailcfg.Node // peerNode is who's making the request
peerNode tailcfg.NodeView // peerNode is who's making the request
peerUser tailcfg.UserProfile // profile of peerNode
}
@ -608,8 +608,8 @@ func (h *peerAPIHandler) logf(format string, a ...any) {
// isAddressValid reports whether addr is a valid destination address for this
// node originating from the peer.
func (h *peerAPIHandler) isAddressValid(addr netip.Addr) bool {
if h.peerNode.SelfNodeV4MasqAddrForThisPeer != nil {
return *h.peerNode.SelfNodeV4MasqAddrForThisPeer == addr
if v := h.peerNode.SelfNodeV4MasqAddrForThisPeer(); v != nil {
return *v == addr
}
pfx := netip.PrefixFrom(addr, addr.BitLen())
return slices.Contains(h.selfNode.Addresses, pfx)
@ -733,7 +733,7 @@ func (h *peerAPIHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
<body>
<h1>Hello, %s (%v)</h1>
This is my Tailscale device. Your device is %v.
`, html.EscapeString(who), h.remoteAddr.Addr(), html.EscapeString(h.peerNode.ComputedName))
`, html.EscapeString(who), h.remoteAddr.Addr(), html.EscapeString(h.peerNode.ComputedName()))
if h.isSelf {
fmt.Fprintf(w, "<p>You are the owner of this node.\n")
@ -1024,7 +1024,7 @@ func (f *incomingFile) PartialFile() ipn.PartialFile {
// canPutFile reports whether h can put a file ("Taildrop") to this node.
func (h *peerAPIHandler) canPutFile() bool {
if h.peerNode.UnsignedPeerAPIOnly {
if h.peerNode.UnsignedPeerAPIOnly() {
// Unsigned peers can't send files.
return false
}
@ -1038,7 +1038,7 @@ func (h *peerAPIHandler) canDebug() bool {
// This node does not expose debug info.
return false
}
if h.peerNode.UnsignedPeerAPIOnly {
if h.peerNode.UnsignedPeerAPIOnly() {
// Unsigned peers can't debug.
return false
}
@ -1047,7 +1047,7 @@ func (h *peerAPIHandler) canDebug() bool {
// canWakeOnLAN reports whether h can send a Wake-on-LAN packet from this node.
func (h *peerAPIHandler) canWakeOnLAN() bool {
if h.peerNode.UnsignedPeerAPIOnly {
if h.peerNode.UnsignedPeerAPIOnly() {
return false
}
return h.isSelf || h.peerHasCap(tailcfg.PeerCapabilityWakeOnLAN)

@ -462,9 +462,9 @@ func TestHandlePeerAPI(t *testing.T) {
e.ph = &peerAPIHandler{
isSelf: tt.isSelf,
selfNode: selfNode,
peerNode: &tailcfg.Node{
peerNode: (&tailcfg.Node{
ComputedName: "some-peer-name",
},
}).View(),
ps: &peerAPIServer{
b: lb,
},
@ -513,9 +513,9 @@ func TestFileDeleteRace(t *testing.T) {
}
ph := &peerAPIHandler{
isSelf: true,
peerNode: &tailcfg.Node{
peerNode: (&tailcfg.Node{
ComputedName: "some-peer-name",
},
}).View(),
selfNode: &tailcfg.Node{
Addresses: []netip.Prefix{netip.MustParsePrefix("100.100.100.101/32")},
},

@ -257,7 +257,7 @@ func (b *LocalBackend) ServeConfig() ipn.ServeConfigView {
return b.serveConfig
}
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer *tailcfg.Node, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
func (b *LocalBackend) HandleIngressTCPConn(ingressPeer tailcfg.NodeView, target ipn.HostPort, srcAddr netip.AddrPort, getConnOrReset func() (net.Conn, bool), sendRST func()) {
b.mu.Lock()
sc := b.serveConfig
b.mu.Unlock()

@ -201,16 +201,16 @@ func TestServeHTTPProxy(t *testing.T) {
},
},
}
b.nodeByAddr = map[netip.Addr]*tailcfg.Node{
netip.MustParseAddr("100.150.151.152"): {
b.nodeByAddr = map[netip.Addr]tailcfg.NodeView{
netip.MustParseAddr("100.150.151.152"): (&tailcfg.Node{
ComputedName: "some-peer",
User: tailcfg.UserID(1),
},
netip.MustParseAddr("100.150.151.153"): {
}).View(),
netip.MustParseAddr("100.150.151.153"): (&tailcfg.Node{
ComputedName: "some-tagged-peer",
Tags: []string{"tag:server", "tag:test"},
User: tailcfg.UserID(1),
},
}).View(),
}
// Start test serve endpoint.

@ -437,8 +437,8 @@ func (h *Handler) serveWhoIs(w http.ResponseWriter, r *http.Request) {
return
}
res := &apitype.WhoIsResponse{
Node: n, // always non-nil per WhoIsResponse contract
UserProfile: &u, // always non-nil per WhoIsResponse contract
Node: n.AsStruct(), // always non-nil per WhoIsResponse contract
UserProfile: &u, // always non-nil per WhoIsResponse contract
CapMap: b.PeerCaps(ipp.Addr()),
}
j, err := json.MarshalIndent(res, "", "\t")

@ -48,17 +48,18 @@ func dnsMapFromNetworkMap(nm *netmap.NetworkMap) dnsMap {
}
}
for _, p := range nm.Peers {
if p.Name == "" {
if p.Name() == "" {
continue
}
for _, a := range p.Addresses {
for i := range p.Addresses().LenIter() {
a := p.Addresses().At(i)
ip := a.Addr()
if ip.Is4() && !have4 {
continue
}
ret[canonMapKey(p.Name)] = ip
if dnsname.HasSuffix(p.Name, suffix) {
ret[canonMapKey(dnsname.TrimSuffix(p.Name, suffix))] = ip
ret[canonMapKey(p.Name())] = ip
if dnsname.HasSuffix(p.Name(), suffix) {
ret[canonMapKey(dnsname.TrimSuffix(p.Name(), suffix))] = ip
}
break
}

@ -12,6 +12,14 @@ import (
"tailscale.com/types/netmap"
)
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
nv := make([]tailcfg.NodeView, len(v))
for i, n := range v {
nv[i] = n.View()
}
return nv
}
func TestDNSMapFromNetworkMap(t *testing.T) {
pfx := netip.MustParsePrefix
ip := netip.MustParseAddr
@ -42,20 +50,20 @@ func TestDNSMapFromNetworkMap(t *testing.T) {
pfx("100.102.103.104/32"),
pfx("100::123/128"),
},
Peers: []*tailcfg.Node{
{
Peers: []tailcfg.NodeView{
(&tailcfg.Node{
Name: "a.tailnet",
Addresses: []netip.Prefix{
pfx("100.0.0.201/32"),
pfx("100::201/128"),
},
},
{
}).View(),
(&tailcfg.Node{
Name: "b.tailnet",
Addresses: []netip.Prefix{
pfx("100::202/128"),
},
},
}).View(),
},
},
want: dnsMap{
@ -74,7 +82,7 @@ func TestDNSMapFromNetworkMap(t *testing.T) {
Addresses: []netip.Prefix{
pfx("100::123/128"),
},
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Name: "a.tailnet",
Addresses: []netip.Prefix{
@ -88,7 +96,7 @@ func TestDNSMapFromNetworkMap(t *testing.T) {
pfx("100::202/128"),
},
},
},
}),
},
want: dnsMap{
"foo": ip("100::123"),

@ -99,7 +99,7 @@ func (ss *sshSession) newIncubatorCommand() (cmd *exec.Cmd) {
gids := strings.Join(ss.conn.userGroupIDs, ",")
remoteUser := ci.uprof.LoginName
if ci.node.IsTagged() {
remoteUser = strings.Join(ci.node.Tags, ",")
remoteUser = strings.Join(ci.node.Tags().AsSlice(), ",")
}
incubatorArgs := []string{

@ -64,7 +64,7 @@ type ipnLocalBackend interface {
GetSSH_HostKeys() ([]gossh.Signer, error)
ShouldRunSSH() bool
NetMap() *netmap.NetworkMap
WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool)
WhoIs(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool)
DoNoiseRequest(req *http.Request) (*http.Response, error)
Dialer() *tsdial.Dialer
TailscaleVarRoot() string
@ -791,7 +791,7 @@ func (c *conn) expandDelegateURLLocked(actionURL string) string {
}
return strings.NewReplacer(
"$SRC_NODE_IP", url.QueryEscape(ci.src.Addr().String()),
"$SRC_NODE_ID", fmt.Sprint(int64(ci.node.ID)),
"$SRC_NODE_ID", fmt.Sprint(int64(ci.node.ID())),
"$DST_NODE_IP", url.QueryEscape(ci.dst.Addr().String()),
"$DST_NODE_ID", dstNodeID,
"$SSH_USER", url.QueryEscape(ci.sshUser),
@ -1220,7 +1220,7 @@ type sshConnInfo struct {
dst netip.AddrPort
// node is srcIP's node.
node *tailcfg.Node
node tailcfg.NodeView
// uprof is node's UserProfile.
uprof tailcfg.UserProfile
@ -1334,7 +1334,7 @@ func (c *conn) principalMatchesTailscaleIdentity(p *tailcfg.SSHPrincipal) bool {
if p.Any {
return true
}
if !p.Node.IsZero() && ci.node != nil && p.Node == ci.node.StableID {
if !p.Node.IsZero() && ci.node.Valid() && p.Node == ci.node.StableID() {
return true
}
if p.NodeIP != "" {
@ -1702,15 +1702,15 @@ func (ss *sshSession) startNewRecording() (_ *recording, err error) {
},
SSHUser: ss.conn.info.sshUser,
LocalUser: ss.conn.localUser.Username,
SrcNode: strings.TrimSuffix(ss.conn.info.node.Name, "."),
SrcNodeID: ss.conn.info.node.StableID,
SrcNode: strings.TrimSuffix(ss.conn.info.node.Name(), "."),
SrcNodeID: ss.conn.info.node.StableID(),
ConnectionID: ss.conn.connID,
}
if !ss.conn.info.node.IsTagged() {
ch.SrcNodeUser = ss.conn.info.uprof.LoginName
ch.SrcNodeUserID = ss.conn.info.node.User
ch.SrcNodeUserID = ss.conn.info.node.User()
} else {
ch.SrcNodeTags = ss.conn.info.node.Tags
ch.SrcNodeTags = ss.conn.info.node.Tags().AsSlice()
}
j, err := json.Marshal(ch)
if err != nil {
@ -1738,7 +1738,7 @@ func (ss *sshSession) notifyControl(ctx context.Context, nodeKey key.NodePublic,
ConnectionID: ss.conn.connID,
CapVersion: tailcfg.CurrentCapabilityVersion,
NodeKey: nodeKey,
SrcNode: ss.conn.info.node.ID,
SrcNode: ss.conn.info.node.ID(),
SSHUser: ss.conn.info.sshUser,
LocalUser: ss.conn.localUser.Username,
RecordingAttempts: attempts,

@ -177,7 +177,7 @@ func TestMatchRule(t *testing.T) {
Principals: []*tailcfg.SSHPrincipal{{Node: "some-node-ID"}},
SSHUsers: map[string]string{"*": "ubuntu"},
},
ci: &sshConnInfo{node: &tailcfg.Node{StableID: "some-node-ID"}},
ci: &sshConnInfo{node: (&tailcfg.Node{StableID: "some-node-ID"}).View()},
wantUser: "ubuntu",
},
{
@ -283,11 +283,11 @@ func (ts *localState) NetMap() *netmap.NetworkMap {
}
}
func (ts *localState) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) {
return &tailcfg.Node{
func (ts *localState) WhoIs(ipp netip.AddrPort) (n tailcfg.NodeView, u tailcfg.UserProfile, ok bool) {
return (&tailcfg.Node{
ID: 2,
StableID: "peer-id",
}, tailcfg.UserProfile{
}).View(), tailcfg.UserProfile{
LoginName: "peer",
}, true
@ -861,7 +861,7 @@ func TestSSH(t *testing.T) {
sshUser: "test",
src: netip.MustParseAddrPort("1.2.3.4:32342"),
dst: netip.MustParseAddrPort("1.2.3.5:22"),
node: &tailcfg.Node{},
node: (&tailcfg.Node{}).View(),
uprof: tailcfg.UserProfile{},
}
sc.action0 = &tailcfg.SSHAction{Accept: true}

@ -388,6 +388,12 @@ func (n *Node) IsTagged() bool {
return len(n.Tags) > 0
}
// IsTagged reports whether the node has any tags.
func (n NodeView) IsTagged() bool { return n.ж.IsTagged() }
// DisplayName wraps Node.DisplayName.
func (n NodeView) DisplayName(forOwner bool) string { return n.ж.DisplayName(forOwner) }
// InitDisplayNames computes and populates n's display name
// fields: n.ComputedName, n.computedHostIfDifferent, and
// n.ComputedNameWithHost.

@ -34,7 +34,7 @@ type NetworkMap struct {
Addresses []netip.Prefix // same as tailcfg.Node.Addresses (IP addresses of this Node directly)
MachineStatus tailcfg.MachineStatus
MachineKey key.MachinePublic
Peers []*tailcfg.Node // sorted by Node.ID
Peers []tailcfg.NodeView // sorted by Node.ID
DNS tailcfg.DNSConfig
// TODO(maisem) : replace with View.
Hostinfo tailcfg.Hostinfo
@ -84,7 +84,7 @@ type NetworkMap struct {
// AnyPeersAdvertiseRoutes reports whether any peer is advertising non-exit node routes.
func (nm *NetworkMap) AnyPeersAdvertiseRoutes() bool {
for _, p := range nm.Peers {
if len(p.PrimaryRoutes) > 0 {
if p.PrimaryRoutes().Len() > 0 {
return true
}
}
@ -94,19 +94,21 @@ func (nm *NetworkMap) AnyPeersAdvertiseRoutes() bool {
// PeerByTailscaleIP returns a peer's Node based on its Tailscale IP.
//
// If nm is nil or no peer is found, ok is false.
func (nm *NetworkMap) PeerByTailscaleIP(ip netip.Addr) (peer *tailcfg.Node, ok bool) {
func (nm *NetworkMap) PeerByTailscaleIP(ip netip.Addr) (peer tailcfg.NodeView, ok bool) {
// TODO(bradfitz):
if nm == nil {
return nil, false
return tailcfg.NodeView{}, false
}
for _, n := range nm.Peers {
for _, a := range n.Addresses {
ad := n.Addresses()
for i := 0; i < ad.Len(); i++ {
a := ad.At(i)
if a.Addr() == ip {
return n, true
}
}
}
return nil, false
return tailcfg.NodeView{}, false
}
// MagicDNSSuffix returns the domain's MagicDNS suffix (even if
@ -153,13 +155,13 @@ func (nm *NetworkMap) VeryConcise() string {
}
// PeerWithStableID finds and returns the peer associated to the inputted StableNodeID.
func (nm *NetworkMap) PeerWithStableID(pid tailcfg.StableNodeID) (_ *tailcfg.Node, ok bool) {
func (nm *NetworkMap) PeerWithStableID(pid tailcfg.StableNodeID) (_ tailcfg.NodeView, ok bool) {
for _, p := range nm.Peers {
if p.StableID == pid {
if p.StableID() == pid {
return p, true
}
}
return nil, false
return tailcfg.NodeView{}, false
}
// printConciseHeader prints a concise header line representing nm to buf.
@ -203,15 +205,17 @@ func (a *NetworkMap) equalConciseHeader(b *NetworkMap) bool {
//
// If this function is changed to access different fields of p, keep
// in nodeConciseEqual in sync.
func printPeerConcise(buf *strings.Builder, p *tailcfg.Node) {
aip := make([]string, len(p.AllowedIPs))
for i, a := range p.AllowedIPs {
func printPeerConcise(buf *strings.Builder, p tailcfg.NodeView) {
aip := make([]string, p.AllowedIPs().Len())
for i := range aip {
a := p.AllowedIPs().At(i)
s := strings.TrimSuffix(fmt.Sprint(a), "/32")
aip[i] = s
}
ep := make([]string, len(p.Endpoints))
for i, e := range p.Endpoints {
ep := make([]string, p.Endpoints().Len())
for i := range ep {
e := p.Endpoints().At(i)
// Align vertically on the ':' between IP and port
colon := strings.IndexByte(e, ':')
spaces := 0
@ -222,21 +226,21 @@ func printPeerConcise(buf *strings.Builder, p *tailcfg.Node) {
ep[i] = fmt.Sprintf("%21v", e+strings.Repeat(" ", spaces))
}
derp := p.DERP
derp := p.DERP()
const derpPrefix = "127.3.3.40:"
if strings.HasPrefix(derp, derpPrefix) {
derp = "D" + derp[len(derpPrefix):]
}
var discoShort string
if !p.DiscoKey.IsZero() {
discoShort = p.DiscoKey.ShortString() + " "
if !p.DiscoKey().IsZero() {
discoShort = p.DiscoKey().ShortString() + " "
}
// Most of the time, aip is just one element, so format the
// table to look good in that case. This will also make multi-
// subnet nodes stand out visually.
fmt.Fprintf(buf, " %v %s%-2v %-15v : %v\n",
p.Key.ShortString(),
p.Key().ShortString(),
discoShort,
derp,
strings.Join(aip, " "),
@ -244,12 +248,12 @@ func printPeerConcise(buf *strings.Builder, p *tailcfg.Node) {
}
// nodeConciseEqual reports whether a and b are equal for the fields accessed by printPeerConcise.
func nodeConciseEqual(a, b *tailcfg.Node) bool {
return a.Key == b.Key &&
a.DERP == b.DERP &&
a.DiscoKey == b.DiscoKey &&
eqCIDRsIgnoreNil(a.AllowedIPs, b.AllowedIPs) &&
eqStringsIgnoreNil(a.Endpoints, b.Endpoints)
func nodeConciseEqual(a, b tailcfg.NodeView) bool {
return a.Key() == b.Key() &&
a.DERP() == b.DERP() &&
a.DiscoKey() == b.DiscoKey() &&
eqViewsIgnoreNil(a.AllowedIPs(), b.AllowedIPs()) &&
eqViewsIgnoreNil(a.Endpoints(), b.Endpoints())
}
func (b *NetworkMap) ConciseDiffFrom(a *NetworkMap) string {
@ -268,7 +272,7 @@ func (b *NetworkMap) ConciseDiffFrom(a *NetworkMap) string {
for len(aps) > 0 && len(bps) > 0 {
pa, pb := aps[0], bps[0]
switch {
case pa.ID == pb.ID:
case pa.ID() == pb.ID():
if !nodeConciseEqual(pa, pb) {
diff.WriteByte('-')
printPeerConcise(&diff, pa)
@ -276,12 +280,12 @@ func (b *NetworkMap) ConciseDiffFrom(a *NetworkMap) string {
printPeerConcise(&diff, pb)
}
aps, bps = aps[1:], bps[1:]
case pa.ID > pb.ID:
case pa.ID() > pb.ID():
// New peer in b.
diff.WriteByte('+')
printPeerConcise(&diff, pb)
bps = bps[1:]
case pb.ID > pa.ID:
case pb.ID() > pa.ID():
// Deleted peer in b.
diff.WriteByte('-')
printPeerConcise(&diff, pa)
@ -316,28 +320,18 @@ const (
AllowSubnetRoutes
)
// eqStringsIgnoreNil reports whether a and b have the same length and
// contents, but ignore whether a or b are nil.
func eqStringsIgnoreNil(a, b []string) bool {
if len(a) != len(b) {
// eqViewsIgnoreNil reports whether a and b have the same length and comparably
// equal values at each index. It's used for comparing views of slices and not
// caring about whether the slices are nil or not.
func eqViewsIgnoreNil[T comparable](a, b interface {
Len() int
At(int) T
}) bool {
if a.Len() != b.Len() {
return false
}
for i, v := range a {
if v != b[i] {
return false
}
}
return true
}
// eqCIDRsIgnoreNil reports whether a and b have the same length and
// contents, but ignore whether a or b are nil.
func eqCIDRsIgnoreNil(a, b []netip.Prefix) bool {
if len(a) != len(b) {
return false
}
for i, v := range a {
if v != b[i] {
for i, n := 0, a.Len(); i < n; i++ {
if a.At(i) != b.At(i) {
return false
}
}

@ -34,6 +34,14 @@ func testDiscoKey(hexPrefix string) (ret key.DiscoPublic) {
return key.DiscoPublicFromRaw32(mem.B(bs[:]))
}
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
nv := make([]tailcfg.NodeView, len(v))
for i, n := range v {
nv[i] = n.View()
}
return nv
}
func TestNetworkMapConcise(t *testing.T) {
for _, tt := range []struct {
name string
@ -44,7 +52,7 @@ func TestNetworkMapConcise(t *testing.T) {
name: "basic",
nm: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
@ -55,7 +63,7 @@ func TestNetworkMapConcise(t *testing.T) {
DERP: "127.3.3.40:4",
Endpoints: []string{"10.2.0.100:12", "10.1.0.100:12345"},
},
},
}),
},
want: "netmap: self: [AQEBA] auth=machine-unknown u=? []\n [AgICA] D2 : 192.168.0.100:12 192.168.0.100:12354\n [AwMDA] D4 : 10.2.0.100:12 10.1.0.100:12345\n",
},
@ -83,23 +91,23 @@ func TestConciseDiffFrom(t *testing.T) {
name: "no_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
want: "",
},
@ -107,23 +115,23 @@ func TestConciseDiffFrom(t *testing.T) {
name: "header_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(2),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
want: "-netmap: self: [AQEBA] auth=machine-unknown u=? []\n+netmap: self: [AgICA] auth=machine-unknown u=? []\n",
},
@ -131,18 +139,18 @@ func TestConciseDiffFrom(t *testing.T) {
name: "peer_add",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 1,
Key: testNodeKey(1),
@ -161,7 +169,7 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:3",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
want: "+ [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n+ [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
},
@ -169,7 +177,7 @@ func TestConciseDiffFrom(t *testing.T) {
name: "peer_remove",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 1,
Key: testNodeKey(1),
@ -188,18 +196,18 @@ func TestConciseDiffFrom(t *testing.T) {
DERP: "127.3.3.40:3",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "192.168.0.100:12354"},
},
},
}),
},
want: "- [AQEBA] D1 : 192.168.0.100:12 192.168.0.100:12354\n- [AwMDA] D3 : 192.168.0.100:12 192.168.0.100:12354\n",
},
@ -207,25 +215,25 @@ func TestConciseDiffFrom(t *testing.T) {
name: "peer_port_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "1.1.1.1:1"},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
DERP: "127.3.3.40:2",
Endpoints: []string{"192.168.0.100:12", "1.1.1.1:2"},
},
},
}),
},
want: "- [AgICA] D2 : 192.168.0.100:12 1.1.1.1:1 \n+ [AgICA] D2 : 192.168.0.100:12 1.1.1.1:2 \n",
},
@ -233,7 +241,7 @@ func TestConciseDiffFrom(t *testing.T) {
name: "disco_key_only_change",
a: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
@ -242,11 +250,11 @@ func TestConciseDiffFrom(t *testing.T) {
DiscoKey: testDiscoKey("f00f00f00f"),
AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
},
},
}),
},
b: &NetworkMap{
NodeKey: testNodeKey(1),
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
ID: 2,
Key: testNodeKey(2),
@ -255,7 +263,7 @@ func TestConciseDiffFrom(t *testing.T) {
DiscoKey: testDiscoKey("ba4ba4ba4b"),
AllowedIPs: []netip.Prefix{netip.PrefixFrom(netaddr.IPv4(100, 102, 103, 104), 32)},
},
},
}),
},
want: "- [AgICA] d:f00f00f00f000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n+ [AgICA] d:ba4ba4ba4b000000 D2 100.102.103.104 : 192.168.0.100:41641 1.1.1.1:41641\n",
},

@ -96,7 +96,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
eps = append(eps, ep.Addr.String())
}
n := tailcfg.Node{
n := &tailcfg.Node{
ID: tailcfg.NodeID(0),
Name: "n1",
Addresses: []netip.Prefix{a1},
@ -106,7 +106,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
e2.SetNetworkMap(&netmap.NetworkMap{
NodeKey: k2.Public(),
PrivateKey: k2,
Peers: []*tailcfg.Node{&n},
Peers: []tailcfg.NodeView{n.View()},
})
p := wgcfg.Peer{
@ -133,7 +133,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
eps = append(eps, ep.Addr.String())
}
n := tailcfg.Node{
n := &tailcfg.Node{
ID: tailcfg.NodeID(0),
Name: "n2",
Addresses: []netip.Prefix{a2},
@ -143,7 +143,7 @@ func setupWGTest(b *testing.B, logf logger.Logf, traf *TrafficGen, a1, a2 netip.
e1.SetNetworkMap(&netmap.NetworkMap{
NodeKey: k1.Public(),
PrivateKey: k1,
Peers: []*tailcfg.Node{&n},
Peers: []tailcfg.NodeView{n.View()},
})
p := wgcfg.Peer{

@ -101,10 +101,10 @@ func (c *Conn) ServeHTTPDebug(w http.ResponseWriter, r *http.Request) {
}
sort.Slice(ent, func(i, j int) bool { return ent[i].pub.Less(ent[j].pub) })
peers := map[key.NodePublic]*tailcfg.Node{}
peers := map[key.NodePublic]tailcfg.NodeView{}
if c.netMap != nil {
for _, p := range c.netMap.Peers {
peers[p.Key] = p
peers[p.Key()] = p
}
}
@ -187,15 +187,15 @@ func printEndpointHTML(w io.Writer, ep *endpoint) {
}
func peerDebugName(p *tailcfg.Node) string {
if p == nil {
func peerDebugName(p tailcfg.NodeView) string {
if !p.Valid() {
return ""
}
n := p.Name
n := p.Name()
if base, _, ok := strings.Cut(n, "."); ok {
return base
}
return p.Hostinfo.Hostname()
return p.Hostinfo().Hostname()
}
func ipPortLess(a, b netip.AddrPort) bool {

@ -725,15 +725,15 @@ func (de *endpoint) setLastPing(ipp netip.AddrPort, now mono.Time) {
// updateFromNode updates the endpoint based on a tailcfg.Node from a NetMap
// update.
func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
if n == nil {
func (de *endpoint) updateFromNode(n tailcfg.NodeView, heartbeatDisabled bool) {
if !n.Valid() {
panic("nil node when updating endpoint")
}
de.mu.Lock()
defer de.mu.Unlock()
de.heartbeatDisabled = heartbeatDisabled
de.expired = n.Expired
de.expired = n.Expired()
epDisco := de.disco.Load()
var discoKey key.DiscoPublic
@ -741,11 +741,11 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
discoKey = epDisco.key
}
if discoKey != n.DiscoKey {
de.c.logf("[v1] magicsock: disco: node %s changed from %s to %s", de.publicKey.ShortString(), discoKey, n.DiscoKey)
if discoKey != n.DiscoKey() {
de.c.logf("[v1] magicsock: disco: node %s changed from %s to %s", de.publicKey.ShortString(), discoKey, n.DiscoKey())
de.disco.Store(&endpointDisco{
key: n.DiscoKey,
short: n.DiscoKey.ShortString(),
key: n.DiscoKey(),
short: n.DiscoKey().ShortString(),
})
de.debugUpdates.Add(EndpointChange{
When: time.Now(),
@ -753,7 +753,7 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
})
de.resetLocked()
}
if n.DERP == "" {
if n.DERP() == "" {
if de.derpAddr.IsValid() {
de.debugUpdates.Add(EndpointChange{
When: time.Now(),
@ -763,7 +763,7 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
}
de.derpAddr = netip.AddrPort{}
} else {
newDerp, _ := netip.ParseAddrPort(n.DERP)
newDerp, _ := netip.ParseAddrPort(n.DERP())
if de.derpAddr != newDerp {
de.debugUpdates.Add(EndpointChange{
When: time.Now(),
@ -780,7 +780,8 @@ func (de *endpoint) updateFromNode(n *tailcfg.Node, heartbeatDisabled bool) {
}
var newIpps []netip.AddrPort
for i, epStr := range n.Endpoints {
for i := range n.Endpoints().LenIter() {
epStr := n.Endpoints().At(i)
if i > math.MaxInt16 {
// Seems unlikely.
continue

@ -711,7 +711,7 @@ func (c *Conn) LastRecvActivityOfNodeKey(nk key.NodePublic) string {
}
// Ping handles a "tailscale ping" CLI query.
func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, size int, cb func(*ipnstate.PingResult)) {
func (c *Conn) Ping(peer tailcfg.NodeView, res *ipnstate.PingResult, size int, cb func(*ipnstate.PingResult)) {
c.mu.Lock()
defer c.mu.Unlock()
if c.privateKey.IsZero() {
@ -719,17 +719,17 @@ func (c *Conn) Ping(peer *tailcfg.Node, res *ipnstate.PingResult, size int, cb f
cb(res)
return
}
if len(peer.Addresses) > 0 {
res.NodeIP = peer.Addresses[0].Addr().String()
if peer.Addresses().Len() > 0 {
res.NodeIP = peer.Addresses().At(0).Addr().String()
}
res.NodeName = peer.Name // prefer DNS name
res.NodeName = peer.Name() // prefer DNS name
if res.NodeName == "" {
res.NodeName = peer.Hostinfo.Hostname() // else hostname
res.NodeName = peer.Hostinfo().Hostname() // else hostname
} else {
res.NodeName, _, _ = strings.Cut(res.NodeName, ".")
}
ep, ok := c.peerMap.endpointForNodeKey(peer.Key)
ep, ok := c.peerMap.endpointForNodeKey(peer.Key())
if !ok {
res.Err = "unknown peer"
cb(res)
@ -753,13 +753,13 @@ func (c *Conn) populateCLIPingResponseLocked(res *ipnstate.PingResult, latency t
// GetEndpointChanges returns the most recent changes for a particular
// endpoint. The returned EndpointChange structs are for debug use only and
// there are no guarantees about order, size, or content.
func (c *Conn) GetEndpointChanges(peer *tailcfg.Node) ([]EndpointChange, error) {
func (c *Conn) GetEndpointChanges(peer tailcfg.NodeView) ([]EndpointChange, error) {
c.mu.Lock()
if c.privateKey.IsZero() {
c.mu.Unlock()
return nil, fmt.Errorf("tailscaled stopped")
}
ep, ok := c.peerMap.endpointForNodeKey(peer.Key)
ep, ok := c.peerMap.endpointForNodeKey(peer.Key())
c.mu.Unlock()
if !ok {
@ -1729,7 +1729,7 @@ func (c *Conn) UpdatePeers(newPeers map[key.NodePublic]struct{}) {
}
}
func nodesEqual(x, y []*tailcfg.Node) bool {
func nodesEqual(x, y []tailcfg.NodeView) bool {
if len(x) != len(y) {
return false
}
@ -1800,8 +1800,8 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
// we'll fall through to the next pass, which allocates but can
// handle full set updates.
for _, n := range nm.Peers {
if ep, ok := c.peerMap.endpointForNodeKey(n.Key); ok {
if n.DiscoKey.IsZero() && !n.IsWireGuardOnly {
if ep, ok := c.peerMap.endpointForNodeKey(n.Key()); ok {
if n.DiscoKey().IsZero() && !n.IsWireGuardOnly() {
// Discokey transitioned from non-zero to zero? This should not
// happen in the wild, however it could mean:
// 1. A node was downgraded from post 0.100 to pre 0.100.
@ -1820,7 +1820,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
c.peerMap.upsertEndpoint(ep, oldDiscoKey) // maybe update discokey mappings in peerMap
continue
}
if n.DiscoKey.IsZero() && !n.IsWireGuardOnly {
if n.DiscoKey().IsZero() && !n.IsWireGuardOnly() {
// Ancient pre-0.100 node, which does not have a disco key, and will only be reachable via DERP.
continue
}
@ -1828,30 +1828,30 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
ep := &endpoint{
c: c,
debugUpdates: ringbuffer.New[EndpointChange](entriesPerBuffer),
publicKey: n.Key,
publicKeyHex: n.Key.UntypedHexString(),
publicKey: n.Key(),
publicKeyHex: n.Key().UntypedHexString(),
sentPing: map[stun.TxID]sentPing{},
endpointState: map[netip.AddrPort]*endpointState{},
heartbeatDisabled: heartbeatDisabled,
isWireguardOnly: n.IsWireGuardOnly,
isWireguardOnly: n.IsWireGuardOnly(),
}
if len(n.Addresses) > 0 {
ep.nodeAddr = n.Addresses[0].Addr()
if n.Addresses().Len() > 0 {
ep.nodeAddr = n.Addresses().At(0).Addr()
}
ep.initFakeUDPAddr()
if n.DiscoKey.IsZero() {
if n.DiscoKey().IsZero() {
ep.disco.Store(nil)
} else {
ep.disco.Store(&endpointDisco{
key: n.DiscoKey,
short: n.DiscoKey.ShortString(),
key: n.DiscoKey(),
short: n.DiscoKey().ShortString(),
})
if debugDisco() { // rather than making a new knob
c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key.ShortString(), n.DiscoKey.ShortString(), logger.ArgWriter(func(w *bufio.Writer) {
c.logf("magicsock: created endpoint key=%s: disco=%s; %v", n.Key().ShortString(), n.DiscoKey().ShortString(), logger.ArgWriter(func(w *bufio.Writer) {
const derpPrefix = "127.3.3.40:"
if strings.HasPrefix(n.DERP, derpPrefix) {
ipp, _ := netip.ParseAddrPort(n.DERP)
if strings.HasPrefix(n.DERP(), derpPrefix) {
ipp, _ := netip.ParseAddrPort(n.DERP())
regionID := int(ipp.Port())
code := c.derpRegionCodeLocked(regionID)
if code != "" {
@ -1860,14 +1860,16 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
fmt.Fprintf(w, "derp=%v%s ", regionID, code)
}
for _, a := range n.AllowedIPs {
for i := range n.AllowedIPs().LenIter() {
a := n.AllowedIPs().At(i)
if a.IsSingleIP() {
fmt.Fprintf(w, "aip=%v ", a.Addr())
} else {
fmt.Fprintf(w, "aip=%v ", a)
}
}
for _, ep := range n.Endpoints {
for i := range n.Endpoints().LenIter() {
ep := n.Endpoints().At(i)
fmt.Fprintf(w, "ep=%v ", ep)
}
}))
@ -1885,7 +1887,7 @@ func (c *Conn) SetNetworkMap(nm *netmap.NetworkMap) {
if c.peerMap.nodeCount() != len(nm.Peers) {
keep := make(map[key.NodePublic]bool, len(nm.Peers))
for _, n := range nm.Peers {
keep[n.Key] = true
keep[n.Key()] = true
}
c.peerMap.forEachEndpoint(func(ep *endpoint) {
if !keep[ep.publicKey] {

@ -284,7 +284,7 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM
Endpoints: epStrings(eps[i]),
DERP: "127.3.3.40:1",
}
nm.Peers = append(nm.Peers, peer)
nm.Peers = append(nm.Peers, peer.View())
}
if mutateNetmap != nil {
@ -304,7 +304,7 @@ func meshStacks(logf logger.Logf, mutateNetmap func(idx int, nm *netmap.NetworkM
m.conn.SetNetworkMap(nm)
peerSet := make(map[key.NodePublic]struct{}, len(nm.Peers))
for _, peer := range nm.Peers {
peerSet[peer.Key] = struct{}{}
peerSet[peer.Key()] = struct{}{}
}
m.conn.UpdatePeers(peerSet)
wg, err := nmcfg.WGCfg(nm, logf, netmap.AllowSingleHosts, "")
@ -657,7 +657,9 @@ func TestDiscokeyChange(t *testing.T) {
}
mu.Lock()
defer mu.Unlock()
nm.Peers[0].DiscoKey = m1DiscoKey
mut := nm.Peers[0].AsStruct()
mut.DiscoKey = m1DiscoKey
nm.Peers[0] = mut.View()
}
cleanupMesh := meshStacks(t.Logf, setm1Key, m1, m2)
@ -1248,13 +1250,13 @@ func addTestEndpoint(tb testing.TB, conn *Conn, sendConn net.PacketConn) (key.No
discoKey := key.DiscoPublicFromRaw32(mem.B([]byte{31: 1}))
nodeKey := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 31: 0}))
conn.SetNetworkMap(&netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: nodeKey,
DiscoKey: discoKey,
Endpoints: []string{sendConn.LocalAddr().String()},
},
},
}),
})
conn.SetPrivateKey(key.NodePrivateFromRaw32(mem.B([]byte{0: 1, 31: 0})))
_, err := conn.ParseEndpoint(nodeKey.UntypedHexString())
@ -1427,6 +1429,14 @@ func BenchmarkReceiveFrom_Native(b *testing.B) {
}
}
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
nv := make([]tailcfg.NodeView, len(v))
for i, n := range v {
nv[i] = n.View()
}
return nv
}
// Test that a netmap update where node changes its node key but
// doesn't change its disco key doesn't result in a broken state.
//
@ -1444,13 +1454,13 @@ func TestSetNetworkMapChangingNodeKey(t *testing.T) {
nodeKey2 := key.NodePublicFromRaw32(mem.B([]byte{0: 'N', 1: 'K', 2: '2', 31: 0}))
conn.SetNetworkMap(&netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: nodeKey1,
DiscoKey: discoKey,
Endpoints: []string{"192.168.1.2:345"},
},
},
}),
})
_, err := conn.ParseEndpoint(nodeKey1.UntypedHexString())
if err != nil {
@ -1459,13 +1469,13 @@ func TestSetNetworkMapChangingNodeKey(t *testing.T) {
for i := 0; i < 3; i++ {
conn.SetNetworkMap(&netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: nodeKey2,
DiscoKey: discoKey,
Endpoints: []string{"192.168.1.2:345"},
},
},
}),
})
}
@ -1792,7 +1802,7 @@ func TestStressSetNetworkMap(t *testing.T) {
}
// Set the netmap.
conn.SetNetworkMap(&netmap.NetworkMap{
Peers: peers,
Peers: nodeViews(peers),
})
// Check invariants.
if err := conn.peerMap.validate(); err != nil {
@ -2237,7 +2247,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
PrivateKey: m.privateKey,
NodeKey: m.privateKey.Public(),
Addresses: []netip.Prefix{tsaip},
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: wgkey.Public(),
Endpoints: []string{wgEp.String()},
@ -2245,7 +2255,7 @@ func TestIsWireGuardOnlyPeer(t *testing.T) {
Addresses: []netip.Prefix{wgaip},
AllowedIPs: []netip.Prefix{wgaip},
},
},
}),
}
m.conn.SetNetworkMap(nm)
@ -2295,7 +2305,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
PrivateKey: m.privateKey,
NodeKey: m.privateKey.Public(),
Addresses: []netip.Prefix{tsaip},
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: wgkey.Public(),
Endpoints: []string{wgEp.String()},
@ -2304,7 +2314,7 @@ func TestIsWireGuardOnlyPeerWithMasquerade(t *testing.T) {
AllowedIPs: []netip.Prefix{wgaip},
SelfNodeV4MasqAddrForThisPeer: ptr.To(masqip.Addr()),
},
},
}),
}
m.conn.SetNetworkMap(nm)
@ -2421,7 +2431,7 @@ func TestIsWireGuardOnlyPickEndpointByPing(t *testing.T) {
PrivateKey: m.privateKey,
NodeKey: m.privateKey.Public(),
Addresses: []netip.Prefix{tsaip},
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: wgkey.Public(),
Endpoints: []string{wgEp.String(), wgEp2.String(), wgEpV6.String()},
@ -2429,7 +2439,7 @@ func TestIsWireGuardOnlyPickEndpointByPing(t *testing.T) {
Addresses: []netip.Prefix{wgaip},
AllowedIPs: []netip.Prefix{wgaip},
},
},
}),
}
applyNetworkMap(t, m, nm)

@ -146,21 +146,22 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
return
}
n := pip.Node
if !n.IsWireGuardOnly {
if n.DiscoKey.IsZero() {
e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key.ShortString())
if !n.IsWireGuardOnly() {
if n.DiscoKey().IsZero() {
e.logf("open-conn-track: timeout opening %v; peer node %v running pre-0.100", flow, n.Key().ShortString())
return
}
if n.DERP == "" {
e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key.ShortString())
if n.DERP() == "" {
e.logf("open-conn-track: timeout opening %v; peer node %v not connected to any DERP relay", flow, n.Key().ShortString())
return
}
}
ps, found := e.getPeerStatusLite(n.Key)
ps, found := e.getPeerStatusLite(n.Key())
if !found {
onlyZeroRoute := true // whether peerForIP returned n only because its /0 route matched
for _, r := range n.AllowedIPs {
for i := range n.AllowedIPs().LenIter() {
r := n.AllowedIPs().At(i)
if r.Bits() != 0 && r.Contains(flow.Dst.Addr()) {
onlyZeroRoute = false
break
@ -178,7 +179,7 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
// node.
return
}
e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to WireGuard", flow, n.Key.ShortString())
e.logf("open-conn-track: timeout opening %v; target node %v in netmap but unknown to WireGuard", flow, n.Key().ShortString())
return
}
@ -189,24 +190,24 @@ func (e *userspaceEngine) onOpenTimeout(flow flowtrack.Tuple) {
_ = ps.LastHandshake
online := "?"
if n.IsWireGuardOnly {
if n.IsWireGuardOnly() {
online = "wg"
} else {
if n.Online != nil {
if *n.Online {
if v := n.Online(); v != nil {
if *v {
online = "yes"
} else {
online = "no"
}
}
if n.LastSeen != nil && online != "yes" {
online += fmt.Sprintf(", lastseen=%v", durFmt(*n.LastSeen))
if n.LastSeen() != nil && online != "yes" {
online += fmt.Sprintf(", lastseen=%v", durFmt(*n.LastSeen()))
}
}
e.logf("open-conn-track: timeout opening %v to node %v; online=%v, lastRecv=%v",
flow, n.Key.ShortString(),
flow, n.Key().ShortString(),
online,
e.magicConn.LastRecvActivityOfNodeKey(n.Key))
e.magicConn.LastRecvActivityOfNodeKey(n.Key()))
}
func durFmt(t time.Time) string {

@ -1226,7 +1226,7 @@ func (e *userspaceEngine) Ping(ip netip.Addr, pingType tailcfg.PingType, size in
}
peer := pip.Node
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key.ShortString(), peer.ComputedName)
e.logf("ping(%v): sending %v ping to %v %v ...", ip, pingType, peer.Key().ShortString(), peer.ComputedName())
switch pingType {
case "disco":
e.magicConn.Ping(peer, res, size, cb)
@ -1254,7 +1254,7 @@ func (e *userspaceEngine) mySelfIPMatchingFamily(dst netip.Addr) (src netip.Addr
return netip.Addr{}, errors.New("no self address in netmap matching address family")
}
func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer tailcfg.NodeView, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
srcIP, err := e.mySelfIPMatchingFamily(destIP)
if err != nil {
res.Err = err.Error()
@ -1295,7 +1295,7 @@ func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer *tailcfg.N
d := time.Since(t0)
res.LatencySeconds = d.Seconds()
res.NodeIP = destIP.String()
res.NodeName = peer.ComputedName
res.NodeName = peer.ComputedName()
cb(res)
})
@ -1303,7 +1303,7 @@ func (e *userspaceEngine) sendICMPEchoRequest(destIP netip.Addr, peer *tailcfg.N
e.tundev.InjectOutbound(icmpPing)
}
func (e *userspaceEngine) sendTSMPPing(ip netip.Addr, peer *tailcfg.Node, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
func (e *userspaceEngine) sendTSMPPing(ip netip.Addr, peer tailcfg.NodeView, res *ipnstate.PingResult, cb func(*ipnstate.PingResult)) {
srcIP, err := e.mySelfIPMatchingFamily(ip)
if err != nil {
res.Err = err.Error()
@ -1337,7 +1337,7 @@ func (e *userspaceEngine) sendTSMPPing(ip netip.Addr, peer *tailcfg.Node, res *i
d := time.Since(t0)
res.LatencySeconds = d.Seconds()
res.NodeIP = ip.String()
res.NodeName = peer.ComputedName
res.NodeName = peer.ComputedName()
res.PeerAPIPort = pong.PeerAPIPort
cb(res)
})
@ -1436,7 +1436,8 @@ func (e *userspaceEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) {
// Check for exact matches before looking for subnet matches.
// TODO(bradfitz): add maps for these. on NetworkMap?
for _, p := range nm.Peers {
for _, a := range p.Addresses {
for i := range p.Addresses().LenIter() {
a := p.Addresses().At(i)
if a.Addr() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
return PeerForIP{Node: p, Route: a}, true
}
@ -1444,7 +1445,7 @@ func (e *userspaceEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) {
}
for _, a := range nm.Addresses {
if a.Addr() == ip && a.IsSingleIP() && tsaddr.IsTailscaleIP(ip) {
return PeerForIP{Node: nm.SelfNode, IsSelf: true, Route: a}, true
return PeerForIP{Node: nm.SelfNode.View(), IsSelf: true, Route: a}, true
}
}
@ -1469,7 +1470,7 @@ func (e *userspaceEngine) PeerForIP(ip netip.Addr) (ret PeerForIP, ok bool) {
// call. But TODO(bradfitz): add a lookup map to netmap.NetworkMap.
if !bestKey.IsZero() {
for _, p := range nm.Peers {
if p.Key == bestKey {
if p.Key() == bestKey {
return PeerForIP{Node: p, Route: best}, true
}
}

@ -84,6 +84,14 @@ func TestNoteReceiveActivity(t *testing.T) {
}
}
func nodeViews(v []*tailcfg.Node) []tailcfg.NodeView {
nv := make([]tailcfg.NodeView, len(v))
for i, n := range v {
nv[i] = n.View()
}
return nv
}
func TestUserspaceEngineReconfig(t *testing.T) {
e, err := NewFakeUserspaceEngine(t.Logf, 0)
if err != nil {
@ -99,11 +107,11 @@ func TestUserspaceEngineReconfig(t *testing.T) {
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
} {
nm := &netmap.NetworkMap{
Peers: []*tailcfg.Node{
Peers: nodeViews([]*tailcfg.Node{
{
Key: nkFromHex(nodeHex),
},
},
}),
}
nk, err := key.ParseNodePublicUntyped(mem.S(nodeHex))
if err != nil {

@ -19,30 +19,31 @@ import (
"tailscale.com/wgengine/wgcfg"
)
func nodeDebugName(n *tailcfg.Node) string {
name := n.Name
func nodeDebugName(n tailcfg.NodeView) string {
name := n.Name()
if name == "" {
name = n.Hostinfo.Hostname()
name = n.Hostinfo().Hostname()
}
if i := strings.Index(name, "."); i != -1 {
name = name[:i]
}
if name == "" && len(n.Addresses) != 0 {
return n.Addresses[0].String()
if name == "" && n.Addresses().Len() != 0 {
return n.Addresses().At(0).String()
}
return name
}
// cidrIsSubnet reports whether cidr is a non-default-route subnet
// exported by node that is not one of its own self addresses.
func cidrIsSubnet(node *tailcfg.Node, cidr netip.Prefix) bool {
func cidrIsSubnet(node tailcfg.NodeView, cidr netip.Prefix) bool {
if cidr.Bits() == 0 {
return false
}
if !cidr.IsSingleIP() {
return true
}
for _, selfCIDR := range node.Addresses {
for i := range node.Addresses().LenIter() {
selfCIDR := node.Addresses().At(i)
if cidr == selfCIDR {
return false
}
@ -85,22 +86,23 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
skippedSubnets := new(bytes.Buffer)
for _, peer := range nm.Peers {
if peer.DiscoKey.IsZero() && peer.DERP == "" && !peer.IsWireGuardOnly {
if peer.DiscoKey().IsZero() && peer.DERP() == "" && !peer.IsWireGuardOnly() {
// Peer predates both DERP and active discovery, we cannot
// communicate with it.
logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key.ShortString())
logf("[v1] wgcfg: skipped peer %s, doesn't offer DERP or disco", peer.Key().ShortString())
continue
}
cfg.Peers = append(cfg.Peers, wgcfg.Peer{
PublicKey: peer.Key,
DiscoKey: peer.DiscoKey,
PublicKey: peer.Key(),
DiscoKey: peer.DiscoKey(),
})
cpeer := &cfg.Peers[len(cfg.Peers)-1]
didExitNodeWarn := false
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer
for _, allowedIP := range peer.AllowedIPs {
if allowedIP.Bits() == 0 && peer.StableID != exitNode {
cpeer.V4MasqAddr = peer.SelfNodeV4MasqAddrForThisPeer()
for i := range peer.AllowedIPs().LenIter() {
allowedIP := peer.AllowedIPs().At(i)
if allowedIP.Bits() == 0 && peer.StableID() != exitNode {
if didExitNodeWarn {
// Don't log about both the IPv4 /0 and IPv6 /0.
continue
@ -109,20 +111,20 @@ func WGCfg(nm *netmap.NetworkMap, logf logger.Logf, flags netmap.WGConfigFlags,
if skippedUnselected.Len() > 0 {
skippedUnselected.WriteString(", ")
}
fmt.Fprintf(skippedUnselected, "%q (%v)", nodeDebugName(peer), peer.Key.ShortString())
fmt.Fprintf(skippedUnselected, "%q (%v)", nodeDebugName(peer), peer.Key().ShortString())
continue
} else if allowedIP.IsSingleIP() && tsaddr.IsTailscaleIP(allowedIP.Addr()) && (flags&netmap.AllowSingleHosts) == 0 {
if skippedIPs.Len() > 0 {
skippedIPs.WriteString(", ")
}
fmt.Fprintf(skippedIPs, "%v from %q (%v)", allowedIP.Addr(), nodeDebugName(peer), peer.Key.ShortString())
fmt.Fprintf(skippedIPs, "%v from %q (%v)", allowedIP.Addr(), nodeDebugName(peer), peer.Key().ShortString())
continue
} else if cidrIsSubnet(peer, allowedIP) {
if (flags & netmap.AllowSubnetRoutes) == 0 {
if skippedSubnets.Len() > 0 {
skippedSubnets.WriteString(", ")
}
fmt.Fprintf(skippedSubnets, "%v from %q (%v)", allowedIP, nodeDebugName(peer), peer.Key.ShortString())
fmt.Fprintf(skippedSubnets, "%v from %q (%v)", allowedIP, nodeDebugName(peer), peer.Key().ShortString())
continue
}
}

@ -52,9 +52,9 @@ var ErrNoChanges = errors.New("no changes made to Engine config")
// PeerForIP is the type returned by Engine.PeerForIP.
type PeerForIP struct {
// Node is the matched node. It's always non-nil when
// Node is the matched node. It's always a valid value when
// Engine.PeerForIP returns ok==true.
Node *tailcfg.Node
Node tailcfg.NodeView
// IsSelf is whether the Node is the local process.
IsSelf bool

Loading…
Cancel
Save