wgengine/router/osrouter: remove orphaned tailnet addrs on cleanup (#20304)
* net/tsaddr: unmap IPv4-mapped IPv6 addrs in IsTailscaleIP IsTailscaleIP branched on ip.Is4() before checking the CGNAT range, so an IPv4-mapped IPv6 address (e.g. ::ffff:100.64.0.1) took the IPv6 path and was tested only against the ULA range, wrongly returning false for a Tailscale CGNAT address. Unmap at the top so both forms are classified identically; Unmap is cheap and IsTailscaleIPv4 stays IPv4-only for callers that need it. Signed-off-by: Brendan Creane <bcreane@gmail.com> * wgengine/router/osrouter: remove orphaned tailnet addrs on cleanup The orphan-address sweep added in #20199 ran only inside Router.Set, so the teardown path (tailscaled --cleanup, and the unconditional cleanup at daemon start) never removed stale Tailscale addresses a previous instance left on a persistent tailscale0 -- it only flushed iptables/nftables. Wire address removal into cleanUp: with no desired config, every Tailscale-range address on the interface is an orphan, so enumerate and delete them all (IPv4 and IPv6, best-effort) in removeOrphanedAddrsForCleanup. tailscaleInterfaceAddrs now yields the interface's addresses as an iter.Seq[netip.Prefix], and the filters compose lazily over it: tailscaleAddrs (every Tailscale-range address; used by cleanup), deletableAddrs (isDeletableAddr: Tailscale-range and deletable now, i.e. excluding v6 when v6 is unavailable; used by the live Set sweep), and orphanedAddrs (drops the desired addresses). The Set sweep ranges the composed iterator directly, so no throwaway slices are built. delAddress is made idempotent: it attempts both the loopback-rule teardown and the address delete and joins their errors, so a missing firewall rule can't leak the address, and it no longer no-ops on v6 (cleanup relies on that to remove v6 orphans even when this process never brought IPv6 up). The Set-time sweep is otherwise unchanged; re-running it on network changes (netmon) for late orphans remains a follow-up (tailscale/corp#43882). Updates #19974 Fixes tailscale/corp#44173 Signed-off-by: Brendan Creane <bcreane@gmail.com> --------- Signed-off-by: Brendan Creane <bcreane@gmail.com>
This commit is contained in:
@@ -70,6 +70,7 @@ const (
|
||||
// IsTailscaleIP reports whether IP is an IP address in a range that
|
||||
// Tailscale assigns from.
|
||||
func IsTailscaleIP(ip netip.Addr) bool {
|
||||
ip = ip.Unmap()
|
||||
if ip.Is4() {
|
||||
return IsTailscaleIPv4(ip)
|
||||
}
|
||||
@@ -78,6 +79,7 @@ func IsTailscaleIP(ip netip.Addr) bool {
|
||||
|
||||
// IsTailscaleIPv4 reports whether an IPv4 IP is an IP address that
|
||||
// Tailscale assigns from.
|
||||
// It will always return false if ip is an "IPv4-mapped IPv6 address".
|
||||
func IsTailscaleIPv4(ip netip.Addr) bool {
|
||||
return CGNATRange().Contains(ip) && !ChromeOSVMRange().Contains(ip)
|
||||
}
|
||||
|
||||
@@ -250,6 +250,12 @@ func TestIsTailscaleIPv4(t *testing.T) {
|
||||
in: netip.MustParseAddr("100.115.92.157"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
// IsTailscaleIPv4 does not unmap, so an IPv4-mapped IPv6 form of a
|
||||
// CGNAT address is not an IPv4 address and must return false.
|
||||
in: netip.AddrFrom16(netip.MustParseAddr("100.67.19.57").As16()),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := IsTailscaleIPv4(tt.in); got != tt.want {
|
||||
@@ -284,6 +290,16 @@ func TestIsTailscaleIP(t *testing.T) {
|
||||
in: netip.MustParseAddr("100.115.92.157"),
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
// IPv4-mapped IPv6 form of a CGNAT address is still a Tailscale IP.
|
||||
in: netip.MustParseAddr("::ffff:100.67.19.57"),
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
// IPv4-mapped IPv6 form of a non-Tailscale address.
|
||||
in: netip.MustParseAddr("::ffff:10.10.10.10"),
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
if got := IsTailscaleIP(tt.in); got != tt.want {
|
||||
|
||||
Reference in New Issue
Block a user