Skip to contentSkip to conversation

udp: Fix wildcard bind conflict check when using hash2

PATCH Applied Martin KaFai Lau 4 participants 5 messages Mar 19 – Mar 24

Subsystems (4)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 udp: Fix wildcard bind conflict check when using hash2

From: Martin KaFai Lau <[email protected]>

When binding a udp_sock to a local address and port, UDP uses two hashes (udptable->hash and udptable->hash2) for collision detection. The current code switches to "hash2" when hslot->count > 10.

"hash2" is keyed by local address and local port. "hash" is keyed by local port only.

The issue can be shown in the following bind sequence (pseudo code):

bind(fd1,  "[fd00::1]:8888")
bind(fd2,  "[fd00::2]:8888")
bind(fd3,  "[fd00::3]:8888")
bind(fd4,  "[fd00::4]:8888")
bind(fd5,  "[fd00::5]:8888")
bind(fd6,  "[fd00::6]:8888")
bind(fd7,  "[fd00::7]:8888")
bind(fd8,  "[fd00::8]:8888")
bind(fd9,  "[fd00::9]:8888")

bind(fd10, "[fd00::10]:8888")

/* Correctly return -EADDRINUSE because "hash" is used
* instead of "hash2". udp_lib_lport_inuse() detects the
* conflict.
*/
bind(fail_fd, "[::]:8888")

/* After one more socket is bound to "[fd00::11]:8888",
* hslot->count exceeds 10 and "hash2" is used instead. */
bind(fd11, "[fd00::11]:8888")
bind(fail_fd, "[::]:8888") /* succeeds unexpectedly */

The same issue applies to the IPv4 wildcard address "0.0.0.0" and the IPv4-mapped wildcard address "::ffff:0.0.0.0". For example, if there are existing sockets bound to "192.168.1.[1-11]:8888", then binding "0.0.0.0:8888" or "[::ffff:0.0.0.0]:8888" can also miss the conflict when hslot->count > 10.

TCP inet_csk_get_port() already has the correct check in inet_use_bhash2_on_bind(). Rename it to inet_use_hash2_on_bind() and move it to inet_hashtables.h so udp.c can reuse it in this fix.

Fixes 30fff9231fad ("udp: bind() optimisation")
---
 include/net/inet_hashtables.h   | 14 ++++++++++++++
 net/ipv4/inet_connection_sock.c | 20 +++-----------------
 net/ipv4/udp.c                  |  2 +-
 3 files changed, 18 insertions(+), 18 deletions(-)
include/net/inet_hashtables.h +14 -0
index 5a979dcab538..6d936e9f2fd3 100644
@@ -264,6 +264,20 @@ inet_bhashfn_portaddr(const struct inet_hashinfo *hinfo, const struct sock *sk,
 	return &hinfo->bhash2[hash & (hinfo->bhash_size - 1)];
 }
 
+static inline bool inet_use_hash2_on_bind(const struct sock *sk)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+	if (sk->sk_family == AF_INET6) {
+		if (ipv6_addr_any(&sk->sk_v6_rcv_saddr))
+			return false;
+
+		if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr))
+			return true;
+	}
+#endif
+	return sk->sk_rcv_saddr != htonl(INADDR_ANY);
+}
+
 struct inet_bind_hashbucket *
 inet_bhash2_addr_any_hashbucket(const struct sock *sk, const struct net *net, int port);
 
net/ipv4/inet_connection_sock.c +3 -17
index 5dfac6ce1110..e961936b6be7 100644
@@ -154,20 +154,6 @@ bool inet_sk_get_local_port_range(const struct sock *sk, int *low, int *high)
 }
 EXPORT_SYMBOL(inet_sk_get_local_port_range);
 
-static bool inet_use_bhash2_on_bind(const struct sock *sk)
-{
-#if IS_ENABLED(CONFIG_IPV6)
-	if (sk->sk_family == AF_INET6) {
-		if (ipv6_addr_any(&sk->sk_v6_rcv_saddr))
-			return false;
-
-		if (!ipv6_addr_v4mapped(&sk->sk_v6_rcv_saddr))
-			return true;
-	}
-#endif
-	return sk->sk_rcv_saddr != htonl(INADDR_ANY);
-}
-
 static bool inet_bind_conflict(const struct sock *sk, struct sock *sk2,
 			       kuid_t uid, bool relax,
 			       bool reuseport_cb_ok, bool reuseport_ok)
@@ -259,7 +245,7 @@ static int inet_csk_bind_conflict(const struct sock *sk,
 	 * checks separately because their spinlocks have to be acquired/released
 	 * independently of each other, to prevent possible deadlocks
 	 */
-	if (inet_use_bhash2_on_bind(sk))
+	if (inet_use_hash2_on_bind(sk))
 		return tb2 && inet_bhash2_conflict(sk, tb2, uid, relax,
 						   reuseport_cb_ok, reuseport_ok);
 
@@ -376,7 +362,7 @@ inet_csk_find_open_port(const struct sock *sk, struct inet_bind_bucket **tb_ret,
 		head = &hinfo->bhash[inet_bhashfn(net, port,
 						  hinfo->bhash_size)];
 		spin_lock_bh(&head->lock);
-		if (inet_use_bhash2_on_bind(sk)) {
+		if (inet_use_hash2_on_bind(sk)) {
 			if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, relax, false))
 				goto next_port;
 		}
@@ -562,7 +548,7 @@ int inet_csk_get_port(struct sock *sk, unsigned short snum)
 				check_bind_conflict = false;
 		}
 
