Files
tailscale/ipn/ipnlocal/resolve.go
T
Brad FitzpatrickandBrad Fitzpatrick 2506ede862 wgengine,ipn/ipnlocal: remove Engine.PeerKeyForIP and the engine's peer route table
The engine kept its own longest-prefix-match table (peerByIPRoute),
rebuilt from the full peer list on every reconfig, to route outbound
packets and answer PeerKeyForIP. That's now the route manager's job:
LocalBackend already installs a PeerByIPPacketFunc backed by the
RouteManager's incrementally-maintained outbound table, so the
engine's copy was redundant state with redundant O(n peers) rebuild
work.

Delete the table, the PeerKeyForIP interface method, and the BART-only
default callback. LocalBackend's peerForIP now queries the
RouteManager's outbound table directly for the subnet-route and
exit-node fallback. Engines running without a LocalBackend (such as
wgengine/bench) must install their own outbound peer lookup, since the
device's standard AllowedIPs trie only covers peers that already
exist and can't lazily create them.

Updates #12542

Change-Id: I25100399e273ed6c2bb1f6136b7cd81bc83e7313
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
2026-07-13 14:38:55 -07:00

160 lines
4.9 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package ipnlocal
import (
"net/netip"
"strings"
"tailscale.com/net/tsaddr"
"tailscale.com/tailcfg"
"tailscale.com/types/key"
"tailscale.com/wgengine"
)
// lookupPeerByIP returns the node public key for the peer that should
// handle traffic to the given IP address. It is installed as the
// [wgengine.Engine.SetPeerByIPPacketFunc] callback: exact node
// addresses hit the nodeByAddr fast path, and subnet routes and
// exit-node default routes fall back to the RouteManager's outbound
// table, so it stays correct under incremental netmap deltas.
//
// It is called by wireguard-go on every outbound packet (not cached),
// so it must be fast.
func (b *LocalBackend) lookupPeerByIP(ip netip.Addr) (key.NodePublic, bool) {
nb := b.currentNode()
if nid, ok := nb.NodeByAddr(ip); ok {
peer, ok := nb.NodeByID(nid)
if !ok {
return key.NodePublic{}, false
}
return peer.Key(), true
}
if pr, ok := nb.routeMgr.Outbound().Lookup(ip); ok {
return pr.Key, true
}
return key.NodePublic{}, false
}
// peerAllowedIPs returns the prefixes from which the peer with the
// given public key is currently allowed to originate traffic, or
// ok=false if the peer is unknown (or currently routable via no
// prefix at all). It is installed as the
// [wgengine.Engine.SetPeerConfigFunc] callback, backing wireguard-go's
// lazy peer creation and per-delta peer sync.
func (b *LocalBackend) peerAllowedIPs(k key.NodePublic) (_ []netip.Prefix, ok bool) {
return b.currentNode().PeerAllowedIPs(k)
}
// resolveMagicDNS resolves a MagicDNS hostname to the owning node's IP
// address, respecting the requested network address family ("tcp4",
// "tcp6", "tcp", etc.). It accepts peer FQDNs ("foo.tail-scale.ts.net"),
// short names ("foo"), and DNS.ExtraRecords entries (service VIPs).
// The hostname must be lowercase with no trailing dot. It is installed
// as the [tsdial.Dialer.SetResolveMagicDNS] callback.
func (b *LocalBackend) resolveMagicDNS(hostname, network string) (_ netip.Addr, ok bool) {
nb := b.currentNode()
if nid, ok := nb.NodeByName(hostname); ok {
n, ok := nb.NodeByID(nid)
if !ok {
b.logf("[unexpected] resolveMagicDNS: NodeByName(%q) returned node %v but NodeByID failed", hostname, nid)
return netip.Addr{}, false
}
if ip, ok := nodeAddrForNetwork(n, network); ok {
return ip, true
}
return netip.Addr{}, false
}
if ip, ok := nb.ExtraDNSByName(hostname); ok && addrFamilyMatch(ip, network) {
return ip, true
}
return netip.Addr{}, false
}
// nodeAddrForNetwork returns the best address from n for the given
// network ("tcp", "tcp4", "tcp6", "udp", "udp4", "udp6"). For
// unqualified networks ("tcp", "udp"), it prefers IPv4.
func nodeAddrForNetwork(n tailcfg.NodeView, network string) (_ netip.Addr, ok bool) {
addrs := n.Addresses()
if addrs.Len() == 0 {
return netip.Addr{}, false
}
want4 := strings.HasSuffix(network, "4")
want6 := strings.HasSuffix(network, "6")
var v6 netip.Addr
for _, pfx := range addrs.All() {
ip := pfx.Addr()
if want4 && ip.Is4() {
return ip, true
}
if want6 && ip.Is6() {
return ip, true
}
if !want4 && !want6 {
if ip.Is4() {
return ip, true
}
if !v6.IsValid() {
v6 = ip
}
}
}
if v6.IsValid() {
return v6, true
}
return netip.Addr{}, false
}
// addrFamilyMatch reports whether ip is compatible with the requested
// network address family.
func addrFamilyMatch(ip netip.Addr, network string) bool {
if strings.HasSuffix(network, "4") {
return ip.Is4()
}
if strings.HasSuffix(network, "6") {
return ip.Is6()
}
return true
}
// peerForIP returns which peer is responsible for a given IP address.
// Despite the name, it can also return the self node (with IsSelf set).
// It handles both Tailscale IPs (returning the owning peer or self) and
// non-Tailscale addresses like subnet-routed IPs or exit-node global
// internet IPs (returning whichever peer would route that traffic).
// It is installed as the [wgengine.Engine.SetPeerForIPFunc] callback.
func (b *LocalBackend) peerForIP(ip netip.Addr) (_ wgengine.PeerForIP, ok bool) {
nb := b.currentNode()
if tsaddr.IsTailscaleIP(ip) {
if nid, ok := nb.NodeByAddr(ip); ok {
n, ok := nb.NodeByID(nid)
if !ok {
b.logf("[unexpected] peerForIP: NodeByAddr(%v) returned node %v but NodeByID failed", ip, nid)
return wgengine.PeerForIP{}, false
}
self := nb.Self()
return wgengine.PeerForIP{
Node: n,
IsSelf: self.Valid() && self.ID() == nid,
Route: netip.PrefixFrom(ip, ip.BitLen()),
}, true
}
}
route, pr, ok := nb.routeMgr.Outbound().LookupPrefixLPM(netip.PrefixFrom(ip, ip.BitLen()))
if !ok {
return wgengine.PeerForIP{}, false
}
nid, ok := nb.NodeByKey(pr.Key)
if !ok {
return wgengine.PeerForIP{}, false
}
n, ok := nb.NodeByID(nid)
if !ok {
return wgengine.PeerForIP{}, false
}
return wgengine.PeerForIP{Node: n, Route: route}, true
}