]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
selftests/namespaces: thirteenth active reference count tests
authorChristian Brauner <brauner@kernel.org>
Wed, 29 Oct 2025 12:20:48 +0000 (13:20 +0100)
committerChristian Brauner <brauner@kernel.org>
Mon, 3 Nov 2025 16:41:20 +0000 (17:41 +0100)
Test that parent stays active as long as ANY child is active.
Create parent user namespace with two child net namespaces.
Parent should remain active until BOTH children are inactive.

Link: https://patch.msgid.link/20251029-work-namespace-nstree-listns-v4-35-2e6f823ebdc0@kernel.org
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Christian Brauner <brauner@kernel.org>
tools/testing/selftests/namespaces/ns_active_ref_test.c

index b1a454dac9d0d0b65cef0c0de645f408dd123600..25f06e62306494c2ff3e18762925dbe47f3121f7 100644 (file)
@@ -1623,4 +1623,198 @@ TEST(ns_deep_hierarchy_propagation)
        ASSERT_LT(ua_fd, 0);
 }
 
+/*
+ * Test that parent stays active as long as ANY child is active.
+ * Create parent user namespace with two child net namespaces.
+ * Parent should remain active until BOTH children are inactive.
+ */
+TEST(ns_parent_multiple_children_refcount)
+{
+       struct file_handle *parent_handle, *net1_handle, *net2_handle;
+       int ret, pipefd[2], syncpipe[2];
+       pid_t pid;
+       int status;
+       __u64 p_id, n1_id, n2_id;
+       char p_buf[sizeof(*parent_handle) + MAX_HANDLE_SZ];
+       char n1_buf[sizeof(*net1_handle) + MAX_HANDLE_SZ];
+       char n2_buf[sizeof(*net2_handle) + MAX_HANDLE_SZ];
+       char sync_byte;
+
+       ASSERT_EQ(pipe(pipefd), 0);
+       ASSERT_EQ(pipe(syncpipe), 0);
+       pid = fork();
+       ASSERT_GE(pid, 0);
+
+       if (pid == 0) {
+               close(pipefd[0]);
+               close(syncpipe[1]);
+
+               /* Create parent user namespace */
+               if (setup_userns() < 0) {
+                       close(pipefd[1]);
+                       exit(1);
+               }
+
+               int p_fd = open("/proc/self/ns/user", O_RDONLY);
+               if (p_fd < 0) {
+                       close(pipefd[1]);
+                       exit(1);
+               }
+               if (ioctl(p_fd, NS_GET_ID, &p_id) < 0) {
+                       close(p_fd);
+                       close(pipefd[1]);
+                       exit(1);
+               }
+               close(p_fd);
+
+               /* Create first network namespace */
+               if (unshare(CLONE_NEWNET) < 0) {
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+
+               int n1_fd = open("/proc/self/ns/net", O_RDONLY);
+               if (n1_fd < 0) {
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+               if (ioctl(n1_fd, NS_GET_ID, &n1_id) < 0) {
+                       close(n1_fd);
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+               /* Keep n1_fd open so first namespace stays active */
+
+               /* Create second network namespace */
+               if (unshare(CLONE_NEWNET) < 0) {
+                       close(n1_fd);
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+
+               int n2_fd = open("/proc/self/ns/net", O_RDONLY);
+               if (n2_fd < 0) {
+                       close(n1_fd);
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+               if (ioctl(n2_fd, NS_GET_ID, &n2_id) < 0) {
+                       close(n1_fd);
+                       close(n2_fd);
+                       close(pipefd[1]);
+                       close(syncpipe[0]);
+                       exit(1);
+               }
+               /* Keep both n1_fd and n2_fd open */
+
+               /* Send all namespace IDs */
+               write(pipefd[1], &p_id, sizeof(p_id));
+               write(pipefd[1], &n1_id, sizeof(n1_id));
+               write(pipefd[1], &n2_id, sizeof(n2_id));
+               close(pipefd[1]);
+
+               /* Wait for parent to signal before exiting */
+               read(syncpipe[0], &sync_byte, 1);
+               close(syncpipe[0]);
+               exit(0);
+       }
+
+       close(pipefd[1]);
+       close(syncpipe[0]);
+
+       /* Read all three namespace IDs - fixed size, no parsing needed */
+       ret = read(pipefd[0], &p_id, sizeof(p_id));
+       if (ret != sizeof(p_id)) {
+               close(pipefd[0]);
+               waitpid(pid, NULL, 0);
+               SKIP(return, "Failed to read parent namespace ID");
+       }
+
+       ret = read(pipefd[0], &n1_id, sizeof(n1_id));
+       if (ret != sizeof(n1_id)) {
+               close(pipefd[0]);
+               waitpid(pid, NULL, 0);
+               SKIP(return, "Failed to read first network namespace ID");
+       }
+
+       ret = read(pipefd[0], &n2_id, sizeof(n2_id));
+       close(pipefd[0]);
+       if (ret != sizeof(n2_id)) {
+               waitpid(pid, NULL, 0);
+               SKIP(return, "Failed to read second network namespace ID");
+       }
+
+       /* Construct file handles from namespace IDs */
+       parent_handle = (struct file_handle *)p_buf;
+       parent_handle->handle_bytes = sizeof(struct nsfs_file_handle);
+       parent_handle->handle_type = FILEID_NSFS;
+       struct nsfs_file_handle *p_fh = (struct nsfs_file_handle *)parent_handle->f_handle;
+       p_fh->ns_id = p_id;
+       p_fh->ns_type = 0;
+       p_fh->ns_inum = 0;
+
+       net1_handle = (struct file_handle *)n1_buf;
+       net1_handle->handle_bytes = sizeof(struct nsfs_file_handle);
+       net1_handle->handle_type = FILEID_NSFS;
+       struct nsfs_file_handle *n1_fh = (struct nsfs_file_handle *)net1_handle->f_handle;
+       n1_fh->ns_id = n1_id;
+       n1_fh->ns_type = 0;
+       n1_fh->ns_inum = 0;
+
+       net2_handle = (struct file_handle *)n2_buf;
+       net2_handle->handle_bytes = sizeof(struct nsfs_file_handle);
+       net2_handle->handle_type = FILEID_NSFS;
+       struct nsfs_file_handle *n2_fh = (struct nsfs_file_handle *)net2_handle->f_handle;
+       n2_fh->ns_id = n2_id;
+       n2_fh->ns_type = 0;
+       n2_fh->ns_inum = 0;
+
+       /* Open both net namespaces while child is still alive */
+       int n1_fd = open_by_handle_at(FD_NSFS_ROOT, net1_handle, O_RDONLY);
+       int n2_fd = open_by_handle_at(FD_NSFS_ROOT, net2_handle, O_RDONLY);
+       if (n1_fd < 0 || n2_fd < 0) {
+               if (n1_fd >= 0) close(n1_fd);
+               if (n2_fd >= 0) close(n2_fd);
+               sync_byte = 'G';
+               write(syncpipe[1], &sync_byte, 1);
+               close(syncpipe[1]);
+               waitpid(pid, NULL, 0);
+               SKIP(return, "Failed to open net namespaces");
+       }
+
+       /* Signal child that we have opened the namespaces */
+       sync_byte = 'G';
+       write(syncpipe[1], &sync_byte, 1);
+       close(syncpipe[1]);
+
+       /* Wait for child to exit */
+       waitpid(pid, &status, 0);
+       ASSERT_TRUE(WIFEXITED(status));
+       ASSERT_EQ(WEXITSTATUS(status), 0);
+
+       /* Parent should be active (has 2 active children) */
+       TH_LOG("Both net namespaces active - parent should be active");
+       int p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
+       ASSERT_GE(p_fd, 0);
+       close(p_fd);
+
+       /* Close first net namespace - parent should STILL be active */
+       TH_LOG("Closing first net ns - parent should still be active");
+       close(n1_fd);
+       p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
+       ASSERT_GE(p_fd, 0);
+       close(p_fd);
+
+       /* Close second net namespace - parent should become inactive */
+       TH_LOG("Closing second net ns - parent should become inactive");
+       close(n2_fd);
+       p_fd = open_by_handle_at(FD_NSFS_ROOT, parent_handle, O_RDONLY);
+       ASSERT_LT(p_fd, 0);
+}
+
 TEST_HARNESS_MAIN