-		if (check_bind_conflict && inet_use_bhash2_on_bind(sk)) {
+		if (check_bind_conflict && inet_use_hash2_on_bind(sk)) {
 			if (inet_bhash2_addr_any_conflict(sk, port, l3mdev, true, true))
 				goto fail_unlock;
 		}
net/ipv4/udp.c +1 -1
index b60fad393e18..cb99a3c27053 100644
@@ -287,7 +287,7 @@ int udp_lib_get_port(struct sock *sk, unsigned short snum,
 	} else {
 		hslot = udp_hashslot(udptable, net, snum);
 		spin_lock_bh(&hslot->lock);
-		if (hslot->count > 10) {
+		if (inet_use_hash2_on_bind(sk) && hslot->count > 10) {
 			int exist;
 			unsigned int slot2 = udp_sk(sk)->udp_portaddr_hash ^ snum;
 
Signature · 1 line
2.52.0
reviewer Reviewed-by on v1 1/1
#1 lore raw
Quoted 43 lines · Martin KaFai Lau

From: Martin KaFai Lau <[email protected]>

When binding a udp_sock to a local address and port, UDP uses two hashes (udptable->hash and udptable->hash2) for collision detection. The current code switches to "hash2" when hslot->count > 10.

"hash2" is keyed by local address and local port. "hash" is keyed by local port only.

The issue can be shown in the following bind sequence (pseudo code):

bind(fd1,  "[fd00::1]:8888")
bind(fd2,  "[fd00::2]:8888")
bind(fd3,  "[fd00::3]:8888")
bind(fd4,  "[fd00::4]:8888")
bind(fd5,  "[fd00::5]:8888")
bind(fd6,  "[fd00::6]:8888")
bind(fd7,  "[fd00::7]:8888")
bind(fd8,  "[fd00::8]:8888")
bind(fd9,  "[fd00::9]:8888")

bind(fd10, "[fd00::10]:8888")

/* Correctly return -EADDRINUSE because "hash" is used
* instead of "hash2". udp_lib_lport_inuse() detects the
* conflict.
*/
bind(fail_fd, "[::]:8888")

/* After one more socket is bound to "[fd00::11]:8888",
* hslot->count exceeds 10 and "hash2" is used instead. */
bind(fd11, "[fd00::11]:8888")
bind(fail_fd, "[::]:8888") /* succeeds unexpectedly */

The same issue applies to the IPv4 wildcard address "0.0.0.0" and the IPv4-mapped wildcard address "::ffff:0.0.0.0". For example, if there are existing sockets bound to "192.168.1.[1-11]:8888", then binding "0.0.0.0:8888" or "[::ffff:0.0.0.0]:8888" can also miss the conflict when hslot->count > 10.

Quoted 8 lines

TCP inet_csk_get_port() already has the correct check in inet_use_bhash2_on_bind(). Rename it to inet_use_hash2_on_bind() and move it to inet_hashtables.h so udp.c can reuse it in this fix.

Fixes 30fff9231fad ("udp: bind() optimisation")

Confirmed this fixes the buggy scenario, thanks !

---8<---

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

from socket import *

sk = []
for i in range(10):

...     s = socket(AF_INET, SOCK_DGRAM)
...     s.bind((f'127.0.0.{i + 1}', 8000))
...     sk.append(s)

...

Quoted 3 lines
Quoted 3 lines
Quoted 3 lines

s = socket(AF_INET, SOCK_DGRAM)
s.bind((f'0.0.0.0', 8000))

Traceback (most recent call last):
File "<python-input-6>", line 1, in <module>

    s.bind((f'0.0.0.0', 8000))
    ~~~~~~^^^^^^^^^^^^^^^^^^^^

OSError: [Errno 98] Address already in use

Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

s.bind((f'127.0.0.11', 8000))

s1 = socket(AF_INET, SOCK_DGRAM)
s1.bind((f'0.0.0.0', 8000))

After:
...

Quoted 1 line
Quoted 1 line
Quoted 1 line

s1.bind((f'0.0.0.0', 8000))

Traceback (most recent call last):
File "<python-input-9>", line 1, in <module>

    s1.bind((f'0.0.0.0', 8000))
    ~~~~~~~^^^^^^^^^^^^^^^^^^^^

OSError: [Errno 98] Address already in use
---8<---

maintainer
#2 lore raw
Quoted 1 line · On Fri, 20 Mar 2026 16:07:10 -0700 Kuniyuki Iwashima wrote:

Confirmed this fixes the buggy scenario, thanks !

Should we start a selftest for UDP wildcard bind behavior or extend bind_wildcard.c or it's odd to ask for it now?

#3 lore raw
Quoted 6 lines · Jakub Kicinski
Quoted 1 line · On Fri, 20 Mar 2026 16:07:10 -0700 Kuniyuki Iwashima wrote:

Confirmed this fixes the buggy scenario, thanks !

Should we start a selftest for UDP wildcard bind behavior or extend bind_wildcard.c or it's odd to ask for it now?

Makes sense.

I simply changed s/SOCK_STREAM/SOCK_DGRAM/ in
bind_wildcard.c and all tests passed.

But, this issue requires UDP-specifc setup and test, so it will need its own FIXTURE and test cases (IPv4 & IPv6).

#4 lore raw
In reply to Martin KaFai Lau
This patch was applied to netdev/net.git (main) by Jakub Kicinski <[email protected]>:

Hello:

This patch was applied to netdev/net.git (main)
by Jakub Kicinski <[email protected]>:

Quoted 8 lines · On Thu, 19 Mar 2026 11:18:17 -0700 you wrote:

From: Martin KaFai Lau <[email protected]>

When binding a udp_sock to a local address and port, UDP uses two hashes (udptable->hash and udptable->hash2) for collision detection. The current code switches to "hash2" when hslot->count > 10.

[...]

Here is the summary with links:
- [net] udp: Fix wildcard bind conflict check when using hash2

    https://git.kernel.org/netdev/net/c/e537dd15d0d4

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html