root/tools/testing/selftests/namespaces/siocgskns_test.c
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <linux/if.h>
#include <linux/sockios.h>
#include <linux/nsfs.h>
#include <arpa/inet.h>
#include "../kselftest_harness.h"
#include "../filesystems/utils.h"
#include "wrappers.h"

#ifndef SIOCGSKNS
#define SIOCGSKNS 0x894C
#endif

#ifndef FD_NSFS_ROOT
#define FD_NSFS_ROOT -10003
#endif

#ifndef FILEID_NSFS
#define FILEID_NSFS 0xf1
#endif

/*
 * Test basic SIOCGSKNS functionality.
 * Create a socket and verify SIOCGSKNS returns the correct network namespace.
 */
TEST(siocgskns_basic)
{
        int sock_fd, netns_fd, current_netns_fd;
        struct stat st1, st2;

        /* Create a TCP socket */
        sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        ASSERT_GE(sock_fd, 0);

        /* Use SIOCGSKNS to get network namespace */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        /* Get current network namespace */
        current_netns_fd = open("/proc/self/ns/net", O_RDONLY);
        ASSERT_GE(current_netns_fd, 0);

        /* Verify they match */
        ASSERT_EQ(fstat(netns_fd, &st1), 0);
        ASSERT_EQ(fstat(current_netns_fd, &st2), 0);
        ASSERT_EQ(st1.st_ino, st2.st_ino);

        close(sock_fd);
        close(netns_fd);
        close(current_netns_fd);
}

/*
 * Test that socket file descriptors keep network namespaces active.
 * Create a network namespace, create a socket in it, then exit the namespace.
 * The namespace should remain active while the socket FD is held.
 */
TEST(siocgskns_keeps_netns_active)
{
        int sock_fd, netns_fd, test_fd;
        int ipc_sockets[2];
        pid_t pid;
        int status;
        struct stat st;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                /* Child: create new netns and socket */
                close(ipc_sockets[0]);

                if (unshare(CLONE_NEWNET) < 0) {
                        TH_LOG("unshare(CLONE_NEWNET) failed: %s", strerror(errno));
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Create a socket in the new network namespace */
                sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
                if (sock_fd < 0) {
                        TH_LOG("socket() failed: %s", strerror(errno));
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Send socket FD to parent via SCM_RIGHTS */
                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1] = {'X'};
                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(sock_fd);
                        close(ipc_sockets[1]);
                        exit(1);
                }

                close(sock_fd);
                close(ipc_sockets[1]);
                exit(0);
        }

        /* Parent: receive socket FD */
        close(ipc_sockets[1]);

        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);
        ASSERT_EQ(n, 1);

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        ASSERT_NE(cmsg, NULL);
        ASSERT_EQ(cmsg->cmsg_type, SCM_RIGHTS);

        memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Wait for child to exit */
        waitpid(pid, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 0);

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        ASSERT_EQ(fstat(netns_fd, &st), 0);

        /*
         * Namespace should still be active because socket FD keeps it alive.
         * Try to access it via /proc/self/fd/<fd>.
         */
        char path[64];
        snprintf(path, sizeof(path), "/proc/self/fd/%d", netns_fd);
        test_fd = open(path, O_RDONLY);
        ASSERT_GE(test_fd, 0);
        close(test_fd);
        close(netns_fd);

        /* Close socket - namespace should become inactive */
        close(sock_fd);

        /* Try SIOCGSKNS again - should fail since socket is closed */
        ASSERT_LT(ioctl(sock_fd, SIOCGSKNS), 0);
}

/*
 * Test SIOCGSKNS with different socket types (TCP, UDP, RAW).
 */
TEST(siocgskns_socket_types)
{
        int sock_tcp, sock_udp, sock_raw;
        int netns_tcp, netns_udp, netns_raw;
        struct stat st_tcp, st_udp, st_raw;

        /* TCP socket */
        sock_tcp = socket(AF_INET, SOCK_STREAM, 0);
        ASSERT_GE(sock_tcp, 0);

        /* UDP socket */
        sock_udp = socket(AF_INET, SOCK_DGRAM, 0);
        ASSERT_GE(sock_udp, 0);

        /* RAW socket (may require privileges) */
        sock_raw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
        if (sock_raw < 0 && (errno == EPERM || errno == EACCES)) {
                sock_raw = -1; /* Skip raw socket test */
        }

        /* Test SIOCGSKNS on TCP */
        netns_tcp = ioctl(sock_tcp, SIOCGSKNS);
        if (netns_tcp < 0) {
                close(sock_tcp);
                close(sock_udp);
                if (sock_raw >= 0) close(sock_raw);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_tcp, 0);
        }

        /* Test SIOCGSKNS on UDP */
        netns_udp = ioctl(sock_udp, SIOCGSKNS);
        ASSERT_GE(netns_udp, 0);

        /* Test SIOCGSKNS on RAW (if available) */
        if (sock_raw >= 0) {
                netns_raw = ioctl(sock_raw, SIOCGSKNS);
                ASSERT_GE(netns_raw, 0);
        }

        /* Verify all return the same network namespace */
        ASSERT_EQ(fstat(netns_tcp, &st_tcp), 0);
        ASSERT_EQ(fstat(netns_udp, &st_udp), 0);
        ASSERT_EQ(st_tcp.st_ino, st_udp.st_ino);

        if (sock_raw >= 0) {
                ASSERT_EQ(fstat(netns_raw, &st_raw), 0);
                ASSERT_EQ(st_tcp.st_ino, st_raw.st_ino);
                close(netns_raw);
                close(sock_raw);
        }

        close(netns_tcp);
        close(netns_udp);
        close(sock_tcp);
        close(sock_udp);
}

