root/tools/testing/selftests/namespaces/listns_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 <linux/nsfs.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include "../kselftest_harness.h"
#include "../filesystems/utils.h"
#include "wrappers.h"

/*
 * Test basic listns() functionality with the unified namespace tree.
 * List all active namespaces globally.
 */
TEST(listns_basic_unified)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = 0,  /* All types */
                .spare2 = 0,
                .user_ns_id = 0,  /* Global listing */
        };
        __u64 ns_ids[100];
        ssize_t ret;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }

        /* Should find at least the initial namespaces */
        ASSERT_GT(ret, 0);
        TH_LOG("Found %zd active namespaces", ret);

        /* Verify all returned IDs are non-zero */
        for (ssize_t i = 0; i < ret; i++) {
                ASSERT_NE(ns_ids[i], 0);
                TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
        }
}

/*
 * Test listns() with type filtering.
 * List only network namespaces.
 */
TEST(listns_filter_by_type)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWNET,  /* Only network namespaces */
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[100];
        ssize_t ret;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }
        ASSERT_GE(ret, 0);

        /* Should find at least init_net */
        ASSERT_GT(ret, 0);
        TH_LOG("Found %zd active network namespaces", ret);

        /* Verify we can open each namespace and it's actually a network namespace */
        for (ssize_t i = 0; i < ret && i < 5; i++) {
                struct nsfs_file_handle nsfh = {
                        .ns_id = ns_ids[i],
                        .ns_type = CLONE_NEWNET,
                        .ns_inum = 0,
                };
                struct file_handle *fh;
                int fd;

                fh = (struct file_handle *)malloc(sizeof(*fh) + sizeof(nsfh));
                ASSERT_NE(fh, NULL);
                fh->handle_bytes = sizeof(nsfh);
                fh->handle_type = 0;
                memcpy(fh->f_handle, &nsfh, sizeof(nsfh));

                fd = open_by_handle_at(-10003, fh, O_RDONLY);
                free(fh);

                if (fd >= 0) {
                        int ns_type;
                        /* Verify it's a network namespace via ioctl */
                        ns_type = ioctl(fd, NS_GET_NSTYPE);
                        if (ns_type >= 0) {
                                ASSERT_EQ(ns_type, CLONE_NEWNET);
                        }
                        close(fd);
                }
        }
}

/*
 * Test listns() pagination.
 * List namespaces in batches.
 */
TEST(listns_pagination)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = 0,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 batch1[2], batch2[2];
        ssize_t ret1, ret2;

        /* Get first batch */
        ret1 = sys_listns(&req, batch1, ARRAY_SIZE(batch1), 0);
        if (ret1 < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }
        ASSERT_GE(ret1, 0);

        if (ret1 == 0)
                SKIP(return, "No namespaces found");

        TH_LOG("First batch: %zd namespaces", ret1);

        /* Get second batch using last ID from first batch */
        if (ret1 == ARRAY_SIZE(batch1)) {
                req.ns_id = batch1[ret1 - 1];
                ret2 = sys_listns(&req, batch2, ARRAY_SIZE(batch2), 0);
                ASSERT_GE(ret2, 0);

                TH_LOG("Second batch: %zd namespaces (after ns_id=%llu)",
                       ret2, (unsigned long long)req.ns_id);

                /* If we got more results, verify IDs are monotonically increasing */
                if (ret2 > 0) {
                        ASSERT_GT(batch2[0], batch1[ret1 - 1]);
                        TH_LOG("Pagination working: %llu > %llu",
                               (unsigned long long)batch2[0],
                               (unsigned long long)batch1[ret1 - 1]);
                }
        } else {
                TH_LOG("All namespaces fit in first batch");
        }
}

/*
 * Test listns() with LISTNS_CURRENT_USER.
 * List namespaces owned by current user namespace.
 */
