]>
Commit | Line | Data |
---|---|---|
6054c1eb | 1 | /* SPDX-License-Identifier: GPL-2.0 */ |
0dc34c77 EB |
2 | #define _ATFILE_SOURCE |
3 | #include <sys/types.h> | |
4 | #include <sys/stat.h> | |
5 | #include <sys/wait.h> | |
6 | #include <sys/inotify.h> | |
7 | #include <sys/mount.h> | |
0dc34c77 EB |
8 | #include <sys/syscall.h> |
9 | #include <stdio.h> | |
10 | #include <string.h> | |
11 | #include <sched.h> | |
12 | #include <fcntl.h> | |
13 | #include <dirent.h> | |
14 | #include <errno.h> | |
15 | #include <unistd.h> | |
9a7b3d91 | 16 | #include <ctype.h> |
d652ccbf | 17 | #include <linux/limits.h> |
0dc34c77 | 18 | |
d182ee13 ND |
19 | #include <linux/net_namespace.h> |
20 | ||
0dc34c77 | 21 | #include "utils.h" |
4952b459 | 22 | #include "list.h" |
0dc34c77 | 23 | #include "ip_common.h" |
eb67e449 | 24 | #include "namespace.h" |
e93d9221 | 25 | #include "json_print.h" |
0dc34c77 | 26 | |
8e2d47dc | 27 | static int usage(void) |
0dc34c77 | 28 | { |
8589eb4e MC |
29 | fprintf(stderr, |
30 | "Usage: ip netns list\n" | |
31 | " ip netns add NAME\n" | |
32 | " ip netns attach NAME PID\n" | |
33 | " ip netns set NAME NETNSID\n" | |
34 | " ip [-all] netns delete [NAME]\n" | |
35 | " ip netns identify [PID]\n" | |
36 | " ip netns pids NAME\n" | |
37 | " ip [-all] netns exec [NAME] cmd ...\n" | |
38 | " ip netns monitor\n" | |
39 | " ip netns list-id\n" | |
40 | "NETNSID := auto | POSITIVE-INT\n"); | |
a05f6511 | 41 | exit(-1); |
0dc34c77 EB |
42 | } |
43 | ||
d652ccbf ND |
44 | /* This socket is used to get nsid */ |
45 | static struct rtnl_handle rtnsh = { .fd = -1 }; | |
46 | ||
4c7d9a58 | 47 | static int have_rtnl_getnsid = -1; |
b2e29223 | 48 | static int saved_netns = -1; |
4c7d9a58 | 49 | |
cd554f2c | 50 | static int ipnetns_accept_msg(struct rtnl_ctrl_data *ctrl, |
4c7d9a58 ND |
51 | struct nlmsghdr *n, void *arg) |
52 | { | |
53 | struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(n); | |
54 | ||
55 | if (n->nlmsg_type == NLMSG_ERROR && | |
56 | (err->error == -EOPNOTSUPP || err->error == -EINVAL)) | |
57 | have_rtnl_getnsid = 0; | |
58 | else | |
59 | have_rtnl_getnsid = 1; | |
60 | return -1; | |
61 | } | |
62 | ||
63 | static int ipnetns_have_nsid(void) | |
64 | { | |
65 | struct { | |
66 | struct nlmsghdr n; | |
67 | struct rtgenmsg g; | |
68 | char buf[1024]; | |
d17b136f PS |
69 | } req = { |
70 | .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | |
71 | .n.nlmsg_flags = NLM_F_REQUEST, | |
72 | .n.nlmsg_type = RTM_GETNSID, | |
73 | .g.rtgen_family = AF_UNSPEC, | |
74 | }; | |
4c7d9a58 ND |
75 | int fd; |
76 | ||
77 | if (have_rtnl_getnsid < 0) { | |
4c7d9a58 ND |
78 | fd = open("/proc/self/ns/net", O_RDONLY); |
79 | if (fd < 0) { | |
c44003f7 LZ |
80 | have_rtnl_getnsid = 0; |
81 | return 0; | |
4c7d9a58 ND |
82 | } |
83 | ||
84 | addattr32(&req.n, 1024, NETNSA_FD, fd); | |
85 | ||
86 | if (rtnl_send(&rth, &req.n, req.n.nlmsg_len) < 0) { | |
87 | perror("request send failed"); | |
88 | exit(1); | |
89 | } | |
90 | rtnl_listen(&rth, ipnetns_accept_msg, NULL); | |
91 | close(fd); | |
92 | } | |
93 | ||
94 | return have_rtnl_getnsid; | |
95 | } | |
96 | ||
974ef93b | 97 | int get_netnsid_from_name(const char *name) |
d182ee13 ND |
98 | { |
99 | struct { | |
100 | struct nlmsghdr n; | |
101 | struct rtgenmsg g; | |
102 | char buf[1024]; | |
86bf43c7 | 103 | } req = { |
d17b136f PS |
104 | .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), |
105 | .n.nlmsg_flags = NLM_F_REQUEST, | |
106 | .n.nlmsg_type = RTM_GETNSID, | |
107 | .g.rtgen_family = AF_UNSPEC, | |
108 | }; | |
86bf43c7 | 109 | struct nlmsghdr *answer; |
d182ee13 ND |
110 | struct rtattr *tb[NETNSA_MAX + 1]; |
111 | struct rtgenmsg *rthdr; | |
9bf2c538 | 112 | int len, fd, ret = -1; |
d182ee13 | 113 | |
974ef93b ND |
114 | netns_nsid_socket_init(); |
115 | ||
d182ee13 ND |
116 | fd = netns_get_fd(name); |
117 | if (fd < 0) | |
118 | return fd; | |
119 | ||
120 | addattr32(&req.n, 1024, NETNSA_FD, fd); | |
86bf43c7 | 121 | if (rtnl_talk(&rtnsh, &req.n, &answer) < 0) { |
d182ee13 ND |
122 | close(fd); |
123 | return -2; | |
124 | } | |
125 | close(fd); | |
126 | ||
127 | /* Validate message and parse attributes */ | |
86bf43c7 | 128 | if (answer->nlmsg_type == NLMSG_ERROR) |
9bf2c538 | 129 | goto out; |
d182ee13 | 130 | |
86bf43c7 HL |
131 | rthdr = NLMSG_DATA(answer); |
132 | len = answer->nlmsg_len - NLMSG_SPACE(sizeof(*rthdr)); | |
d182ee13 | 133 | if (len < 0) |
9bf2c538 | 134 | goto out; |
d182ee13 ND |
135 | |
136 | parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len); | |
137 | ||
86bf43c7 | 138 | if (tb[NETNSA_NSID]) { |
9bf2c538 | 139 | ret = rta_getattr_u32(tb[NETNSA_NSID]); |
86bf43c7 | 140 | } |
d182ee13 | 141 | |
9bf2c538 | 142 | out: |
86bf43c7 | 143 | free(answer); |
9bf2c538 | 144 | return ret; |
d182ee13 ND |
145 | } |
146 | ||
d652ccbf ND |
147 | struct nsid_cache { |
148 | struct hlist_node nsid_hash; | |
149 | struct hlist_node name_hash; | |
150 | int nsid; | |
2f29d6bb | 151 | char name[0]; |
d652ccbf ND |
152 | }; |
153 | ||
154 | #define NSIDMAP_SIZE 128 | |
155 | #define NSID_HASH_NSID(nsid) (nsid & (NSIDMAP_SIZE - 1)) | |
156 | #define NSID_HASH_NAME(name) (namehash(name) & (NSIDMAP_SIZE - 1)) | |
157 | ||
158 | static struct hlist_head nsid_head[NSIDMAP_SIZE]; | |
159 | static struct hlist_head name_head[NSIDMAP_SIZE]; | |
160 | ||
161 | static struct nsid_cache *netns_map_get_by_nsid(int nsid) | |
162 | { | |
163 | uint32_t h = NSID_HASH_NSID(nsid); | |
164 | struct hlist_node *n; | |
165 | ||
166 | hlist_for_each(n, &nsid_head[h]) { | |
167 | struct nsid_cache *c = container_of(n, struct nsid_cache, | |
168 | nsid_hash); | |
169 | if (c->nsid == nsid) | |
170 | return c; | |
171 | } | |
172 | ||
173 | return NULL; | |
174 | } | |
175 | ||
9580bad7 ND |
176 | char *get_name_from_nsid(int nsid) |
177 | { | |
178 | struct nsid_cache *c; | |
179 | ||
180 | netns_nsid_socket_init(); | |
181 | netns_map_init(); | |
182 | ||
183 | c = netns_map_get_by_nsid(nsid); | |
184 | if (c) | |
185 | return c->name; | |
186 | ||
187 | return NULL; | |
188 | } | |
189 | ||
2f29d6bb | 190 | static int netns_map_add(int nsid, const char *name) |
d652ccbf ND |
191 | { |
192 | struct nsid_cache *c; | |
193 | uint32_t h; | |
194 | ||
195 | if (netns_map_get_by_nsid(nsid) != NULL) | |
196 | return -EEXIST; | |
197 | ||
a1b4a274 | 198 | c = malloc(sizeof(*c) + strlen(name) + 1); |
d652ccbf ND |
199 | if (c == NULL) { |
200 | perror("malloc"); | |
201 | return -ENOMEM; | |
202 | } | |
203 | c->nsid = nsid; | |
204 | strcpy(c->name, name); | |
205 | ||
206 | h = NSID_HASH_NSID(nsid); | |
207 | hlist_add_head(&c->nsid_hash, &nsid_head[h]); | |
208 | ||
209 | h = NSID_HASH_NAME(name); | |
210 | hlist_add_head(&c->name_hash, &name_head[h]); | |
211 | ||
212 | return 0; | |
213 | } | |
214 | ||
215 | static void netns_map_del(struct nsid_cache *c) | |
216 | { | |
217 | hlist_del(&c->name_hash); | |
218 | hlist_del(&c->nsid_hash); | |
219 | free(c); | |
220 | } | |
221 | ||
e29a8e05 AA |
222 | void netns_nsid_socket_init(void) |
223 | { | |
224 | if (rtnsh.fd > -1 || !ipnetns_have_nsid()) | |
225 | return; | |
226 | ||
227 | if (rtnl_open(&rtnsh, 0) < 0) { | |
228 | fprintf(stderr, "Cannot open rtnetlink\n"); | |
229 | exit(1); | |
230 | } | |
231 | ||
232 | } | |
233 | ||
d652ccbf ND |
234 | void netns_map_init(void) |
235 | { | |
236 | static int initialized; | |
237 | struct dirent *entry; | |
238 | DIR *dir; | |
239 | int nsid; | |
240 | ||
241 | if (initialized || !ipnetns_have_nsid()) | |
242 | return; | |
243 | ||
d652ccbf ND |
244 | dir = opendir(NETNS_RUN_DIR); |
245 | if (!dir) | |
246 | return; | |
247 | ||
248 | while ((entry = readdir(dir)) != NULL) { | |
249 | if (strcmp(entry->d_name, ".") == 0) | |
250 | continue; | |
251 | if (strcmp(entry->d_name, "..") == 0) | |
252 | continue; | |
253 | nsid = get_netnsid_from_name(entry->d_name); | |
254 | ||
255 | if (nsid >= 0) | |
256 | netns_map_add(nsid, entry->d_name); | |
257 | } | |
258 | closedir(dir); | |
259 | initialized = 1; | |
260 | } | |
261 | ||
262 | static int netns_get_name(int nsid, char *name) | |
263 | { | |
264 | struct dirent *entry; | |
265 | DIR *dir; | |
266 | int id; | |
267 | ||
268 | dir = opendir(NETNS_RUN_DIR); | |
269 | if (!dir) | |
270 | return -ENOENT; | |
271 | ||
272 | while ((entry = readdir(dir)) != NULL) { | |
273 | if (strcmp(entry->d_name, ".") == 0) | |
274 | continue; | |
275 | if (strcmp(entry->d_name, "..") == 0) | |
276 | continue; | |
277 | id = get_netnsid_from_name(entry->d_name); | |
278 | ||
279 | if (nsid == id) { | |
280 | strcpy(name, entry->d_name); | |
281 | closedir(dir); | |
282 | return 0; | |
283 | } | |
284 | } | |
285 | closedir(dir); | |
286 | return -ENOENT; | |
287 | } | |
288 | ||
cd554f2c | 289 | int print_nsid(struct nlmsghdr *n, void *arg) |
d652ccbf ND |
290 | { |
291 | struct rtgenmsg *rthdr = NLMSG_DATA(n); | |
292 | struct rtattr *tb[NETNSA_MAX+1]; | |
293 | int len = n->nlmsg_len; | |
294 | FILE *fp = (FILE *)arg; | |
295 | struct nsid_cache *c; | |
296 | char name[NAME_MAX]; | |
297 | int nsid; | |
298 | ||
299 | if (n->nlmsg_type != RTM_NEWNSID && n->nlmsg_type != RTM_DELNSID) | |
300 | return 0; | |
301 | ||
302 | len -= NLMSG_SPACE(sizeof(*rthdr)); | |
303 | if (len < 0) { | |
304 | fprintf(stderr, "BUG: wrong nlmsg len %d in %s\n", len, | |
305 | __func__); | |
306 | return -1; | |
307 | } | |
308 | ||
309 | parse_rtattr(tb, NETNSA_MAX, NETNS_RTA(rthdr), len); | |
310 | if (tb[NETNSA_NSID] == NULL) { | |
311 | fprintf(stderr, "BUG: NETNSA_NSID is missing %s\n", __func__); | |
312 | return -1; | |
313 | } | |
314 | ||
e93d9221 | 315 | open_json_object(NULL); |
d652ccbf | 316 | if (n->nlmsg_type == RTM_DELNSID) |
e93d9221 | 317 | print_bool(PRINT_ANY, "deleted", "Deleted ", true); |
d652ccbf ND |
318 | |
319 | nsid = rta_getattr_u32(tb[NETNSA_NSID]); | |
e93d9221 | 320 | print_uint(PRINT_ANY, "nsid", "nsid %u ", nsid); |
d652ccbf ND |
321 | |
322 | c = netns_map_get_by_nsid(nsid); | |
323 | if (c != NULL) { | |
e93d9221 SH |
324 | print_string(PRINT_ANY, "name", |
325 | "(iproute2 netns name: %s)", c->name); | |
d652ccbf ND |
326 | netns_map_del(c); |
327 | } | |
328 | ||
329 | /* During 'ip monitor nsid', no chance to have new nsid in cache. */ | |
330 | if (c == NULL && n->nlmsg_type == RTM_NEWNSID) | |
331 | if (netns_get_name(nsid, name) == 0) { | |
e93d9221 SH |
332 | print_string(PRINT_ANY, "name", |
333 | "(iproute2 netns name: %s)", name); | |
d652ccbf ND |
334 | netns_map_add(nsid, name); |
335 | } | |
336 | ||
e93d9221 SH |
337 | print_string(PRINT_FP, NULL, "\n", NULL); |
338 | close_json_object(); | |
d652ccbf ND |
339 | fflush(fp); |
340 | return 0; | |
341 | } | |
342 | ||
343 | static int netns_list_id(int argc, char **argv) | |
344 | { | |
345 | if (!ipnetns_have_nsid()) { | |
346 | fprintf(stderr, | |
347 | "RTM_GETNSID is not supported by the kernel.\n"); | |
348 | return -ENOTSUP; | |
349 | } | |
350 | ||
efb0b383 | 351 | if (rtnl_nsiddump_req(&rth, AF_UNSPEC) < 0) { |
d652ccbf ND |
352 | perror("Cannot send dump request"); |
353 | exit(1); | |
354 | } | |
e93d9221 SH |
355 | |
356 | new_json_obj(json); | |
d652ccbf | 357 | if (rtnl_dump_filter(&rth, print_nsid, stdout) < 0) { |
e93d9221 | 358 | delete_json_obj(); |
d652ccbf ND |
359 | fprintf(stderr, "Dump terminated\n"); |
360 | exit(1); | |
361 | } | |
e93d9221 | 362 | delete_json_obj(); |
d652ccbf ND |
363 | return 0; |
364 | } | |
365 | ||
0dc34c77 EB |
366 | static int netns_list(int argc, char **argv) |
367 | { | |
368 | struct dirent *entry; | |
369 | DIR *dir; | |
d182ee13 | 370 | int id; |
0dc34c77 EB |
371 | |
372 | dir = opendir(NETNS_RUN_DIR); | |
373 | if (!dir) | |
a05f6511 | 374 | return 0; |
0dc34c77 | 375 | |
e93d9221 | 376 | new_json_obj(json); |
0dc34c77 EB |
377 | while ((entry = readdir(dir)) != NULL) { |
378 | if (strcmp(entry->d_name, ".") == 0) | |
379 | continue; | |
380 | if (strcmp(entry->d_name, "..") == 0) | |
381 | continue; | |
e93d9221 SH |
382 | |
383 | open_json_object(NULL); | |
384 | print_string(PRINT_ANY, "name", | |
385 | "%s", entry->d_name); | |
4c7d9a58 ND |
386 | if (ipnetns_have_nsid()) { |
387 | id = get_netnsid_from_name(entry->d_name); | |
388 | if (id >= 0) | |
e93d9221 SH |
389 | print_uint(PRINT_ANY, "id", |
390 | " (id: %d)", id); | |
4c7d9a58 | 391 | } |
e93d9221 SH |
392 | print_string(PRINT_FP, NULL, "\n", NULL); |
393 | close_json_object(); | |
0dc34c77 EB |
394 | } |
395 | closedir(dir); | |
e93d9221 | 396 | delete_json_obj(); |
a05f6511 | 397 | return 0; |
0dc34c77 EB |
398 | } |
399 | ||
903818fb MC |
400 | static int do_switch(void *arg) |
401 | { | |
402 | char *netns = arg; | |
403 | ||
404 | /* we just changed namespaces. clear any vrf association | |
405 | * with prior namespace before exec'ing command | |
406 | */ | |
407 | vrf_reset(); | |
408 | ||
409 | return netns_switch(netns); | |
410 | } | |
411 | ||
b13ba03f VK |
412 | static int on_netns_exec(char *nsname, void *arg) |
413 | { | |
414 | char **argv = arg; | |
56f5daac | 415 | |
903818fb MC |
416 | printf("\nnetns: %s\n", nsname); |
417 | cmd_exec(argv[0], argv, true, do_switch, nsname); | |
b13ba03f VK |
418 | return 0; |
419 | } | |
420 | ||
421 | static int netns_exec(int argc, char **argv) | |
422 | { | |
423 | /* Setup the proper environment for apps that are not netns | |
424 | * aware, and execute a program in that environment. | |
425 | */ | |
b13ba03f VK |
426 | if (argc < 1 && !do_all) { |
427 | fprintf(stderr, "No netns name specified\n"); | |
428 | return -1; | |
429 | } | |
430 | if ((argc < 2 && !do_all) || (argc < 1 && do_all)) { | |
431 | fprintf(stderr, "No command specified\n"); | |
432 | return -1; | |
433 | } | |
434 | ||
435 | if (do_all) | |
903818fb | 436 | return netns_foreach(on_netns_exec, argv); |
ee9369a0 | 437 | |
b13ba03f VK |
438 | /* ip must return the status of the child, |
439 | * but do_cmd() will add a minus to this, | |
440 | * so let's add another one here to cancel it. | |
441 | */ | |
903818fb | 442 | return -cmd_exec(argv[1], argv + 1, !!batch_mode, do_switch, argv[0]); |
b13ba03f VK |
443 | } |
444 | ||
9a7b3d91 EB |
445 | static int is_pid(const char *str) |
446 | { | |
447 | int ch; | |
56f5daac | 448 | |
9a7b3d91 EB |
449 | for (; (ch = *str); str++) { |
450 | if (!isdigit(ch)) | |
451 | return 0; | |
452 | } | |
453 | return 1; | |
454 | } | |
455 | ||
456 | static int netns_pids(int argc, char **argv) | |
457 | { | |
458 | const char *name; | |
ea343669 | 459 | char net_path[PATH_MAX]; |
9a7b3d91 EB |
460 | int netns; |
461 | struct stat netst; | |
462 | DIR *dir; | |
463 | struct dirent *entry; | |
464 | ||
465 | if (argc < 1) { | |
466 | fprintf(stderr, "No netns name specified\n"); | |
a05f6511 | 467 | return -1; |
9a7b3d91 EB |
468 | } |
469 | if (argc > 1) { | |
470 | fprintf(stderr, "extra arguments specified\n"); | |
a05f6511 | 471 | return -1; |
9a7b3d91 EB |
472 | } |
473 | ||
474 | name = argv[0]; | |
475 | snprintf(net_path, sizeof(net_path), "%s/%s", NETNS_RUN_DIR, name); | |
476 | netns = open(net_path, O_RDONLY); | |
477 | if (netns < 0) { | |
478 | fprintf(stderr, "Cannot open network namespace: %s\n", | |
479 | strerror(errno)); | |
a05f6511 | 480 | return -1; |
9a7b3d91 EB |
481 | } |
482 | if (fstat(netns, &netst) < 0) { | |
483 | fprintf(stderr, "Stat of netns failed: %s\n", | |
484 | strerror(errno)); | |
a05f6511 | 485 | return -1; |
9a7b3d91 EB |
486 | } |
487 | dir = opendir("/proc/"); | |
488 | if (!dir) { | |
489 | fprintf(stderr, "Open of /proc failed: %s\n", | |
490 | strerror(errno)); | |
a05f6511 | 491 | return -1; |
9a7b3d91 | 492 | } |
56f5daac | 493 | while ((entry = readdir(dir))) { |
ea343669 | 494 | char pid_net_path[PATH_MAX]; |
9a7b3d91 | 495 | struct stat st; |
56f5daac | 496 | |
9a7b3d91 EB |
497 | if (!is_pid(entry->d_name)) |
498 | continue; | |
499 | snprintf(pid_net_path, sizeof(pid_net_path), "/proc/%s/ns/net", | |
500 | entry->d_name); | |
501 | if (stat(pid_net_path, &st) != 0) | |
502 | continue; | |
503 | if ((st.st_dev == netst.st_dev) && | |
504 | (st.st_ino == netst.st_ino)) { | |
505 | printf("%s\n", entry->d_name); | |
506 | } | |
507 | } | |
508 | closedir(dir); | |
a05f6511 | 509 | return 0; |
0612519e | 510 | |
9a7b3d91 EB |
511 | } |
512 | ||
9c49438a | 513 | int netns_identify_pid(const char *pidstr, char *name, int len) |
9a7b3d91 | 514 | { |
ea343669 | 515 | char net_path[PATH_MAX]; |
9a7b3d91 EB |
516 | int netns; |
517 | struct stat netst; | |
518 | DIR *dir; | |
519 | struct dirent *entry; | |
520 | ||
9c49438a | 521 | name[0] = '\0'; |
9a7b3d91 EB |
522 | |
523 | snprintf(net_path, sizeof(net_path), "/proc/%s/ns/net", pidstr); | |
524 | netns = open(net_path, O_RDONLY); | |
525 | if (netns < 0) { | |
526 | fprintf(stderr, "Cannot open network namespace: %s\n", | |
527 | strerror(errno)); | |
a05f6511 | 528 | return -1; |
9a7b3d91 EB |
529 | } |
530 | if (fstat(netns, &netst) < 0) { | |
531 | fprintf(stderr, "Stat of netns failed: %s\n", | |
532 | strerror(errno)); | |
a05f6511 | 533 | return -1; |
9a7b3d91 EB |
534 | } |
535 | dir = opendir(NETNS_RUN_DIR); | |
536 | if (!dir) { | |
537 | /* Succeed treat a missing directory as an empty directory */ | |
538 | if (errno == ENOENT) | |
a05f6511 | 539 | return 0; |
9a7b3d91 EB |
540 | |
541 | fprintf(stderr, "Failed to open directory %s:%s\n", | |
542 | NETNS_RUN_DIR, strerror(errno)); | |
a05f6511 | 543 | return -1; |
9a7b3d91 EB |
544 | } |
545 | ||
56f5daac | 546 | while ((entry = readdir(dir))) { |
ea343669 | 547 | char name_path[PATH_MAX]; |
9a7b3d91 EB |
548 | struct stat st; |
549 | ||
550 | if (strcmp(entry->d_name, ".") == 0) | |
551 | continue; | |
552 | if (strcmp(entry->d_name, "..") == 0) | |
553 | continue; | |
554 | ||
555 | snprintf(name_path, sizeof(name_path), "%s/%s", NETNS_RUN_DIR, | |
556 | entry->d_name); | |
557 | ||
558 | if (stat(name_path, &st) != 0) | |
559 | continue; | |
560 | ||
561 | if ((st.st_dev == netst.st_dev) && | |
562 | (st.st_ino == netst.st_ino)) { | |
18f156bf | 563 | strlcpy(name, entry->d_name, len); |
9a7b3d91 EB |
564 | } |
565 | } | |
566 | closedir(dir); | |
a05f6511 | 567 | return 0; |
0612519e | 568 | |
9a7b3d91 EB |
569 | } |
570 | ||
9c49438a DA |
571 | static int netns_identify(int argc, char **argv) |
572 | { | |
573 | const char *pidstr; | |
574 | char name[256]; | |
575 | int rc; | |
576 | ||
577 | if (argc < 1) { | |
578 | pidstr = "self"; | |
579 | } else if (argc > 1) { | |
580 | fprintf(stderr, "extra arguments specified\n"); | |
581 | return -1; | |
582 | } else { | |
583 | pidstr = argv[0]; | |
584 | if (!is_pid(pidstr)) { | |
585 | fprintf(stderr, "Specified string '%s' is not a pid\n", | |
586 | pidstr); | |
587 | return -1; | |
588 | } | |
589 | } | |
590 | ||
591 | rc = netns_identify_pid(pidstr, name, sizeof(name)); | |
592 | if (!rc) | |
593 | printf("%s\n", name); | |
594 | ||
595 | return rc; | |
596 | } | |
597 | ||
33724939 | 598 | static int on_netns_del(char *nsname, void *arg) |
0dc34c77 | 599 | { |
ea343669 | 600 | char netns_path[PATH_MAX]; |
0dc34c77 | 601 | |
33724939 | 602 | snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, nsname); |
0dc34c77 EB |
603 | umount2(netns_path, MNT_DETACH); |
604 | if (unlink(netns_path) < 0) { | |
14645ec2 | 605 | fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n", |
0dc34c77 | 606 | netns_path, strerror(errno)); |
a05f6511 | 607 | return -1; |
0dc34c77 | 608 | } |
a05f6511 | 609 | return 0; |
0dc34c77 EB |
610 | } |
611 | ||
33724939 VK |
612 | static int netns_delete(int argc, char **argv) |
613 | { | |
614 | if (argc < 1 && !do_all) { | |
615 | fprintf(stderr, "No netns name specified\n"); | |
616 | return -1; | |
617 | } | |
618 | ||
619 | if (do_all) | |
620 | return netns_foreach(on_netns_del, NULL); | |
621 | ||
622 | return on_netns_del(argv[0], NULL); | |
623 | } | |
624 | ||
c1cbb18a | 625 | static int create_netns_dir(void) |
626 | { | |
627 | /* Create the base netns directory if it doesn't exist */ | |
628 | if (mkdir(NETNS_RUN_DIR, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)) { | |
629 | if (errno != EEXIST) { | |
630 | fprintf(stderr, "mkdir %s failed: %s\n", | |
631 | NETNS_RUN_DIR, strerror(errno)); | |
632 | return -1; | |
633 | } | |
634 | } | |
635 | ||
636 | return 0; | |
637 | } | |
638 | ||
b2e29223 MC |
639 | /* Obtain a FD for the current namespace, so we can reenter it later */ |
640 | static void netns_save(void) | |
641 | { | |
642 | if (saved_netns != -1) | |
643 | return; | |
644 | ||
645 | saved_netns = open("/proc/self/ns/net", O_RDONLY | O_CLOEXEC); | |
646 | if (saved_netns == -1) { | |
647 | perror("Cannot open init namespace"); | |
648 | exit(1); | |
649 | } | |
650 | } | |
651 | ||
652 | static void netns_restore(void) | |
653 | { | |
654 | if (saved_netns == -1) | |
655 | return; | |
656 | ||
657 | if (setns(saved_netns, CLONE_NEWNET)) { | |
658 | perror("setns"); | |
659 | exit(1); | |
660 | } | |
661 | ||
662 | close(saved_netns); | |
663 | saved_netns = -1; | |
664 | } | |
665 | ||
e3dbcb2a | 666 | static int netns_add(int argc, char **argv, bool create) |
0dc34c77 EB |
667 | { |
668 | /* This function creates a new network namespace and | |
669 | * a new mount namespace and bind them into a well known | |
670 | * location in the filesystem based on the name provided. | |
671 | * | |
e3dbcb2a MC |
672 | * If create is true, a new namespace will be created, |
673 | * otherwise an existing one will be attached to the file. | |
674 | * | |
0dc34c77 EB |
675 | * The mount namespace is created so that any necessary |
676 | * userspace tweaks like remounting /sys, or bind mounting | |
e3dbcb2a | 677 | * a new /etc/resolv.conf can be shared between users. |
0dc34c77 | 678 | */ |
e3dbcb2a | 679 | char netns_path[PATH_MAX], proc_path[PATH_MAX]; |
0dc34c77 | 680 | const char *name; |
e3dbcb2a | 681 | pid_t pid; |
223f4d8e | 682 | int fd; |
58a3e827 | 683 | int made_netns_run_dir_mount = 0; |
0dc34c77 | 684 | |
e3dbcb2a MC |
685 | if (create) { |
686 | if (argc < 1) { | |
687 | fprintf(stderr, "No netns name specified\n"); | |
688 | return -1; | |
689 | } | |
690 | } else { | |
691 | if (argc < 2) { | |
692 | fprintf(stderr, "No netns name and PID specified\n"); | |
693 | return -1; | |
694 | } | |
695 | ||
696 | if (get_s32(&pid, argv[1], 0) || !pid) { | |
697 | fprintf(stderr, "Invalid PID: %s\n", argv[1]); | |
698 | return -1; | |
699 | } | |
0dc34c77 EB |
700 | } |
701 | name = argv[0]; | |
702 | ||
703 | snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name); | |
704 | ||
c1cbb18a | 705 | if (create_netns_dir()) |
706 | return -1; | |
0dc34c77 | 707 | |
d259f030 | 708 | /* Make it possible for network namespace mounts to propagate between |
58a3e827 EB |
709 | * mount namespaces. This makes it likely that a unmounting a network |
710 | * namespace file in one namespace will unmount the network namespace | |
711 | * file in all namespaces allowing the network namespace to be freed | |
712 | * sooner. | |
713 | */ | |
714 | while (mount("", NETNS_RUN_DIR, "none", MS_SHARED | MS_REC, NULL)) { | |
715 | /* Fail unless we need to make the mount point */ | |
716 | if (errno != EINVAL || made_netns_run_dir_mount) { | |
717 | fprintf(stderr, "mount --make-shared %s failed: %s\n", | |
718 | NETNS_RUN_DIR, strerror(errno)); | |
a05f6511 | 719 | return -1; |
58a3e827 EB |
720 | } |
721 | ||
722 | /* Upgrade NETNS_RUN_DIR to a mount point */ | |
d6a4076b | 723 | if (mount(NETNS_RUN_DIR, NETNS_RUN_DIR, "none", MS_BIND | MS_REC, NULL)) { |
58a3e827 EB |
724 | fprintf(stderr, "mount --bind %s %s failed: %s\n", |
725 | NETNS_RUN_DIR, NETNS_RUN_DIR, strerror(errno)); | |
a05f6511 | 726 | return -1; |
58a3e827 EB |
727 | } |
728 | made_netns_run_dir_mount = 1; | |
729 | } | |
730 | ||
0dc34c77 | 731 | /* Create the filesystem state */ |
223f4d8e EB |
732 | fd = open(netns_path, O_RDONLY|O_CREAT|O_EXCL, 0); |
733 | if (fd < 0) { | |
55713c8c | 734 | fprintf(stderr, "Cannot create namespace file \"%s\": %s\n", |
0dc34c77 | 735 | netns_path, strerror(errno)); |
a05f6511 | 736 | return -1; |
0dc34c77 | 737 | } |
223f4d8e | 738 | close(fd); |
e3dbcb2a MC |
739 | |
740 | if (create) { | |
80a931d4 | 741 | netns_save(); |
e3dbcb2a MC |
742 | if (unshare(CLONE_NEWNET) < 0) { |
743 | fprintf(stderr, "Failed to create a new network namespace \"%s\": %s\n", | |
744 | name, strerror(errno)); | |
745 | goto out_delete; | |
746 | } | |
747 | ||
748 | strcpy(proc_path, "/proc/self/ns/net"); | |
749 | } else { | |
750 | snprintf(proc_path, sizeof(proc_path), "/proc/%d/ns/net", pid); | |
0dc34c77 EB |
751 | } |
752 | ||
753 | /* Bind the netns last so I can watch for it */ | |
e3dbcb2a MC |
754 | if (mount(proc_path, netns_path, "none", MS_BIND, NULL) < 0) { |
755 | fprintf(stderr, "Bind %s -> %s failed: %s\n", | |
756 | proc_path, netns_path, strerror(errno)); | |
0dc34c77 EB |
757 | goto out_delete; |
758 | } | |
b2e29223 MC |
759 | netns_restore(); |
760 | ||
a05f6511 | 761 | return 0; |
0dc34c77 | 762 | out_delete: |
e3dbcb2a | 763 | if (create) { |
b2e29223 | 764 | netns_restore(); |
e3dbcb2a MC |
765 | netns_delete(argc, argv); |
766 | } else if (unlink(netns_path) < 0) { | |
767 | fprintf(stderr, "Cannot remove namespace file \"%s\": %s\n", | |
768 | netns_path, strerror(errno)); | |
769 | } | |
a05f6511 | 770 | return -1; |
0dc34c77 EB |
771 | } |
772 | ||
974ef93b | 773 | int set_netnsid_from_name(const char *name, int nsid) |
d182ee13 ND |
774 | { |
775 | struct { | |
776 | struct nlmsghdr n; | |
777 | struct rtgenmsg g; | |
778 | char buf[1024]; | |
d17b136f PS |
779 | } req = { |
780 | .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg)), | |
781 | .n.nlmsg_flags = NLM_F_REQUEST, | |
782 | .n.nlmsg_type = RTM_NEWNSID, | |
783 | .g.rtgen_family = AF_UNSPEC, | |
784 | }; | |
d182ee13 ND |
785 | int fd, err = 0; |
786 | ||
974ef93b ND |
787 | netns_nsid_socket_init(); |
788 | ||
d182ee13 ND |
789 | fd = netns_get_fd(name); |
790 | if (fd < 0) | |
791 | return fd; | |
792 | ||
793 | addattr32(&req.n, 1024, NETNSA_FD, fd); | |
794 | addattr32(&req.n, 1024, NETNSA_NSID, nsid); | |
86bf43c7 | 795 | if (rtnl_talk(&rth, &req.n, NULL) < 0) |
d182ee13 ND |
796 | err = -2; |
797 | ||
798 | close(fd); | |
799 | return err; | |
800 | } | |
801 | ||
802 | static int netns_set(int argc, char **argv) | |
803 | { | |
ea343669 | 804 | char netns_path[PATH_MAX]; |
d182ee13 | 805 | const char *name; |
ebe3ce2f | 806 | int netns, nsid; |
d182ee13 ND |
807 | |
808 | if (argc < 1) { | |
809 | fprintf(stderr, "No netns name specified\n"); | |
810 | return -1; | |
811 | } | |
812 | if (argc < 2) { | |
813 | fprintf(stderr, "No nsid specified\n"); | |
814 | return -1; | |
815 | } | |
816 | name = argv[0]; | |
375d51ca CB |
817 | /* If a negative nsid is specified the kernel will select the nsid. */ |
818 | if (strcmp(argv[1], "auto") == 0) | |
819 | nsid = -1; | |
ebe3ce2f | 820 | else if (get_integer(&nsid, argv[1], 0)) |
acbe9118 | 821 | invarg("Invalid \"netnsid\" value\n", argv[1]); |
ebe3ce2f ND |
822 | else if (nsid < 0) |
823 | invarg("\"netnsid\" value should be >= 0\n", argv[1]); | |
d182ee13 ND |
824 | |
825 | snprintf(netns_path, sizeof(netns_path), "%s/%s", NETNS_RUN_DIR, name); | |
826 | netns = open(netns_path, O_RDONLY | O_CLOEXEC); | |
827 | if (netns < 0) { | |
828 | fprintf(stderr, "Cannot open network namespace \"%s\": %s\n", | |
829 | name, strerror(errno)); | |
830 | return -1; | |
831 | } | |
832 | ||
833 | return set_netnsid_from_name(name, nsid); | |
834 | } | |
0dc34c77 EB |
835 | |
836 | static int netns_monitor(int argc, char **argv) | |
837 | { | |
838 | char buf[4096]; | |
839 | struct inotify_event *event; | |
840 | int fd; | |
56f5daac | 841 | |
0dc34c77 EB |
842 | fd = inotify_init(); |
843 | if (fd < 0) { | |
844 | fprintf(stderr, "inotify_init failed: %s\n", | |
845 | strerror(errno)); | |
a05f6511 | 846 | return -1; |
0dc34c77 | 847 | } |
c1cbb18a | 848 | |
849 | if (create_netns_dir()) | |
850 | return -1; | |
851 | ||
0dc34c77 EB |
852 | if (inotify_add_watch(fd, NETNS_RUN_DIR, IN_CREATE | IN_DELETE) < 0) { |
853 | fprintf(stderr, "inotify_add_watch failed: %s\n", | |
854 | strerror(errno)); | |
a05f6511 | 855 | return -1; |
0dc34c77 | 856 | } |
56f5daac | 857 | for (;;) { |
0dc34c77 | 858 | ssize_t len = read(fd, buf, sizeof(buf)); |
56f5daac | 859 | |
0dc34c77 EB |
860 | if (len < 0) { |
861 | fprintf(stderr, "read failed: %s\n", | |
862 | strerror(errno)); | |
a05f6511 | 863 | return -1; |
0dc34c77 EB |
864 | } |
865 | for (event = (struct inotify_event *)buf; | |
866 | (char *)event < &buf[len]; | |
867 | event = (struct inotify_event *)((char *)event + sizeof(*event) + event->len)) { | |
868 | if (event->mask & IN_CREATE) | |
869 | printf("add %s\n", event->name); | |
870 | if (event->mask & IN_DELETE) | |
871 | printf("delete %s\n", event->name); | |
872 | } | |
873 | } | |
a05f6511 | 874 | return 0; |
0dc34c77 EB |
875 | } |
876 | ||
79928fd0 MC |
877 | static int invalid_name(const char *name) |
878 | { | |
d3f0b091 MC |
879 | return !*name || strlen(name) > NAME_MAX || |
880 | strchr(name, '/') || !strcmp(name, ".") || !strcmp(name, ".."); | |
79928fd0 MC |
881 | } |
882 | ||
0dc34c77 EB |
883 | int do_netns(int argc, char **argv) |
884 | { | |
e29a8e05 | 885 | netns_nsid_socket_init(); |
d652ccbf | 886 | |
e29a8e05 AA |
887 | if (argc < 1) { |
888 | netns_map_init(); | |
0dc34c77 | 889 | return netns_list(0, NULL); |
e29a8e05 | 890 | } |
0dc34c77 | 891 | |
79928fd0 MC |
892 | if (argc > 1 && invalid_name(argv[1])) { |
893 | fprintf(stderr, "Invalid netns name \"%s\"\n", argv[1]); | |
894 | exit(-1); | |
895 | } | |
896 | ||
0dc34c77 | 897 | if ((matches(*argv, "list") == 0) || (matches(*argv, "show") == 0) || |
e29a8e05 AA |
898 | (matches(*argv, "lst") == 0)) { |
899 | netns_map_init(); | |
0dc34c77 | 900 | return netns_list(argc-1, argv+1); |
e29a8e05 | 901 | } |
0dc34c77 | 902 | |
e29a8e05 AA |
903 | if ((matches(*argv, "list-id") == 0)) { |
904 | netns_map_init(); | |
d652ccbf | 905 | return netns_list_id(argc-1, argv+1); |
e29a8e05 | 906 | } |
d652ccbf | 907 | |
0dc34c77 | 908 | if (matches(*argv, "help") == 0) |
8e2d47dc | 909 | return usage(); |
0dc34c77 EB |
910 | |
911 | if (matches(*argv, "add") == 0) | |
e3dbcb2a | 912 | return netns_add(argc-1, argv+1, true); |
0dc34c77 | 913 | |
d182ee13 ND |
914 | if (matches(*argv, "set") == 0) |
915 | return netns_set(argc-1, argv+1); | |
916 | ||
0dc34c77 EB |
917 | if (matches(*argv, "delete") == 0) |
918 | return netns_delete(argc-1, argv+1); | |
919 | ||
9a7b3d91 EB |
920 | if (matches(*argv, "identify") == 0) |
921 | return netns_identify(argc-1, argv+1); | |
922 | ||
923 | if (matches(*argv, "pids") == 0) | |
924 | return netns_pids(argc-1, argv+1); | |
925 | ||
0dc34c77 EB |
926 | if (matches(*argv, "exec") == 0) |
927 | return netns_exec(argc-1, argv+1); | |
928 | ||
929 | if (matches(*argv, "monitor") == 0) | |
930 | return netns_monitor(argc-1, argv+1); | |
931 | ||
e3dbcb2a MC |
932 | if (matches(*argv, "attach") == 0) |
933 | return netns_add(argc-1, argv+1, false); | |
934 | ||
0dc34c77 | 935 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip netns help\".\n", *argv); |
a05f6511 | 936 | exit(-1); |
0dc34c77 | 937 | } |