]>
Commit | Line | Data |
---|---|---|
1949f82c DA |
1 | /* |
2 | * ipvrf.c "ip vrf" | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or | |
5 | * modify it under the terms of the GNU General Public License | |
6 | * as published by the Free Software Foundation; either version | |
7 | * 2 of the License, or (at your option) any later version. | |
8 | * | |
9 | * Authors: David Ahern <dsa@cumulusnetworks.com> | |
10 | * | |
11 | */ | |
12 | ||
13 | #include <sys/types.h> | |
14 | #include <sys/stat.h> | |
15 | #include <sys/socket.h> | |
16 | #include <sys/mount.h> | |
17 | #include <linux/bpf.h> | |
18 | #include <linux/if.h> | |
19 | #include <fcntl.h> | |
20 | #include <stdio.h> | |
21 | #include <stdlib.h> | |
22 | #include <unistd.h> | |
23 | #include <string.h> | |
508f3c23 LB |
24 | #ifdef HAVE_LIBBSD |
25 | #include <bsd/string.h> | |
26 | #endif | |
46afa694 | 27 | #include <dirent.h> |
1949f82c DA |
28 | #include <errno.h> |
29 | #include <limits.h> | |
30 | ||
31 | #include "rt_names.h" | |
32 | #include "utils.h" | |
33 | #include "ip_common.h" | |
34 | #include "bpf_util.h" | |
35 | ||
36 | #define CGRP_PROC_FILE "/cgroup.procs" | |
37 | ||
1dddb605 DA |
38 | static struct link_filter vrf_filter; |
39 | ||
1949f82c DA |
40 | static void usage(void) |
41 | { | |
1dddb605 DA |
42 | fprintf(stderr, "Usage: ip vrf show [NAME] ...\n"); |
43 | fprintf(stderr, " ip vrf exec [NAME] cmd ...\n"); | |
1949f82c DA |
44 | fprintf(stderr, " ip vrf identify [PID]\n"); |
45 | fprintf(stderr, " ip vrf pids [NAME]\n"); | |
46 | ||
47 | exit(-1); | |
48 | } | |
49 | ||
46afa694 DA |
50 | /* |
51 | * parse process based cgroup file looking for PATH/vrf/NAME where | |
52 | * NAME is the name of the vrf the process is associated with | |
53 | */ | |
b5efa597 | 54 | static int vrf_identify(pid_t pid, char *name, size_t len) |
1949f82c DA |
55 | { |
56 | char path[PATH_MAX]; | |
57 | char buf[4096]; | |
58 | char *vrf, *end; | |
b5efa597 DA |
59 | FILE *fp; |
60 | ||
61 | snprintf(path, sizeof(path), "/proc/%d/cgroup", pid); | |
62 | fp = fopen(path, "r"); | |
63 | if (!fp) | |
64 | return -1; | |
65 | ||
66 | memset(name, 0, len); | |
67 | ||
68 | while (fgets(buf, sizeof(buf), fp)) { | |
46afa694 DA |
69 | /* want the controller-less cgroup */ |
70 | if (strstr(buf, "::/") == NULL) | |
71 | continue; | |
72 | ||
73 | vrf = strstr(buf, "/vrf/"); | |
b5efa597 | 74 | if (vrf) { |
46afa694 | 75 | vrf += 5; /* skip past "/vrf/" */ |
b5efa597 DA |
76 | end = strchr(vrf, '\n'); |
77 | if (end) | |
78 | *end = '\0'; | |
79 | ||
532b8874 | 80 | strlcpy(name, vrf, len); |
b5efa597 DA |
81 | break; |
82 | } | |
83 | } | |
84 | ||
85 | fclose(fp); | |
86 | ||
87 | return 0; | |
88 | } | |
89 | ||
90 | static int ipvrf_identify(int argc, char **argv) | |
91 | { | |
92 | char vrf[32]; | |
93 | int rc; | |
1949f82c | 94 | unsigned int pid; |
1949f82c DA |
95 | |
96 | if (argc < 1) | |
97 | pid = getpid(); | |
98 | else if (argc > 1) | |
99 | invarg("Extra arguments specified\n", argv[1]); | |
100 | else if (get_unsigned(&pid, argv[0], 10)) | |
101 | invarg("Invalid pid\n", argv[0]); | |
102 | ||
b5efa597 DA |
103 | rc = vrf_identify(pid, vrf, sizeof(vrf)); |
104 | if (!rc) { | |
105 | if (vrf[0] != '\0') | |
106 | printf("%s\n", vrf); | |
107 | } else { | |
108 | fprintf(stderr, "Failed to lookup vrf association: %s\n", | |
109 | strerror(errno)); | |
1949f82c | 110 | } |
1949f82c DA |
111 | |
112 | return rc; | |
113 | } | |
114 | ||
46afa694 DA |
115 | /* read PATH/vrf/NAME/cgroup.procs file */ |
116 | static void read_cgroup_pids(const char *base_path, char *name) | |
1949f82c DA |
117 | { |
118 | char path[PATH_MAX]; | |
119 | char buf[4096]; | |
f443565f | 120 | FILE *fp; |
1949f82c | 121 | |
46afa694 DA |
122 | if (snprintf(path, sizeof(path), "%s/vrf/%s%s", |
123 | base_path, name, CGRP_PROC_FILE) >= sizeof(path)) | |
124 | return; | |
1949f82c | 125 | |
f443565f DA |
126 | fp = fopen(path, "r"); |
127 | if (!fp) | |
46afa694 | 128 | return; /* no cgroup file, nothing to show */ |
1949f82c | 129 | |
46afa694 | 130 | /* dump contents (pids) of cgroup.procs */ |
f443565f DA |
131 | while (fgets(buf, sizeof(buf), fp)) { |
132 | char *nl, comm[32]; | |
133 | ||
134 | nl = strchr(buf, '\n'); | |
135 | if (nl) | |
136 | *nl = '\0'; | |
46afa694 | 137 | |
f443565f DA |
138 | if (get_command_name(buf, comm, sizeof(comm))) |
139 | strcpy(comm, "<terminated?>"); | |
140 | ||
141 | printf("%5s %s\n", buf, comm); | |
1949f82c DA |
142 | } |
143 | ||
f443565f | 144 | fclose(fp); |
46afa694 DA |
145 | } |
146 | ||
6a978383 DA |
147 | /* recurse path looking for PATH[/NETNS]/vrf/NAME */ |
148 | static int recurse_dir(char *base_path, char *name, const char *netns) | |
46afa694 DA |
149 | { |
150 | char path[PATH_MAX]; | |
151 | struct dirent *de; | |
152 | struct stat fstat; | |
153 | int rc; | |
154 | DIR *d; | |
155 | ||
156 | d = opendir(base_path); | |
157 | if (!d) | |
158 | return -1; | |
159 | ||
160 | while ((de = readdir(d)) != NULL) { | |
161 | if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) | |
162 | continue; | |
163 | ||
164 | if (!strcmp(de->d_name, "vrf")) { | |
6a978383 DA |
165 | const char *pdir = strrchr(base_path, '/'); |
166 | ||
167 | /* found a 'vrf' directory. if it is for the given | |
168 | * namespace then dump the cgroup pids | |
169 | */ | |
170 | if (*netns == '\0' || | |
171 | (pdir && !strcmp(pdir+1, netns))) | |
172 | read_cgroup_pids(base_path, name); | |
173 | ||
46afa694 DA |
174 | continue; |
175 | } | |
176 | ||
177 | /* is this a subdir that needs to be walked */ | |
178 | if (snprintf(path, sizeof(path), "%s/%s", | |
179 | base_path, de->d_name) >= sizeof(path)) | |
180 | continue; | |
181 | ||
182 | if (lstat(path, &fstat) < 0) | |
183 | continue; | |
184 | ||
185 | if (S_ISDIR(fstat.st_mode)) { | |
6a978383 | 186 | rc = recurse_dir(path, name, netns); |
46afa694 DA |
187 | if (rc != 0) |
188 | goto out; | |
189 | } | |
190 | } | |
191 | ||
192 | rc = 0; | |
193 | out: | |
194 | closedir(d); | |
1949f82c DA |
195 | |
196 | return rc; | |
197 | } | |
198 | ||
6a978383 DA |
199 | static int ipvrf_get_netns(char *netns, int len) |
200 | { | |
201 | if (netns_identify_pid("self", netns, len-3)) { | |
202 | fprintf(stderr, "Failed to get name of network namespace: %s\n", | |
203 | strerror(errno)); | |
204 | return -1; | |
205 | } | |
206 | ||
207 | if (*netns != '\0') | |
208 | strcat(netns, "-ns"); | |
209 | ||
210 | return 0; | |
211 | } | |
212 | ||
46afa694 DA |
213 | static int ipvrf_pids(int argc, char **argv) |
214 | { | |
215 | char *mnt, *vrf; | |
6a978383 DA |
216 | char netns[256]; |
217 | int ret = -1; | |
46afa694 DA |
218 | |
219 | if (argc != 1) { | |
220 | fprintf(stderr, "Invalid arguments\n"); | |
221 | return -1; | |
222 | } | |
223 | ||
224 | vrf = argv[0]; | |
b5377431 DA |
225 | if (!name_is_vrf(vrf)) { |
226 | fprintf(stderr, "Invalid VRF name\n"); | |
227 | return -1; | |
228 | } | |
46afa694 DA |
229 | |
230 | mnt = find_cgroup2_mount(); | |
231 | if (!mnt) | |
232 | return -1; | |
233 | ||
6a978383 DA |
234 | if (ipvrf_get_netns(netns, sizeof(netns)) < 0) |
235 | goto out; | |
236 | ||
237 | ret = recurse_dir(mnt, vrf, netns); | |
46afa694 | 238 | |
6a978383 | 239 | out: |
46afa694 DA |
240 | free(mnt); |
241 | ||
242 | return ret; | |
243 | } | |
244 | ||
1949f82c DA |
245 | /* load BPF program to set sk_bound_dev_if for sockets */ |
246 | static char bpf_log_buf[256*1024]; | |
247 | ||
248 | static int prog_load(int idx) | |
249 | { | |
250 | struct bpf_insn prog[] = { | |
251 | BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), | |
252 | BPF_MOV64_IMM(BPF_REG_3, idx), | |
ab91aee4 SH |
253 | BPF_MOV64_IMM(BPF_REG_2, |
254 | offsetof(struct bpf_sock, bound_dev_if)), | |
255 | BPF_STX_MEM(BPF_W, BPF_REG_1, BPF_REG_3, | |
256 | offsetof(struct bpf_sock, bound_dev_if)), | |
1949f82c DA |
257 | BPF_MOV64_IMM(BPF_REG_0, 1), /* r0 = verdict */ |
258 | BPF_EXIT_INSN(), | |
259 | }; | |
260 | ||
261 | return bpf_prog_load(BPF_PROG_TYPE_CGROUP_SOCK, prog, sizeof(prog), | |
262 | "GPL", bpf_log_buf, sizeof(bpf_log_buf)); | |
263 | } | |
264 | ||
265 | static int vrf_configure_cgroup(const char *path, int ifindex) | |
266 | { | |
267 | int rc = -1, cg_fd, prog_fd = -1; | |
268 | ||
269 | cg_fd = open(path, O_DIRECTORY | O_RDONLY); | |
270 | if (cg_fd < 0) { | |
ab91aee4 SH |
271 | fprintf(stderr, |
272 | "Failed to open cgroup path: '%s'\n", | |
273 | strerror(errno)); | |
1949f82c DA |
274 | goto out; |
275 | } | |
276 | ||
277 | /* | |
278 | * Load bpf program into kernel and attach to cgroup to affect | |
279 | * socket creates | |
280 | */ | |
281 | prog_fd = prog_load(ifindex); | |
282 | if (prog_fd < 0) { | |
c94112fa DA |
283 | fprintf(stderr, "Failed to load BPF prog: '%s'\n", |
284 | strerror(errno)); | |
9b036afd DA |
285 | |
286 | if (errno != EPERM) { | |
287 | fprintf(stderr, | |
288 | "Kernel compiled with CGROUP_BPF enabled?\n"); | |
289 | } | |
1949f82c DA |
290 | goto out; |
291 | } | |
292 | ||
293 | if (bpf_prog_attach_fd(prog_fd, cg_fd, BPF_CGROUP_INET_SOCK_CREATE)) { | |
294 | fprintf(stderr, "Failed to attach prog to cgroup: '%s'\n", | |
295 | strerror(errno)); | |
1949f82c DA |
296 | goto out; |
297 | } | |
298 | ||
299 | rc = 0; | |
300 | out: | |
301 | close(cg_fd); | |
302 | close(prog_fd); | |
303 | ||
304 | return rc; | |
305 | } | |
306 | ||
46afa694 DA |
307 | /* get base path for controller-less cgroup for a process. |
308 | * path returned does not include /vrf/NAME if it exists | |
309 | */ | |
310 | static int vrf_path(char *vpath, size_t len) | |
311 | { | |
312 | char path[PATH_MAX]; | |
313 | char buf[4096]; | |
314 | char *vrf; | |
315 | FILE *fp; | |
316 | ||
317 | snprintf(path, sizeof(path), "/proc/%d/cgroup", getpid()); | |
318 | fp = fopen(path, "r"); | |
319 | if (!fp) | |
320 | return -1; | |
321 | ||
322 | vpath[0] = '\0'; | |
323 | ||
324 | while (fgets(buf, sizeof(buf), fp)) { | |
325 | char *start, *nl; | |
326 | ||
327 | start = strstr(buf, "::/"); | |
328 | if (!start) | |
329 | continue; | |
330 | ||
331 | /* advance past '::' */ | |
332 | start += 2; | |
333 | ||
334 | nl = strchr(start, '\n'); | |
335 | if (nl) | |
336 | *nl = '\0'; | |
337 | ||
338 | vrf = strstr(start, "/vrf"); | |
339 | if (vrf) | |
340 | *vrf = '\0'; | |
341 | ||
18f156bf | 342 | strlcpy(vpath, start, len); |
46afa694 DA |
343 | |
344 | /* if vrf path is just / then return nothing */ | |
345 | if (!strcmp(vpath, "/")) | |
346 | vpath[0] = '\0'; | |
347 | ||
348 | break; | |
349 | } | |
350 | ||
351 | fclose(fp); | |
352 | ||
353 | return 0; | |
354 | } | |
355 | ||
1949f82c DA |
356 | static int vrf_switch(const char *name) |
357 | { | |
358 | char path[PATH_MAX], *mnt, pid[16]; | |
6a978383 | 359 | char vpath[PATH_MAX], netns[256]; |
2917b4f4 | 360 | int ifindex = 0; |
1949f82c DA |
361 | int rc = -1, len, fd = -1; |
362 | ||
2917b4f4 DA |
363 | if (strcmp(name, "default")) { |
364 | ifindex = name_is_vrf(name); | |
365 | if (!ifindex) { | |
1949f82c DA |
366 | fprintf(stderr, "Invalid VRF name\n"); |
367 | return -1; | |
368 | } | |
1949f82c DA |
369 | } |
370 | ||
371 | mnt = find_cgroup2_mount(); | |
372 | if (!mnt) | |
373 | return -1; | |
374 | ||
6a978383 DA |
375 | /* -1 on length to add '/' to the end */ |
376 | if (ipvrf_get_netns(netns, sizeof(netns) - 1) < 0) | |
6ac5943b | 377 | goto out; |
6a978383 | 378 | |
46afa694 DA |
379 | if (vrf_path(vpath, sizeof(vpath)) < 0) { |
380 | fprintf(stderr, "Failed to get base cgroup path: %s\n", | |
381 | strerror(errno)); | |
6ac5943b | 382 | goto out; |
46afa694 DA |
383 | } |
384 | ||
6a978383 DA |
385 | /* if path already ends in netns then don't add it again */ |
386 | if (*netns != '\0') { | |
387 | char *pdir = strrchr(vpath, '/'); | |
388 | ||
389 | if (!pdir) | |
390 | pdir = vpath; | |
391 | else | |
392 | pdir++; | |
393 | ||
394 | if (strcmp(pdir, netns) == 0) | |
395 | *pdir = '\0'; | |
396 | ||
397 | strcat(netns, "/"); | |
398 | } | |
399 | ||
1949f82c DA |
400 | /* path to cgroup; make sure buffer has room to cat "/cgroup.procs" |
401 | * to the end of the path | |
402 | */ | |
46afa694 | 403 | len = snprintf(path, sizeof(path) - sizeof(CGRP_PROC_FILE), |
6a978383 DA |
404 | "%s%s/%svrf/%s", |
405 | mnt, vpath, netns, ifindex ? name : ""); | |
1949f82c DA |
406 | if (len > sizeof(path) - sizeof(CGRP_PROC_FILE)) { |
407 | fprintf(stderr, "Invalid path to cgroup2 mount\n"); | |
408 | goto out; | |
409 | } | |
410 | ||
411 | if (make_path(path, 0755)) { | |
412 | fprintf(stderr, "Failed to setup vrf cgroup2 directory\n"); | |
413 | goto out; | |
414 | } | |
415 | ||
2917b4f4 | 416 | if (ifindex && vrf_configure_cgroup(path, ifindex)) |
1949f82c DA |
417 | goto out; |
418 | ||
419 | /* | |
420 | * write pid to cgroup.procs making process part of cgroup | |
421 | */ | |
422 | strcat(path, CGRP_PROC_FILE); | |
423 | fd = open(path, O_RDWR | O_APPEND); | |
424 | if (fd < 0) { | |
425 | fprintf(stderr, "Failed to open cgroups.procs file: %s.\n", | |
426 | strerror(errno)); | |
427 | goto out; | |
428 | } | |
429 | ||
430 | snprintf(pid, sizeof(pid), "%d", getpid()); | |
431 | if (write(fd, pid, strlen(pid)) < 0) { | |
432 | fprintf(stderr, "Failed to join cgroup\n"); | |
6ac5943b | 433 | goto out2; |
1949f82c DA |
434 | } |
435 | ||
436 | rc = 0; | |
6ac5943b PS |
437 | out2: |
438 | close(fd); | |
1949f82c DA |
439 | out: |
440 | free(mnt); | |
1949f82c | 441 | |
ba2fc55b LB |
442 | drop_cap(); |
443 | ||
1949f82c DA |
444 | return rc; |
445 | } | |
446 | ||
447 | static int ipvrf_exec(int argc, char **argv) | |
448 | { | |
449 | if (argc < 1) { | |
450 | fprintf(stderr, "No VRF name specified\n"); | |
451 | return -1; | |
452 | } | |
453 | if (argc < 2) { | |
454 | fprintf(stderr, "No command specified\n"); | |
455 | return -1; | |
456 | } | |
457 | ||
458 | if (vrf_switch(argv[0])) | |
459 | return -1; | |
460 | ||
461 | return -cmd_exec(argv[1], argv + 1, !!batch_mode); | |
462 | } | |
463 | ||
ee9369a0 DA |
464 | /* reset VRF association of current process to default VRF; |
465 | * used by netns_exec | |
466 | */ | |
467 | void vrf_reset(void) | |
468 | { | |
469 | char vrf[32]; | |
470 | ||
471 | if (vrf_identify(getpid(), vrf, sizeof(vrf)) || | |
472 | (vrf[0] == '\0')) | |
473 | return; | |
474 | ||
475 | vrf_switch("default"); | |
476 | } | |
477 | ||
1dddb605 DA |
478 | static int ipvrf_filter_req(struct nlmsghdr *nlh, int reqlen) |
479 | { | |
480 | struct rtattr *linkinfo; | |
481 | int err; | |
482 | ||
483 | if (vrf_filter.kind) { | |
484 | linkinfo = addattr_nest(nlh, reqlen, IFLA_LINKINFO); | |
485 | ||
486 | err = addattr_l(nlh, reqlen, IFLA_INFO_KIND, vrf_filter.kind, | |
487 | strlen(vrf_filter.kind)); | |
488 | if (err) | |
489 | return err; | |
490 | ||
491 | addattr_nest_end(nlh, linkinfo); | |
492 | } | |
493 | ||
494 | return 0; | |
495 | } | |
496 | ||
497 | /* input arg is linkinfo */ | |
498 | static __u32 vrf_table_linkinfo(struct rtattr *li[]) | |
499 | { | |
500 | struct rtattr *attr[IFLA_VRF_MAX + 1]; | |
501 | ||
502 | if (li[IFLA_INFO_DATA]) { | |
503 | parse_rtattr_nested(attr, IFLA_VRF_MAX, li[IFLA_INFO_DATA]); | |
504 | ||
505 | if (attr[IFLA_VRF_TABLE]) | |
506 | return rta_getattr_u32(attr[IFLA_VRF_TABLE]); | |
507 | } | |
508 | ||
509 | return 0; | |
510 | } | |
511 | ||
512 | static int ipvrf_print(struct nlmsghdr *n) | |
513 | { | |
514 | struct ifinfomsg *ifi = NLMSG_DATA(n); | |
515 | struct rtattr *tb[IFLA_MAX+1]; | |
516 | struct rtattr *li[IFLA_INFO_MAX+1]; | |
517 | int len = n->nlmsg_len; | |
518 | const char *name; | |
519 | __u32 tb_id; | |
520 | ||
521 | len -= NLMSG_LENGTH(sizeof(*ifi)); | |
522 | if (len < 0) | |
523 | return 0; | |
524 | ||
525 | if (vrf_filter.ifindex && vrf_filter.ifindex != ifi->ifi_index) | |
526 | return 0; | |
527 | ||
528 | parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifi), len); | |
529 | ||
530 | /* kernel does not support filter by master device */ | |
531 | if (tb[IFLA_MASTER]) { | |
532 | int master = *(int *)RTA_DATA(tb[IFLA_MASTER]); | |
533 | ||
534 | if (vrf_filter.master && master != vrf_filter.master) | |
535 | return 0; | |
536 | } | |
537 | ||
538 | if (!tb[IFLA_IFNAME]) { | |
539 | fprintf(stderr, | |
540 | "BUG: device with ifindex %d has nil ifname\n", | |
541 | ifi->ifi_index); | |
542 | return 0; | |
543 | } | |
544 | name = rta_getattr_str(tb[IFLA_IFNAME]); | |
545 | ||
546 | /* missing LINKINFO means not VRF. e.g., kernel does not | |
547 | * support filtering on kind, so userspace needs to handle | |
548 | */ | |
549 | if (!tb[IFLA_LINKINFO]) | |
550 | return 0; | |
551 | ||
552 | parse_rtattr_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO]); | |
553 | ||
554 | if (!li[IFLA_INFO_KIND]) | |
555 | return 0; | |
556 | ||
557 | if (strcmp(RTA_DATA(li[IFLA_INFO_KIND]), "vrf")) | |
558 | return 0; | |
559 | ||
560 | tb_id = vrf_table_linkinfo(li); | |
561 | if (!tb_id) { | |
562 | fprintf(stderr, | |
563 | "BUG: VRF %s is missing table id\n", name); | |
564 | return 0; | |
565 | } | |
566 | ||
567 | printf("%-16s %5u", name, tb_id); | |
568 | ||
569 | printf("\n"); | |
570 | return 1; | |
571 | } | |
572 | ||
573 | static int ipvrf_show(int argc, char **argv) | |
1949f82c | 574 | { |
1dddb605 DA |
575 | struct nlmsg_chain linfo = { NULL, NULL}; |
576 | int rc = 0; | |
577 | ||
578 | vrf_filter.kind = "vrf"; | |
579 | ||
580 | if (argc > 1) | |
581 | usage(); | |
582 | ||
583 | if (argc == 1) { | |
584 | __u32 tb_id; | |
585 | ||
586 | tb_id = ipvrf_get_table(argv[0]); | |
587 | if (!tb_id) { | |
588 | fprintf(stderr, "Invalid VRF\n"); | |
589 | return 1; | |
590 | } | |
591 | printf("%s %u\n", argv[0], tb_id); | |
592 | return 0; | |
1949f82c DA |
593 | } |
594 | ||
1dddb605 DA |
595 | if (ip_linkaddr_list(0, ipvrf_filter_req, &linfo, NULL) == 0) { |
596 | struct nlmsg_list *l; | |
597 | unsigned nvrf = 0; | |
598 | int n; | |
599 | ||
600 | n = printf("%-16s %5s\n", "Name", "Table"); | |
601 | printf("%.*s\n", n-1, "-----------------------"); | |
602 | for (l = linfo.head; l; l = l->next) | |
603 | nvrf += ipvrf_print(&l->h); | |
604 | ||
605 | if (!nvrf) | |
606 | printf("No VRF has been configured\n"); | |
607 | } else | |
608 | rc = 1; | |
609 | ||
610 | free_nlmsg_chain(&linfo); | |
611 | ||
612 | return rc; | |
613 | } | |
614 | ||
615 | int do_ipvrf(int argc, char **argv) | |
616 | { | |
617 | if (argc == 0) | |
618 | return ipvrf_show(0, NULL); | |
619 | ||
1949f82c DA |
620 | if (matches(*argv, "identify") == 0) |
621 | return ipvrf_identify(argc-1, argv+1); | |
622 | ||
623 | if (matches(*argv, "pids") == 0) | |
624 | return ipvrf_pids(argc-1, argv+1); | |
625 | ||
626 | if (matches(*argv, "exec") == 0) | |
627 | return ipvrf_exec(argc-1, argv+1); | |
628 | ||
1dddb605 DA |
629 | if (matches(*argv, "show") == 0 || |
630 | matches(*argv, "lst") == 0 || | |
631 | matches(*argv, "list") == 0) | |
632 | return ipvrf_show(argc-1, argv+1); | |
633 | ||
1949f82c DA |
634 | if (matches(*argv, "help") == 0) |
635 | usage(); | |
636 | ||
637 | fprintf(stderr, "Command \"%s\" is unknown, try \"ip vrf help\".\n", | |
638 | *argv); | |
639 | ||
640 | exit(-1); | |
641 | } |