Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge branch 'tcp-update-bind-bucket-state-on-port-release'

Jakub Sitnicki says:

====================
tcp: Update bind bucket state on port release

TL;DR
-----

This is another take on addressing the issue we already raised earlier [1].

This time around, instead of trying to relax the bind-conflict checks in
connect(), we make an attempt to fix the tcp bind bucket state accounting.

The goal of this patch set is to make the bind buckets return to "port
reusable by ephemeral connections" state when all sockets blocking the port
from reuse get unhashed.

Changelog
---------
Changes in v5:
- Fix initial port-addr bucket state on saddr update with ip_dynaddr=1
- Add Kuniyuki's tag for tests
- Link to v4: https://lore.kernel.org/r/20250913-update-bind-bucket-state-on-unhash-v4-0-33a567594df7@cloudflare.com

Changes in v4:
- Drop redundant sk_is_connect_bind helper doc comment
- Link to v3: https://lore.kernel.org/r/20250910-update-bind-bucket-state-on-unhash-v3-0-023caaf4ae3c@cloudflare.com

Changes in v3:
- Move the flag from inet_flags to sk_userlocks (Kuniyuki)
- Rename the flag from AUTOBIND to CONNECT_BIND to avoid a name clash (Kuniyuki)
- Drop unreachable code for sk_state == TCP_NEW_SYN_RECV (Kuniyuki)
- Move the helper to inet_hashtables where it's used
- Reword patch 1 description for conciseness
- Link to v2: https://lore.kernel.org/r/20250821-update-bind-bucket-state-on-unhash-v2-0-0c204543a522@cloudflare.com

Changes in v2:
- Rename the inet_sock flag from LAZY_BIND to AUTOBIND (Eric)
- Clear the AUTOBIND flag on disconnect path (Eric)
- Add a test to cover the disconnect case (Eric)
- Link to RFC v1: https://lore.kernel.org/r/20250808-update-bind-bucket-state-on-unhash-v1-0-faf85099d61b@cloudflare.com

Situation
---------

We observe the following scenario in production:

inet_bind_bucket
state for port 54321
--------------------

