Close Serve listeners for removed node addresses

perfloop/tailscale · RESOURCE LEAK

https://perfloop.ai/t/oss/case_bpvs286kq5

Verdict

VERIFIED · settled 2026-10-10

What happened: The assertion is violated on the comparison and satisfied with this change.

Hypothesis

After a node loses an address, a kernel-mode local Serve listener for that address can remain owned by the backend. It is no longer in the new address set, yet it is neither closed nor removed, so it can retain a listener resource and its accept loop. The source establishes retained ownership, but not the rate of address changes or platform-specific socket behavior.

On the accepted control-map update path, `LocalBackend.setNetMapLocked` installs the new map before the Serve reconciliation. With valid Serve TCP configuration and non-netstack mode, `updateServeTCPPortNetMapAddrListenersLocked` removes an entry only when its port is absent from `ports`. It never compares the entry address with `nm.GetAddresses()`. Its later nested loop adds missing current address-port pairs. Thus, when R old addresses disappear while P configured TCP ports remain, the map can retain up to R×P obsolete entries; each was created with a listener context and a launched `Run` loop. A live OS socket additionally requires that its prior listen succeeded.

The required property is that reconciliation leaves `serveListeners` containing only current address-port pairs. A Case should run a non-netstack backend with valid Serve TCP ports, replace a netmap with one that removes an address while retaining those ports, and verify that every obsolete listener is closed and deleted while current pairs remain usable. It should also verify that no obsolete socket or `Run` loop remains; that controlled test is the independent falsifier for platform-level behavior.

Change to test: Reconcile `serveListeners` against the complete current address-and-port set on each eligible map update, closing and deleting every obsolete key. Preserve listeners whose address and configured port remain desired, and stop only new accepts on removed keys.

Where it lives

perfloop/tailscale · ipn/ipnlocal/local.go

Evidence

The assertion is violated on the comparison and satisfied with this change: `After a kernel-mode Serve map update with valid configured TCP ports, serveListeners contains exactly the current netmap address-port pairs; removed pairs have no listener socket or Run loop, and unchanged and new current pairs remain usable.`

Checks: 7 of 7 passed. Verification: no defect found.

Timeline