mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket
@ 2026-09-23  6:05 Jiayuan Chen
  2026-09-23  6:05 ` [PATCH net 2/2] selftests/net: check auto-selected port with SO_REUSEPORT Jiayuan Chen
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Jiayuan Chen @ 2026-09-23  6:05 UTC (permalink / raw)
  To: netdev
  Cc: Jiayuan Chen, Willem de Bruijn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	Tom Herbert, linux-kernel, linux-kselftest

Observed with two independent servers in the same process:

  fd1 = socket(AF_INET, SOCK_DGRAM, 0);
  setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, ...);
  bind(fd1, port 0);          /* got 40000 */

  fd2 = socket(AF_INET, SOCK_DGRAM, 0);
  setsockopt(fd2, SOL_SOCKET, SO_REUSEPORT, ...);
  bind(fd2, port 0);          /* got 40000 as well */

Both sockets end up on the same port and join the same reuseport
group, so each of them takes part of the other's datagrams. TCP does
not do this.

udp_lib_lport_inuse() keeps the reuseport rule when it scans for a
free port: a socket with the same uid and SO_REUSEPORT set is not a
conflict, so its port is never marked in the bitmap and the scan can
hand it out again. That rule only makes sense when the user asks for
a specific port.

TCP fixed the same thing in commit 0643ee4fd1b7 ("inet: Fix get port
to handle zero port number with soreuseport set"): reuseport is only
honoured for an explicit port, not during a port scan.

Do the same for UDP. Mark compatible reuseport sockets in the bitmap
during a scan and only skip them when checking a specific port. Using
bitmap to tell the two modes apart is not explicit, but
udp_lib_lport_inuse() already does that for the port match itself.

Note that this still collides when both sockets also have SO_REUSEADDR
set: udp_lib_lport_inuse() skips such a pair before it reaches the
reuseport check, so the port is never marked either. That is not a
common setup and is left as is here.

Fixes: ba418fa357a7 ("soreuseport: UDP/IPv4 implementation")
Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
 net/ipv4/udp.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index b3887c42adfd..61cb1e3f5d93 100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -147,11 +147,10 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,
 		    (!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
 		     sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
 		    inet_rcv_saddr_equal(sk, sk2, true)) {
-			if (sk2->sk_reuseport && sk->sk_reuseport &&
+			if (!bitmap && sk2->sk_reuseport && sk->sk_reuseport &&
 			    !rcu_access_pointer(sk->sk_reuseport_cb) &&
 			    uid_eq(uid, sk_uid(sk2))) {
-				if (!bitmap)
-					return 0;
+				return 0;
 			} else {
 				if (!bitmap)
 					return 1;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* [PATCH net 2/2] selftests/net: check auto-selected port with SO_REUSEPORT
  2026-09-23  6:05 [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Jiayuan Chen
@ 2026-09-23  6:05 ` Jiayuan Chen
  2026-09-23 10:24 ` [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Eric Dumazet
  2026-09-24  9:07 ` netdev-bot+sashiko
  2 siblings, 0 replies; 4+ messages in thread
From: Jiayuan Chen @ 2026-09-23  6:05 UTC (permalink / raw)
  To: netdev
  Cc: Jiayuan Chen, Willem de Bruijn, David S. Miller, Eric Dumazet,
	Jakub Kicinski, Paolo Abeni, Simon Horman, Shuah Khan,
	Tom Herbert, linux-kernel, linux-kselftest

Use IP_LOCAL_PORT_RANGE to limit a socket to 8 ports and bind 8
SO_REUSEPORT sockets to port 0. They must get 8 distinct ports and
the 9th bind must fail with EADDRINUSE. TCP and UDP should both
behave this way.

  # ./ip_local_port_range.sh

Before the udp fix:

  ok 5 ip_local_port_range.ip4_tcp.exhaust_8_port_range_reuseport
  not ok 12 ip_local_port_range.ip4_udp.exhaust_8_port_range_reuseport
  ok 33 ip_local_port_range.ip6_tcp.exhaust_8_port_range_reuseport
  not ok 40 ip_local_port_range.ip6_udp.exhaust_8_port_range_reuseport

After:

  ok 5 ip_local_port_range.ip4_tcp.exhaust_8_port_range_reuseport
  ok 12 ip_local_port_range.ip4_udp.exhaust_8_port_range_reuseport
  ok 33 ip_local_port_range.ip6_tcp.exhaust_8_port_range_reuseport
  ok 40 ip_local_port_range.ip6_udp.exhaust_8_port_range_reuseport

Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
---
 .../selftests/net/ip_local_port_range.c       | 27 ++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/tools/testing/selftests/net/ip_local_port_range.c b/tools/testing/selftests/net/ip_local_port_range.c
