mirror of https://gitee.com/openkylin/linux.git
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 <calvinowens@fb.com> who created an easy
reproduction which leads to a fix.
Fixes: e32ea7e747
("soreuseport: fast reuseport UDP socket selection")
Signed-off-by: Martin KaFai Lau <kafai@fb.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
2479c2c9f4
commit
7ece54a60e
|
@ -284,6 +284,7 @@ int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
|||
struct net *net = sock_net(sk);
|
||||
__be32 v4addr = 0;
|
||||
unsigned short snum;
|
||||
bool saved_ipv6only;
|
||||
int addr_type = 0;
|
||||
int err = 0;
|
||||
|
||||
|
@ -389,19 +390,21 @@ int inet6_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
|
|||
if (!(addr_type & IPV6_ADDR_MULTICAST))
|
||||
np->saddr = addr->sin6_addr;
|
||||
|
||||
saved_ipv6only = sk->sk_ipv6only;
|
||||
if (addr_type != IPV6_ADDR_ANY && addr_type != IPV6_ADDR_MAPPED)
|
||||
sk->sk_ipv6only = 1;
|
||||
|
||||
/* Make sure we are allowed to bind here. */
|
||||
if ((snum || !inet->bind_address_no_port) &&
|
||||
sk->sk_prot->get_port(sk, snum)) {
|
||||
sk->sk_ipv6only = saved_ipv6only;
|
||||
inet_reset_saddr(sk);
|
||||
err = -EADDRINUSE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (addr_type != IPV6_ADDR_ANY) {
|
||||
if (addr_type != IPV6_ADDR_ANY)
|
||||
sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
|
||||
if (addr_type != IPV6_ADDR_MAPPED)
|
||||
sk->sk_ipv6only = 1;
|
||||
}
|
||||
if (snum)
|
||||
sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
|
||||
inet->inet_sport = htons(inet->inet_num);
|
||||
|
|
Loading…
Reference in New Issue