tcp_port_share.c 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
  2. // Copyright (c) 2025 Cloudflare, Inc.
  3. /* Tests for TCP port sharing (bind bucket reuse). */
  4. #include <arpa/inet.h>
  5. #include <net/if.h>
  6. #include <sys/ioctl.h>
  7. #include <fcntl.h>
  8. #include <sched.h>
  9. #include <stdlib.h>
  10. #include "kselftest_harness.h"
  11. #define DST_PORT 30000
  12. #define SRC_PORT 40000
  13. struct sockaddr_inet {
  14. union {
  15. struct sockaddr_storage ss;
  16. struct sockaddr_in6 v6;
  17. struct sockaddr_in v4;
  18. struct sockaddr sa;
  19. };
  20. socklen_t len;
  21. char str[INET6_ADDRSTRLEN + __builtin_strlen("[]:65535") + 1];
  22. };
  23. const int one = 1;
  24. static int disconnect(int fd)
  25. {
  26. return connect(fd, &(struct sockaddr){ AF_UNSPEC }, sizeof(struct sockaddr));
  27. }
  28. static int getsockname_port(int fd)
  29. {
  30. struct sockaddr_inet addr = {};
  31. int err;
  32. addr.len = sizeof(addr);
  33. err = getsockname(fd, &addr.sa, &addr.len);
  34. if (err)
  35. return -1;
  36. switch (addr.sa.sa_family) {
  37. case AF_INET:
  38. return ntohs(addr.v4.sin_port);
  39. case AF_INET6:
  40. return ntohs(addr.v6.sin6_port);
  41. default:
  42. errno = EAFNOSUPPORT;
  43. return -1;
  44. }
  45. }
  46. static void make_inet_addr(int af, const char *ip, __u16 port,
  47. struct sockaddr_inet *addr)
  48. {
  49. const char *fmt = "";
  50. memset(addr, 0, sizeof(*addr));
  51. switch (af) {
  52. case AF_INET:
  53. addr->len = sizeof(addr->v4);
  54. addr->v4.sin_family = af;
  55. addr->v4.sin_port = htons(port);
  56. inet_pton(af, ip, &addr->v4.sin_addr);
  57. fmt = "%s:%hu";
  58. break;
  59. case AF_INET6:
  60. addr->len = sizeof(addr->v6);
  61. addr->v6.sin6_family = af;
  62. addr->v6.sin6_port = htons(port);
  63. inet_pton(af, ip, &addr->v6.sin6_addr);
  64. fmt = "[%s]:%hu";
  65. break;
  66. }
  67. snprintf(addr->str, sizeof(addr->str), fmt, ip, port);
  68. }
  69. FIXTURE(tcp_port_share) {};
  70. FIXTURE_VARIANT(tcp_port_share) {
  71. int domain;
  72. /* IP to listen on and connect to */
  73. const char *dst_ip;
  74. /* Primary IP to connect from */
  75. const char *src1_ip;
  76. /* Secondary IP to connect from */
  77. const char *src2_ip;
  78. /* IP to bind to in order to block the source port */
  79. const char *bind_ip;
  80. };
  81. FIXTURE_VARIANT_ADD(tcp_port_share, ipv4) {
  82. .domain = AF_INET,
  83. .dst_ip = "127.0.0.1",
  84. .src1_ip = "127.1.1.1",
  85. .src2_ip = "127.2.2.2",
  86. .bind_ip = "127.3.3.3",
  87. };
  88. FIXTURE_VARIANT_ADD(tcp_port_share, ipv6) {
  89. .domain = AF_INET6,
  90. .dst_ip = "::1",
  91. .src1_ip = "2001:db8::1",
  92. .src2_ip = "2001:db8::2",
  93. .bind_ip = "2001:db8::3",
  94. };
  95. FIXTURE_SETUP(tcp_port_share)
  96. {
  97. int sc;
  98. ASSERT_EQ(unshare(CLONE_NEWNET), 0);
  99. ASSERT_EQ(system("ip link set dev lo up"), 0);
  100. ASSERT_EQ(system("ip addr add dev lo 2001:db8::1/32 nodad"), 0);
  101. ASSERT_EQ(system("ip addr add dev lo 2001:db8::2/32 nodad"), 0);
  102. ASSERT_EQ(system("ip addr add dev lo 2001:db8::3/32 nodad"), 0);
  103. sc = open("/proc/sys/net/ipv4/ip_local_port_range", O_WRONLY);
  104. ASSERT_GE(sc, 0);
  105. ASSERT_GT(dprintf(sc, "%hu %hu\n", SRC_PORT, SRC_PORT), 0);
  106. ASSERT_EQ(close(sc), 0);
  107. }
  108. FIXTURE_TEARDOWN(tcp_port_share) {}
  109. /* Verify that an ephemeral port becomes available again after the socket
  110. * bound to it and blocking it from reuse is closed.
  111. */
  112. TEST_F(tcp_port_share, can_reuse_port_after_bind_and_close)
  113. {
  114. const typeof(variant) v = variant;
  115. struct sockaddr_inet addr;
  116. int c1, c2, ln, pb;
  117. /* Listen on <dst_ip>:<DST_PORT> */
  118. ln = socket(v->domain, SOCK_STREAM, 0);
  119. ASSERT_GE(ln, 0) TH_LOG("socket(): %m");
  120. ASSERT_EQ(setsockopt(ln, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
  121. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  122. ASSERT_EQ(bind(ln, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  123. ASSERT_EQ(listen(ln, 2), 0);
  124. /* Connect from <src1_ip>:<SRC_PORT> */
  125. c1 = socket(v->domain, SOCK_STREAM, 0);
  126. ASSERT_GE(c1, 0) TH_LOG("socket(): %m");
  127. ASSERT_EQ(setsockopt(c1, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0);
  128. make_inet_addr(v->domain, v->src1_ip, 0, &addr);
  129. ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  130. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  131. ASSERT_EQ(connect(c1, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str);
  132. ASSERT_EQ(getsockname_port(c1), SRC_PORT);
  133. /* Bind to <bind_ip>:<SRC_PORT>. Block the port from reuse. */
  134. pb = socket(v->domain, SOCK_STREAM, 0);
  135. ASSERT_GE(pb, 0) TH_LOG("socket(): %m");
  136. ASSERT_EQ(setsockopt(pb, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
  137. make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr);
  138. ASSERT_EQ(bind(pb, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  139. /* Try to connect from <src2_ip>:<SRC_PORT>. Expect failure. */
  140. c2 = socket(v->domain, SOCK_STREAM, 0);
  141. ASSERT_GE(c2, 0) TH_LOG("socket");
  142. ASSERT_EQ(setsockopt(c2, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0);
  143. make_inet_addr(v->domain, v->src2_ip, 0, &addr);
  144. ASSERT_EQ(bind(c2, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  145. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  146. ASSERT_EQ(connect(c2, &addr.sa, addr.len), -1) TH_LOG("connect(%s)", addr.str);
  147. ASSERT_EQ(errno, EADDRNOTAVAIL) TH_LOG("%m");
  148. /* Unbind from <bind_ip>:<SRC_PORT>. Unblock the port for reuse. */
  149. ASSERT_EQ(close(pb), 0);
  150. /* Connect again from <src2_ip>:<SRC_PORT> */
  151. EXPECT_EQ(connect(c2, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str);
  152. EXPECT_EQ(getsockname_port(c2), SRC_PORT);
  153. ASSERT_EQ(close(c2), 0);
  154. ASSERT_EQ(close(c1), 0);
  155. ASSERT_EQ(close(ln), 0);
  156. }
  157. /* Verify that a socket auto-bound during connect() blocks port reuse after
  158. * disconnect (connect(AF_UNSPEC)) followed by an explicit port bind().
  159. */
  160. TEST_F(tcp_port_share, port_block_after_disconnect)
  161. {
  162. const typeof(variant) v = variant;
  163. struct sockaddr_inet addr;
  164. int c1, c2, ln, pb;
  165. /* Listen on <dst_ip>:<DST_PORT> */
  166. ln = socket(v->domain, SOCK_STREAM, 0);
  167. ASSERT_GE(ln, 0) TH_LOG("socket(): %m");
  168. ASSERT_EQ(setsockopt(ln, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
  169. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  170. ASSERT_EQ(bind(ln, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  171. ASSERT_EQ(listen(ln, 2), 0);
  172. /* Connect from <src1_ip>:<SRC_PORT> */
  173. c1 = socket(v->domain, SOCK_STREAM, 0);
  174. ASSERT_GE(c1, 0) TH_LOG("socket(): %m");
  175. ASSERT_EQ(setsockopt(c1, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0);
  176. make_inet_addr(v->domain, v->src1_ip, 0, &addr);
  177. ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  178. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  179. ASSERT_EQ(connect(c1, &addr.sa, addr.len), 0) TH_LOG("connect(%s): %m", addr.str);
  180. ASSERT_EQ(getsockname_port(c1), SRC_PORT);
  181. /* Disconnect the socket and bind it to <bind_ip>:<SRC_PORT> to block the port */
  182. ASSERT_EQ(disconnect(c1), 0) TH_LOG("disconnect: %m");
  183. ASSERT_EQ(setsockopt(c1, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
  184. make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr);
  185. ASSERT_EQ(bind(c1, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  186. /* Trigger port-addr bucket state update with another bind() and close() */
  187. pb = socket(v->domain, SOCK_STREAM, 0);
  188. ASSERT_GE(pb, 0) TH_LOG("socket(): %m");
  189. ASSERT_EQ(setsockopt(pb, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)), 0);
  190. make_inet_addr(v->domain, v->bind_ip, SRC_PORT, &addr);
  191. ASSERT_EQ(bind(pb, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  192. ASSERT_EQ(close(pb), 0);
  193. /* Connect from <src2_ip>:<SRC_PORT>. Expect failure. */
  194. c2 = socket(v->domain, SOCK_STREAM, 0);
  195. ASSERT_GE(c2, 0) TH_LOG("socket: %m");
  196. ASSERT_EQ(setsockopt(c2, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)), 0);
  197. make_inet_addr(v->domain, v->src2_ip, 0, &addr);
  198. ASSERT_EQ(bind(c2, &addr.sa, addr.len), 0) TH_LOG("bind(%s): %m", addr.str);
  199. make_inet_addr(v->domain, v->dst_ip, DST_PORT, &addr);
  200. EXPECT_EQ(connect(c2, &addr.sa, addr.len), -1) TH_LOG("connect(%s)", addr.str);
  201. EXPECT_EQ(errno, EADDRNOTAVAIL) TH_LOG("%m");
  202. ASSERT_EQ(close(c2), 0);
  203. ASSERT_EQ(close(c1), 0);
  204. ASSERT_EQ(close(ln), 0);
  205. }
  206. TEST_HARNESS_MAIN