xref: /linux/tools/testing/selftests/namespaces/siocgskns_test.c (revision 8ebfb9896c97ab609222460e705f425cb3f0aad0)
1 // SPDX-License-Identifier: GPL-2.0
2 #define _GNU_SOURCE
3 #include <errno.h>
4 #include <fcntl.h>
5 #include <limits.h>
6 #include <sched.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <sys/ioctl.h>
11 #include <sys/socket.h>
12 #include <sys/stat.h>
13 #include <sys/types.h>
14 #include <sys/wait.h>
15 #include <unistd.h>
16 #include <linux/if.h>
17 #include <linux/sockios.h>
18 #include <linux/nsfs.h>
19 #include <arpa/inet.h>
20 #include "../kselftest_harness.h"
21 #include "../filesystems/utils.h"
22 #include "wrappers.h"
23 
24 #ifndef SIOCGSKNS
25 #define SIOCGSKNS 0x894C
26 #endif
27 
28 #ifndef FD_NSFS_ROOT
29 #define FD_NSFS_ROOT -10003
30 #endif
31 
32 #ifndef FILEID_NSFS
33 #define FILEID_NSFS 0xf1
34 #endif
35 
36 /*
37  * Test basic SIOCGSKNS functionality.
38  * Create a socket and verify SIOCGSKNS returns the correct network namespace.
39  */
40 TEST(siocgskns_basic)
41 {
42 	int sock_fd, netns_fd, current_netns_fd;
43 	struct stat st1, st2;
44 
45 	/* Create a TCP socket */
46 	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
47 	ASSERT_GE(sock_fd, 0);
48 
49 	/* Use SIOCGSKNS to get network namespace */
50 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
51 	if (netns_fd < 0) {
52 		close(sock_fd);
53 		if (errno == ENOTTY || errno == EINVAL)
54 			SKIP(return, "SIOCGSKNS not supported");
55 		ASSERT_GE(netns_fd, 0);
56 	}
57 
58 	/* Get current network namespace */
59 	current_netns_fd = open("/proc/self/ns/net", O_RDONLY);
60 	ASSERT_GE(current_netns_fd, 0);
61 
62 	/* Verify they match */
63 	ASSERT_EQ(fstat(netns_fd, &st1), 0);
64 	ASSERT_EQ(fstat(current_netns_fd, &st2), 0);
65 	ASSERT_EQ(st1.st_ino, st2.st_ino);
66 
67 	close(sock_fd);
68 	close(netns_fd);
69 	close(current_netns_fd);
70 }
71 
72 /*
73  * Test that socket file descriptors keep network namespaces active.
74  * Create a network namespace, create a socket in it, then exit the namespace.
75  * The namespace should remain active while the socket FD is held.
76  */
77 TEST(siocgskns_keeps_netns_active)
78 {
79 	int sock_fd, netns_fd, test_fd;
80 	int ipc_sockets[2];
81 	pid_t pid;
82 	int status;
83 	struct stat st;
84 
85 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
86 
87 	pid = fork();
88 	ASSERT_GE(pid, 0);
89 
90 	if (pid == 0) {
91 		/* Child: create new netns and socket */
92 		close(ipc_sockets[0]);
93 
94 		if (unshare(CLONE_NEWNET) < 0) {
95 			TH_LOG("unshare(CLONE_NEWNET) failed: %s", strerror(errno));
96 			close(ipc_sockets[1]);
97 			exit(1);
98 		}
99 
100 		/* Create a socket in the new network namespace */
101 		sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
102 		if (sock_fd < 0) {
103 			TH_LOG("socket() failed: %s", strerror(errno));
104 			close(ipc_sockets[1]);
105 			exit(1);
106 		}
107 
108 		/* Send socket FD to parent via SCM_RIGHTS */
109 		struct msghdr msg = {0};
110 		struct iovec iov = {0};
111 		char buf[1] = {'X'};
112 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
113 
114 		iov.iov_base = buf;
115 		iov.iov_len = 1;
116 		msg.msg_iov = &iov;
117 		msg.msg_iovlen = 1;
118 		msg.msg_control = cmsg_buf;
119 		msg.msg_controllen = sizeof(cmsg_buf);
120 
121 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
122 		cmsg->cmsg_level = SOL_SOCKET;
123 		cmsg->cmsg_type = SCM_RIGHTS;
124 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
125 		memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
126 
127 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
128 			close(sock_fd);
129 			close(ipc_sockets[1]);
130 			exit(1);
131 		}
132 
133 		close(sock_fd);
134 		close(ipc_sockets[1]);
135 		exit(0);
136 	}
137 
138 	/* Parent: receive socket FD */
139 	close(ipc_sockets[1]);
140 
141 	struct msghdr msg = {0};
142 	struct iovec iov = {0};
143 	char buf[1];
144 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
145 
146 	iov.iov_base = buf;
147 	iov.iov_len = 1;
148 	msg.msg_iov = &iov;
149 	msg.msg_iovlen = 1;
150 	msg.msg_control = cmsg_buf;
151 	msg.msg_controllen = sizeof(cmsg_buf);
152 
153 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
154 	close(ipc_sockets[0]);
155 	ASSERT_EQ(n, 1);
156 
157 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
158 	ASSERT_NE(cmsg, NULL);
159 	ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);
160 
161 	memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));
162 
163 	/* Wait for child to exit */
164 	waitpid(pid, &status, 0);
165 	ASSERT_TRUE(WIFEXITED(status));
166 	ASSERT_EQ(WEXITSTATUS(status), 0);
167 
168 	/* Get network namespace from socket */
169 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
170 	if (netns_fd < 0) {
171 		close(sock_fd);
172 		if (errno == ENOTTY || errno == EINVAL)
173 			SKIP(return, "SIOCGSKNS not supported");
174 		ASSERT_GE(netns_fd, 0);
175 	}
176 
177 	ASSERT_EQ(fstat(netns_fd, &st), 0);
178 
179 	/*
180 	 * Namespace should still be active because socket FD keeps it alive.
181 	 * Try to access it via /proc/self/fd/<fd>.
182 	 */
183 	char path[64];
184 	snprintf(path, sizeof(path), "/proc/self/fd/%d", netns_fd);
185 	test_fd = open(path, O_RDONLY);
186 	ASSERT_GE(test_fd, 0);
187 	close(test_fd);
188 	close(netns_fd);
189 
190 	/* Close socket - namespace should become inactive */
191 	close(sock_fd);
192 
193 	/* Try SIOCGSKNS again - should fail since socket is closed */
194 	ASSERT_LT(ioctl(sock_fd, SIOCGSKNS), 0);
195 }
196 
197 /*
198  * Test SIOCGSKNS with different socket types (TCP, UDP, RAW).
199  */
200 TEST(siocgskns_socket_types)
201 {
202 	int sock_tcp, sock_udp, sock_raw;
203 	int netns_tcp, netns_udp, netns_raw;
204 	struct stat st_tcp, st_udp, st_raw;
205 
206 	/* TCP socket */
207 	sock_tcp = socket(AF_INET, SOCK_STREAM, 0);
208 	ASSERT_GE(sock_tcp, 0);
209 
210 	/* UDP socket */
211 	sock_udp = socket(AF_INET, SOCK_DGRAM, 0);
212 	ASSERT_GE(sock_udp, 0);
213 
214 	/* RAW socket (may require privileges) */
215 	sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
216 	if (sock_raw < 0 && (errno == EPERM || errno == EACCES)) {
217 		sock_raw = -1; /* Skip raw socket test */
218 	}
219 
220 	/* Test SIOCGSKNS on TCP */
221 	netns_tcp = ioctl(sock_tcp, SIOCGSKNS);
222 	if (netns_tcp < 0) {
223 		close(sock_tcp);
224 		close(sock_udp);
225 		if (sock_raw >= 0) close(sock_raw);
226 		if (errno == ENOTTY || errno == EINVAL)
227 			SKIP(return, "SIOCGSKNS not supported");
228 		ASSERT_GE(netns_tcp, 0);
229 	}
230 
231 	/* Test SIOCGSKNS on UDP */
232 	netns_udp = ioctl(sock_udp, SIOCGSKNS);
233 	ASSERT_GE(netns_udp, 0);
234 
235 	/* Test SIOCGSKNS on RAW (if available) */
236 	if (sock_raw >= 0) {
237 		netns_raw = ioctl(sock_raw, SIOCGSKNS);
238 		ASSERT_GE(netns_raw, 0);
239 	}
240 
241 	/* Verify all return the same network namespace */
242 	ASSERT_EQ(fstat(netns_tcp, &st_tcp), 0);
243 	ASSERT_EQ(fstat(netns_udp, &st_udp), 0);
244 	ASSERT_EQ(st_tcp.st_ino, st_udp.st_ino);
245 
246 	if (sock_raw >= 0) {
247 		ASSERT_EQ(fstat(netns_raw, &st_raw), 0);
248 		ASSERT_EQ(st_tcp.st_ino, st_raw.st_ino);
249 		close(netns_raw);
250 		close(sock_raw);
251 	}
252 
253 	close(netns_tcp);
254 	close(netns_udp);
255 	close(sock_tcp);
256 	close(sock_udp);
257 }
258 
259 /*
260  * Test SIOCGSKNS across setns.
261  * Create a socket in netns A, switch to netns B, verify SIOCGSKNS still
262  * returns netns A.
263  */
264 TEST(siocgskns_across_setns)
265 {
266 	int sock_fd, netns_a_fd, netns_b_fd, result_fd;
267 	struct stat st_a;
268 
269 	/* Get current netns (A) */
270 	netns_a_fd = open("/proc/self/ns/net", O_RDONLY);
271 	ASSERT_GE(netns_a_fd, 0);
272 	ASSERT_EQ(fstat(netns_a_fd, &st_a), 0);
273 
274 	/* Create socket in netns A */
275 	sock_fd = socket(AF_INET, SOCK_STREAM, 0);
276 	ASSERT_GE(sock_fd, 0);
277 
278 	/* Create new netns (B) */
279 	ASSERT_EQ(unshare(CLONE_NEWNET), 0);
280 
281 	netns_b_fd = open("/proc/self/ns/net", O_RDONLY);
282 	ASSERT_GE(netns_b_fd, 0);
283 
284 	/* Get netns from socket created in A */
285 	result_fd = ioctl(sock_fd, SIOCGSKNS);
286 	if (result_fd < 0) {
287 		close(sock_fd);
288 		setns(netns_a_fd, CLONE_NEWNET);
289 		close(netns_a_fd);
290 		close(netns_b_fd);
291 		if (errno == ENOTTY || errno == EINVAL)
292 			SKIP(return, "SIOCGSKNS not supported");
293 		ASSERT_GE(result_fd, 0);
294 	}
295 
296 	/* Verify it still points to netns A */
297 	struct stat st_result_stat;
298 	ASSERT_EQ(fstat(result_fd, &st_result_stat), 0);
299 	ASSERT_EQ(st_a.st_ino, st_result_stat.st_ino);
300 
301 	close(result_fd);
302 	close(sock_fd);
303 	close(netns_b_fd);
304 
305 	/* Restore original netns */
306 	ASSERT_EQ(setns(netns_a_fd, CLONE_NEWNET), 0);
307 	close(netns_a_fd);
308 }
309 
310 /*
311  * Test SIOCGSKNS fails on non-socket file descriptors.
312  */
313 TEST(siocgskns_non_socket)
314 {
315 	int fd;
316 	int pipefd[2];
317 
318 	/* Test on regular file */
319 	fd = open("/dev/null", O_RDONLY);
320 	ASSERT_GE(fd, 0);
321 
322 	ASSERT_LT(ioctl(fd, SIOCGSKNS), 0);
323 	ASSERT_TRUE(errno == ENOTTY || errno == EINVAL);
324 	close(fd);
325 
326 	/* Test on pipe */
327 	ASSERT_EQ(pipe(pipefd), 0);
328 
329 	ASSERT_LT(ioctl(pipefd[0], SIOCGSKNS), 0);
330 	ASSERT_TRUE(errno == ENOTTY || errno == EINVAL);
331 
332 	close(pipefd[0]);
333 	close(pipefd[1]);
334 }
335 
336 /*
337  * Test multiple sockets keep the same network namespace active.
338  * Create multiple sockets, verify closing some doesn't affect others.
339  */
340 TEST(siocgskns_multiple_sockets)
341 {
342 	int socks[5];
343 	int netns_fds[5];
344 	int i;
345 	struct stat st;
346 	ino_t netns_ino;
347 
348 	/* Create new network namespace */
349 	ASSERT_EQ(unshare(CLONE_NEWNET), 0);
350 
351 	/* Create multiple sockets */
352 	for (i = 0; i < 5; i++) {
353 		socks[i] = socket(AF_INET, SOCK_STREAM, 0);
354 		ASSERT_GE(socks[i], 0);
355 	}
356 
357 	/* Get netns from all sockets */
358 	for (i = 0; i < 5; i++) {
359 		netns_fds[i] = ioctl(socks[i], SIOCGSKNS);
360 		if (netns_fds[i] < 0) {
361 			int j;
362 			for (j = 0; j <= i; j++) {
363 				close(socks[j]);
364 				if (j < i && netns_fds[j] >= 0)
365 					close(netns_fds[j]);
366 			}
367 			if (errno == ENOTTY || errno == EINVAL)
368 				SKIP(return, "SIOCGSKNS not supported");
369 			ASSERT_GE(netns_fds[i], 0);
370 		}
371 	}
372 
373 	/* Verify all point to same netns */
374 	ASSERT_EQ(fstat(netns_fds[0], &st), 0);
375 	netns_ino = st.st_ino;
376 
377 	for (i = 1; i < 5; i++) {
378 		ASSERT_EQ(fstat(netns_fds[i], &st), 0);
379 		ASSERT_EQ(st.st_ino, netns_ino);
380 	}
381 
382 	/* Close some sockets */
383 	for (i = 0; i < 3; i++) {
384 		close(socks[i]);
385 	}
386 
387 	/* Remaining netns FDs should still be valid */
388 	for (i = 3; i < 5; i++) {
389 		char path[64];
390 		snprintf(path, sizeof(path), "/proc/self/fd/%d", netns_fds[i]);
391 		int test_fd = open(path, O_RDONLY);
392 		ASSERT_GE(test_fd, 0);
393 		close(test_fd);
394 	}
395 
396 	/* Cleanup */
397 	for (i = 0; i < 5; i++) {
398 		if (i >= 3)
399 			close(socks[i]);
400 		close(netns_fds[i]);
401 	}
402 }
403 
404 /*
405  * Test socket keeps netns active after creating process exits.
406  * Verify that as long as the socket FD exists, the namespace remains active.
407  */
408 TEST(siocgskns_netns_lifecycle)
409 {
410 	int sock_fd, netns_fd;
411 	int ipc_sockets[2];
412 	int syncpipe[2];
413 	pid_t pid;
414 	int status;
415 	char sync_byte;
416 	struct stat st;
417 	ino_t netns_ino;
418 
419 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
420 
421 	ASSERT_EQ(pipe(syncpipe), 0);
422 
423 	pid = fork();
424 	ASSERT_GE(pid, 0);
425 
426 	if (pid == 0) {
427 		/* Child */
428 		close(ipc_sockets[0]);
429 		close(syncpipe[1]);
430 
431 		if (unshare(CLONE_NEWNET) < 0) {
432 			close(ipc_sockets[1]);
433 			close(syncpipe[0]);
434 			exit(1);
435 		}
436 
437 		sock_fd = socket(AF_INET, SOCK_STREAM, 0);
438 		if (sock_fd < 0) {
439 			close(ipc_sockets[1]);
440 			close(syncpipe[0]);
441 			exit(1);
442 		}
443 
444 		/* Send socket to parent */
445 		struct msghdr msg = {0};
446 		struct iovec iov = {0};
447 		char buf[1] = {'X'};
448 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
449 
450 		iov.iov_base = buf;
451 		iov.iov_len = 1;
452 		msg.msg_iov = &iov;
453 		msg.msg_iovlen = 1;
454 		msg.msg_control = cmsg_buf;
455 		msg.msg_controllen = sizeof(cmsg_buf);
456 
457 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
458 		cmsg->cmsg_level = SOL_SOCKET;
459 		cmsg->cmsg_type = SCM_RIGHTS;
460 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
461 		memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
462 
463 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
464 			close(sock_fd);
465 			close(ipc_sockets[1]);
466 			close(syncpipe[0]);
467 			exit(1);
468 		}
469 
470 		close(sock_fd);
471 		close(ipc_sockets[1]);
472 
473 		/* Wait for parent signal */
474 		read(syncpipe[0], &sync_byte, 1);
475 		close(syncpipe[0]);
476 		exit(0);
477 	}
478 
479 	/* Parent */
480 	close(ipc_sockets[1]);
481 	close(syncpipe[0]);
482 
483 	/* Receive socket FD */
484 	struct msghdr msg = {0};
485 	struct iovec iov = {0};
486 	char buf[1];
487 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
488 
489 	iov.iov_base = buf;
490 	iov.iov_len = 1;
491 	msg.msg_iov = &iov;
492 	msg.msg_iovlen = 1;
493 	msg.msg_control = cmsg_buf;
494 	msg.msg_controllen = sizeof(cmsg_buf);
495 
496 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
497 	close(ipc_sockets[0]);
498 	ASSERT_EQ(n, 1);
499 
500 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
501 	ASSERT_NE(cmsg, NULL);
502 	memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));
503 
504 	/* Get netns from socket while child is alive */
505 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
506 	if (netns_fd < 0) {
507 		sync_byte = 'G';
508 		write(syncpipe[1], &sync_byte, 1);
509 		close(syncpipe[1]);
510 		close(sock_fd);
511 		waitpid(pid, NULL, 0);
512 		if (errno == ENOTTY || errno == EINVAL)
513 			SKIP(return, "SIOCGSKNS not supported");
514 		ASSERT_GE(netns_fd, 0);
515 	}
516 	ASSERT_EQ(fstat(netns_fd, &st), 0);
517 	netns_ino = st.st_ino;
518 
519 	/* Signal child to exit */
520 	sync_byte = 'G';
521 	write(syncpipe[1], &sync_byte, 1);
522 	close(syncpipe[1]);
523 
524 	waitpid(pid, &status, 0);
525 	ASSERT_TRUE(WIFEXITED(status));
526 
527 	/*
528 	 * Socket FD should still keep namespace active even after
529 	 * the creating process exited.
530 	 */
531 	int test_fd = ioctl(sock_fd, SIOCGSKNS);
532 	ASSERT_GE(test_fd, 0);
533 
534 	struct stat st_test;
535 	ASSERT_EQ(fstat(test_fd, &st_test), 0);
536 	ASSERT_EQ(st_test.st_ino, netns_ino);
537 
538 	close(test_fd);
539 	close(netns_fd);
540 
541 	/* Close socket - namespace should become inactive */
542 	close(sock_fd);
543 }
544 
545 /*
546  * Test IPv6 sockets also work with SIOCGSKNS.
547  */
548 TEST(siocgskns_ipv6)
549 {
550 	int sock_fd, netns_fd, current_netns_fd;
551 	struct stat st1, st2;
552 
553 	/* Create an IPv6 TCP socket */
554 	sock_fd = socket(AF_INET6, SOCK_STREAM, 0);
555 	ASSERT_GE(sock_fd, 0);
556 
557 	/* Use SIOCGSKNS */
558 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
559 	if (netns_fd < 0) {
560 		close(sock_fd);
561 		if (errno == ENOTTY || errno == EINVAL)
562 			SKIP(return, "SIOCGSKNS not supported");
563 		ASSERT_GE(netns_fd, 0);
564 	}
565 
566 	/* Verify it matches current namespace */
567 	current_netns_fd = open("/proc/self/ns/net", O_RDONLY);
568 	ASSERT_GE(current_netns_fd, 0);
569 
570 	ASSERT_EQ(fstat(netns_fd, &st1), 0);
571 	ASSERT_EQ(fstat(current_netns_fd, &st2), 0);
572 	ASSERT_EQ(st1.st_ino, st2.st_ino);
573 
574 	close(sock_fd);
575 	close(netns_fd);
576 	close(current_netns_fd);
577 }
578 
579 /*
580  * Test that socket-kept netns appears in listns() output.
581  * Verify that a network namespace kept alive by a socket FD appears in
582  * listns() output even after the creating process exits, and that it
583  * disappears when the socket is closed.
584  */
585 TEST(siocgskns_listns_visibility)
586 {
587 	int sock_fd, netns_fd, owner_fd;
588 	int ipc_sockets[2];
589 	pid_t pid;
590 	int status;
591 	__u64 netns_id, owner_id;
592 	struct ns_id_req req = {
593 		.size = sizeof(req),
594 		.spare = 0,
595 		.ns_id = 0,
596 		.ns_type = CLONE_NEWNET,
597 		.spare2 = 0,
598 		.user_ns_id = 0,
599 	};
600 	__u64 ns_ids[256];
601 	int ret, i;
602 	bool found_netns = false;
603 
604 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
605 
606 	pid = fork();
607 	ASSERT_GE(pid, 0);
608 
609 	if (pid == 0) {
610 		/* Child: create new netns and socket */
611 		close(ipc_sockets[0]);
612 
613 		if (unshare(CLONE_NEWNET) < 0) {
614 			close(ipc_sockets[1]);
615 			exit(1);
616 		}
617 
618 		sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
619 		if (sock_fd < 0) {
620 			close(ipc_sockets[1]);
621 			exit(1);
622 		}
623 
624 		/* Send socket FD to parent via SCM_RIGHTS */
625 		struct msghdr msg = {0};
626 		struct iovec iov = {0};
627 		char buf[1] = {'X'};
628 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
629 
630 		iov.iov_base = buf;
631 		iov.iov_len = 1;
632 		msg.msg_iov = &iov;
633 		msg.msg_iovlen = 1;
634 		msg.msg_control = cmsg_buf;
635 		msg.msg_controllen = sizeof(cmsg_buf);
636 
637 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
638 		cmsg->cmsg_level = SOL_SOCKET;
639 		cmsg->cmsg_type = SCM_RIGHTS;
640 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
641 		memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
642 
643 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
644 			close(sock_fd);
645 			close(ipc_sockets[1]);
646 			exit(1);
647 		}
648 
649 		close(sock_fd);
650 		close(ipc_sockets[1]);
651 		exit(0);
652 	}
653 
654 	/* Parent: receive socket FD */
655 	close(ipc_sockets[1]);
656 
657 	struct msghdr msg = {0};
658 	struct iovec iov = {0};
659 	char buf[1];
660 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
661 
662 	iov.iov_base = buf;
663 	iov.iov_len = 1;
664 	msg.msg_iov = &iov;
665 	msg.msg_iovlen = 1;
666 	msg.msg_control = cmsg_buf;
667 	msg.msg_controllen = sizeof(cmsg_buf);
668 
669 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
670 	close(ipc_sockets[0]);
671 	ASSERT_EQ(n, 1);
672 
673 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
674 	ASSERT_NE(cmsg, NULL);
675 	memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));
676 
677 	/* Wait for child to exit */
678 	waitpid(pid, &status, 0);
679 	ASSERT_TRUE(WIFEXITED(status));
680 	ASSERT_EQ(WEXITSTATUS(status), 0);
681 
682 	/* Get network namespace from socket */
683 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
684 	if (netns_fd < 0) {
685 		close(sock_fd);
686 		if (errno == ENOTTY || errno == EINVAL)
687 			SKIP(return, "SIOCGSKNS not supported");
688 		ASSERT_GE(netns_fd, 0);
689 	}
690 
691 	/* Get namespace ID */
692 	ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
693 	if (ret < 0) {
694 		close(sock_fd);
695 		close(netns_fd);
696 		if (errno == ENOTTY || errno == EINVAL)
697 			SKIP(return, "NS_GET_ID not supported");
698 		ASSERT_EQ(ret, 0);
699 	}
700 
701 	/* Get owner user namespace */
702 	owner_fd = ioctl(netns_fd, NS_GET_USERNS);
703 	if (owner_fd < 0) {
704 		close(sock_fd);
705 		close(netns_fd);
706 		if (errno == ENOTTY || errno == EINVAL)
707 			SKIP(return, "NS_GET_USERNS not supported");
708 		ASSERT_GE(owner_fd, 0);
709 	}
710 
711 	/* Get owner namespace ID */
712 	ret = ioctl(owner_fd, NS_GET_ID, &owner_id);
713 	if (ret < 0) {
714 		close(owner_fd);
715 		close(sock_fd);
716 		close(netns_fd);
717 		ASSERT_EQ(ret, 0);
718 	}
719 	close(owner_fd);
720 
721 	/* Namespace should appear in listns() output */
722 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
723 	if (ret < 0) {
724 		close(sock_fd);
725 		close(netns_fd);
726 		if (errno == ENOSYS)
727 			SKIP(return, "listns() not supported");
728 		TH_LOG("listns failed: %s", strerror(errno));
729 		ASSERT_GE(ret, 0);
730 	}
731 
732 	/* Search for our network namespace in the list */
733 	for (i = 0; i < ret; i++) {
734 		if (ns_ids[i] == netns_id) {
735 			found_netns = true;
736 			break;
737 		}
738 	}
739 
740 	ASSERT_TRUE(found_netns);
741 	TH_LOG("Found netns %llu in listns() output (kept alive by socket)", netns_id);
742 
743 	/* Now verify with owner filtering */
744 	req.user_ns_id = owner_id;
745 	found_netns = false;
746 
747 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
748 	ASSERT_GE(ret, 0);
749 
750 	for (i = 0; i < ret; i++) {
751 		if (ns_ids[i] == netns_id) {
752 			found_netns = true;
753 			break;
754 		}
755 	}
756 
757 	ASSERT_TRUE(found_netns);
758 	TH_LOG("Found netns %llu owned by userns %llu", netns_id, owner_id);
759 
760 	/* Close socket - namespace should become inactive and disappear from listns() */
761 	close(sock_fd);
762 	close(netns_fd);
763 
764 	/* Verify it's no longer in listns() output */
765 	req.user_ns_id = 0;
766 	found_netns = false;
767 
768 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
769 	ASSERT_GE(ret, 0);
770 
771 	for (i = 0; i < ret; i++) {
772 		if (ns_ids[i] == netns_id) {
773 			found_netns = true;
774 			break;
775 		}
776 	}
777 
778 	ASSERT_FALSE(found_netns);
779 	TH_LOG("Netns %llu correctly disappeared from listns() after socket closed", netns_id);
780 }
781 
782 /*
783  * Test that socket-kept netns can be reopened via file handle.
784  * Verify that a network namespace kept alive by a socket FD can be
785  * reopened using file handles even after the creating process exits.
786  */
787 TEST(siocgskns_file_handle)
788 {
789 	int sock_fd, netns_fd, reopened_fd;
790 	int ipc_sockets[2];
791 	pid_t pid;
792 	int status;
793 	struct stat st1, st2;
794 	ino_t netns_ino;
795 	__u64 netns_id;
796 	struct file_handle *handle;
797 	struct nsfs_file_handle *nsfs_fh;
798 	int ret;
799 
800 	/* Allocate file_handle structure for nsfs */
801 	handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
802 	ASSERT_NE(handle, NULL);
803 	handle->handle_bytes = sizeof(struct nsfs_file_handle);
804 	handle->handle_type = FILEID_NSFS;
805 
806 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
807 
808 	pid = fork();
809 	ASSERT_GE(pid, 0);
810 
811 	if (pid == 0) {
812 		/* Child: create new netns and socket */
813 		close(ipc_sockets[0]);
814 
815 		if (unshare(CLONE_NEWNET) < 0) {
816 			close(ipc_sockets[1]);
817 			exit(1);
818 		}
819 
820 		sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
821 		if (sock_fd < 0) {
822 			close(ipc_sockets[1]);
823 			exit(1);
824 		}
825 
826 		/* Send socket FD to parent via SCM_RIGHTS */
827 		struct msghdr msg = {0};
828 		struct iovec iov = {0};
829 		char buf[1] = {'X'};
830 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
831 
832 		iov.iov_base = buf;
833 		iov.iov_len = 1;
834 		msg.msg_iov = &iov;
835 		msg.msg_iovlen = 1;
836 		msg.msg_control = cmsg_buf;
837 		msg.msg_controllen = sizeof(cmsg_buf);
838 
839 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
840 		cmsg->cmsg_level = SOL_SOCKET;
841 		cmsg->cmsg_type = SCM_RIGHTS;
842 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
843 		memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
844 
845 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
846 			close(sock_fd);
847 			close(ipc_sockets[1]);
848 			exit(1);
849 		}
850 
851 		close(sock_fd);
852 		close(ipc_sockets[1]);
853 		exit(0);
854 	}
855 
856 	/* Parent: receive socket FD */
857 	close(ipc_sockets[1]);
858 
859 	struct msghdr msg = {0};
860 	struct iovec iov = {0};
861 	char buf[1];
862 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
863 
864 	iov.iov_base = buf;
865 	iov.iov_len = 1;
866 	msg.msg_iov = &iov;
867 	msg.msg_iovlen = 1;
868 	msg.msg_control = cmsg_buf;
869 	msg.msg_controllen = sizeof(cmsg_buf);
870 
871 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
872 	close(ipc_sockets[0]);
873 	ASSERT_EQ(n, 1);
874 
875 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
876 	ASSERT_NE(cmsg, NULL);
877 	memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));
878 
879 	/* Wait for child to exit */
880 	waitpid(pid, &status, 0);
881 	ASSERT_TRUE(WIFEXITED(status));
882 	ASSERT_EQ(WEXITSTATUS(status), 0);
883 
884 	/* Get network namespace from socket */
885 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
886 	if (netns_fd < 0) {
887 		free(handle);
888 		close(sock_fd);
889 		if (errno == ENOTTY || errno == EINVAL)
890 			SKIP(return, "SIOCGSKNS not supported");
891 		ASSERT_GE(netns_fd, 0);
892 	}
893 
894 	ASSERT_EQ(fstat(netns_fd, &st1), 0);
895 	netns_ino = st1.st_ino;
896 
897 	/* Get namespace ID */
898 	ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
899 	if (ret < 0) {
900 		free(handle);
901 		close(sock_fd);
902 		close(netns_fd);
903 		if (errno == ENOTTY || errno == EINVAL)
904 			SKIP(return, "NS_GET_ID not supported");
905 		ASSERT_EQ(ret, 0);
906 	}
907 
908 	/* Construct file handle from namespace ID */
909 	nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
910 	nsfs_fh->ns_id = netns_id;
911 	nsfs_fh->ns_type = 0;  /* Type field not needed for reopening */
912 	nsfs_fh->ns_inum = 0;  /* Inum field not needed for reopening */
913 
914 	TH_LOG("Constructed file handle for netns %lu (id=%llu)", netns_ino, netns_id);
915 
916 	/* Reopen namespace using file handle (while socket still keeps it alive) */
917 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
918 	if (reopened_fd < 0) {
919 		free(handle);
920 		close(sock_fd);
921 		if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
922 			SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
923 		TH_LOG("open_by_handle_at failed: %s", strerror(errno));
924 		ASSERT_GE(reopened_fd, 0);
925 	}
926 
927 	/* Verify it's the same namespace */
928 	ASSERT_EQ(fstat(reopened_fd, &st2), 0);
929 	ASSERT_EQ(st1.st_ino, st2.st_ino);
930 	ASSERT_EQ(st1.st_dev, st2.st_dev);
931 
932 	TH_LOG("Successfully reopened netns %lu via file handle", netns_ino);
933 
934 	close(reopened_fd);
935 
936 	/* Close the netns FD */
937 	close(netns_fd);
938 
939 	/* Try to reopen via file handle - should fail since namespace is now inactive */
940 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
941 	ASSERT_LT(reopened_fd, 0);
942 	TH_LOG("Correctly failed to reopen inactive netns: %s", strerror(errno));
943 
944 	/* Get network namespace from socket */
945 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
946 	if (netns_fd < 0) {
947 		free(handle);
948 		close(sock_fd);
949 		if (errno == ENOTTY || errno == EINVAL)
950 			SKIP(return, "SIOCGSKNS not supported");
951 		ASSERT_GE(netns_fd, 0);
952 	}
953 
954 	/* Reopen namespace using file handle (while socket still keeps it alive) */
955 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
956 	if (reopened_fd < 0) {
957 		free(handle);
958 		close(sock_fd);
959 		if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
960 			SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
961 		TH_LOG("open_by_handle_at failed: %s", strerror(errno));
962 		ASSERT_GE(reopened_fd, 0);
963 	}
964 
965 	/* Verify it's the same namespace */
966 	ASSERT_EQ(fstat(reopened_fd, &st2), 0);
967 	ASSERT_EQ(st1.st_ino, st2.st_ino);
968 	ASSERT_EQ(st1.st_dev, st2.st_dev);
969 
970 	TH_LOG("Successfully reopened netns %lu via file handle", netns_ino);
971 
972 	/* Close socket - namespace should become inactive */
973 	close(sock_fd);
974 	free(handle);
975 }
976 
977 /*
978  * Test combined listns() and file handle operations with socket-kept netns.
979  * Create a netns, keep it alive with a socket, verify it appears in listns(),
980  * then reopen it via file handle obtained from listns() entry.
981  */
982 TEST(siocgskns_listns_and_file_handle)
983 {
984 	int sock_fd, netns_fd, userns_fd, reopened_fd;
985 	int ipc_sockets[2];
986 	pid_t pid;
987 	int status;
988 	struct stat st;
989 	ino_t netns_ino;
990 	__u64 netns_id, userns_id;
991 	struct ns_id_req req = {
992 		.size = sizeof(req),
993 		.spare = 0,
994 		.ns_id = 0,
995 		.ns_type = CLONE_NEWNET | CLONE_NEWUSER,
996 		.spare2 = 0,
997 		.user_ns_id = 0,
998 	};
999 	__u64 ns_ids[256];
1000 	int ret, i;
1001 	bool found_netns = false, found_userns = false;
1002 	struct file_handle *handle;
1003 	struct nsfs_file_handle *nsfs_fh;
1004 
1005 	/* Allocate file_handle structure for nsfs */
1006 	handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
1007 	ASSERT_NE(handle, NULL);
1008 	handle->handle_bytes = sizeof(struct nsfs_file_handle);
1009 	handle->handle_type = FILEID_NSFS;
1010 
1011 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
1012 
1013 	pid = fork();
1014 	ASSERT_GE(pid, 0);
1015 
1016 	if (pid == 0) {
1017 		/* Child: create new userns and netns with socket */
1018 		close(ipc_sockets[0]);
1019 
1020 		if (setup_userns() < 0) {
1021 			close(ipc_sockets[1]);
1022 			exit(1);
1023 		}
1024 
1025 		if (unshare(CLONE_NEWNET) < 0) {
1026 			close(ipc_sockets[1]);
1027 			exit(1);
1028 		}
1029 
1030 		sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
1031 		if (sock_fd < 0) {
1032 			close(ipc_sockets[1]);
1033 			exit(1);
1034 		}
1035 
1036 		/* Send socket FD to parent via SCM_RIGHTS */
1037 		struct msghdr msg = {0};
1038 		struct iovec iov = {0};
1039 		char buf[1] = {'X'};
1040 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
1041 
1042 		iov.iov_base = buf;
1043 		iov.iov_len = 1;
1044 		msg.msg_iov = &iov;
1045 		msg.msg_iovlen = 1;
1046 		msg.msg_control = cmsg_buf;
1047 		msg.msg_controllen = sizeof(cmsg_buf);
1048 
1049 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1050 		cmsg->cmsg_level = SOL_SOCKET;
1051 		cmsg->cmsg_type = SCM_RIGHTS;
1052 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
1053 		memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
1054 
1055 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
1056 			close(sock_fd);
1057 			close(ipc_sockets[1]);
1058 			exit(1);
1059 		}
1060 
1061 		close(sock_fd);
1062 		close(ipc_sockets[1]);
1063 		exit(0);
1064 	}
1065 
1066 	/* Parent: receive socket FD */
1067 	close(ipc_sockets[1]);
1068 
1069 	struct msghdr msg = {0};
1070 	struct iovec iov = {0};
1071 	char buf[1];
1072 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
1073 
1074 	iov.iov_base = buf;
1075 	iov.iov_len = 1;
1076 	msg.msg_iov = &iov;
1077 	msg.msg_iovlen = 1;
1078 	msg.msg_control = cmsg_buf;
1079 	msg.msg_controllen = sizeof(cmsg_buf);
1080 
1081 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
1082 	close(ipc_sockets[0]);
1083 	ASSERT_EQ(n, 1);
1084 
1085 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1086 	ASSERT_NE(cmsg, NULL);
1087 	memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));
1088 
1089 	/* Wait for child to exit */
1090 	waitpid(pid, &status, 0);
1091 	ASSERT_TRUE(WIFEXITED(status));
1092 	ASSERT_EQ(WEXITSTATUS(status), 0);
1093 
1094 	/* Get network namespace from socket */
1095 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
1096 	if (netns_fd < 0) {
1097 		free(handle);
1098 		close(sock_fd);
1099 		if (errno == ENOTTY || errno == EINVAL)
1100 			SKIP(return, "SIOCGSKNS not supported");
1101 		ASSERT_GE(netns_fd, 0);
1102 	}
1103 
1104 	ASSERT_EQ(fstat(netns_fd, &st), 0);
1105 	netns_ino = st.st_ino;
1106 
1107 	/* Get namespace ID */
1108 	ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
1109 	if (ret < 0) {
1110 		free(handle);
1111 		close(sock_fd);
1112 		close(netns_fd);
1113 		if (errno == ENOTTY || errno == EINVAL)
1114 			SKIP(return, "NS_GET_ID not supported");
1115 		ASSERT_EQ(ret, 0);
1116 	}
1117 
1118 	/* Get owner user namespace */
1119 	userns_fd = ioctl(netns_fd, NS_GET_USERNS);
1120 	if (userns_fd < 0) {
1121 		free(handle);
1122 		close(sock_fd);
1123 		close(netns_fd);
1124 		if (errno == ENOTTY || errno == EINVAL)
1125 			SKIP(return, "NS_GET_USERNS not supported");
1126 		ASSERT_GE(userns_fd, 0);
1127 	}
1128 
1129 	/* Get owner namespace ID */
1130 	ret = ioctl(userns_fd, NS_GET_ID, &userns_id);
1131 	if (ret < 0) {
1132 		close(userns_fd);
1133 		free(handle);
1134 		close(sock_fd);
1135 		close(netns_fd);
1136 		ASSERT_EQ(ret, 0);
1137 	}
1138 	close(userns_fd);
1139 
1140 	TH_LOG("Testing netns %lu (id=%llu) owned by userns id=%llu", netns_ino, netns_id, userns_id);
1141 
1142 	/* Verify namespace appears in listns() */
1143 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1144 	if (ret < 0) {
1145 		free(handle);
1146 		close(sock_fd);
1147 		close(netns_fd);
1148 		if (errno == ENOSYS)
1149 			SKIP(return, "listns() not supported");
1150 		TH_LOG("listns failed: %s", strerror(errno));
1151 		ASSERT_GE(ret, 0);
1152 	}
1153 
1154 	found_netns = false;
1155 	found_userns = false;
1156 	for (i = 0; i < ret; i++) {
1157 		if (ns_ids[i] == netns_id)
1158 			found_netns = true;
1159 		if (ns_ids[i] == userns_id)
1160 			found_userns = true;
1161 	}
1162 	ASSERT_TRUE(found_netns);
1163 	ASSERT_TRUE(found_userns);
1164 	TH_LOG("Found netns %llu in listns() output", netns_id);
1165 
1166 	/* Construct file handle from namespace ID */
1167 	nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
1168 	nsfs_fh->ns_id = netns_id;
1169 	nsfs_fh->ns_type = 0;
1170 	nsfs_fh->ns_inum = 0;
1171 
1172 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
1173 	if (reopened_fd < 0) {
1174 		free(handle);
1175 		close(sock_fd);
1176 		if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
1177 			SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
1178 		TH_LOG("open_by_handle_at failed: %s", strerror(errno));
1179 		ASSERT_GE(reopened_fd, 0);
1180 	}
1181 
1182 	struct stat reopened_st;
1183 	ASSERT_EQ(fstat(reopened_fd, &reopened_st), 0);
1184 	ASSERT_EQ(reopened_st.st_ino, netns_ino);
1185 
1186 	TH_LOG("Successfully reopened netns %lu via file handle (socket-kept)", netns_ino);
1187 
1188 	close(reopened_fd);
1189 	close(netns_fd);
1190 
1191 	/* Try to reopen via file handle - should fail since namespace is now inactive */
1192 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
1193 	ASSERT_LT(reopened_fd, 0);
1194 	TH_LOG("Correctly failed to reopen inactive netns: %s", strerror(errno));
1195 
1196 	/* Get network namespace from socket */
1197 	netns_fd = ioctl(sock_fd, SIOCGSKNS);
1198 	if (netns_fd < 0) {
1199 		free(handle);
1200 		close(sock_fd);
1201 		if (errno == ENOTTY || errno == EINVAL)
1202 			SKIP(return, "SIOCGSKNS not supported");
1203 		ASSERT_GE(netns_fd, 0);
1204 	}
1205 
1206 	/* Verify namespace appears in listns() */
1207 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1208 	if (ret < 0) {
1209 		free(handle);
1210 		close(sock_fd);
1211 		close(netns_fd);
1212 		if (errno == ENOSYS)
1213 			SKIP(return, "listns() not supported");
1214 		TH_LOG("listns failed: %s", strerror(errno));
1215 		ASSERT_GE(ret, 0);
1216 	}
1217 
1218 	found_netns = false;
1219 	found_userns = false;
1220 	for (i = 0; i < ret; i++) {
1221 		if (ns_ids[i] == netns_id)
1222 			found_netns = true;
1223 		if (ns_ids[i] == userns_id)
1224 			found_userns = true;
1225 	}
1226 	ASSERT_TRUE(found_netns);
1227 	ASSERT_TRUE(found_userns);
1228 	TH_LOG("Found netns %llu in listns() output", netns_id);
1229 
1230 	close(netns_fd);
1231 
1232 	/* Verify namespace appears in listns() */
1233 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1234 	if (ret < 0) {
1235 		free(handle);
1236 		close(sock_fd);
1237 		close(netns_fd);
1238 		if (errno == ENOSYS)
1239 			SKIP(return, "listns() not supported");
1240 		TH_LOG("listns failed: %s", strerror(errno));
1241 		ASSERT_GE(ret, 0);
1242 	}
1243 
1244 	found_netns = false;
1245 	found_userns = false;
1246 	for (i = 0; i < ret; i++) {
1247 		if (ns_ids[i] == netns_id)
1248 			found_netns = true;
1249 		if (ns_ids[i] == userns_id)
1250 			found_userns = true;
1251 	}
1252 	ASSERT_FALSE(found_netns);
1253 	ASSERT_FALSE(found_userns);
1254 	TH_LOG("Netns %llu correctly disappeared from listns() after socket closed", netns_id);
1255 
1256 	close(sock_fd);
1257 	free(handle);
1258 }
1259 
1260 /*
1261  * Test multi-level namespace resurrection across three user namespace levels.
1262  *
1263  * This test creates a complex namespace hierarchy with three levels of user
1264  * namespaces and a network namespace at the deepest level. It verifies that
1265  * the resurrection semantics work correctly when SIOCGSKNS is called on a
1266  * socket from an inactive namespace tree, and that listns() and
1267  * open_by_handle_at() correctly respect visibility rules.
1268  *
1269  * Hierarchy after child processes exit (all with 0 active refcount):
1270  *
1271  *          net_L3A (0)                <- Level 3 network namespace
1272  *              |
1273  *              +
1274  *          userns_L3 (0)              <- Level 3 user namespace
1275  *              |
1276  *              +
1277  *          userns_L2 (0)              <- Level 2 user namespace
1278  *              |
1279  *              +
1280  *          userns_L1 (0)              <- Level 1 user namespace
1281  *              |
1282  *              x
1283  *          init_user_ns
1284  *
1285  * The test verifies:
1286  * 1. SIOCGSKNS on a socket from inactive net_L3A resurrects the entire chain
1287  * 2. After resurrection, all namespaces are visible in listns()
1288  * 3. Resurrected namespaces can be reopened via file handles
1289  * 4. Closing the netns FD cascades down: the entire ownership chain
1290  *    (userns_L3 -> userns_L2 -> userns_L1) becomes inactive again
1291  * 5. Inactive namespaces disappear from listns() and cannot be reopened
1292  * 6. Calling SIOCGSKNS again on the same socket resurrects the tree again
1293  * 7. After second resurrection, namespaces are visible and can be reopened
1294  */
1295 TEST(siocgskns_multilevel_resurrection)
1296 {
1297 	int ipc_sockets[2];
1298 	pid_t pid_l1, pid_l2, pid_l3;
1299 	int status;
1300 
1301 	/* Namespace file descriptors to be received from child */
1302 	int sock_L3A_fd = -1;
1303 	int netns_L3A_fd = -1;
1304 	__u64 netns_L3A_id;
1305 	__u64 userns_L1_id, userns_L2_id, userns_L3_id;
1306 
1307 	/* For listns() and file handle testing */
1308 	struct ns_id_req req = {
1309 		.size = sizeof(req),
1310 		.spare = 0,
1311 		.ns_id = 0,
1312 		.ns_type = CLONE_NEWNET | CLONE_NEWUSER,
1313 		.spare2 = 0,
1314 		.user_ns_id = 0,
1315 	};
1316 	__u64 ns_ids[256];
1317 	int ret, i;
1318 	struct file_handle *handle;
1319 	struct nsfs_file_handle *nsfs_fh;
1320 	int reopened_fd;
1321 
1322 	/* Allocate file handle for testing */
1323 	handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
1324 	ASSERT_NE(handle, NULL);
1325 	handle->handle_bytes = sizeof(struct nsfs_file_handle);
1326 	handle->handle_type = FILEID_NSFS;
1327 
1328 	EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);
1329 
1330 	/*
1331 	 * Fork level 1 child that creates userns_L1
1332 	 */
1333 	pid_l1 = fork();
1334 	ASSERT_GE(pid_l1, 0);
1335 
1336 	if (pid_l1 == 0) {
1337 		/* Level 1 child */
1338 		int ipc_L2[2];
1339 		close(ipc_sockets[0]);
1340 
1341 		/* Create userns_L1 */
1342 		if (setup_userns() < 0) {
1343 			close(ipc_sockets[1]);
1344 			exit(1);
1345 		}
1346 
1347 		/* Create socketpair for communicating with L2 child */
1348 		if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_L2) < 0) {
1349 			close(ipc_sockets[1]);
1350 			exit(1);
1351 		}
1352 
1353 		/*
1354 		 * Fork level 2 child that creates userns_L2
1355 		 */
1356 		pid_l2 = fork();
1357 		if (pid_l2 < 0) {
1358 			close(ipc_sockets[1]);
1359 			close(ipc_L2[0]);
1360 			close(ipc_L2[1]);
1361 			exit(1);
1362 		}
1363 
1364 		if (pid_l2 == 0) {
1365 			/* Level 2 child */
1366 			int ipc_L3[2];
1367 			close(ipc_L2[0]);
1368 
1369 			/* Create userns_L2 (nested inside userns_L1) */
1370 			if (setup_userns() < 0) {
1371 				close(ipc_L2[1]);
1372 				exit(1);
1373 			}
1374 
1375 			/* Create socketpair for communicating with L3 child */
1376 			if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_L3) < 0) {
1377 				close(ipc_L2[1]);
1378 				exit(1);
1379 			}
1380 
1381 			/*
1382 			 * Fork level 3 child that creates userns_L3 and network namespaces
1383 			 */
1384 			pid_l3 = fork();
1385 			if (pid_l3 < 0) {
1386 				close(ipc_L2[1]);
1387 				close(ipc_L3[0]);
1388 				close(ipc_L3[1]);
1389 				exit(1);
1390 			}
1391 
1392 			if (pid_l3 == 0) {
1393 				/* Level 3 child - the deepest level */
1394 				int sock_fd;
1395 				close(ipc_L3[0]);
1396 
1397 				/* Create userns_L3 (nested inside userns_L2) */
1398 				if (setup_userns() < 0) {
1399 					close(ipc_L3[1]);
1400 					exit(1);
1401 				}
1402 
1403 				/* Create network namespace at level 3 */
1404 				if (unshare(CLONE_NEWNET) < 0) {
1405 					close(ipc_L3[1]);
1406 					exit(1);
1407 				}
1408 
1409 				/* Create socket in net_L3A */
1410 				sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
1411 				if (sock_fd < 0) {
1412 					close(ipc_L3[1]);
1413 					exit(1);
1414 				}
1415 
1416 				/* Send socket FD to L2 parent */
1417 				struct msghdr msg = {0};
1418 				struct iovec iov = {0};
1419 				char buf[1] = {'X'};
1420 				char cmsg_buf[CMSG_SPACE(sizeof(int))];
1421 
1422 				iov.iov_base = buf;
1423 				iov.iov_len = 1;
1424 				msg.msg_iov = &iov;
1425 				msg.msg_iovlen = 1;
1426 				msg.msg_control = cmsg_buf;
1427 				msg.msg_controllen = sizeof(cmsg_buf);
1428 
1429 				struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1430 				cmsg->cmsg_level = SOL_SOCKET;
1431 				cmsg->cmsg_type = SCM_RIGHTS;
1432 				cmsg->cmsg_len = CMSG_LEN(sizeof(int));
1433 				memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));
1434 
1435 				if (sendmsg(ipc_L3[1], &msg, 0) < 0) {
1436 					close(sock_fd);
1437 					close(ipc_L3[1]);
1438 					exit(1);
1439 				}
1440 
1441 				close(sock_fd);
1442 				close(ipc_L3[1]);
1443 				exit(0);
1444 			}
1445 
1446 			/* Level 2 child - receive from L3 and forward to L1 */
1447 			close(ipc_L3[1]);
1448 
1449 			struct msghdr msg = {0};
1450 			struct iovec iov = {0};
1451 			char buf[1];
1452 			char cmsg_buf[CMSG_SPACE(sizeof(int))];
1453 			int received_fd;
1454 
1455 			iov.iov_base = buf;
1456 			iov.iov_len = 1;
1457 			msg.msg_iov = &iov;
1458 			msg.msg_iovlen = 1;
1459 			msg.msg_control = cmsg_buf;
1460 			msg.msg_controllen = sizeof(cmsg_buf);
1461 
1462 			ssize_t n = recvmsg(ipc_L3[0], &msg, 0);
1463 			close(ipc_L3[0]);
1464 
1465 			if (n != 1) {
1466 				close(ipc_L2[1]);
1467 				waitpid(pid_l3, NULL, 0);
1468 				exit(1);
1469 			}
1470 
1471 			struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1472 			if (!cmsg) {
1473 				close(ipc_L2[1]);
1474 				waitpid(pid_l3, NULL, 0);
1475 				exit(1);
1476 			}
1477 			memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
1478 
1479 			/* Wait for L3 child */
1480 			waitpid(pid_l3, NULL, 0);
1481 
1482 			/* Forward the socket FD to L1 parent */
1483 			memset(&msg, 0, sizeof(msg));
1484 			buf[0] = 'Y';
1485 			iov.iov_base = buf;
1486 			iov.iov_len = 1;
1487 			msg.msg_iov = &iov;
1488 			msg.msg_iovlen = 1;
1489 			msg.msg_control = cmsg_buf;
1490 			msg.msg_controllen = sizeof(cmsg_buf);
1491 
1492 			cmsg = CMSG_FIRSTHDR(&msg);
1493 			cmsg->cmsg_level = SOL_SOCKET;
1494 			cmsg->cmsg_type = SCM_RIGHTS;
1495 			cmsg->cmsg_len = CMSG_LEN(sizeof(int));
1496 			memcpy(CMSG_DATA(cmsg), &received_fd, sizeof(int));
1497 
1498 			if (sendmsg(ipc_L2[1], &msg, 0) < 0) {
1499 				close(received_fd);
1500 				close(ipc_L2[1]);
1501 				exit(1);
1502 			}
1503 
1504 			close(received_fd);
1505 			close(ipc_L2[1]);
1506 			exit(0);
1507 		}
1508 
1509 		/* Level 1 child - receive from L2 and forward to parent */
1510 		close(ipc_L2[1]);
1511 
1512 		struct msghdr msg = {0};
1513 		struct iovec iov = {0};
1514 		char buf[1];
1515 		char cmsg_buf[CMSG_SPACE(sizeof(int))];
1516 		int received_fd;
1517 
1518 		iov.iov_base = buf;
1519 		iov.iov_len = 1;
1520 		msg.msg_iov = &iov;
1521 		msg.msg_iovlen = 1;
1522 		msg.msg_control = cmsg_buf;
1523 		msg.msg_controllen = sizeof(cmsg_buf);
1524 
1525 		ssize_t n = recvmsg(ipc_L2[0], &msg, 0);
1526 		close(ipc_L2[0]);
1527 
1528 		if (n != 1) {
1529 			close(ipc_sockets[1]);
1530 			waitpid(pid_l2, NULL, 0);
1531 			exit(1);
1532 		}
1533 
1534 		struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1535 		if (!cmsg) {
1536 			close(ipc_sockets[1]);
1537 			waitpid(pid_l2, NULL, 0);
1538 			exit(1);
1539 		}
1540 		memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));
1541 
1542 		/* Wait for L2 child */
1543 		waitpid(pid_l2, NULL, 0);
1544 
1545 		/* Forward the socket FD to parent */
1546 		memset(&msg, 0, sizeof(msg));
1547 		buf[0] = 'Z';
1548 		iov.iov_base = buf;
1549 		iov.iov_len = 1;
1550 		msg.msg_iov = &iov;
1551 		msg.msg_iovlen = 1;
1552 		msg.msg_control = cmsg_buf;
1553 		msg.msg_controllen = sizeof(cmsg_buf);
1554 
1555 		cmsg = CMSG_FIRSTHDR(&msg);
1556 		cmsg->cmsg_level = SOL_SOCKET;
1557 		cmsg->cmsg_type = SCM_RIGHTS;
1558 		cmsg->cmsg_len = CMSG_LEN(sizeof(int));
1559 		memcpy(CMSG_DATA(cmsg), &received_fd, sizeof(int));
1560 
1561 		if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
1562 			close(received_fd);
1563 			close(ipc_sockets[1]);
1564 			exit(1);
1565 		}
1566 
1567 		close(received_fd);
1568 		close(ipc_sockets[1]);
1569 		exit(0);
1570 	}
1571 
1572 	/* Parent - receive the socket from the deepest level */
1573 	close(ipc_sockets[1]);
1574 
1575 	struct msghdr msg = {0};
1576 	struct iovec iov = {0};
1577 	char buf[1];
1578 	char cmsg_buf[CMSG_SPACE(sizeof(int))];
1579 
1580 	iov.iov_base = buf;
1581 	iov.iov_len = 1;
1582 	msg.msg_iov = &iov;
1583 	msg.msg_iovlen = 1;
1584 	msg.msg_control = cmsg_buf;
1585 	msg.msg_controllen = sizeof(cmsg_buf);
1586 
1587 	ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
1588 	close(ipc_sockets[0]);
1589 
1590 	if (n != 1) {
1591 		free(handle);
1592 		waitpid(pid_l1, NULL, 0);
1593 		SKIP(return, "Failed to receive socket from child");
1594 	}
1595 
1596 	struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
1597 	if (!cmsg) {
1598 		free(handle);
1599 		waitpid(pid_l1, NULL, 0);
1600 		SKIP(return, "Failed to receive socket from child");
1601 	}
1602 	memcpy(&sock_L3A_fd, CMSG_DATA(cmsg), sizeof(int));
1603 
1604 	/* Wait for L1 child */
1605 	waitpid(pid_l1, &status, 0);
1606 	ASSERT_TRUE(WIFEXITED(status));
1607 	ASSERT_EQ(WEXITSTATUS(status), 0);
1608 
1609 	/*
1610 	 * At this point, all child processes have exited. The socket itself
1611 	 * doesn't keep the namespace active - we need to call SIOCGSKNS which
1612 	 * will resurrect the entire namespace tree by taking active references.
1613 	 */
1614 
1615 	/* Get network namespace from socket - this resurrects the tree */
1616 	netns_L3A_fd = ioctl(sock_L3A_fd, SIOCGSKNS);
1617 	if (netns_L3A_fd < 0) {
1618 		free(handle);
1619 		close(sock_L3A_fd);
1620 		if (errno == ENOTTY || errno == EINVAL)
1621 			SKIP(return, "SIOCGSKNS not supported");
1622 		ASSERT_GE(netns_L3A_fd, 0);
1623 	}
1624 
1625 	/* Get namespace ID for net_L3A */
1626 	ret = ioctl(netns_L3A_fd, NS_GET_ID, &netns_L3A_id);
1627 	if (ret < 0) {
1628 		free(handle);
1629 		close(sock_L3A_fd);
1630 		close(netns_L3A_fd);
1631 		if (errno == ENOTTY || errno == EINVAL)
1632 			SKIP(return, "NS_GET_ID not supported");
1633 		ASSERT_EQ(ret, 0);
1634 	}
1635 
1636 	/* Get owner user namespace chain: userns_L3 -> userns_L2 -> userns_L1 */
1637 	int userns_L3_fd = ioctl(netns_L3A_fd, NS_GET_USERNS);
1638 	if (userns_L3_fd < 0) {
1639 		free(handle);
1640 		close(sock_L3A_fd);
1641 		close(netns_L3A_fd);
1642 		if (errno == ENOTTY || errno == EINVAL)
1643 			SKIP(return, "NS_GET_USERNS not supported");
1644 		ASSERT_GE(userns_L3_fd, 0);
1645 	}
1646 
1647 	ret = ioctl(userns_L3_fd, NS_GET_ID, &userns_L3_id);
1648 	ASSERT_EQ(ret, 0);
1649 
1650 	int userns_L2_fd = ioctl(userns_L3_fd, NS_GET_USERNS);
1651 	ASSERT_GE(userns_L2_fd, 0);
1652 	ret = ioctl(userns_L2_fd, NS_GET_ID, &userns_L2_id);
1653 	ASSERT_EQ(ret, 0);
1654 
1655 	int userns_L1_fd = ioctl(userns_L2_fd, NS_GET_USERNS);
1656 	ASSERT_GE(userns_L1_fd, 0);
1657 	ret = ioctl(userns_L1_fd, NS_GET_ID, &userns_L1_id);
1658 	ASSERT_EQ(ret, 0);
1659 
1660 	close(userns_L1_fd);
1661 	close(userns_L2_fd);
1662 	close(userns_L3_fd);
1663 
1664 	TH_LOG("Multi-level hierarchy: net_L3A (id=%llu) -> userns_L3 (id=%llu) -> userns_L2 (id=%llu) -> userns_L1 (id=%llu)",
1665 	       netns_L3A_id, userns_L3_id, userns_L2_id, userns_L1_id);
1666 
1667 	/*
1668 	 * Test 1: Verify net_L3A is visible in listns() after resurrection.
1669 	 * The entire ownership chain should be resurrected and visible.
1670 	 */
1671 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1672 	if (ret < 0) {
1673 		free(handle);
1674 		close(sock_L3A_fd);
1675 		close(netns_L3A_fd);
1676 		if (errno == ENOSYS)
1677 			SKIP(return, "listns() not supported");
1678 		ASSERT_GE(ret, 0);
1679 	}
1680 
1681 	bool found_netns_L3A = false;
1682 	bool found_userns_L1 = false;
1683 	bool found_userns_L2 = false;
1684 	bool found_userns_L3 = false;
1685 
1686 	for (i = 0; i < ret; i++) {
1687 		if (ns_ids[i] == netns_L3A_id)
1688 			found_netns_L3A = true;
1689 		if (ns_ids[i] == userns_L1_id)
1690 			found_userns_L1 = true;
1691 		if (ns_ids[i] == userns_L2_id)
1692 			found_userns_L2 = true;
1693 		if (ns_ids[i] == userns_L3_id)
1694 			found_userns_L3 = true;
1695 	}
1696 
1697 	ASSERT_TRUE(found_netns_L3A);
1698 	ASSERT_TRUE(found_userns_L1);
1699 	ASSERT_TRUE(found_userns_L2);
1700 	ASSERT_TRUE(found_userns_L3);
1701 	TH_LOG("Resurrection verified: all namespaces in hierarchy visible in listns()");
1702 
1703 	/*
1704 	 * Test 2: Verify net_L3A can be reopened via file handle.
1705 	 */
1706 	nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
1707 	nsfs_fh->ns_id = netns_L3A_id;
1708 	nsfs_fh->ns_type = 0;
1709 	nsfs_fh->ns_inum = 0;
1710 
1711 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
1712 	if (reopened_fd < 0) {
1713 		free(handle);
1714 		close(sock_L3A_fd);
1715 		close(netns_L3A_fd);
1716 		if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
1717 			SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
1718 		TH_LOG("open_by_handle_at failed: %s", strerror(errno));
1719 		ASSERT_GE(reopened_fd, 0);
1720 	}
1721 
1722 	close(reopened_fd);
1723 	TH_LOG("File handle test passed: net_L3A can be reopened");
1724 
1725 	/*
1726 	 * Test 3: Verify that when we close the netns FD (dropping the last
1727 	 * active reference), the entire tree becomes inactive and disappears
1728 	 * from listns(). The cascade goes: net_L3A drops -> userns_L3 drops ->
1729 	 * userns_L2 drops -> userns_L1 drops.
1730 	 */
1731 	close(netns_L3A_fd);
1732 
1733 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1734 	ASSERT_GE(ret, 0);
1735 
1736 	found_netns_L3A = false;
1737 	found_userns_L1 = false;
1738 	found_userns_L2 = false;
1739 	found_userns_L3 = false;
1740 
1741 	for (i = 0; i < ret; i++) {
1742 		if (ns_ids[i] == netns_L3A_id)
1743 			found_netns_L3A = true;
1744 		if (ns_ids[i] == userns_L1_id)
1745 			found_userns_L1 = true;
1746 		if (ns_ids[i] == userns_L2_id)
1747 			found_userns_L2 = true;
1748 		if (ns_ids[i] == userns_L3_id)
1749 			found_userns_L3 = true;
1750 	}
1751 
1752 	ASSERT_FALSE(found_netns_L3A);
1753 	ASSERT_FALSE(found_userns_L1);
1754 	ASSERT_FALSE(found_userns_L2);
1755 	ASSERT_FALSE(found_userns_L3);
1756 	TH_LOG("Cascade test passed: all namespaces disappeared after netns FD closed");
1757 
1758 	/*
1759 	 * Test 4: Verify file handle no longer works for inactive namespace.
1760 	 */
1761 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
1762 	if (reopened_fd >= 0) {
1763 		close(reopened_fd);
1764 		free(handle);
1765 		ASSERT_TRUE(false); /* Should have failed */
1766 	}
1767 	TH_LOG("Inactive namespace correctly cannot be reopened via file handle");
1768 
1769 	/*
1770 	 * Test 5: Verify that calling SIOCGSKNS again resurrects the tree again.
1771 	 * The socket is still valid, so we can call SIOCGSKNS on it to resurrect
1772 	 * the namespace tree once more.
1773 	 */
1774 	netns_L3A_fd = ioctl(sock_L3A_fd, SIOCGSKNS);
1775 	ASSERT_GE(netns_L3A_fd, 0);
1776 
1777 	TH_LOG("Called SIOCGSKNS again to resurrect the namespace tree");
1778 
1779 	/* Verify the namespace tree is resurrected and visible in listns() */
1780 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
1781 	ASSERT_GE(ret, 0);
1782 
1783 	found_netns_L3A = false;
1784 	found_userns_L1 = false;
1785 	found_userns_L2 = false;
1786 	found_userns_L3 = false;
1787 
1788 	for (i = 0; i < ret; i++) {
1789 		if (ns_ids[i] == netns_L3A_id)
1790 			found_netns_L3A = true;
1791 		if (ns_ids[i] == userns_L1_id)
1792 			found_userns_L1 = true;
1793 		if (ns_ids[i] == userns_L2_id)
1794 			found_userns_L2 = true;
1795 		if (ns_ids[i] == userns_L3_id)
1796 			found_userns_L3 = true;
1797 	}
1798 
1799 	ASSERT_TRUE(found_netns_L3A);
1800 	ASSERT_TRUE(found_userns_L1);
1801 	ASSERT_TRUE(found_userns_L2);
1802 	ASSERT_TRUE(found_userns_L3);
1803 	TH_LOG("Second resurrection verified: all namespaces in hierarchy visible in listns() again");
1804 
1805 	/* Verify we can reopen via file handle again */
1806 	reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
1807 	if (reopened_fd < 0) {
1808 		free(handle);
1809 		close(sock_L3A_fd);
1810 		close(netns_L3A_fd);
1811 		TH_LOG("open_by_handle_at failed after second resurrection: %s", strerror(errno));
1812 		ASSERT_GE(reopened_fd, 0);
1813 	}
1814 
1815 	close(reopened_fd);
1816 	TH_LOG("File handle test passed: net_L3A can be reopened after second resurrection");
1817 
1818 	/* Final cleanup */
1819 	close(sock_L3A_fd);
1820 	close(netns_L3A_fd);
1821 	free(handle);
1822 }
1823 
1824 TEST_HARNESS_MAIN
1825