From: Kuniyuki Iwashima kuniyu@amazon.com Date: Fri, 10 Mar 2023 13:25:47 -0800
From: Paul Holzinger pholzing@redhat.com Date: Fri, 10 Mar 2023 17:01:31 +0100
Hi all,
there seems to be a regression which allows you to bind the same port twice when the first bind call bound to all ip addresses (i. e. dual stack).
A second bind call for the same port will succeed if you try to bind to a specific ipv4 (e. g. 127.0.0.1), binding to 0.0.0.0 or an ipv6 address fails correctly with EADDRINUSE.
I included a small c program below to show the issue. Normally the second bind call should fail, this was the case before v6.1.
I bisected the regression to commit 5456262d2baa ("net: Fix incorrect address comparison when searching for a bind2 bucket").
I also checked that the issue is still present in v6.3-rc1.
Thanks for the detailed report.
It seems we should take care of the special case in inet_bind2_bucket_match_addr_any().
I confimed this change fixes the regression. I'll check other paths that 5456262d2baa touched.
---8<--- diff --git a/net/ipv4/inet_hashtables.c b/net/ipv4/inet_hashtables.c index e41fdc38ce19..62c5f7501571 100644 --- a/net/ipv4/inet_hashtables.c +++ b/net/ipv4/inet_hashtables.c @@ -828,8 +828,15 @@ bool inet_bind2_bucket_match_addr_any(const struct inet_bind2_bucket *tb, const #if IS_ENABLED(CONFIG_IPV6) struct in6_addr addr_any = {};
- if (sk->sk_family != tb->family) - return false; + if (sk->sk_family != tb->family) { + if (sk->sk_family == AF_INET6) + return net_eq(ib2_net(tb), net) && tb->port == port && + tb->l3mdev == l3mdev && tb->rcv_saddr == 0; + else + return net_eq(ib2_net(tb), net) && tb->port == port && + tb->l3mdev == l3mdev && + ipv6_addr_equal(&tb->v6_rcv_saddr, &in6addr_any); + }
if (sk->sk_family == AF_INET6) return net_eq(ib2_net(tb), net) && tb->port == port && ---8<---
Tested:
---8<---
from socket import *
s = socket(AF_INET6, SOCK_STREAM, 0) s2 = socket(AF_INET, SOCK_STREAM, 0)
s.bind(('::', 0)) s
<socket.socket fd=3, family=AddressFamily.AF_INET6, type=SocketKind.SOCK_STREAM, proto=0, laddr=('::', 53147, 0, 0)>
s2.bind(('0.0.0.0', s.getsockname()[1]))
Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 98] Address already in use
s2.bind(('127.0.0.1', s.getsockname()[1]))
Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 98] Address already in use
s3 = socket(AF_INET, SOCK_STREAM, 0) s4 = socket(AF_INET6, SOCK_STREAM, 0)
s3.bind(('0.0.0.0', 0)) s3
<socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 58359)>
s4.bind(('::0', s3.getsockname()[1]))
Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 98] Address already in use
s4.bind(('::1', s3.getsockname()[1]))
Traceback (most recent call last): File "<stdin>", line 1, in <module> OSError: [Errno 98] Address already in use ---8<---
Thanks, Kuniyuki
I'll fix it.
Thanks, Kuniyuki
Original report: https://github.com/containers/podman/issues/17719
#regzbot introduced: 5456262d2baa
#include <sys/socket.h> #include <sys/un.h> #include <stdlib.h> #include <stdio.h> #include <netinet/in.h> #include <unistd.h> int main(int argc, char *argv[]) { int ret, sock1, sock2; struct sockaddr_in6 addr; struct sockaddr_in addr2; sock1 = socket(AF_INET6, SOCK_STREAM, 0); if (sock1 == -1) { perror("socket1"); exit(1); } sock2 = socket(AF_INET, SOCK_STREAM, 0); if (sock2 == -1) { perror("socket2"); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sin6_family = AF_INET6; addr.sin6_addr = in6addr_any; addr.sin6_port = htons(8080); memset(&addr2, 0, sizeof(addr2)); addr2.sin_family = AF_INET; addr2.sin_addr.s_addr = htonl(INADDR_LOOPBACK); addr2.sin_port = htons(8080); ret = bind(sock1, (struct sockaddr *)&addr, sizeof(addr)); if (ret == -1) { perror("bind1"); exit(1); } printf("bind1 ret: %d\n", ret); if ((listen(sock1, 5)) != 0) { perror("listen1"); exit(1); } ret = bind(sock2, (struct sockaddr *)&addr2, sizeof(addr2)); if (ret == -1) { perror("bind2"); exit(1); } printf("bind2 ret: %d\n", ret); if ((listen(sock2, 5)) != 0) { perror("listen2"); exit(1); } // uncomment pause() to see with ss -tlpn the bound ports // pause(); return 0; }
Best regards,
Paul