Skip to content

Commit 7ece54a

Browse files
iamkafaidavem330
authored andcommitted
ipv6: Fix SO_REUSEPORT UDP socket with implicit sk_ipv6only
If a sk_v6_rcv_saddr is !IPV6_ADDR_ANY and !IPV6_ADDR_MAPPED, it implicitly implies it is an ipv6only socket. However, in inet6_bind(), this addr_type checking and setting sk->sk_ipv6only to 1 are only done after sk->sk_prot->get_port(sk, snum) has been completed successfully. This inconsistency between sk_v6_rcv_saddr and sk_ipv6only confuses the 'get_port()'. In particular, when binding SO_REUSEPORT UDP sockets, udp_reuseport_add_sock(sk,...) is called. udp_reuseport_add_sock() checks "ipv6_only_sock(sk2) == ipv6_only_sock(sk)" before adding sk to sk2->sk_reuseport_cb. In this case, ipv6_only_sock(sk2) could be 1 while ipv6_only_sock(sk) is still 0 here. The end result is, reuseport_alloc(sk) is called instead of adding sk to the existing sk2->sk_reuseport_cb. It can be reproduced by binding two SO_REUSEPORT UDP sockets on an IPv6 address (!ANY and !MAPPED). Only one of the socket will receive packet. The fix is to set the implicit sk_ipv6only before calling get_port(). The original sk_ipv6only has to be saved such that it can be restored in case get_port() failed. The situation is similar to the inet_reset_saddr(sk) after get_port() has failed. Thanks to Calvin Owens <[email protected]> who created an easy reproduction which leads to a fix. Fixes: e32ea7e ("soreuseport: fast reuseport UDP socket selection") Signed-off-by: Martin KaFai Lau <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 2479c2c commit 7ece54a

File tree

1 file changed

+7
-4
lines changed

1 file changed

+7
-4
lines changed

net/ipv6/af_inet6.c

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
284284
struct net *net = sock_net(sk);
285285
__be32 v4addr = 0;
286286
unsigned short snum;
287+
bool saved_ipv6only;
287288
int addr_type = 0;
288289
int err = 0;
289290

@@ -389,19 +390,21 @@ int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
389390
if (!(addr_type & IPV6_ADDR_MULTICAST))
390391
np->saddr = addr->sin6_addr;
391392

393+
saved_ipv6only = sk->sk_ipv6only;
394+
if (addr_type != IPV6_ADDR_ANY && addr_type != IPV6_ADDR_MAPPED)
395+
sk->sk_ipv6only = 1;
396+
392397
/* Make sure we are allowed to bind here. */
393398
if ((snum || !inet->bind_address_no_port) &&
394399
sk->sk_prot->get_port(sk, snum)) {
400+
sk->sk_ipv6only = saved_ipv6only;
395401
inet_reset_saddr(sk);
396402
err = -EADDRINUSE;
397403
goto out;
398404
}
399405

400-
if (addr_type != IPV6_ADDR_ANY) {
406+
if (addr_type != IPV6_ADDR_ANY)
401407
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
402-
if (addr_type != IPV6_ADDR_MAPPED)
403-
sk->sk_ipv6only = 1;
404-
}
405408
if (snum)
406409
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
407410
inet->inet_sport = htons(inet->inet_num);

0 commit comments

Comments
 (0)