/*
 * Test SIOCGSKNS across setns.
 * Create a socket in netns A, switch to netns B, verify SIOCGSKNS still
 * returns netns A.
 */
TEST(siocgskns_across_setns)
{
        int sock_fd, netns_a_fd, netns_b_fd, result_fd;
        struct stat st_a;

        /* Get current netns (A) */
        netns_a_fd = open("/proc/self/ns/net", O_RDONLY);
        ASSERT_GE(netns_a_fd, 0);
        ASSERT_EQ(fstat(netns_a_fd, &st_a), 0);

        /* Create socket in netns A */
        sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        ASSERT_GE(sock_fd, 0);

        /* Create new netns (B) */
        ASSERT_EQ(unshare(CLONE_NEWNET), 0);

        netns_b_fd = open("/proc/self/ns/net", O_RDONLY);
        ASSERT_GE(netns_b_fd, 0);

        /* Get netns from socket created in A */
        result_fd = ioctl(sock_fd, SIOCGSKNS);
        if (result_fd < 0) {
                close(sock_fd);
                setns(netns_a_fd, CLONE_NEWNET);
                close(netns_a_fd);
                close(netns_b_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(result_fd, 0);
        }

        /* Verify it still points to netns A */
        struct stat st_result_stat;
        ASSERT_EQ(fstat(result_fd, &st_result_stat), 0);
        ASSERT_EQ(st_a.st_ino, st_result_stat.st_ino);

        close(result_fd);
        close(sock_fd);
        close(netns_b_fd);

        /* Restore original netns */
        ASSERT_EQ(setns(netns_a_fd, CLONE_NEWNET), 0);
        close(netns_a_fd);
}

/*
 * Test SIOCGSKNS fails on non-socket file descriptors.
 */
TEST(siocgskns_non_socket)
{
        int fd;
        int pipefd[2];

        /* Test on regular file */
        fd = open("/dev/null", O_RDONLY);
        ASSERT_GE(fd, 0);

        ASSERT_LT(ioctl(fd, SIOCGSKNS), 0);
        ASSERT_TRUE(errno == ENOTTY || errno == EINVAL);
        close(fd);

        /* Test on pipe */
        ASSERT_EQ(pipe(pipefd), 0);

        ASSERT_LT(ioctl(pipefd[0], SIOCGSKNS), 0);
        ASSERT_TRUE(errno == ENOTTY || errno == EINVAL);

        close(pipefd[0]);
        close(pipefd[1]);
}

/*
 * Test multiple sockets keep the same network namespace active.
 * Create multiple sockets, verify closing some doesn't affect others.
 */
TEST(siocgskns_multiple_sockets)
{
        int socks[5];
        int netns_fds[5];
        int i;
        struct stat st;
        ino_t netns_ino;

        /* Create new network namespace */
        ASSERT_EQ(unshare(CLONE_NEWNET), 0);

        /* Create multiple sockets */
        for (i = 0; i < 5; i++) {
                socks[i] = socket(AF_INET, SOCK_STREAM, 0);
                ASSERT_GE(socks[i], 0);
        }

        /* Get netns from all sockets */
        for (i = 0; i < 5; i++) {
                netns_fds[i] = ioctl(socks[i], SIOCGSKNS);
                if (netns_fds[i] < 0) {
                        int j;
                        for (j = 0; j <= i; j++) {
                                close(socks[j]);
                                if (j < i && netns_fds[j] >= 0)
                                        close(netns_fds[j]);
                        }
                        if (errno == ENOTTY || errno == EINVAL)
                                SKIP(return, "SIOCGSKNS not supported");
                        ASSERT_GE(netns_fds[i], 0);
                }
        }

        /* Verify all point to same netns */
        ASSERT_EQ(fstat(netns_fds[0], &st), 0);
        netns_ino = st.st_ino;

        for (i = 1; i < 5; i++) {
                ASSERT_EQ(fstat(netns_fds[i], &st), 0);
                ASSERT_EQ(st.st_ino, netns_ino);
        }

        /* Close some sockets */
        for (i = 0; i < 3; i++) {
                close(socks[i]);
        }

        /* Remaining netns FDs should still be valid */
        for (i = 3; i < 5; i++) {
                char path[64];
                snprintf(path, sizeof(path), "/proc/self/fd/%d", netns_fds[i]);
                int test_fd = open(path, O_RDONLY);
                ASSERT_GE(test_fd, 0);
                close(test_fd);
        }

        /* Cleanup */
        for (i = 0; i < 5; i++) {
                if (i >= 3)
                        close(socks[i]);
                close(netns_fds[i]);
        }
}

/*
 * Test socket keeps netns active after creating process exits.
 * Verify that as long as the socket FD exists, the namespace remains active.
 */
TEST(siocgskns_netns_lifecycle)
{
        int sock_fd, netns_fd;
        int ipc_sockets[2];
        int syncpipe[2];
        pid_t pid;
        int status;
        char sync_byte;
        struct stat st;
        ino_t netns_ino;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        ASSERT_EQ(pipe(syncpipe), 0);

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                /* Child */
                close(ipc_sockets[0]);
                close(syncpipe[1]);

                if (unshare(CLONE_NEWNET) < 0) {
                        close(ipc_sockets[1]);
                        close(syncpipe[0]);
                        exit(1);
                }

                sock_fd = socket(AF_INET, SOCK_STREAM, 0);
                if (sock_fd < 0) {
                        close(ipc_sockets[1]);
                        close(syncpipe[0]);
                        exit(1);
                }

                /* Send socket to parent */
                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1] = {'X'};
                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(sock_fd);
                        close(ipc_sockets[1]);
                        close(syncpipe[0]);
                        exit(1);
                }

                close(sock_fd);
                close(ipc_sockets[1]);

                /* Wait for parent signal */
                read(syncpipe[0], &sync_byte, 1);
                close(syncpipe[0]);
                exit(0);
        }

        /* Parent */
        close(ipc_sockets[1]);
        close(syncpipe[0]);

        /* Receive socket FD */
        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);
        ASSERT_EQ(n, 1);

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        ASSERT_NE(cmsg, NULL);
        memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Get netns from socket while child is alive */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                sync_byte = 'G';
                write(syncpipe[1], &sync_byte, 1);
                close(syncpipe[1]);
                close(sock_fd);
                waitpid(pid, NULL, 0);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }
        ASSERT_EQ(fstat(netns_fd, &st), 0);
        netns_ino = st.st_ino;

        /* Signal child to exit */
        sync_byte = 'G';
        write(syncpipe[1], &sync_byte, 1);
        close(syncpipe[1]);

        waitpid(pid, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));

        /*
         * Socket FD should still keep namespace active even after
         * the creating process exited.
         */
        int test_fd = ioctl(sock_fd, SIOCGSKNS);
        ASSERT_GE(test_fd, 0);

        struct stat st_test;
        ASSERT_EQ(fstat(test_fd, &st_test), 0);
        ASSERT_EQ(st_test.st_ino, netns_ino);

        close(test_fd);
        close(netns_fd);

        /* Close socket - namespace should become inactive */
        close(sock_fd);
}