TEST(listns_current_user)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = 0,
                .spare2 = 0,
                .user_ns_id = LISTNS_CURRENT_USER,
        };
        __u64 ns_ids[100];
        ssize_t ret;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }
        ASSERT_GE(ret, 0);

        /* Should find at least the initial namespaces if we're in init_user_ns */
        TH_LOG("Found %zd namespaces owned by current user namespace", ret);

        for (ssize_t i = 0; i < ret; i++)
                TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
}

/*
 * Test that listns() only returns active namespaces.
 * Create a namespace, let it become inactive, verify it's not listed.
 */
TEST(listns_only_active)
{
        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_before[100], ns_ids_after[100];
        ssize_t ret_before, ret_after;
        int pipefd[2];
        pid_t pid;
        __u64 new_ns_id = 0;
        int status;

        /* Get initial list */
        ret_before = sys_listns(&req, ns_ids_before, ARRAY_SIZE(ns_ids_before), 0);
        if (ret_before < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }
        ASSERT_GE(ret_before, 0);

        TH_LOG("Before: %zd active network namespaces", ret_before);

        /* Create a new namespace in a child process and get its ID */
        ASSERT_EQ(pipe(pipefd), 0);

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

        if (pid == 0) {
                int fd;
                __u64 ns_id;

                close(pipefd[0]);

                /* Create new network namespace */
                if (unshare(CLONE_NEWNET) < 0) {
                        close(pipefd[1]);
                        exit(1);
                }

                /* Get its ID */
                fd = open("/proc/self/ns/net", O_RDONLY);
                if (fd < 0) {
                        close(pipefd[1]);
                        exit(1);
                }

                if (ioctl(fd, NS_GET_ID, &ns_id) < 0) {
                        close(fd);
                        close(pipefd[1]);
                        exit(1);
                }
                close(fd);

                /* Send ID to parent */
                write(pipefd[1], &ns_id, sizeof(ns_id));
                close(pipefd[1]);

                /* Keep namespace active briefly */
                usleep(100000);
                exit(0);
        }

        /* Parent reads the new namespace ID */
        {
                int bytes;

                close(pipefd[1]);
                bytes = read(pipefd[0], &new_ns_id, sizeof(new_ns_id));
                close(pipefd[0]);

                if (bytes == sizeof(new_ns_id)) {
                        __u64 ns_ids_during[100];
                        int ret_during;

                        TH_LOG("Child created namespace with ID %llu", (unsigned long long)new_ns_id);

                        /* List namespaces while child is still alive - should see new one */
                        ret_during = sys_listns(&req, ns_ids_during, ARRAY_SIZE(ns_ids_during), 0);
                        ASSERT_GE(ret_during, 0);
                        TH_LOG("During: %d active network namespaces", ret_during);

                        /* Should have more namespaces than before */
                        ASSERT_GE(ret_during, ret_before);
                }
        }

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

        /* Give time for namespace to become inactive */
        usleep(100000);

        /* List namespaces after child exits - should not see new one */
        ret_after = sys_listns(&req, ns_ids_after, ARRAY_SIZE(ns_ids_after), 0);
        ASSERT_GE(ret_after, 0);
        TH_LOG("After: %zd active network namespaces", ret_after);

        /* Verify the new namespace ID is not in the after list */
        if (new_ns_id != 0) {
                bool found = false;

                for (ssize_t i = 0; i < ret_after; i++) {
                        if (ns_ids_after[i] == new_ns_id) {
                                found = true;
                                break;
                        }
                }
                ASSERT_FALSE(found);
        }
}

/*
 * Test listns() with specific user namespace ID.
 * Create a user namespace and list namespaces it owns.
 */
