Re: [PATCH net 2/2] udp: remove a disconnected socket from the 4-tuple hash table

From: Kuniyuki Iwashima

Date: Mon Sep 21 2026 - 21:39:43 EST


On Thu, Sep 17, 2026 at 2:26 AM Shardul Bankar
<shardul.b@xxxxxxxxxxxxxxxxxx> wrote:
>
> A UDP socket bound to a specific address and port keeps its entry in the
> 4-tuple hash table after it is disconnected:
>
> sk binds to 127.0.0.1:21001
> sk connects to 127.0.0.2:20001 // filed in the 4-tuple table
> sk disconnects, connect(AF_UNSPEC) // still filed, peer now 0.0.0.0:0
>
> __udp_disconnect() takes a socket out of that table only as a side effect
> of ->rehash() or ->unhash(), and it skips ->rehash() when
> SOCK_BINDADDR_LOCK is set and ->unhash() when SOCK_BINDPORT_LOCK is set.
> commit 6996a2d2d0a6 ("udp: Unhash auto-bound connected sk from 4-tuple hash
> table when disconnected.") fixed the same end state for a wildcard-bound
> socket, by a path this one does not take.
>
> The entry is counted whether or not anything hits it. hash4_cnt on the
> hash2 slot stays raised for as long as the socket lives, so udp_has_hash4()
> keeps sending every packet for that address and port through the 4-tuple
> lookup first.
>
> On IPv6 it can also be hit. __udp_disconnect() does not clear sk_v6_daddr,
> so udp_v6_rehash() files the entry under the peer the socket was connected
> to with a zero dport, and inet6_match() compares that same
> field: a datagram from the former peer with a zero source port matches,
> and source port zero is accepted on receive. On IPv4 the peer is cleared,
> so a match would need a zero source address as well, which the routing
> layer rejects as martian. The stale sk_v6_daddr is a separate defect, not
> addressed here; removing the entry closes this path either way.
>
> The entry can also be relocated. __udp_disconnect() clears sk_bound_dev_if,
> so a subsequent SO_BINDTODEVICE calls ->rehash(), and because the receive
> address is still specific udp_lib_rehash() moves the entry instead of
> removing it, into the bucket that (rcv_saddr, num, 0, 0) hashes to -- a
> pure function of the address and port, so every socket reaching this state
> on one address and port collects in one bucket. The bucket cannot be chosen
> from outside, as udp_ehashfn() is seeded with a per-boot secret. This last
> one became reachable only with commit 644f9108f3a5 ("udp: Make rehash4
> independent in udp_lib_rehash()"), which moved the hash4 handling out of a
> branch a disconnected socket does not take; the stale entry itself dates
> from the commit in Fixes.
>
> Take the socket out of the table before __udp_disconnect() runs, while it
> still matches how it was filed. This also reaches the wildcard case ahead
> of udp_lib_rehash()'s udp_unhash4() branch, leaving that branch unreachable
> from udp_disconnect(); removing it belongs in net-next. udp_disconnect()
> and udp_abort() are the only UDP entries into __udp_disconnect(), which is
> shared with raw, ping and l2tp sockets that are not struct udp_sock:
> ping_prot.obj_size is sizeof(struct inet_sock), so udp_hashed4() on one
> would read past the allocation.
>
> Fixes: 78c91ae2c6de ("ipv4/udp: Add 4-tuple hash for connected socket")
> Assisted-by: LLM
> Signed-off-by: Shardul Bankar <shardul.b@xxxxxxxxxxxxxxxxxx>

Reviewed-by: Kuniyuki Iwashima <kuniyu@xxxxxxxxxx>