/*
 * Test IPv6 sockets also work with SIOCGSKNS.
 */
TEST(siocgskns_ipv6)
{
        int sock_fd, netns_fd, current_netns_fd;
        struct stat st1, st2;

        /* Create an IPv6 TCP socket */
        sock_fd = socket(AF_INET6, SOCK_STREAM, 0);
        ASSERT_GE(sock_fd, 0);

        /* Use SIOCGSKNS */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        /* Verify it matches current namespace */
        current_netns_fd = open("/proc/self/ns/net", O_RDONLY);
        ASSERT_GE(current_netns_fd, 0);

        ASSERT_EQ(fstat(netns_fd, &st1), 0);
        ASSERT_EQ(fstat(current_netns_fd, &st2), 0);
        ASSERT_EQ(st1.st_ino, st2.st_ino);

        close(sock_fd);
        close(netns_fd);
        close(current_netns_fd);
}

/*
 * Test that socket-kept netns appears in listns() output.
 * Verify that a network namespace kept alive by a socket FD appears in
 * listns() output even after the creating process exits, and that it
 * disappears when the socket is closed.
 */
TEST(siocgskns_listns_visibility)
{
        int sock_fd, netns_fd, owner_fd;
        int ipc_sockets[2];
        pid_t pid;
        int status;
        __u64 netns_id, owner_id;
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWNET,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[256];
        int ret, i;
        bool found_netns = false;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                /* Child: create new netns and socket */
                close(ipc_sockets[0]);

                if (unshare(CLONE_NEWNET) < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
                if (sock_fd < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Send socket FD to parent via SCM_RIGHTS */
                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1] = {'X'};
                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(sock_fd);
                        close(ipc_sockets[1]);
                        exit(1);
                }

                close(sock_fd);
                close(ipc_sockets[1]);
                exit(0);
        }

        /* Parent: receive socket FD */
        close(ipc_sockets[1]);

        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);
        ASSERT_EQ(n, 1);

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        ASSERT_NE(cmsg, NULL);
        memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Wait for child to exit */
        waitpid(pid, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 0);

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        /* Get namespace ID */
        ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
        if (ret < 0) {
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_ID not supported");
                ASSERT_EQ(ret, 0);
        }

        /* Get owner user namespace */
        owner_fd = ioctl(netns_fd, NS_GET_USERNS);
        if (owner_fd < 0) {
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_USERNS not supported");
                ASSERT_GE(owner_fd, 0);
        }

        /* Get owner namespace ID */
        ret = ioctl(owner_fd, NS_GET_ID, &owner_id);
        if (ret < 0) {
                close(owner_fd);
                close(sock_fd);
                close(netns_fd);
                ASSERT_EQ(ret, 0);
        }
        close(owner_fd);

        /* Namespace should appear in listns() output */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s", strerror(errno));
                ASSERT_GE(ret, 0);
        }

        /* Search for our network namespace in the list */
        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id) {
                        found_netns = true;
                        break;
                }
        }

        ASSERT_TRUE(found_netns);
        TH_LOG("Found netns %llu in listns() output (kept alive by socket)", netns_id);

        /* Now verify with owner filtering */
        req.user_ns_id = owner_id;
        found_netns = false;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        ASSERT_GE(ret, 0);

        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id) {
                        found_netns = true;
                        break;
                }
        }

        ASSERT_TRUE(found_netns);
        TH_LOG("Found netns %llu owned by userns %llu", netns_id, owner_id);

        /* Close socket - namespace should become inactive and disappear from listns() */
        close(sock_fd);
        close(netns_fd);

        /* Verify it's no longer in listns() output */
        req.user_ns_id = 0;
        found_netns = false;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        ASSERT_GE(ret, 0);

        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id) {
                        found_netns = true;
                        break;
                }
        }

        ASSERT_FALSE(found_netns);
        TH_LOG("Netns %llu correctly disappeared from listns() after socket closed", netns_id);
}