TEST(listns_specific_userns)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = 0,
                .spare2 = 0,
                .user_ns_id = 0,  /* Will be filled with created userns ID */
        };
        __u64 ns_ids[100];
        int sv[2];
        pid_t pid;
        int status;
        __u64 user_ns_id = 0;
        int bytes;
        ssize_t ret;

        ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);

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

        if (pid == 0) {
                int fd;
                __u64 ns_id;
                char buf;

                close(sv[0]);

                /* Create new user namespace */
                if (setup_userns() < 0) {
                        close(sv[1]);
                        exit(1);
                }

                /* Get user namespace ID */
                fd = open("/proc/self/ns/user", O_RDONLY);
                if (fd < 0) {
                        close(sv[1]);
                        exit(1);
                }

                if (ioctl(fd, NS_GET_ID, &ns_id) < 0) {
                        close(fd);
                        close(sv[1]);
                        exit(1);
                }
                close(fd);

                /* Send ID to parent */
                if (write(sv[1], &ns_id, sizeof(ns_id)) != sizeof(ns_id)) {
                        close(sv[1]);
                        exit(1);
                }

                /* Create some namespaces owned by this user namespace */
                unshare(CLONE_NEWNET);
                unshare(CLONE_NEWUTS);

                /* Wait for parent signal */
                if (read(sv[1], &buf, 1) != 1) {
                        close(sv[1]);
                        exit(1);
                }
                close(sv[1]);
                exit(0);
        }

        /* Parent */
        close(sv[1]);
        bytes = read(sv[0], &user_ns_id, sizeof(user_ns_id));

        if (bytes != sizeof(user_ns_id)) {
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                SKIP(return, "Failed to get user namespace ID from child");
        }

        TH_LOG("Child created user namespace with ID %llu", (unsigned long long)user_ns_id);

        /* List namespaces owned by this user namespace */
        req.user_ns_id = user_ns_id;
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

        if (ret < 0) {
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                if (errno == ENOSYS) {
                        SKIP(return, "listns() not supported");
                }
                ASSERT_GE(ret, 0);
        }

        TH_LOG("Found %zd namespaces owned by user namespace %llu", ret,
               (unsigned long long)user_ns_id);

        /* Should find at least the network and UTS namespaces we created */
        if (ret > 0) {
                for (ssize_t i = 0; i < ret && i < 10; i++)
                        TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
        }

        /* Signal child to exit */
        if (write(sv[0], "X", 1) != 1) {
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                ASSERT_TRUE(false);
        }
        close(sv[0]);
        waitpid(pid, &status, 0);
}

/*
 * Test listns() with multiple namespace types filter.
 */
TEST(listns_multiple_types)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWNET | CLONE_NEWUTS,  /* Network and UTS */
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[100];
        ssize_t ret;

        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (ret < 0) {
                if (errno == ENOSYS)
                        SKIP(return, "listns() not supported");
                TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
                ASSERT_TRUE(false);
        }
        ASSERT_GE(ret, 0);

        TH_LOG("Found %zd active network/UTS namespaces", ret);

        for (ssize_t i = 0; i < ret; i++)
                TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
}

/*
 * Test that hierarchical active reference propagation keeps parent
 * user namespaces visible in listns().
 */
