xref: /linux/tools/testing/selftests/namespaces/listns_test.c (revision 4080b9d946f4eddd055d7470e55505f8da434c33)
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 <linux/nsfs.h>
11 #include <sys/ioctl.h>
12 #include <sys/stat.h>
13 #include <sys/syscall.h>
14 #include <sys/types.h>
15 #include <sys/wait.h>
16 #include <unistd.h>
17 #include "../kselftest_harness.h"
18 #include "../filesystems/utils.h"
19 #include "wrappers.h"
20 
21 /*
22  * Test basic listns() functionality with the unified namespace tree.
23  * List all active namespaces globally.
24  */
25 TEST(listns_basic_unified)
26 {
27 	struct ns_id_req req = {
28 		.size = sizeof(req),
29 		.spare = 0,
30 		.ns_id = 0,
31 		.ns_type = 0,  /* All types */
32 		.spare2 = 0,
33 		.user_ns_id = 0,  /* Global listing */
34 	};
35 	__u64 ns_ids[100];
36 	ssize_t ret;
37 
38 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
39 	if (ret < 0) {
40 		if (errno == ENOSYS)
41 			SKIP(return, "listns() not supported");
42 		TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
43 		ASSERT_TRUE(false);
44 	}
45 
46 	/* Should find at least the initial namespaces */
47 	ASSERT_GT(ret, 0);
48 	TH_LOG("Found %zd active namespaces", ret);
49 
50 	/* Verify all returned IDs are non-zero */
51 	for (ssize_t i = 0; i < ret; i++) {
52 		ASSERT_NE(ns_ids[i], 0);
53 		TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
54 	}
55 }
56 
57 /*
58  * Test listns() with type filtering.
59  * List only network namespaces.
60  */
61 TEST(listns_filter_by_type)
62 {
63 	struct ns_id_req req = {
64 		.size = sizeof(req),
65 		.spare = 0,
66 		.ns_id = 0,
67 		.ns_type = CLONE_NEWNET,  /* Only network namespaces */
68 		.spare2 = 0,
69 		.user_ns_id = 0,
70 	};
71 	__u64 ns_ids[100];
72 	ssize_t ret;
73 
74 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
75 	if (ret < 0) {
76 		if (errno == ENOSYS)
77 			SKIP(return, "listns() not supported");
78 		TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
79 		ASSERT_TRUE(false);
80 	}
81 	ASSERT_GE(ret, 0);
82 
83 	/* Should find at least init_net */
84 	ASSERT_GT(ret, 0);
85 	TH_LOG("Found %zd active network namespaces", ret);
86 
87 	/* Verify we can open each namespace and it's actually a network namespace */
88 	for (ssize_t i = 0; i < ret && i < 5; i++) {
89 		struct nsfs_file_handle nsfh = {
90 			.ns_id = ns_ids[i],
91 			.ns_type = CLONE_NEWNET,
92 			.ns_inum = 0,
93 		};
94 		struct file_handle *fh;
95 		int fd;
96 
97 		fh = (struct file_handle *)malloc(sizeof(*fh) + sizeof(nsfh));
98 		ASSERT_NE(fh, NULL);
99 		fh->handle_bytes = sizeof(nsfh);
100 		fh->handle_type = 0;
101 		memcpy(fh->f_handle, &nsfh, sizeof(nsfh));
102 
103 		fd = open_by_handle_at(-10003, fh, O_RDONLY);
104 		free(fh);
105 
106 		if (fd >= 0) {
107 			int ns_type;
108 			/* Verify it's a network namespace via ioctl */
109 			ns_type = ioctl(fd, NS_GET_NSTYPE);
110 			if (ns_type >= 0) {
111 				ASSERT_EQ(ns_type, CLONE_NEWNET);
112 			}
113 			close(fd);
114 		}
115 	}
116 }
117 
118 /*
119  * Test listns() pagination.
120  * List namespaces in batches.
121  */
122 TEST(listns_pagination)
123 {
124 	struct ns_id_req req = {
125 		.size = sizeof(req),
126 		.spare = 0,
127 		.ns_id = 0,
128 		.ns_type = 0,
129 		.spare2 = 0,
130 		.user_ns_id = 0,
131 	};
132 	__u64 batch1[2], batch2[2];
133 	ssize_t ret1, ret2;
134 
135 	/* Get first batch */
136 	ret1 = sys_listns(&req, batch1, ARRAY_SIZE(batch1), 0);
137 	if (ret1 < 0) {
138 		if (errno == ENOSYS)
139 			SKIP(return, "listns() not supported");
140 		TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
141 		ASSERT_TRUE(false);
142 	}
143 	ASSERT_GE(ret1, 0);
144 
145 	if (ret1 == 0)
146 		SKIP(return, "No namespaces found");
147 
148 	TH_LOG("First batch: %zd namespaces", ret1);
149 
150 	/* Get second batch using last ID from first batch */
151 	if (ret1 == ARRAY_SIZE(batch1)) {
152 		req.ns_id = batch1[ret1 - 1];
153 		ret2 = sys_listns(&req, batch2, ARRAY_SIZE(batch2), 0);
154 		ASSERT_GE(ret2, 0);
155 
156 		TH_LOG("Second batch: %zd namespaces (after ns_id=%llu)",
157 		       ret2, (unsigned long long)req.ns_id);
158 
159 		/* If we got more results, verify IDs are monotonically increasing */
160 		if (ret2 > 0) {
161 			ASSERT_GT(batch2[0], batch1[ret1 - 1]);
162 			TH_LOG("Pagination working: %llu > %llu",
163 			       (unsigned long long)batch2[0],
164 			       (unsigned long long)batch1[ret1 - 1]);
165 		}
166 	} else {
167 		TH_LOG("All namespaces fit in first batch");
168 	}
169 }
170 
171 /*
172  * Test listns() with LISTNS_CURRENT_USER.
173  * List namespaces owned by current user namespace.
174  */
175 TEST(listns_current_user)
176 {
177 	struct ns_id_req req = {
178 		.size = sizeof(req),
179 		.spare = 0,
180 		.ns_id = 0,
181 		.ns_type = 0,
182 		.spare2 = 0,
183 		.user_ns_id = LISTNS_CURRENT_USER,
184 	};
185 	__u64 ns_ids[100];
186 	ssize_t ret;
187 
188 	ret = sys_listns(&req, ns_ids, ARRAY_SIZE(ns_ids), 0);
189 	if (ret < 0) {
190 		if (errno == ENOSYS)
191 			SKIP(return, "listns() not supported");
192 		TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
193 		ASSERT_TRUE(false);
194 	}
195 	ASSERT_GE(ret, 0);
196 
197 	/* Should find at least the initial namespaces if we're in init_user_ns */
198 	TH_LOG("Found %zd namespaces owned by current user namespace", ret);
199 
200 	for (ssize_t i = 0; i < ret; i++)
201 		TH_LOG("  [%zd] ns_id: %llu", i, (unsigned long long)ns_ids[i]);
202 }
203 
204 /*
205  * Test that listns() only returns active namespaces.
206  * Create a namespace, let it become inactive, verify it's not listed.
207  */
208 TEST(listns_only_active)
209 {
210 	struct ns_id_req req = {
211 		.size = sizeof(req),
212 		.spare = 0,
213 		.ns_id = 0,
214 		.ns_type = CLONE_NEWNET,
215 		.spare2 = 0,
216 		.user_ns_id = 0,
217 	};
218 	__u64 ns_ids_before[100], ns_ids_after[100];
219 	ssize_t ret_before, ret_after;
220 	int pipefd[2];
221 	pid_t pid;
222 	__u64 new_ns_id = 0;
223 	int status;
224 
225 	/* Get initial list */
226 	ret_before = sys_listns(&req, ns_ids_before, ARRAY_SIZE(ns_ids_before), 0);
227 	if (ret_before < 0) {
228 		if (errno == ENOSYS)
229 			SKIP(return, "listns() not supported");
230 		TH_LOG("listns failed: %s (errno=%d)", strerror(errno), errno);
231 		ASSERT_TRUE(false);
232 	}
233 	ASSERT_GE(ret_before, 0);
234 
235 	TH_LOG("Before: %zd active network namespaces", ret_before);
236 
237 	/* Create a new namespace in a child process and get its ID */
238 	ASSERT_EQ(pipe(pipefd), 0);
239 
240 	pid = fork();
241 	ASSERT_GE(pid, 0);
242 
243 	if (pid == 0) {
244 		int fd;
245 		__u64 ns_id;
246 
247 		close(pipefd[0]);
248 
249 		/* Create new network namespace */
250 		if (unshare(CLONE_NEWNET) < 0) {
251 			close(pipefd[1]);
252 			exit(1);
253 		}
254 
255 		/* Get its ID */
256 		fd = open("/proc/self/ns/net", O_RDONLY);
257 		if (fd < 0) {
258 			close(pipefd[1]);
259 			exit(1);
260 		}
261 
262 		if (ioctl(fd, NS_GET_ID, &ns_id) < 0) {
263 			close(fd);
264 			close(pipefd[1]);
265 			exit(1);
266 		}
267 		close(fd);
268 
269 		/* Send ID to parent */
270 		write(pipefd[1], &ns_id, sizeof(ns_id));
271 		close(pipefd[1]);
272 
273 		/* Keep namespace active briefly */
274 		usleep(100000);
275 		exit(0);
276 	}
277 
278 	/* Parent reads the new namespace ID */
279 	{
280 		int bytes;
281 
282 		close(pipefd[1]);
283 		bytes = read(pipefd[0], &new_ns_id, sizeof(new_ns_id));
284 		close(pipefd[0]);
285 
286 		if (bytes == sizeof(new_ns_id)) {
287 			__u64 ns_ids_during[100];
288 			int ret_during;
289 
290 			TH_LOG("Child created namespace with ID %llu", (unsigned long long)new_ns_id);
291 
292 			/* List namespaces while child is still alive - should see new one */
293 			ret_during = sys_listns(&req, ns_ids_during, ARRAY_SIZE(ns_ids_during), 0);
294 			ASSERT_GE(ret_during, 0);
295 			TH_LOG("During: %d active network namespaces", ret_during);
296 
297 			/* Should have more namespaces than before */
298 			ASSERT_GE(ret_during, ret_before);
299 		}
300 	}
301 
302 	/* Wait for child to exit */
303 	waitpid(pid, &status, 0);
304 
305 	/* Give time for namespace to become inactive */
306 	usleep(100000);
307 
308 	/* List namespaces after child exits - should not see new one */
309 	ret_after = sys_listns(&req, ns_ids_after, ARRAY_SIZE(ns_ids_after), 0);
310 	ASSERT_GE(ret_after, 0);
311 	TH_LOG("After: %zd active network namespaces", ret_after);
312 
313 	/* Verify the new namespace ID is not in the after list */
314 	if (new_ns_id != 0) {
315 		bool found = false;
316 
317 		for (ssize_t i = 0; i < ret_after; i++) {
318 			if (ns_ids_after[i] == new_ns_id) {
319 				found = true;
320 				break;
321 			}
322 		}
323 		ASSERT_FALSE(found);
324 	}
325 }
326 
327 TEST_HARNESS_MAIN
328