/*
 * Test that socket-kept netns can be reopened via file handle.
 * Verify that a network namespace kept alive by a socket FD can be
 * reopened using file handles even after the creating process exits.
 */
TEST(siocgskns_file_handle)
{
        int sock_fd, netns_fd, reopened_fd;
        int ipc_sockets[2];
        pid_t pid;
        int status;
        struct stat st1, st2;
        ino_t netns_ino;
        __u64 netns_id;
        struct file_handle *handle;
        struct nsfs_file_handle *nsfs_fh;
        int ret;

        /* Allocate file_handle structure for nsfs */
        handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
        ASSERT_NE(handle, NULL);
        handle->handle_bytes = sizeof(struct nsfs_file_handle);
        handle->handle_type = FILEID_NSFS;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                /* Child: create new netns and socket */
                close(ipc_sockets[0]);

                if (unshare(CLONE_NEWNET) < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
                if (sock_fd < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Send socket FD to parent via SCM_RIGHTS */
                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1] = {'X'};
                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(sock_fd);
                        close(ipc_sockets[1]);
                        exit(1);
                }

                close(sock_fd);
                close(ipc_sockets[1]);
                exit(0);
        }

        /* Parent: receive socket FD */
        close(ipc_sockets[1]);

        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);
        ASSERT_EQ(n, 1);

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        ASSERT_NE(cmsg, NULL);
        memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Wait for child to exit */
        waitpid(pid, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 0);

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        ASSERT_EQ(fstat(netns_fd, &st1), 0);
        netns_ino = st1.st_ino;

        /* Get namespace ID */
        ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
        if (ret < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_ID not supported");
                ASSERT_EQ(ret, 0);
        }

        /* Construct file handle from namespace ID */
        nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
        nsfs_fh->ns_id = netns_id;
        nsfs_fh->ns_type = 0;  /* Type field not needed for reopening */
        nsfs_fh->ns_inum = 0;  /* Inum field not needed for reopening */

        TH_LOG("Constructed file handle for netns %lu (id=%llu)", netns_ino, netns_id);

        /* Reopen namespace using file handle (while socket still keeps it alive) */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
                        SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
                TH_LOG("open_by_handle_at failed: %s", strerror(errno));
                ASSERT_GE(reopened_fd, 0);
        }

        /* Verify it's the same namespace */
        ASSERT_EQ(fstat(reopened_fd, &st2), 0);
        ASSERT_EQ(st1.st_ino, st2.st_ino);
        ASSERT_EQ(st1.st_dev, st2.st_dev);

        TH_LOG("Successfully reopened netns %lu via file handle", netns_ino);

        close(reopened_fd);

        /* Close the netns FD */
        close(netns_fd);

        /* Try to reopen via file handle - should fail since namespace is now inactive */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        ASSERT_LT(reopened_fd, 0);
        TH_LOG("Correctly failed to reopen inactive netns: %s", strerror(errno));

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        /* Reopen namespace using file handle (while socket still keeps it alive) */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
                        SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
                TH_LOG("open_by_handle_at failed: %s", strerror(errno));
                ASSERT_GE(reopened_fd, 0);
        }

        /* Verify it's the same namespace */
        ASSERT_EQ(fstat(reopened_fd, &st2), 0);
        ASSERT_EQ(st1.st_ino, st2.st_ino);
        ASSERT_EQ(st1.st_dev, st2.st_dev);

        TH_LOG("Successfully reopened netns %lu via file handle", netns_ino);

        /* Close socket - namespace should become inactive */
        close(sock_fd);
        free(handle);
}

/*
 * Test combined listns() and file handle operations with socket-kept netns.
 * Create a netns, keep it alive with a socket, verify it appears in listns(),
 * then reopen it via file handle obtained from listns() entry.
 */