TEST(listns_hierarchical_visibility)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = CLONE_NEWUSER,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 parent_ns_id = 0, child_ns_id = 0;
        int sv[2];
        pid_t pid;
        int status;
        int bytes;
        __u64 ns_ids[100];
        ssize_t ret;
        bool found_parent, found_child;

        ASSERT_EQ(socketpair(AF_UNIX, SOCK_STREAM, 0, sv), 0);

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

        if (pid == 0) {
                int fd;
                char buf;

                close(sv[0]);

                /* Create parent user namespace */
                if (setup_userns() < 0) {
                        close(sv[1]);
                        exit(1);
                }

                fd = open("/proc/self/ns/user", O_RDONLY);
                if (fd < 0) {
                        close(sv[1]);
                        exit(1);
                }

                if (ioctl(fd, NS_GET_ID, &parent_ns_id) < 0) {
                        close(fd);
                        close(sv[1]);
                        exit(1);
                }
                close(fd);

                /* Create child user namespace */
                if (setup_userns() < 0) {
                        close(sv[1]);
                        exit(1);
                }

                fd = open("/proc/self/ns/user", O_RDONLY);
                if (fd < 0) {
                        close(sv[1]);
                        exit(1);
                }

                if (ioctl(fd, NS_GET_ID, &child_ns_id) < 0) {
                        close(fd);
                        close(sv[1]);
                        exit(1);
                }
                close(fd);

                /* Send both IDs to parent */
                if (write(sv[1], &parent_ns_id, sizeof(parent_ns_id)) != sizeof(parent_ns_id)) {
                        close(sv[1]);
                        exit(1);
                }
                if (write(sv[1], &child_ns_id, sizeof(child_ns_id)) != sizeof(child_ns_id)) {
                        close(sv[1]);
                        exit(1);
                }

                /* Wait for parent signal */
                if (read(sv[1], &buf, 1) != 1) {
                        close(sv[1]);
                        exit(1);
                }
                close(sv[1]);
                exit(0);
        }

        /* Parent */
        close(sv[1]);

        /* Read both namespace IDs */
        bytes = read(sv[0], &parent_ns_id, sizeof(parent_ns_id));
        bytes += read(sv[0], &child_ns_id, sizeof(child_ns_id));

        if (bytes != (int)(2 * sizeof(__u64))) {
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                SKIP(return, "Failed to get namespace IDs from child");
        }

        TH_LOG("Parent user namespace ID: %llu", (unsigned long long)parent_ns_id);
        TH_LOG("Child user namespace ID: %llu", (unsigned long long)child_ns_id);

        /* List all user namespaces */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);

        if (ret < 0 && errno == ENOSYS) {
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                SKIP(return, "listns() not supported");
        }

        ASSERT_GE(ret, 0);
        TH_LOG("Found %zd active user namespaces", ret);

        /* Both parent and child should be visible (active due to child process) */
        found_parent = false;
        found_child = false;
        for (ssize_t i = 0; i < ret; i++) {
                if (ns_ids[i] == parent_ns_id)
                        found_parent = true;
                if (ns_ids[i] == child_ns_id)
                        found_child = true;
        }

        TH_LOG("Parent namespace %s, child namespace %s",
               found_parent ? "found" : "NOT FOUND",
               found_child ? "found" : "NOT FOUND");

        ASSERT_TRUE(found_child);
        /* With hierarchical propagation, parent should also be active */
        ASSERT_TRUE(found_parent);

        /* Signal child to exit */
        if (write(sv[0], "X", 1) != 1) {
                close(sv[0]);
                kill(pid, SIGKILL);
                waitpid(pid, NULL, 0);
                ASSERT_TRUE(false);
        }
        close(sv[0]);
        waitpid(pid, &status, 0);
}

/*
 * Test error cases for listns().
 */
TEST(listns_error_cases)
{
        struct ns_id_req req = {
                .size = sizeof(req),
                .spare = 0,
                .ns_id = 0,
                .ns_type = 0,
                .spare2 = 0,
                .user_ns_id = 0,
        };
        __u64 ns_ids[10];
        int ret;

        /* Test with invalid flags */
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0xFFFF);
        if (errno == ENOSYS) {
                /* listns() not supported, skip this check */
        } else {
                ASSERT_LT(ret, 0);
                ASSERT_EQ(errno, EINVAL);
        }

        /* Test with NULL ns_ids array */
        ret = sys_listns(&req, NULL, 10, 0);
        ASSERT_LT(ret, 0);

        /* Test with invalid spare field */
        req.spare = 1;
        ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
        if (errno == ENOSYS) {
                /* listns() not supported, skip this check */
        } else {
                ASSERT_LT(ret, 0);
                ASSERT_EQ(errno, EINVAL);
        }
        req.spare = 0;

        /* Test with huge nr_ns_ids */
        ret = sys_listns(&req, ns_ids, 2000000, 0);
        if (errno == ENOSYS) {
                /* listns() not supported, skip this check */
        } else {
                ASSERT_LT(ret, 0);
        }
}

TEST_HARNESS_MAIN