Files
tailscale/net/dns/resolver
Brad Fitzpatrick 2fc4be0f76 net/dns/resolver: only accept UDP DNS replies from the queried resolver
The forwarder's upstream UDP socket is unconnected, so any host that
could send a datagram to its ephemeral port reached the single
ReadFromUDPAddrPort call that decided the query: a datagram with the
right 16-bit transaction ID was returned to the client as the
resolver's answer no matter what address it came from, and a datagram
with the wrong ID failed the query outright, handing it to the TCP
fallback (or to nothing at all, with TCP retries disabled).

sendUDP now keeps reading until a datagram arrives that could be a
reply to the query it sent: one from the resolver's address and port
carrying the transaction ID of the request. Other datagrams are
dropped and counted, so neither a spoofed reply nor a single stray
datagram can answer or end the query. This is the same source check
the kernel applies to a connected socket; the socket stays unconnected
because its ListenPacket path is what binds the query to a link
(IP_BOUND_IF on macOS, ForwardLinkSelector elsewhere). The read loop
still ends when the query context expires and closeOnCtxDone closes
the conn, as before.

On Windows an oversized datagram is reported as a truncation error
without a source address, so there the transaction ID remains the only
check, as before.

Thanks to Ben Carman for the report!

Updates tailscale/corp#48187

Reported-by: Ben Carman
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
Change-Id: Ibabbec5c82739b66caa6fb943faa376c39adbf64
2026-09-15 09:44:43 -07:00
..