TEST(siocgskns_listns_and_file_handle)
{
        int sock_fd, netns_fd, userns_fd, reopened_fd;
        int ipc_sockets[2];
        pid_t pid;
        int status;
        struct stat st;
        ino_t netns_ino;
        __u64 netns_id, userns_id;
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWNET | CLONE_NEWUSER,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[256];
        int ret, i;
        bool found_netns = false, found_userns = false;
        struct file_handle *handle;
        struct nsfs_file_handle *nsfs_fh;

        /* Allocate file_handle structure for nsfs */
        handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
        ASSERT_NE(handle, NULL);
        handle->handle_bytes = sizeof(struct nsfs_file_handle);
        handle->handle_type = FILEID_NSFS;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        pid = fork();
        ASSERT_GE(pid, 0);

        if (pid == 0) {
                /* Child: create new userns and netns with socket */
                close(ipc_sockets[0]);

                if (setup_userns() < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                if (unshare(CLONE_NEWNET) < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
                if (sock_fd < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Send socket FD to parent via SCM_RIGHTS */
                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1] = {'X'};
                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(sock_fd);
                        close(ipc_sockets[1]);
                        exit(1);
                }

                close(sock_fd);
                close(ipc_sockets[1]);
                exit(0);
        }

        /* Parent: receive socket FD */
        close(ipc_sockets[1]);

        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);
        ASSERT_EQ(n, 1);

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        ASSERT_NE(cmsg, NULL);
        memcpy(&sock_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Wait for child to exit */
        waitpid(pid, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 0);

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        ASSERT_EQ(fstat(netns_fd, &st), 0);
        netns_ino = st.st_ino;

        /* Get namespace ID */
        ret = ioctl(netns_fd, NS_GET_ID, &netns_id);
        if (ret < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_ID not supported");
                ASSERT_EQ(ret, 0);
        }

        /* Get owner user namespace */
        userns_fd = ioctl(netns_fd, NS_GET_USERNS);
        if (userns_fd < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_USERNS not supported");
                ASSERT_GE(userns_fd, 0);
        }

        /* Get owner namespace ID */
        ret = ioctl(userns_fd, NS_GET_ID, &userns_id);
        if (ret < 0) {
                close(userns_fd);
                free(handle);
                close(sock_fd);
                close(netns_fd);
                ASSERT_EQ(ret, 0);
        }
        close(userns_fd);

        TH_LOG("Testing netns %lu (id=%llu) owned by userns id=%llu", netns_ino, netns_id, userns_id);

        /* Verify namespace appears in listns() */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s", strerror(errno));
                ASSERT_GE(ret, 0);
        }

        found_netns = false;
        found_userns = false;
        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id)
                        found_netns = true;
                if (ns_ids[i] == userns_id)
                        found_userns = true;
        }
        ASSERT_TRUE(found_netns);
        ASSERT_TRUE(found_userns);
        TH_LOG("Found netns %llu in listns() output", netns_id);

        /* Construct file handle from namespace ID */
        nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
        nsfs_fh->ns_id = netns_id;
        nsfs_fh->ns_type = 0;
        nsfs_fh->ns_inum = 0;

        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
                        SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
                TH_LOG("open_by_handle_at failed: %s", strerror(errno));
                ASSERT_GE(reopened_fd, 0);
        }

        struct stat reopened_st;
        ASSERT_EQ(fstat(reopened_fd, &reopened_st), 0);
        ASSERT_EQ(reopened_st.st_ino, netns_ino);

        TH_LOG("Successfully reopened netns %lu via file handle (socket-kept)", netns_ino);

        close(reopened_fd);
        close(netns_fd);

        /* Try to reopen via file handle - should fail since namespace is now inactive */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        ASSERT_LT(reopened_fd, 0);
        TH_LOG("Correctly failed to reopen inactive netns: %s", strerror(errno));

        /* Get network namespace from socket */
        netns_fd = ioctl(sock_fd, SIOCGSKNS);
        if (netns_fd < 0) {
                free(handle);
                close(sock_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_fd, 0);
        }

        /* Verify namespace appears in listns() */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s", strerror(errno));
                ASSERT_GE(ret, 0);
        }

        found_netns = false;
        found_userns = false;
        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id)
                        found_netns = true;
                if (ns_ids[i] == userns_id)
                        found_userns = true;
        }
        ASSERT_TRUE(found_netns);
        ASSERT_TRUE(found_userns);
        TH_LOG("Found netns %llu in listns() output", netns_id);

        close(netns_fd);

        /* Verify namespace appears in listns() */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                free(handle);
                close(sock_fd);
                close(netns_fd);
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s", strerror(errno));
                ASSERT_GE(ret, 0);
        }

        found_netns = false;
        found_userns = false;
        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_id)
                        found_netns = true;
                if (ns_ids[i] == userns_id)
                        found_userns = true;
        }
        ASSERT_FALSE(found_netns);
        ASSERT_FALSE(found_userns);
        TH_LOG("Netns %llu correctly disappeared from listns() after socket closed", netns_id);

        close(sock_fd);
        free(handle);
}

/*
 * Test multi-level namespace resurrection across three user namespace levels.
 *
 * This test creates a complex namespace hierarchy with three levels of user
 * namespaces and a network namespace at the deepest level. It verifies that
 * the resurrection semantics work correctly when SIOCGSKNS is called on a
 * socket from an inactive namespace tree, and that listns() and
 * open_by_handle_at() correctly respect visibility rules.
 *
 * Hierarchy after child processes exit (all with 0 active refcount):
 *
 *          net_L3A (0)                <- Level 3 network namespace
 *              |
 *              +
 *          userns_L3 (0)              <- Level 3 user namespace
 *              |
 *              +
 *          userns_L2 (0)              <- Level 2 user namespace
 *              |
 *              +
 *          userns_L1 (0)              <- Level 1 user namespace
 *              |
 *              x
 *          init_user_ns
 *
 * The test verifies:
 * 1. SIOCGSKNS on a socket from inactive net_L3A resurrects the entire chain
 * 2. After resurrection, all namespaces are visible in listns()
 * 3. Resurrected namespaces can be reopened via file handles
 * 4. Closing the netns FD cascades down: the entire ownership chain
 *    (userns_L3 -> userns_L2 -> userns_L1) becomes inactive again
 * 5. Inactive namespaces disappear from listns() and cannot be reopened
 * 6. Calling SIOCGSKNS again on the same socket resurrects the tree again
 * 7. After second resurrection, namespaces are visible and can be reopened
 */
