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 | #ifndef IPPROTO_MPTCP |
20 | #define IPPROTO_MPTCP 262 |
21 | #endif |
22 | |
23 | static __u32 pack_port_range(__u16 lo, __u16 hi) |
24 | { |
25 | return (hi << 16) | (lo << 0); |
26 | } |
27 | |
28 | static void unpack_port_range(__u32 range, __u16 *lo, __u16 *hi) |
29 | { |
30 | *lo = range & 0xffff; |
31 | *hi = range >> 16; |
32 | } |
33 | |
34 | static 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 | |
47 | static 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 | |
77 | static 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 | |
104 | static 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 | |
119 | FIXTURE(ip_local_port_range) {}; |
120 | |
121 | FIXTURE_SETUP(ip_local_port_range) |
122 | { |
123 | } |
124 | |
125 | FIXTURE_TEARDOWN(ip_local_port_range) |
126 | { |
127 | } |
128 | |
129 | FIXTURE_VARIANT(ip_local_port_range) { |
130 | int so_domain; |
131 | int so_type; |
132 | int so_protocol; |
133 | }; |
134 | |
135 | FIXTURE_VARIANT_ADD(ip_local_port_range, ip4_tcp) { |
136 | .so_domain = AF_INET, |
137 | .so_type = SOCK_STREAM, |
138 | .so_protocol = 0, |
139 | }; |
140 | |
141 | FIXTURE_VARIANT_ADD(ip_local_port_range, ip4_udp) { |
142 | .so_domain = AF_INET, |
143 | .so_type = SOCK_DGRAM, |
144 | .so_protocol = 0, |
145 | }; |
146 | |
147 | FIXTURE_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 | |
153 | FIXTURE_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 | |
159 | FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_tcp) { |
160 | .so_domain = AF_INET6, |
161 | .so_type = SOCK_STREAM, |
162 | .so_protocol = 0, |
163 | }; |
164 | |
165 | FIXTURE_VARIANT_ADD(ip_local_port_range, ip6_udp) { |
166 | .so_domain = AF_INET6, |
167 | .so_type = SOCK_DGRAM, |
168 | .so_protocol = 0, |
169 | }; |
170 | |
171 | FIXTURE_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 | |
177 | FIXTURE_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 | |
183 | TEST_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(lo: 40222, hi: 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(lo: 40333, hi: 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 | |
215 | TEST_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 check |
228 | * that the range wasn't clamped to a single port from |
229 | * 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(lo: t->range_lo, hi: 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 | |
263 | TEST_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(lo: t->range_lo, hi: 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 | |
304 | TEST_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(lo: 40000, hi: 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(lo: 40000, hi: 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 | |
355 | TEST_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; |
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(lo: 40100, hi: 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 | |
414 | XFAIL_ADD(ip_local_port_range, ip4_stcp, late_bind); |
415 | XFAIL_ADD(ip_local_port_range, ip6_stcp, late_bind); |
416 | |
417 | TEST_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: &range); |
428 | ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed" ); |
429 | |
430 | unpack_port_range(range, lo: &lo, hi: &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(lo: 12345, hi: 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: &range); |
440 | ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed" ); |
441 | |
442 | unpack_port_range(range, lo: &lo, hi: &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(lo: 0, hi: 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: &range); |
453 | ASSERT_TRUE(!err) TH_LOG("getsockopt(IP_LOCAL_PORT_RANGE) failed" ); |
454 | |
455 | unpack_port_range(range, lo: &lo, hi: &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 | |
463 | TEST_HARNESS_MAIN |
464 | |