index e6834a6cfc8f..04feb702fff4 100644
--- a/tools/testing/selftests/net/ip_local_port_range.c
+++ b/tools/testing/selftests/net/ip_local_port_range.c
@@ -301,7 +301,9 @@ TEST_F(ip_local_port_range, single_port_range)
 	}
 }
 
-TEST_F(ip_local_port_range, exhaust_8_port_range)
+static void exhaust_8_port_range(struct __test_metadata *_metadata,
+				 const FIXTURE_VARIANT(ip_local_port_range) *variant,
+				 bool reuseport)
 {
 	__u8 port_set = 0;
 	int i, fd, err;
@@ -313,6 +315,11 @@ TEST_F(ip_local_port_range, exhaust_8_port_range)
 		fd = socket(variant->so_domain, variant->so_type, variant->so_protocol);
 		ASSERT_GE(fd, 0) TH_LOG("socket failed");
 
+		if (reuseport) {
+			err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof(int));
+			ASSERT_TRUE(!err) TH_LOG("setsockopt(SO_REUSEPORT) failed");
+		}
+
 		range = pack_port_range(40000, 40007);
 		err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range));
 		ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed");
@@ -335,6 +342,11 @@ TEST_F(ip_local_port_range, exhaust_8_port_range)
 	fd = socket(variant->so_domain, variant->so_type, variant->so_protocol);
 	ASSERT_GE(fd, 0) TH_LOG("socket failed");
 
+	if (reuseport) {
+		err = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof(int));
+		ASSERT_TRUE(!err) TH_LOG("setsockopt(SO_REUSEPORT) failed");
+	}
+
 	range = pack_port_range(40000, 40007);
 	err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range));
 	ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed");
@@ -352,6 +364,19 @@ TEST_F(ip_local_port_range, exhaust_8_port_range)
 	}
 }
 
