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

selftests/net: Cover the IP_LOCAL_PORT_RANGE socket option

Exercise IP_LOCAL_PORT_RANGE socket option in various scenarios:

1. pass invalid values to setsockopt
2. pass a range outside of the per-netns port range
3. configure a single-port range
4. exhaust a configured multi-port range
5. check interaction with late-bind (IP_BIND_ADDRESS_NO_PORT)
6. set then get the per-socket port range

Reviewed-by: Kuniyuki Iwashima <kuniyu@amazon.com>
Signed-off-by: Jakub Sitnicki <jakub@cloudflare.com>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Jakub Sitnicki and committed by
Jakub Kicinski
ae543965 91d0b78c

+454
+2
tools/testing/selftests/net/Makefile
··· 45 45 TEST_PROGS += stress_reuseport_listen.sh 46 46 TEST_PROGS += l2_tos_ttl_inherit.sh 47 47 TEST_PROGS += bind_bhash.sh 48 + TEST_PROGS += ip_local_port_range.sh 48 49 TEST_PROGS_EXTENDED := in_netns.sh setup_loopback.sh setup_veth.sh 49 50 TEST_PROGS_EXTENDED += toeplitz_client.sh toeplitz.sh 50 51 TEST_GEN_FILES = socket nettest ··· 77 76 TEST_GEN_FILES += sctp_hello 78 77 TEST_GEN_FILES += csum 79 78 TEST_GEN_FILES += nat6to4.o 79 + TEST_GEN_FILES += ip_local_port_range 80 80 81 81 TEST_FILES := settings 82 82
+447
tools/testing/selftests/net/ip_local_port_range.c
··· 1 + // SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause 2 + // Copyright (c) 2023 Cloudflare 3 + 4 + /* Test IP_LOCAL_PORT_RANGE socket option: IPv4 + IPv6, TCP + UDP. 5 + * 6 + * Tests assume that net.ipv4.ip_local_port_range is [40000, 49999]. 7 + * Don't run these directly but with ip_local_port_range.sh script. 8 + */ 9 + 10 + #include <fcntl.h> 11 + #include <netinet/ip.h> 12 + 13 + #include "../kselftest_harness.h" 14 + 15 + #ifndef IP_LOCAL_PORT_RANGE 16 + #define IP_LOCAL_PORT_RANGE 51 17 + #endif 18 + 19 + static __u32 pack_port_range(__u16 lo, __u16 hi) 20 + { 21 + return (hi << 16) | (lo << 0); 22 + } 23 + 24 + static void unpack_port_range(__u32 range, __u16 *lo, __u16 *hi) 25 + { 26 + *lo = range & 0xffff; 27 + *hi = range >> 16; 28 + } 29 + 30 + static int get_so_domain(int fd) 31 + { 32 + int domain, err; 33 + socklen_t len; 34 + 35 + len = sizeof(domain); 36 + err = getsockopt(fd, SOL_SOCKET, SO_DOMAIN, &domain, &len); 37 + if (err) 38 + return -1; 39 + 40 + return domain; 41 + } 42 + 43 + static int bind_to_loopback_any_port(int fd) 44 + { 45 + union { 46 + struct sockaddr sa; 47 + struct sockaddr_in v4; 48 + struct sockaddr_in6 v6; 49 + } addr; 50 + socklen_t addr_len; 51 + 52 + memset(&addr, 0, sizeof(addr)); 53 + switch (get_so_domain(fd)) { 54 + case AF_INET: 55 + addr.v4.sin_family = AF_INET; 56 + addr.v4.sin_port = htons(0); 57 + addr.v4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 58 + addr_len = sizeof(addr.v4); 59 + break; 60 + case AF_INET6: 61 + addr.v6.sin6_family = AF_INET6; 62 + addr.v6.sin6_port = htons(0); 63 + addr.v6.sin6_addr = in6addr_loopback; 64 + addr_len = sizeof(addr.v6); 65 + break; 66 + default: 67 + return -1; 68 + } 69 + 70 + return bind(fd, &addr.sa, addr_len); 71 + } 72 + 73 + static int get_sock_port(int fd) 74 + { 75 + union { 76 + struct sockaddr sa; 77 + struct sockaddr_in v4; 78 + struct sockaddr_in6 v6; 79 + } addr; 80 + socklen_t addr_len; 81 + int err; 82 + 83 + addr_len = sizeof(addr); 84 + memset(&addr, 0, sizeof(addr)); 85 + err = getsockname(fd, &addr.sa, &addr_len); 86 + if (err) 87 + return -1; 88 + 89 + switch (addr.sa.sa_family) { 90 + case AF_INET: 91 + return ntohs(addr.v4.sin_port); 92 + case AF_INET6: 93 + return ntohs(addr.v6.sin6_port); 94 + default: 95 + errno = EAFNOSUPPORT; 96 + return -1; 97 + } 98 + } 99 + 100 + static int get_ip_local_port_range(int fd, __u32 *range) 101 + { 102 + socklen_t len; 103 + __u32 val; 104 + int err; 105 + 106 + len = sizeof(val); 107 + err = getsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &val, &len); 108 + if (err) 109 + return -1; 110 + 111 + *range = val; 112 + return 0; 113 + } 114 + 115 + FIXTURE(ip_local_port_range) {}; 116 + 117 + FIXTURE_SETUP(ip_local_port_range) 118 + { 119 + } 120 + 121 + FIXTURE_TEARDOWN(ip_local_port_range) 122 + { 123 + } 124 + 125 + FIXTURE_VARIANT(ip_local_port_range) { 126 + int so_domain; 127 + int so_type; 128 + int so_protocol; 129 + }; 130 + 131 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip4_tcp) { 132 + .so_domain = AF_INET, 133 + .so_type = SOCK_STREAM, 134 + .so_protocol = 0, 135 + }; 136 + 137 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip4_udp) { 138 + .so_domain = AF_INET, 139 + .so_type = SOCK_DGRAM, 140 + .so_protocol = 0, 141 + }; 142 + 143 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip4_stcp) { 144 + .so_domain = AF_INET, 145 + .so_type = SOCK_STREAM, 146 + .so_protocol = IPPROTO_SCTP, 147 + }; 148 + 149 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_tcp) { 150 + .so_domain = AF_INET6, 151 + .so_type = SOCK_STREAM, 152 + .so_protocol = 0, 153 + }; 154 + 155 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_udp) { 156 + .so_domain = AF_INET6, 157 + .so_type = SOCK_DGRAM, 158 + .so_protocol = 0, 159 + }; 160 + 161 + FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_stcp) { 162 + .so_domain = AF_INET6, 163 + .so_type = SOCK_STREAM, 164 + .so_protocol = IPPROTO_SCTP, 165 + }; 166 + 167 + TEST_F(ip_local_port_range, invalid_option_value) 168 + { 169 + __u16 val16; 170 + __u32 val32; 171 + __u64 val64; 172 + int fd, err; 173 + 174 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 175 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 176 + 177 + /* Too few bytes */ 178 + val16 = 40000; 179 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &val16, sizeof(val16)); 180 + EXPECT_TRUE(err) TH_LOG("expected setsockopt(IP_LOCAL_PORT_RANGE) to fail"); 181 + EXPECT_EQ(errno, EINVAL); 182 + 183 + /* Empty range: low port > high port */ 184 + val32 = pack_port_range(40222, 40111); 185 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &val32, sizeof(val32)); 186 + EXPECT_TRUE(err) TH_LOG("expected setsockopt(IP_LOCAL_PORT_RANGE) to fail"); 187 + EXPECT_EQ(errno, EINVAL); 188 + 189 + /* Too many bytes */ 190 + val64 = pack_port_range(40333, 40444); 191 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &val64, sizeof(val64)); 192 + EXPECT_TRUE(err) TH_LOG("expected setsockopt(IP_LOCAL_PORT_RANGE) to fail"); 193 + EXPECT_EQ(errno, EINVAL); 194 + 195 + err = close(fd); 196 + ASSERT_TRUE(!err) TH_LOG("close failed"); 197 + } 198 + 199 + TEST_F(ip_local_port_range, port_range_out_of_netns_range) 200 + { 201 + const struct test { 202 + __u16 range_lo; 203 + __u16 range_hi; 204 + } tests[] = { 205 + { 30000, 39999 }, /* socket range below netns range */ 206 + { 50000, 59999 }, /* socket range above netns range */ 207 + }; 208 + const struct test *t; 209 + 210 + for (t = tests; t < tests + ARRAY_SIZE(tests); t++) { 211 + /* Bind a couple of sockets, not just one, to check 212 + * that the range wasn't clamped to a single port from 213 + * the netns range. That is [40000, 40000] or [49999, 214 + * 49999], respectively for each test case. 215 + */ 216 + int fds[2], i; 217 + 218 + TH_LOG("lo %5hu, hi %5hu", t->range_lo, t->range_hi); 219 + 220 + for (i = 0; i < ARRAY_SIZE(fds); i++) { 221 + int fd, err, port; 222 + __u32 range; 223 + 224 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 225 + ASSERT_GE(fd, 0) TH_LOG("#%d: socket failed", i); 226 + 227 + range = pack_port_range(t->range_lo, t->range_hi); 228 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 229 + ASSERT_TRUE(!err) TH_LOG("#%d: setsockopt(IP_LOCAL_PORT_RANGE) failed", i); 230 + 231 + err = bind_to_loopback_any_port(fd); 232 + ASSERT_TRUE(!err) TH_LOG("#%d: bind failed", i); 233 + 234 + /* Check that socket port range outside of ephemeral range is ignored */ 235 + port = get_sock_port(fd); 236 + ASSERT_GE(port, 40000) TH_LOG("#%d: expected port within netns range", i); 237 + ASSERT_LE(port, 49999) TH_LOG("#%d: expected port within netns range", i); 238 + 239 + fds[i] = fd; 240 + } 241 + 242 + for (i = 0; i < ARRAY_SIZE(fds); i++) 243 + ASSERT_TRUE(close(fds[i]) == 0) TH_LOG("#%d: close failed", i); 244 + } 245 + } 246 + 247 + TEST_F(ip_local_port_range, single_port_range) 248 + { 249 + const struct test { 250 + __u16 range_lo; 251 + __u16 range_hi; 252 + __u16 expected; 253 + } tests[] = { 254 + /* single port range within ephemeral range */ 255 + { 45000, 45000, 45000 }, 256 + /* first port in the ephemeral range (clamp from above) */ 257 + { 0, 40000, 40000 }, 258 + /* last port in the ephemeral range (clamp from below) */ 259 + { 49999, 0, 49999 }, 260 + }; 261 + const struct test *t; 262 + 263 + for (t = tests; t < tests + ARRAY_SIZE(tests); t++) { 264 + int fd, err, port; 265 + __u32 range; 266 + 267 + TH_LOG("lo %5hu, hi %5hu, expected %5hu", 268 + t->range_lo, t->range_hi, t->expected); 269 + 270 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 271 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 272 + 273 + range = pack_port_range(t->range_lo, t->range_hi); 274 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 275 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 276 + 277 + err = bind_to_loopback_any_port(fd); 278 + ASSERT_TRUE(!err) TH_LOG("bind failed"); 279 + 280 + port = get_sock_port(fd); 281 + ASSERT_EQ(port, t->expected) TH_LOG("unexpected local port"); 282 + 283 + err = close(fd); 284 + ASSERT_TRUE(!err) TH_LOG("close failed"); 285 + } 286 + } 287 + 288 + TEST_F(ip_local_port_range, exhaust_8_port_range) 289 + { 290 + __u8 port_set = 0; 291 + int i, fd, err; 292 + __u32 range; 293 + __u16 port; 294 + int fds[8]; 295 + 296 + for (i = 0; i < ARRAY_SIZE(fds); i++) { 297 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 298 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 299 + 300 + range = pack_port_range(40000, 40007); 301 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 302 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 303 + 304 + err = bind_to_loopback_any_port(fd); 305 + ASSERT_TRUE(!err) TH_LOG("bind failed"); 306 + 307 + port = get_sock_port(fd); 308 + ASSERT_GE(port, 40000) TH_LOG("expected port within sockopt range"); 309 + ASSERT_LE(port, 40007) TH_LOG("expected port within sockopt range"); 310 + 311 + port_set |= 1 << (port - 40000); 312 + fds[i] = fd; 313 + } 314 + 315 + /* Check that all every port from the test range is in use */ 316 + ASSERT_EQ(port_set, 0xff) TH_LOG("expected all ports to be busy"); 317 + 318 + /* Check that bind() fails because the whole range is busy */ 319 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 320 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 321 + 322 + range = pack_port_range(40000, 40007); 323 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 324 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 325 + 326 + err = bind_to_loopback_any_port(fd); 327 + ASSERT_TRUE(err) TH_LOG("expected bind to fail"); 328 + ASSERT_EQ(errno, EADDRINUSE); 329 + 330 + err = close(fd); 331 + ASSERT_TRUE(!err) TH_LOG("close failed"); 332 + 333 + for (i = 0; i < ARRAY_SIZE(fds); i++) { 334 + err = close(fds[i]); 335 + ASSERT_TRUE(!err) TH_LOG("close failed"); 336 + } 337 + } 338 + 339 + TEST_F(ip_local_port_range, late_bind) 340 + { 341 + union { 342 + struct sockaddr sa; 343 + struct sockaddr_in v4; 344 + struct sockaddr_in6 v6; 345 + } addr; 346 + socklen_t addr_len; 347 + const int one = 1; 348 + int fd, err; 349 + __u32 range; 350 + __u16 port; 351 + 352 + if (variant->so_protocol == IPPROTO_SCTP) 353 + SKIP(return, "SCTP doesn't support IP_BIND_ADDRESS_NO_PORT"); 354 + 355 + fd = socket(variant->so_domain, variant->so_type, 0); 356 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 357 + 358 + range = pack_port_range(40100, 40199); 359 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 360 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 361 + 362 + err = setsockopt(fd, SOL_IP, IP_BIND_ADDRESS_NO_PORT, &one, sizeof(one)); 363 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_BIND_ADDRESS_NO_PORT) failed"); 364 + 365 + err = bind_to_loopback_any_port(fd); 366 + ASSERT_TRUE(!err) TH_LOG("bind failed"); 367 + 368 + port = get_sock_port(fd); 369 + ASSERT_EQ(port, 0) TH_LOG("getsockname failed"); 370 + 371 + /* Invalid destination */ 372 + memset(&addr, 0, sizeof(addr)); 373 + switch (variant->so_domain) { 374 + case AF_INET: 375 + addr.v4.sin_family = AF_INET; 376 + addr.v4.sin_port = htons(0); 377 + addr.v4.sin_addr.s_addr = htonl(INADDR_ANY); 378 + addr_len = sizeof(addr.v4); 379 + break; 380 + case AF_INET6: 381 + addr.v6.sin6_family = AF_INET6; 382 + addr.v6.sin6_port = htons(0); 383 + addr.v6.sin6_addr = in6addr_any; 384 + addr_len = sizeof(addr.v6); 385 + break; 386 + default: 387 + ASSERT_TRUE(false) TH_LOG("unsupported socket domain"); 388 + } 389 + 390 + /* connect() doesn't need to succeed for late bind to happen */ 391 + connect(fd, &addr.sa, addr_len); 392 + 393 + port = get_sock_port(fd); 394 + ASSERT_GE(port, 40100); 395 + ASSERT_LE(port, 40199); 396 + 397 + err = close(fd); 398 + ASSERT_TRUE(!err) TH_LOG("close failed"); 399 + } 400 + 401 + TEST_F(ip_local_port_range, get_port_range) 402 + { 403 + __u16 lo, hi; 404 + __u32 range; 405 + int fd, err; 406 + 407 + fd = socket(variant->so_domain, variant->so_type, variant->so_protocol); 408 + ASSERT_GE(fd, 0) TH_LOG("socket failed"); 409 + 410 + /* Get range before it will be set */ 411 + err = get_ip_local_port_range(fd, &range); 412 + ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed"); 413 + 414 + unpack_port_range(range, &lo, &hi); 415 + ASSERT_EQ(lo, 0) TH_LOG("unexpected low port"); 416 + ASSERT_EQ(hi, 0) TH_LOG("unexpected high port"); 417 + 418 + range = pack_port_range(12345, 54321); 419 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 420 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 421 + 422 + /* Get range after it has been set */ 423 + err = get_ip_local_port_range(fd, &range); 424 + ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed"); 425 + 426 + unpack_port_range(range, &lo, &hi); 427 + ASSERT_EQ(lo, 12345) TH_LOG("unexpected low port"); 428 + ASSERT_EQ(hi, 54321) TH_LOG("unexpected high port"); 429 + 430 + /* Unset the port range */ 431 + range = pack_port_range(0, 0); 432 + err = setsockopt(fd, SOL_IP, IP_LOCAL_PORT_RANGE, &range, sizeof(range)); 433 + ASSERT_TRUE(!err) TH_LOG("setsockopt(IP_LOCAL_PORT_RANGE) failed"); 434 + 435 + /* Get range after it has been unset */ 436 + err = get_ip_local_port_range(fd, &range); 437 + ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed"); 438 + 439 + unpack_port_range(range, &lo, &hi); 440 + ASSERT_EQ(lo, 0) TH_LOG("unexpected low port"); 441 + ASSERT_EQ(hi, 0) TH_LOG("unexpected high port"); 442 + 443 + err = close(fd); 444 + ASSERT_TRUE(!err) TH_LOG("close failed"); 445 + } 446 + 447 + TEST_HARNESS_MAIN
+5
tools/testing/selftests/net/ip_local_port_range.sh
··· 1 + #!/bin/sh 2 + # SPDX-License-Identifier: GPL-2.0 3 + 4 + ./in_netns.sh \ 5 + sh -c 'sysctl -q -w net.ipv4.ip_local_port_range="40000 49999" && ./ip_local_port_range'