(bucket doesn't exist)

// Process A opens a long-lived connection:
s1 = socket(AF_INET, SOCK_STREAM)
s1.setsockopt(IP_BIND_ADDRESS_NO_PORT)
s1.setsockopt(IP_LOCAL_PORT_RANGE, 54000..54500)
s1.bind(192.0.2.10, 0)
s1.connect(192.51.100.1, 443)
tb->fastreuse = -1
tb->fastreuseport = -1
s1.getsockname() -> 192.0.2.10:54321
s1.send()
s1.recv()
// ... s1 stays open.

// Process B opens a short-lived connection:
s2 = socket(AF_INET, SOCK_STREAM)
s2.setsockopt(SO_REUSEADDR)
s2.bind(192.0.2.20, 0)
tb->fastreuse = 0
tb->fastreuseport = 0
s2.connect(192.51.100.2, 53)
s2.getsockname() -> 192.0.2.20:54321
s2.send()
s2.recv()
s2.close()

// bucket remains in this
// state even though port
// was released by s2
tb->fastreuse = 0
tb->fastreuseport = 0

// Process A attempts to open another connection
// when there is connection pressure from
// 192.0.2.30:54000..54500 to 192.51.100.1:443.
// Assume only port 54321 is still available.

s3 = socket(AF_INET, SOCK_STREAM)
s3.setsockopt(IP_BIND_ADDRESS_NO_PORT)
s3.setsockopt(IP_LOCAL_PORT_RANGE, 54000..54500)
s3.bind(192.0.2.30, 0)
s3.connect(192.51.100.1, 443) -> EADDRNOTAVAIL (99)

Problem
-------

We end up in a state where Process A can't reuse ephemeral port 54321 for
as long as there are sockets, like s1, that keep the bind bucket alive. The
bucket does not return to "reusable" state even when all sockets which
blocked it from reuse, like s2, are gone.

The ephemeral port becomes available for use again only after all sockets
bound to it are gone and the bind bucket is destroyed.

Programs which behave like Process B in this scenario - that is, binding to
an IP address without setting IP_BIND_ADDRESS_NO_PORT - might be considered
poorly written. However, the reality is that such implementation is not
actually uncommon. Trying to fix each and every such program is like
playing whack-a-mole.

For instance, it could be any software using Golang's net.Dialer with
LocalAddr provided:

dialer := &net.Dialer{
LocalAddr: &net.TCPAddr{IP: srcIP},
}
conn, err := dialer.Dial("tcp4", dialTarget)

Or even a ubiquitous tool like dig when using a specific local address:

$ dig -b 127.1.1.1 +tcp +short example.com

Hence, we are proposing a systematic fix in the network stack itself.

Solution
--------

Please see the description in patch 1.

[1] https://lore.kernel.org/r/20250714-connect-port-search-harder-v3-0-b1a41f249865@cloudflare.com

Reported-by: Lee Valentine <lvalentine@cloudflare.com>
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
====================

Link: https://patch.msgid.link/20250917-update-bind-bucket-state-on-unhash-v5-0-57168b661b47@cloudflare.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>

+322 -8
+3 -2
include/net/inet_connection_sock.h
··· 316 316 void inet_csk_listen_stop(struct sock *sk); 317 317 318 318 /* update the fast reuse flag when adding a socket */ 319 - void inet_csk_update_fastreuse(struct inet_bind_bucket *tb, 320 - struct sock *sk); 319 + void inet_csk_update_fastreuse(const struct sock *sk, 320 + struct inet_bind_bucket *tb, 321 + struct inet_bind2_bucket *tb2); 321 322 322 323 struct dst_entry *inet_csk_update_pmtu(struct sock *sk, u32 mtu); 323 324
+2
include/net/inet_hashtables.h
··· 108 108 struct hlist_node bhash_node; 109 109 /* List of sockets hashed to this bucket */ 110 110 struct hlist_head owners; 111 + signed char fastreuse; 112 + signed char fastreuseport; 111 113 }; 112 114 113 115 static inline struct net *ib_net(const struct inet_bind_bucket *ib)
+2 -1
include/net/inet_timewait_sock.h
··· 70 70 unsigned int tw_transparent : 1, 71 71 tw_flowlabel : 20, 72 72 tw_usec_ts : 1, 73 - tw_pad : 2, /* 2 bits hole */ 73 + tw_connect_bind : 1, 74 + tw_pad : 1, /* 1 bit hole */ 74 75 tw_tos : 8; 75 76 u32 tw_txhash; 76 77 u32 tw_priority;
+4
include/net/sock.h
··· 1494 1494 1495 1495 #define SOCK_BINDADDR_LOCK 4 1496 1496 #define SOCK_BINDPORT_LOCK 8 1497 + /** 1498 + * define SOCK_CONNECT_BIND - &sock->sk_userlocks flag for auto-bind at connect() time 1499 + */ 1500 + #define SOCK_CONNECT_BIND 16 1497 1501 1498 1502 struct socket_alloc { 1499 1503 struct socket socket;
+8 -4
net/ipv4/inet_connection_sock.c
··· 423 423 } 424 424 425 425 static inline int sk_reuseport_match(struct inet_bind_bucket *tb, 426 - struct sock *sk) 426 + const struct sock *sk) 427 427 { 428 428 if (tb->fastreuseport <= 0) 429 429 return 0; ··· 453 453 ipv6_only_sock(sk), true, false); 454 454 } 455 455 456 - void inet_csk_update_fastreuse(struct inet_bind_bucket *tb, 457 - struct sock *sk) 456 + void inet_csk_update_fastreuse(const struct sock *sk, 457 + struct inet_bind_bucket *tb, 458 + struct inet_bind2_bucket *tb2) 458 459 { 459 460 bool reuse = sk->sk_reuse && sk->sk_state != TCP_LISTEN; 460 461 ··· 502 501 tb->fastreuseport = 0; 503 502 } 504 503 } 504 + 505 + tb2->fastreuse = tb->fastreuse; 506 + tb2->fastreuseport = tb->fastreuseport; 505 507 } 506 508 507 509 /* Obtain a reference to a local port for the given sock, ··· 586 582 } 587 583 588 584 success: 589 - inet_csk_update_fastreuse(tb, sk); 585 + inet_csk_update_fastreuse(sk, tb, tb2); 590 586 591 587 if (!inet_csk(sk)->icsk_bind_hash) 592 588 inet_bind_hash(sk, tb, tb2, port);
+43 -1
net/ipv4/inet_hashtables.c
··· 58 58 sk->sk_daddr, sk->sk_dport); 59 59 } 60 60 61 + static bool sk_is_connect_bind(const struct sock *sk) 62 + { 63 + if (sk->sk_state == TCP_TIME_WAIT) 64 + return inet_twsk(sk)->tw_connect_bind; 65 + else 66 + return sk->sk_userlocks & SOCK_CONNECT_BIND; 67 + } 68 + 61 69 /* 62 70 * Allocate and initialize a new local port bind bucket. 63 71 * The bindhash mutex for snum's hash chain must be held here. ··· 95 87 */ 96 88 void inet_bind_bucket_destroy(struct inet_bind_bucket *tb) 97 89 { 90 + const struct inet_bind2_bucket *tb2; 91 + 98 92 if (hlist_empty(&tb->bhash2)) { 99 93 hlist_del_rcu(&tb->node); 100 94 kfree_rcu(tb, rcu); 95 + return; 101 96 } 97 + 98 + if (tb->fastreuse == -1 && tb->fastreuseport == -1) 99 + return; 100 + hlist_for_each_entry(tb2, &tb->bhash2, bhash_node) { 101 + if (tb2->fastreuse != -1 || tb2->fastreuseport != -1) 102 + return; 103 + } 104 + tb->fastreuse = -1; 105 + tb->fastreuseport = -1; 102 106 } 103 107 104 108 bool inet_bind_bucket_match(const struct inet_bind_bucket *tb, const struct net *net, ··· 141 121 #else 142 122 tb2->rcv_saddr = sk->sk_rcv_saddr; 143 123 #endif 124 + tb2->fastreuse = 0; 125 + tb2->fastreuseport = 0; 144 126 INIT_HLIST_HEAD(&tb2->owners); 145 127 hlist_add_head(&tb2->node, &head->chain); 146 128 hlist_add_head(&tb2->bhash_node, &tb->bhash2); ··· 165 143 /* Caller must hold hashbucket lock for this tb with local BH disabled */ 166 144 void inet_bind2_bucket_destroy(struct kmem_cache *cachep, struct inet_bind2_bucket *tb) 167 145 { 146 + const struct sock *sk; 147 + 168 148 if (hlist_empty(&tb->owners)) { 169 149 __hlist_del(&tb->node); 170 150 __hlist_del(&tb->bhash_node); 171 151 kmem_cache_free(cachep, tb); 152 + return; 172 153 } 154 + 155 + if (tb->fastreuse == -1 && tb->fastreuseport == -1) 156 + return; 157 + sk_for_each_bound(sk, &tb->owners) { 158 + if (!sk_is_connect_bind(sk)) 159 + return; 160 + } 161 + tb->fastreuse = -1; 162 + tb->fastreuseport = -1; 173 163 } 174 164 175 165 static bool inet_bind2_bucket_addr_match(const struct inet_bind2_bucket *tb2, ··· 225 191 tb = inet_csk(sk)->icsk_bind_hash; 226 192 inet_csk(sk)->icsk_bind_hash = NULL; 227 193 inet_sk(sk)->inet_num = 0; 194 + sk->sk_userlocks &= ~SOCK_CONNECT_BIND; 228 195 229 196 spin_lock(&head2->lock); 230 197 if (inet_csk(sk)->icsk_bind2_hash) { ··· 312 277 } 313 278 } 314 279 if (update_fastreuse) 315 - inet_csk_update_fastreuse(tb, child); 280 + inet_csk_update_fastreuse(child, tb, tb2); 316 281 inet_bind_hash(child, tb, tb2, port); 317 282 spin_unlock(&head2->lock); 318 283 spin_unlock(&head->lock); ··· 985 950 if (!tb2) { 986 951 tb2 = new_tb2; 987 952 inet_bind2_bucket_init(tb2, net, head2, inet_csk(sk)->icsk_bind_hash, sk); 953 + if (sk_is_connect_bind(sk)) { 954 + tb2->fastreuse = -1; 955 + tb2->fastreuseport = -1; 956 + } 988 957 } 989 958 inet_csk(sk)->icsk_bind2_hash = tb2; 990 959 sk_add_bind_node(sk, &tb2->owners); ··· 1159 1120 head2, tb, sk); 1160 1121 if (!tb2) 1161 1122 goto error; 1123 + tb2->fastreuse = -1; 1124 + tb2->fastreuseport = -1; 1162 1125 } 1163 1126 1164 1127 /* Here we want to add a little bit of randomness to the next source ··· 1173 1132 1174 1133 /* Head lock still held and bh's disabled */ 1175 1134 inet_bind_hash(sk, tb, tb2, port); 1135 + sk->sk_userlocks |= SOCK_CONNECT_BIND; 1176 1136 1177 1137 if (sk_unhashed(sk)) { 1178 1138 inet_sk(sk)->inet_sport = htons(port);
+1
net/ipv4/inet_timewait_sock.c
··· 208 208 tw->tw_hash = sk->sk_hash; 209 209 tw->tw_ipv6only = 0; 210 210 tw->tw_transparent = inet_test_bit(TRANSPARENT, sk); 211 + tw->tw_connect_bind = !!(sk->sk_userlocks & SOCK_CONNECT_BIND); 211 212 tw->tw_prot = sk->sk_prot_creator; 212 213 atomic64_set(&tw->tw_cookie, atomic64_read(&sk->sk_cookie)); 213 214 twsk_net_set(tw, sock_net(sk));
+1
tools/testing/selftests/net/Makefile
··· 122 122 TEST_PROGS += ipv6_force_forwarding.sh 123 123 TEST_GEN_PROGS += ipv6_fragmentation 124 124 TEST_PROGS += route_hint.sh 125 + TEST_GEN_PROGS += tcp_port_share 125 126 126 127 # YNL files, must be before "include ..lib.mk" 127 128 YNL_GEN_FILES := busy_poller
+258
tools/testing/selftests/net/tcp_port_share.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 + // Copyright (c) 2025 Cloudflare, Inc. 3 + 4 + /* Tests for TCP port sharing (bind bucket reuse). */ 5 + 6 + #include <arpa/inet.h> 7 + #include <net/if.h> 8 + #include <sys/ioctl.h> 9 + #include <fcntl.h> 10 + #include <sched.h> 11 + #include <stdlib.h> 12 + 13 + #include "../kselftest_harness.h" 14 + 15 + #define DST_PORT 30000 16 + #define SRC_PORT 40000 17 + 18 + struct sockaddr_inet { 19 + union { 20 + struct sockaddr_storage ss; 21 + struct sockaddr_in6 v6; 22 + struct sockaddr_in v4; 23 + struct sockaddr sa; 24 + }; 25 + socklen_t len; 26 + char str[INET6_ADDRSTRLEN + __builtin_strlen("[]:65535") + 1]; 27 + }; 28 + 29 + const int one = 1; 30 + 31 + static int disconnect(int fd) 32 + { 33 + return connect(fd, &(struct sockaddr){ AF_UNSPEC }, sizeof(struct sockaddr)); 34 + } 35 + 36 + static int getsockname_port(int fd) 37 + { 38 + struct sockaddr_inet addr = {}; 39 + int err; 40 + 41 + addr.len = sizeof(addr); 42 + err = getsockname(fd, &addr.sa, &addr.len); 43 + if (err) 44 + return -1; 45 + 46 + switch (addr.sa.sa_family) { 47 + case AF_INET: 48 + return ntohs(addr.v4.sin_port); 49 + case AF_INET6: 50 + return ntohs(addr.v6.sin6_port); 51 + default: 52 + errno = EAFNOSUPPORT; 53 + return -1; 54 + } 55 + } 56 + 57 + static void make_inet_addr(int af, const char *ip, __u16 port, 58 + struct sockaddr_inet *addr) 59 + { 60 + const char *fmt = ""; 61 + 62 + memset(addr, 0, sizeof(*addr)); 63 + 64 + switch (af) { 65 + case AF_INET: 66 + addr->len = sizeof(addr->v4); 67 + addr->v4.sin_family = af; 68 + addr->v4.sin_port = htons(port); 69 + inet_pton(af, ip, &addr->v4.sin_addr); 70 + fmt = "%s:%hu"; 71 + break; 72 + case AF_INET6: 73 + addr->len = sizeof(addr->v6); 74 + addr->v6.sin6_family = af; 75 + addr->v6.sin6_port = htons(port); 76 + inet_pton(af, ip, &addr->v6.sin6_addr); 77 + fmt = "[%s]:%hu"; 78 + break; 79 + } 80 + 81 + snprintf(addr->str, sizeof(addr->str), fmt, ip, port); 82 + } 83 + 84 + FIXTURE(tcp_port_share) {}; 85 + 86 + FIXTURE_VARIANT(tcp_port_share) { 87 + int domain; 88 + /* IP to listen on and connect to */ 89 + const char *dst_ip; 90 + /* Primary IP to connect from */ 91 + const char *src1_ip; 92 + /* Secondary IP to connect from */ 93 + const char *src2_ip; 94 + /* IP to bind to in order to block the source port */ 95 + const char *bind_ip; 96 + }; 97 + 98 + FIXTURE_VARIANT_ADD(tcp_port_share, ipv4) { 99 + .domain = AF_INET, 100 + .dst_ip = "127.0.0.1", 101 + .src1_ip = "127.1.1.1", 102 + .src2_ip = "127.2.2.2", 103 + .bind_ip = "127.3.3.3", 104 + }; 105 + 106 + FIXTURE_VARIANT_ADD(tcp_port_share, ipv6) { 107 + .domain = AF_INET6, 108 + .dst_ip = "::1", 109 + .src1_ip = "2001:db8::1", 110 + .src2_ip = "2001:db8::2", 111 + .bind_ip = "2001:db8::3", 112 + }; 113 + 114 + FIXTURE_SETUP(tcp_port_share) 115 + { 116 + int sc; 117 + 118 + ASSERT_EQ(unshare(CLONE_NEWNET), 0); 119 + ASSERT_EQ(system("ip link set dev lo up"), 0); 120 + ASSERT_EQ(system("ip addr add dev lo 2001:db8::1/32 nodad"), 0); 121 + ASSERT_EQ(system("ip addr add dev lo 2001:db8::2/32 nodad"), 0); 122 + ASSERT_EQ(system("ip addr add dev lo 2001:db8::3/32 nodad"), 0); 123 + 124 + sc = open("/proc/sys/net/ipv4/ip_local_port_range", O_WRONLY); 125 + ASSERT_GE(sc, 0); 126 + ASSERT_GT(dprintf(sc, "%hu %hu\n", SRC_PORT, SRC_PORT), 0); 127 + ASSERT_EQ(close(sc), 0); 128 + } 129 + 130 + FIXTURE_TEARDOWN(tcp_port_share) {} 131 + 132 + /* Verify that an ephemeral port becomes available again after the socket 133 + * bound to it and blocking it from reuse is closed. 134 + */ 135 + TEST_F(tcp_port_share, can_reuse_port_after_bind_and_close) 136 + { 137 + const typeof(variant) v = variant; 138 + struct sockaddr_inet addr; 139 + int c1, c2, ln, pb; 140 + 141 + /* Listen on <dst_ip>:<DST_PORT> */ 142 + ln = socket(v->domain, SOCK_STREAM, 0); 143 + ASSERT_GE(ln, 0) TH_LOG("socket(): %m"); 144 + ASSERT_EQ(setsockopt(ln, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0); 145 + 146 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 147 + ASSERT_EQ(bind(ln, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 148 + ASSERT_EQ(listen(ln, 2), 0); 149 + 150 + /* Connect from <src1_ip>:<SRC_PORT> */ 151 + c1 = socket(v->domain, SOCK_STREAM, 0); 152 + ASSERT_GE(c1, 0) TH_LOG("socket(): %m"); 153 + ASSERT_EQ(setsockopt(c1, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0); 154 + 155 + make_inet_addr(v->domain, v->src1_ip, 0, &addr); 156 + ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 157 + 158 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 159 + ASSERT_EQ(connect(c1, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str); 160 + ASSERT_EQ(getsockname_port(c1), SRC_PORT); 161 + 162 + /* Bind to <bind_ip>:<SRC_PORT>. Block the port from reuse. */ 163 + pb = socket(v->domain, SOCK_STREAM, 0); 164 + ASSERT_GE(pb, 0) TH_LOG("socket(): %m"); 165 + ASSERT_EQ(setsockopt(pb, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0); 166 + 167 + make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr); 168 + ASSERT_EQ(bind(pb, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 169 + 170 + /* Try to connect from <src2_ip>:<SRC_PORT>. Expect failure. */ 171 + c2 = socket(v->domain, SOCK_STREAM, 0); 172 + ASSERT_GE(c2, 0) TH_LOG("socket"); 173 + ASSERT_EQ(setsockopt(c2, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0); 174 + 175 + make_inet_addr(v->domain, v->src2_ip, 0, &addr); 176 + ASSERT_EQ(bind(c2, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 177 + 178 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 179 + ASSERT_EQ(connect(c2, &addr.sa, addr.len), -1) TH_LOG("connect(%s)", addr.str); 180 + ASSERT_EQ(errno, EADDRNOTAVAIL) TH_LOG("%m"); 181 + 182 + /* Unbind from <bind_ip>:<SRC_PORT>. Unblock the port for reuse. */ 183 + ASSERT_EQ(close(pb), 0); 184 + 185 + /* Connect again from <src2_ip>:<SRC_PORT> */ 186 + EXPECT_EQ(connect(c2, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str); 187 + EXPECT_EQ(getsockname_port(c2), SRC_PORT); 188 + 189 + ASSERT_EQ(close(c2), 0); 190 + ASSERT_EQ(close(c1), 0); 191 + ASSERT_EQ(close(ln), 0); 192 + } 193 + 194 + /* Verify that a socket auto-bound during connect() blocks port reuse after 195 + * disconnect (connect(AF_UNSPEC)) followed by an explicit port bind(). 196 + */ 197 + TEST_F(tcp_port_share, port_block_after_disconnect) 198 + { 199 + const typeof(variant) v = variant; 200 + struct sockaddr_inet addr; 201 + int c1, c2, ln, pb; 202 + 203 + /* Listen on <dst_ip>:<DST_PORT> */ 204 + ln = socket(v->domain, SOCK_STREAM, 0); 205 + ASSERT_GE(ln, 0) TH_LOG("socket(): %m"); 206 + ASSERT_EQ(setsockopt(ln, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0); 207 + 208 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 209 + ASSERT_EQ(bind(ln, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 210 + ASSERT_EQ(listen(ln, 2), 0); 211 + 212 + /* Connect from <src1_ip>:<SRC_PORT> */ 213 + c1 = socket(v->domain, SOCK_STREAM, 0); 214 + ASSERT_GE(c1, 0) TH_LOG("socket(): %m"); 215 + ASSERT_EQ(setsockopt(c1, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0); 216 + 217 + make_inet_addr(v->domain, v->src1_ip, 0, &addr); 218 + ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 219 + 220 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 221 + ASSERT_EQ(connect(c1, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str); 222 + ASSERT_EQ(getsockname_port(c1), SRC_PORT); 223 + 224 + /* Disconnect the socket and bind it to <bind_ip>:<SRC_PORT> to block the port */ 225 + ASSERT_EQ(disconnect(c1), 0) TH_LOG("disconnect: %m"); 226 + ASSERT_EQ(setsockopt(c1, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0); 227 + 228 + make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr); 229 + ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 230 + 231 + /* Trigger port-addr bucket state update with another bind() and close() */ 232 + pb = socket(v->domain, SOCK_STREAM, 0); 233 + ASSERT_GE(pb, 0) TH_LOG("socket(): %m"); 234 + ASSERT_EQ(setsockopt(pb, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0); 235 + 236 + make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr); 237 + ASSERT_EQ(bind(pb, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 238 + 239 + ASSERT_EQ(close(pb), 0); 240 + 241 + /* Connect from <src2_ip>:<SRC_PORT>. Expect failure. */ 242 + c2 = socket(v->domain, SOCK_STREAM, 0); 243 + ASSERT_GE(c2, 0) TH_LOG("socket: %m"); 244 + ASSERT_EQ(setsockopt(c2, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0); 245 + 246 + make_inet_addr(v->domain, v->src2_ip, 0, &addr); 247 + ASSERT_EQ(bind(c2, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str); 248 + 249 + make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr); 250 + EXPECT_EQ(connect(c2, &addr.sa, addr.len), -1) TH_LOG("connect(%s)", addr.str); 251 + EXPECT_EQ(errno, EADDRNOTAVAIL) TH_LOG("%m"); 252 + 253 + ASSERT_EQ(close(c2), 0); 254 + ASSERT_EQ(close(c1), 0); 255 + ASSERT_EQ(close(ln), 0); 256 + } 257 + 258 + TEST_HARNESS_MAIN