+TEST_F(ip_local_port_range, exhaust_8_port_range)
+{
+	exhaust_8_port_range(_metadata, variant, false);
+}
+
+/* Auto-selected port must not land on a port already taken by a
+ * SO_REUSEPORT socket, so all 8 sockets must get distinct ports.
+ */
+TEST_F(ip_local_port_range, exhaust_8_port_range_reuseport)
+{
+	exhaust_8_port_range(_metadata, variant, true);
+}
+
 TEST_F(ip_local_port_range, late_bind)
 {
 	union {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket
  2026-09-23  6:05 [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Jiayuan Chen
  2026-09-23  6:05 ` [PATCH net 2/2] selftests/net: check auto-selected port with SO_REUSEPORT Jiayuan Chen
@ 2026-09-23 10:24 ` Eric Dumazet
  2026-09-24  9:07 ` netdev-bot+sashiko
  2 siblings, 0 replies; 4+ messages in thread
From: Eric Dumazet @ 2026-09-23 10:24 UTC (permalink / raw)
  To: Jiayuan Chen
  Cc: netdev, Willem de Bruijn, David S. Miller, Jakub Kicinski,
	Paolo Abeni, Simon Horman, Shuah Khan, Tom Herbert, linux-kernel,
	linux-kselftest

On Wed, Sep 23, 2026 at 8:06 AM Jiayuan Chen <jiayuan.chen@linux.dev> wrote:
>
> Observed with two independent servers in the same process:
>
>   fd1 = socket(AF_INET, SOCK_DGRAM, 0);
>   setsockopt(fd1, SOL_SOCKET, SO_REUSEPORT, ...);
>   bind(fd1, port 0);          /* got 40000 */
>
>   fd2 = socket(AF_INET, SOCK_DGRAM, 0);
>   setsockopt(fd2, SOL_SOCKET, SO_REUSEPORT, ...);
>   bind(fd2, port 0);          /* got 40000 as well */
>
> Both sockets end up on the same port and join the same reuseport
> group, so each of them takes part of the other's datagrams. TCP does
> not do this.
>
> udp_lib_lport_inuse() keeps the reuseport rule when it scans for a
> free port: a socket with the same uid and SO_REUSEPORT set is not a
> conflict, so its port is never marked in the bitmap and the scan can
> hand it out again. That rule only makes sense when the user asks for
> a specific port.
>
> TCP fixed the same thing in commit 0643ee4fd1b7 ("inet: Fix get port
> to handle zero port number with soreuseport set"): reuseport is only
> honoured for an explicit port, not during a port scan.
>
> Do the same for UDP. Mark compatible reuseport sockets in the bitmap
> during a scan and only skip them when checking a specific port. Using
> bitmap to tell the two modes apart is not explicit, but
> udp_lib_lport_inuse() already does that for the port match itself.
>
> Note that this still collides when both sockets also have SO_REUSEADDR
> set: udp_lib_lport_inuse() skips such a pair before it reaches the
> reuseport check, so the port is never marked either. That is not a
> common setup and is left as is here.
>
> Fixes: ba418fa357a7 ("soreuseport: UDP/IPv4 implementation")
> Signed-off-by: Jiayuan Chen <jiayuan.chen@linux.dev>
> ---
>  net/ipv4/udp.c | 5 ++---
>  1 file changed, 2 insertions(+), 3 deletions(-)
>
> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index b3887c42adfd..61cb1e3f5d93 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -147,11 +147,10 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,
>                     (!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
>                      sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
>                     inet_rcv_saddr_equal(sk, sk2, true)) {
> -                       if (sk2->sk_reuseport && sk->sk_reuseport &&
> +                       if (!bitmap && sk2->sk_reuseport && sk->sk_reuseport &&
>                             !rcu_access_pointer(sk->sk_reuseport_cb) &&
>                             uid_eq(uid, sk_uid(sk2))) {
> -                               if (!bitmap)
> -                                       return 0;
> +                               return 0;
>                         } else {
>                                 if (!bitmap)
>                                         return 1;
> --
> 2.43.0
>

The fix is right and matches what TCP does in inet_csk_find_open_port(),
but it only covers one of the three cases where UDP's scan differs from TCP's.

On an unpatched kernel, binding 8 sockets to port 0 inside an 8-port
IP_LOCAL_PORT_RANGE:

udp SO_REUSEPORT 7 distinct ports, 9th bind succeeds udp SO_REUSEADDR
7 distinct ports,
9th bind succeeds udp REUSEADDR+REUSEPORT 6 distinct ports, 9th bind succeeds

(TCP gets 8 distinct and EADDRINUSE in all three.) Your patch only
fixes the first one;
the SO_REUSEADDR cases bail out of the conflict test before the reuseport check
so the bitmap is never marked. SO_REUSEADDR on its own is pretty common for UDP,
so I don't think we should leave it.

TCP handles all three because inet_csk_find_open_port() passes relax=false,
which disables both relaxations.

Please add a comment on top of the loop saying that @bitmap means "scanning",
since it is not obvious — or use !num, which is equivalent here since
udp_lib_get_port()
passes snum == 0 on the scan path.

Please also extend patch 2 to cover SO_REUSEADDR and SO_REUSEADDR|SO_REUSEPORT;
the helper refactor is already there, just make the bool a flags argument.

Given this is a 13 year old bind() behaviour change and the collision
window is tiny
with the default ephemeral range, net-next seems like the better target.

Perhaps a good candidate for "Cc: stable+noautosel@kernel.org # lets
not break applications..."



diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
index bb8cfc62cb00431faefd0371a9de9346b9bb..8628b723ec946eed6337209f62f683ba4252
100644
--- a/net/ipv4/udp.c
+++ b/net/ipv4/udp.c
@@ -143,21 +143,18 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,
                if (net_eq(sock_net(sk2), net) &&
                    sk2 != sk &&
                    (bitmap || udp_sk(sk2)->udp_port_hash == num) &&
-                   (!sk2->sk_reuse || !sk->sk_reuse) &&
+                   (bitmap || !sk2->sk_reuse || !sk->sk_reuse) &&
                    (!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
                     sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
                    inet_rcv_saddr_equal(sk, sk2, true)) {
-                       if (sk2->sk_reuseport && sk->sk_reuseport &&
-                           !rcu_access_pointer(sk->sk_reuseport_cb) &&
-                           uid_eq(uid, sk_uid(sk2))) {
-                               if (!bitmap)
+                       if (!bitmap) {
+                               if (sk2->sk_reuseport && sk->sk_reuseport &&
+                                   !rcu_access_pointer(sk->sk_reuseport_cb) &&
+                                   uid_eq(uid, sk_uid(sk2)))
                                        return 0;
-                       } else {
-                               if (!bitmap)
-                                       return 1;
-                               __set_bit(udp_sk(sk2)->udp_port_hash >> log,
-                                         bitmap);
+                               return 1;
                        }
+                       __set_bit(udp_sk(sk2)->udp_port_hash >> log, bitmap);
                }
        }
        return 0;

^ permalink raw reply	[flat|nested] 4+ messages in thread

* Re: [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket
  2026-09-23  6:05 [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Jiayuan Chen
  2026-09-23  6:05 ` [PATCH net 2/2] selftests/net: check auto-selected port with SO_REUSEPORT Jiayuan Chen
  2026-09-23 10:24 ` [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Eric Dumazet
@ 2026-09-24  9:07 ` netdev-bot+sashiko
  2 siblings, 0 replies; 4+ messages in thread
From: netdev-bot+sashiko @ 2026-09-24  9:07 UTC (permalink / raw)
  To: jiayuan.chen
  Cc: netdev, willemdebruijn.kernel, davem, edumazet, kuba, pabeni,
	horms, shuah, therbert, linux-kernel, linux-kselftest

Thank you for your contribution! Sashiko AI review found 1 potential
issue(s) to consider.

Critical: 0 · High: 1 · Medium: 0 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] Incomplete fix.

--

udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket

This commit changes udp_lib_lport_inuse() so that, during a port scan
(bitmap != NULL), compatible SO_REUSEPORT sockets are marked in the bitmap
instead of being skipped. The reuseport exemption now applies only when a
specific port is requested. This matches TCP commit 0643ee4fd1b7.

> diff --git a/net/ipv4/udp.c b/net/ipv4/udp.c
> index b090bd1f59e86..a7ade3171becc 100644
> --- a/net/ipv4/udp.c
> +++ b/net/ipv4/udp.c
> @@ -147,11 +147,10 @@ static int udp_lib_lport_inuse(struct net *net, __u16 num,
>  		    (!sk2->sk_bound_dev_if || !sk->sk_bound_dev_if ||
>  		     sk2->sk_bound_dev_if == sk->sk_bound_dev_if) &&
>  		    inet_rcv_saddr_equal(sk, sk2, true)) {
> -			if (sk2->sk_reuseport && sk->sk_reuseport &&
> +			if (!bitmap && sk2->sk_reuseport && sk->sk_reuseport &&

[Severity: High]
Doesn't this leave the same collision in place when both sockets also have
SO_REUSEADDR set?

The new !bitmap check sits inside the outer filter in
udp_lib_lport_inuse(), and that filter is also evaluated in scan mode:

net/ipv4/udp.c:udp_lib_lport_inuse() {
    ...
		    (bitmap || udp_sk(sk2)->udp_port_hash == num) &&
		    (!sk2->sk_reuse || !sk->sk_reuse) &&
    ...
}

If sk and sk2 both have sk_reuse set, the outer condition is false. In that
case neither the new reuseport branch nor the __set_bit() runs, so sk2's
port is never marked in the bitmap.

udp_lib_get_port() then treats the port as free:

    if (low <= snum && snum <= high &&
        !test_bit(snum >> udptable->log, bitmap) &&
        !inet_is_local_reserved_port(net, snum))
            goto found;

At found:, since sk->sk_reuseport is set, udp_reuseport_add_sock() finds
sk2 (same port hash, same bound dev, sk2->sk_reuseport, same uid) and does:

    return reuseport_add_sock(sk, sk2,
                              inet_rcv_saddr_any(sk));

The unrelated socket then joins the existing reuseport group and gets part
of its datagrams. That is the symptom described in the commit message.

IPv6 goes through the same path via udp_v6_get_port()->udp_lib_get_port().
Implicit autobind from connect()/sendmsg() on an unbound socket calls
udp_lib_get_port(sk, 0, ...) and reaches it as well.

The commit message says:

    Note that this still collides when both sockets also have SO_REUSEADDR
    set: udp_lib_lport_inuse() skips such a pair before it reaches the
    reuseport check, so the port is never marked either. That is not a
    common setup and is left as is here.

Is that really uncommon? Server code and libraries often set SO_REUSEADDR
together with SO_REUSEPORT.

The message also says UDP now does the same as TCP. TCP's scan calls
inet_bind_conflict() with relax == false, and that treats a reuse pair as a
conflict:

net/ipv4/inet_connection_sock.c:inet_bind_conflict() {
    ...
		if (sk->sk_reuse && sk2->sk_reuse &&
		    sk2->sk_state != TCP_LISTEN) {
			if (!relax || (!reuseport_ok && sk->sk_reuseport &&
    ...
				return true;
    ...
}

So TCP never hands out an occupied port during a scan because of
SO_REUSEADDR, but the UDP scan still does.

Could the outer term be changed to something like:

    (bitmap || !sk2->sk_reuse || !sk->sk_reuse) &&

so that every compatible occupied port is marked during the scan? The
SO_REUSEADDR and SO_REUSEPORT exemptions would still apply on the
explicit-port path.

Separately, sockets that have only SO_REUSEADDR set can also be given the
same auto-selected port today. That behaviour predates this patch, but the
same change would fix it too.

>  			    !rcu_access_pointer(sk->sk_reuseport_cb) &&
>  			    uid_eq(uid, sk_uid(sk2))) {
> -				if (!bitmap)
> -					return 0;
> +				return 0;
>  			} else {
>  				if (!bitmap)
>  					return 1;

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260923060547.118748-1-jiayuan.chen%40linux.dev

^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-09-24  9:07 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-23  6:05 [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Jiayuan Chen
2026-09-23  6:05 ` [PATCH net 2/2] selftests/net: check auto-selected port with SO_REUSEPORT Jiayuan Chen
2026-09-23 10:24 ` [PATCH net 1/2] udp: fix auto-selected port colliding with an existing SO_REUSEPORT socket Eric Dumazet
2026-09-24  9:07 ` netdev-bot+sashiko

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®