TEST(siocgskns_multilevel_resurrection)
{
        int ipc_sockets[2];
        pid_t pid_l1, pid_l2, pid_l3;
        int status;

        /* Namespace file descriptors to be received from child */
        int sock_L3A_fd = -1;
        int netns_L3A_fd = -1;
        __u64 netns_L3A_id;
        __u64 userns_L1_id, userns_L2_id, userns_L3_id;

        /* For listns() and file handle testing */
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWNET | CLONE_NEWUSER,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[256];
        int ret, i;
        struct file_handle *handle;
        struct nsfs_file_handle *nsfs_fh;
        int reopened_fd;

        /* Allocate file handle for testing */
        handle = malloc(sizeof(struct file_handle) + sizeof(struct nsfs_file_handle));
        ASSERT_NE(handle, NULL);
        handle->handle_bytes = sizeof(struct nsfs_file_handle);
        handle->handle_type = FILEID_NSFS;

        EXPECT_EQ(socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_sockets), 0);

        /*
         * Fork level 1 child that creates userns_L1
         */
        pid_l1 = fork();
        ASSERT_GE(pid_l1, 0);

        if (pid_l1 == 0) {
                /* Level 1 child */
                int ipc_L2[2];
                close(ipc_sockets[0]);

                /* Create userns_L1 */
                if (setup_userns() < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /* Create socketpair for communicating with L2 child */
                if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_L2) < 0) {
                        close(ipc_sockets[1]);
                        exit(1);
                }

                /*
                 * Fork level 2 child that creates userns_L2
                 */
                pid_l2 = fork();
                if (pid_l2 < 0) {
                        close(ipc_sockets[1]);
                        close(ipc_L2[0]);
                        close(ipc_L2[1]);
                        exit(1);
                }

                if (pid_l2 == 0) {
                        /* Level 2 child */
                        int ipc_L3[2];
                        close(ipc_L2[0]);

                        /* Create userns_L2 (nested inside userns_L1) */
                        if (setup_userns() < 0) {
                                close(ipc_L2[1]);
                                exit(1);
                        }

                        /* Create socketpair for communicating with L3 child */
                        if (socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, ipc_L3) < 0) {
                                close(ipc_L2[1]);
                                exit(1);
                        }

                        /*
                         * Fork level 3 child that creates userns_L3 and network namespaces
                         */
                        pid_l3 = fork();
                        if (pid_l3 < 0) {
                                close(ipc_L2[1]);
                                close(ipc_L3[0]);
                                close(ipc_L3[1]);
                                exit(1);
                        }

                        if (pid_l3 == 0) {
                                /* Level 3 child - the deepest level */
                                int sock_fd;
                                close(ipc_L3[0]);

                                /* Create userns_L3 (nested inside userns_L2) */
                                if (setup_userns() < 0) {
                                        close(ipc_L3[1]);
                                        exit(1);
                                }

                                /* Create network namespace at level 3 */
                                if (unshare(CLONE_NEWNET) < 0) {
                                        close(ipc_L3[1]);
                                        exit(1);
                                }

                                /* Create socket in net_L3A */
                                sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
                                if (sock_fd < 0) {
                                        close(ipc_L3[1]);
                                        exit(1);
                                }

                                /* Send socket FD to L2 parent */
                                struct msghdr msg = {0};
                                struct iovec iov = {0};
                                char buf[1] = {'X'};
                                char cmsg_buf[CMSG_SPACE(sizeof(int))];

                                iov.iov_base = buf;
                                iov.iov_len = 1;
                                msg.msg_iov = &iov;
                                msg.msg_iovlen = 1;
                                msg.msg_control = cmsg_buf;
                                msg.msg_controllen = sizeof(cmsg_buf);

                                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                                cmsg->cmsg_level = SOL_SOCKET;
                                cmsg->cmsg_type = SCM_RIGHTS;
                                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                                memcpy(CMSG_DATA(cmsg), &sock_fd, sizeof(int));

                                if (sendmsg(ipc_L3[1], &msg, 0) < 0) {
                                        close(sock_fd);
                                        close(ipc_L3[1]);
                                        exit(1);
                                }

                                close(sock_fd);
                                close(ipc_L3[1]);
                                exit(0);
                        }

                        /* Level 2 child - receive from L3 and forward to L1 */
                        close(ipc_L3[1]);

                        struct msghdr msg = {0};
                        struct iovec iov = {0};
                        char buf[1];
                        char cmsg_buf[CMSG_SPACE(sizeof(int))];
                        int received_fd;

                        iov.iov_base = buf;
                        iov.iov_len = 1;
                        msg.msg_iov = &iov;
                        msg.msg_iovlen = 1;
                        msg.msg_control = cmsg_buf;
                        msg.msg_controllen = sizeof(cmsg_buf);

                        ssize_t n = recvmsg(ipc_L3[0], &msg, 0);
                        close(ipc_L3[0]);

                        if (n != 1) {
                                close(ipc_L2[1]);
                                waitpid(pid_l3, NULL, 0);
                                exit(1);
                        }

                        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                        if (!cmsg) {
                                close(ipc_L2[1]);
                                waitpid(pid_l3, NULL, 0);
                                exit(1);
                        }
                        memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));

                        /* Wait for L3 child */
                        waitpid(pid_l3, NULL, 0);

                        /* Forward the socket FD to L1 parent */
                        memset(&msg, 0, sizeof(msg));
                        buf[0] = 'Y';
                        iov.iov_base = buf;
                        iov.iov_len = 1;
                        msg.msg_iov = &iov;
                        msg.msg_iovlen = 1;
                        msg.msg_control = cmsg_buf;
                        msg.msg_controllen = sizeof(cmsg_buf);

                        cmsg = CMSG_FIRSTHDR(&msg);
                        cmsg->cmsg_level = SOL_SOCKET;
                        cmsg->cmsg_type = SCM_RIGHTS;
                        cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                        memcpy(CMSG_DATA(cmsg), &received_fd, sizeof(int));

                        if (sendmsg(ipc_L2[1], &msg, 0) < 0) {
                                close(received_fd);
                                close(ipc_L2[1]);
                                exit(1);
                        }

                        close(received_fd);
                        close(ipc_L2[1]);
                        exit(0);
                }

                /* Level 1 child - receive from L2 and forward to parent */
                close(ipc_L2[1]);

                struct msghdr msg = {0};
                struct iovec iov = {0};
                char buf[1];
                char cmsg_buf[CMSG_SPACE(sizeof(int))];
                int received_fd;

                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                ssize_t n = recvmsg(ipc_L2[0], &msg, 0);
                close(ipc_L2[0]);

                if (n != 1) {
                        close(ipc_sockets[1]);
                        waitpid(pid_l2, NULL, 0);
                        exit(1);
                }

                struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
                if (!cmsg) {
                        close(ipc_sockets[1]);
                        waitpid(pid_l2, NULL, 0);
                        exit(1);
                }
                memcpy(&received_fd, CMSG_DATA(cmsg), sizeof(int));

                /* Wait for L2 child */
                waitpid(pid_l2, NULL, 0);

                /* Forward the socket FD to parent */
                memset(&msg, 0, sizeof(msg));
                buf[0] = 'Z';
                iov.iov_base = buf;
                iov.iov_len = 1;
                msg.msg_iov = &iov;
                msg.msg_iovlen = 1;
                msg.msg_control = cmsg_buf;
                msg.msg_controllen = sizeof(cmsg_buf);

                cmsg = CMSG_FIRSTHDR(&msg);
                cmsg->cmsg_level = SOL_SOCKET;
                cmsg->cmsg_type = SCM_RIGHTS;
                cmsg->cmsg_len = CMSG_LEN(sizeof(int));
                memcpy(CMSG_DATA(cmsg), &received_fd, sizeof(int));

                if (sendmsg(ipc_sockets[1], &msg, 0) < 0) {
                        close(received_fd);
                        close(ipc_sockets[1]);
                        exit(1);
                }

                close(received_fd);
                close(ipc_sockets[1]);
                exit(0);
        }

        /* Parent - receive the socket from the deepest level */
        close(ipc_sockets[1]);

        struct msghdr msg = {0};
        struct iovec iov = {0};
        char buf[1];
        char cmsg_buf[CMSG_SPACE(sizeof(int))];

        iov.iov_base = buf;
        iov.iov_len = 1;
        msg.msg_iov = &iov;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg_buf;
        msg.msg_controllen = sizeof(cmsg_buf);

        ssize_t n = recvmsg(ipc_sockets[0], &msg, 0);
        close(ipc_sockets[0]);

        if (n != 1) {
                free(handle);
                waitpid(pid_l1, NULL, 0);
                SKIP(return, "Failed to receive socket from child");
        }

        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        if (!cmsg) {
                free(handle);
                waitpid(pid_l1, NULL, 0);
                SKIP(return, "Failed to receive socket from child");
        }
        memcpy(&sock_L3A_fd, CMSG_DATA(cmsg), sizeof(int));

        /* Wait for L1 child */
        waitpid(pid_l1, &status, 0);
        ASSERT_TRUE(WIFEXITED(status));
        ASSERT_EQ(WEXITSTATUS(status), 0);

        /*
         * At this point, all child processes have exited. The socket itself
         * doesn't keep the namespace active - we need to call SIOCGSKNS which
         * will resurrect the entire namespace tree by taking active references.
         */

        /* Get network namespace from socket - this resurrects the tree */
        netns_L3A_fd = ioctl(sock_L3A_fd, SIOCGSKNS);
        if (netns_L3A_fd < 0) {
                free(handle);
                close(sock_L3A_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "SIOCGSKNS not supported");
                ASSERT_GE(netns_L3A_fd, 0);
        }

        /* Get namespace ID for net_L3A */
        ret = ioctl(netns_L3A_fd, NS_GET_ID, &netns_L3A_id);
        if (ret < 0) {
                free(handle);
                close(sock_L3A_fd);
                close(netns_L3A_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_ID not supported");
                ASSERT_EQ(ret, 0);
        }

        /* Get owner user namespace chain: userns_L3 -> userns_L2 -> userns_L1 */
        int userns_L3_fd = ioctl(netns_L3A_fd, NS_GET_USERNS);
        if (userns_L3_fd < 0) {
                free(handle);
                close(sock_L3A_fd);
                close(netns_L3A_fd);
                if (errno == ENOTTY || errno == EINVAL)
                        SKIP(return, "NS_GET_USERNS not supported");
                ASSERT_GE(userns_L3_fd, 0);
        }

        ret = ioctl(userns_L3_fd, NS_GET_ID, &userns_L3_id);
        ASSERT_EQ(ret, 0);

        int userns_L2_fd = ioctl(userns_L3_fd, NS_GET_USERNS);
        ASSERT_GE(userns_L2_fd, 0);
        ret = ioctl(userns_L2_fd, NS_GET_ID, &userns_L2_id);
        ASSERT_EQ(ret, 0);

        int userns_L1_fd = ioctl(userns_L2_fd, NS_GET_USERNS);
        ASSERT_GE(userns_L1_fd, 0);
        ret = ioctl(userns_L1_fd, NS_GET_ID, &userns_L1_id);
        ASSERT_EQ(ret, 0);

        close(userns_L1_fd);
        close(userns_L2_fd);
        close(userns_L3_fd);

        TH_LOG("Multi-level hierarchy: net_L3A (id=%llu) -> userns_L3 (id=%llu) -> userns_L2 (id=%llu) -> userns_L1 (id=%llu)",
               netns_L3A_id, userns_L3_id, userns_L2_id, userns_L1_id);

        /*
         * Test 1: Verify net_L3A is visible in listns() after resurrection.
         * The entire ownership chain should be resurrected and visible.
         */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                free(handle);
                close(sock_L3A_fd);
                close(netns_L3A_fd);
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                ASSERT_GE(ret, 0);
        }

        bool found_netns_L3A = false;
        bool found_userns_L1 = false;
        bool found_userns_L2 = false;
        bool found_userns_L3 = false;

        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_L3A_id)
                        found_netns_L3A = true;
                if (ns_ids[i] == userns_L1_id)
                        found_userns_L1 = true;
                if (ns_ids[i] == userns_L2_id)
                        found_userns_L2 = true;
                if (ns_ids[i] == userns_L3_id)
                        found_userns_L3 = true;
        }

        ASSERT_TRUE(found_netns_L3A);
        ASSERT_TRUE(found_userns_L1);
        ASSERT_TRUE(found_userns_L2);
        ASSERT_TRUE(found_userns_L3);
        TH_LOG("Resurrection verified: all namespaces in hierarchy visible in listns()");

        /*
         * Test 2: Verify net_L3A can be reopened via file handle.
         */
        nsfs_fh = (struct nsfs_file_handle *)handle->f_handle;
        nsfs_fh->ns_id = netns_L3A_id;
        nsfs_fh->ns_type = 0;
        nsfs_fh->ns_inum = 0;

        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd < 0) {
                free(handle);
                close(sock_L3A_fd);
                close(netns_L3A_fd);
                if (errno == EOPNOTSUPP || errno == ENOSYS || errno == EBADF)
                        SKIP(return, "open_by_handle_at with FD_NSFS_ROOT not supported");
                TH_LOG("open_by_handle_at failed: %s", strerror(errno));
                ASSERT_GE(reopened_fd, 0);
        }

        close(reopened_fd);
        TH_LOG("File handle test passed: net_L3A can be reopened");

        /*
         * Test 3: Verify that when we close the netns FD (dropping the last
         * active reference), the entire tree becomes inactive and disappears
         * from listns(). The cascade goes: net_L3A drops -> userns_L3 drops ->
         * userns_L2 drops -> userns_L1 drops.
         */
        close(netns_L3A_fd);

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        ASSERT_GE(ret, 0);

        found_netns_L3A = false;
        found_userns_L1 = false;
        found_userns_L2 = false;
        found_userns_L3 = false;

        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_L3A_id)
                        found_netns_L3A = true;
                if (ns_ids[i] == userns_L1_id)
                        found_userns_L1 = true;
                if (ns_ids[i] == userns_L2_id)
                        found_userns_L2 = true;
                if (ns_ids[i] == userns_L3_id)
                        found_userns_L3 = true;
        }

        ASSERT_FALSE(found_netns_L3A);
        ASSERT_FALSE(found_userns_L1);
        ASSERT_FALSE(found_userns_L2);
        ASSERT_FALSE(found_userns_L3);
        TH_LOG("Cascade test passed: all namespaces disappeared after netns FD closed");

        /*
         * Test 4: Verify file handle no longer works for inactive namespace.
         */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd >= 0) {
                close(reopened_fd);
                free(handle);
                ASSERT_TRUE(false); /* Should have failed */
        }
        TH_LOG("Inactive namespace correctly cannot be reopened via file handle");

        /*
         * Test 5: Verify that calling SIOCGSKNS again resurrects the tree again.
         * The socket is still valid, so we can call SIOCGSKNS on it to resurrect
         * the namespace tree once more.
         */
        netns_L3A_fd = ioctl(sock_L3A_fd, SIOCGSKNS);
        ASSERT_GE(netns_L3A_fd, 0);

        TH_LOG("Called SIOCGSKNS again to resurrect the namespace tree");

        /* Verify the namespace tree is resurrected and visible in listns() */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        ASSERT_GE(ret, 0);

        found_netns_L3A = false;
        found_userns_L1 = false;
        found_userns_L2 = false;
        found_userns_L3 = false;

        for (i = 0; i < ret; i++) {
                if (ns_ids[i] == netns_L3A_id)
                        found_netns_L3A = true;
                if (ns_ids[i] == userns_L1_id)
                        found_userns_L1 = true;
                if (ns_ids[i] == userns_L2_id)
                        found_userns_L2 = true;
                if (ns_ids[i] == userns_L3_id)
                        found_userns_L3 = true;
        }

        ASSERT_TRUE(found_netns_L3A);
        ASSERT_TRUE(found_userns_L1);
        ASSERT_TRUE(found_userns_L2);
        ASSERT_TRUE(found_userns_L3);
        TH_LOG("Second resurrection verified: all namespaces in hierarchy visible in listns() again");

        /* Verify we can reopen via file handle again */
        reopened_fd = open_by_handle_at(FD_NSFS_ROOT, handle, O_RDONLY);
        if (reopened_fd < 0) {
                free(handle);
                close(sock_L3A_fd);
                close(netns_L3A_fd);
                TH_LOG("open_by_handle_at failed after second resurrection: %s", strerror(errno));
                ASSERT_GE(reopened_fd, 0);
        }

        close(reopened_fd);
        TH_LOG("File handle test passed: net_L3A can be reopened after second resurrection");

        /* Final cleanup */
        close(sock_L3A_fd);
        close(netns_L3A_fd);
        free(handle);
}

TEST_HARNESS_MAIN