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>
160 lines
4.9 KiB
Go
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
|
|
}
|