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