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