]> git.proxmox.com Git - mirror_iproute2.git/blob - ip/ipnexthop.c
Merge branch 'master' into next
[mirror_iproute2.git] / ip / ipnexthop.c
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * ip nexthop
4 *
5 * Copyright (c) 2017-19 David Ahern <dsahern@gmail.com>
6 */
7
8 #include <linux/nexthop.h>
9 #include <stdio.h>
10 #include <string.h>
11 #include <rt_names.h>
12 #include <errno.h>
13
14 #include "utils.h"
15 #include "ip_common.h"
16
17 static struct {
18 unsigned int flushed;
19 unsigned int groups;
20 unsigned int ifindex;
21 unsigned int master;
22 } filter;
23
24 enum {
25 IPNH_LIST,
26 IPNH_FLUSH,
27 };
28
29 #define RTM_NHA(h) ((struct rtattr *)(((char *)(h)) + \
30 NLMSG_ALIGN(sizeof(struct nhmsg))))
31
32 static void usage(void) __attribute__((noreturn));
33
34 static void usage(void)
35 {
36 fprintf(stderr,
37 "Usage: ip nexthop { list | flush } SELECTOR\n"
38 " ip nexthop { add | replace } id ID NH [ protocol ID ]\n"
39 " ip nexthop { get| del } id ID\n"
40 "SELECTOR := [ id ID ] [ dev DEV ] [ vrf NAME ] [ master DEV ]\n"
41 " [ groups ]\n"
42 "NH := { blackhole | [ via ADDRESS ] [ dev DEV ] [ onlink ]\n"
43 " [ encap ENCAPTYPE ENCAPHDR ] | group GROUP ] }\n"
44 "GROUP := [ id[,weight]>/<id[,weight]>/... ]\n"
45 "ENCAPTYPE := [ mpls ]\n"
46 "ENCAPHDR := [ MPLSLABEL ]\n");
47 exit(-1);
48 }
49
50 static int nh_dump_filter(struct nlmsghdr *nlh, int reqlen)
51 {
52 int err;
53
54 if (filter.ifindex) {
55 err = addattr32(nlh, reqlen, NHA_OIF, filter.ifindex);
56 if (err)
57 return err;
58 }
59
60 if (filter.groups) {
61 addattr_l(nlh, reqlen, NHA_GROUPS, NULL, 0);
62 if (err)
63 return err;
64 }
65
66 if (filter.master) {
67 addattr32(nlh, reqlen, NHA_MASTER, filter.master);
68 if (err)
69 return err;
70 }
71
72 return 0;
73 }
74
75 static struct rtnl_handle rth_del = { .fd = -1 };
76
77 static int delete_nexthop(__u32 id)
78 {
79 struct {
80 struct nlmsghdr n;
81 struct nhmsg nhm;
82 char buf[64];
83 } req = {
84 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
85 .n.nlmsg_flags = NLM_F_REQUEST,
86 .n.nlmsg_type = RTM_DELNEXTHOP,
87 .nhm.nh_family = AF_UNSPEC,
88 };
89
90 req.n.nlmsg_seq = ++rth_del.seq;
91
92 addattr32(&req.n, sizeof(req), NHA_ID, id);
93
94 if (rtnl_talk(&rth_del, &req.n, NULL) < 0)
95 return -1;
96 return 0;
97 }
98
99 static int flush_nexthop(struct nlmsghdr *nlh, void *arg)
100 {
101 struct nhmsg *nhm = NLMSG_DATA(nlh);
102 struct rtattr *tb[NHA_MAX+1];
103 __u32 id = 0;
104 int len;
105
106 len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
107 if (len < 0) {
108 fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
109 return -1;
110 }
111
112 parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
113 if (tb[NHA_ID])
114 id = rta_getattr_u32(tb[NHA_ID]);
115
116 if (id && !delete_nexthop(id))
117 filter.flushed++;
118
119 return 0;
120 }
121
122 static int ipnh_flush(unsigned int all)
123 {
124 int rc = -2;
125
126 if (all) {
127 filter.groups = 1;
128 filter.ifindex = 0;
129 filter.master = 0;
130 }
131
132 if (rtnl_open(&rth_del, 0) < 0) {
133 fprintf(stderr, "Cannot open rtnetlink\n");
134 return EXIT_FAILURE;
135 }
136 again:
137 if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
138 perror("Cannot send dump request");
139 goto out;
140 }
141
142 if (rtnl_dump_filter(&rth, flush_nexthop, stdout) < 0) {
143 fprintf(stderr, "Dump terminated. Failed to flush nexthops\n");
144 goto out;
145 }
146
147 /* if deleting all, then remove groups first */
148 if (all && filter.groups) {
149 filter.groups = 0;
150 goto again;
151 }
152
153 rc = 0;
154 out:
155 rtnl_close(&rth_del);
156 if (!filter.flushed)
157 printf("Nothing to flush\n");
158 else
159 printf("Flushed %d nexthops\n", filter.flushed);
160
161 return rc;
162 }
163
164 static void print_nh_group(FILE *fp, const struct rtattr *grps_attr)
165 {
166 struct nexthop_grp *nhg = RTA_DATA(grps_attr);
167 int num = RTA_PAYLOAD(grps_attr) / sizeof(*nhg);
168 int i;
169
170 if (!num || num * sizeof(*nhg) != RTA_PAYLOAD(grps_attr)) {
171 fprintf(fp, "<invalid nexthop group>");
172 return;
173 }
174
175 open_json_array(PRINT_JSON, "group");
176 print_string(PRINT_FP, NULL, "%s", "group ");
177 for (i = 0; i < num; ++i) {
178 open_json_object(NULL);
179
180 if (i)
181 print_string(PRINT_FP, NULL, "%s", "/");
182
183 print_uint(PRINT_ANY, "id", "%u", nhg[i].id);
184 if (nhg[i].weight)
185 print_uint(PRINT_ANY, "weight", ",%u", nhg[i].weight + 1);
186
187 close_json_object();
188 }
189 print_string(PRINT_FP, NULL, "%s", " ");
190 close_json_array(PRINT_JSON, NULL);
191 }
192
193 int print_nexthop(struct nlmsghdr *n, void *arg)
194 {
195 struct nhmsg *nhm = NLMSG_DATA(n);
196 struct rtattr *tb[NHA_MAX+1];
197 FILE *fp = (FILE *)arg;
198 int len;
199
200 SPRINT_BUF(b1);
201
202 if (n->nlmsg_type != RTM_DELNEXTHOP &&
203 n->nlmsg_type != RTM_NEWNEXTHOP) {
204 fprintf(stderr, "Not a nexthop: %08x %08x %08x\n",
205 n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
206 return -1;
207 }
208
209 len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm));
210 if (len < 0) {
211 close_json_object();
212 fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
213 return -1;
214 }
215
216 parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len);
217
218 open_json_object(NULL);
219
220 if (n->nlmsg_type == RTM_DELROUTE)
221 print_bool(PRINT_ANY, "deleted", "Deleted ", true);
222
223 if (tb[NHA_ID])
224 print_uint(PRINT_ANY, "id", "id %u ",
225 rta_getattr_u32(tb[NHA_ID]));
226
227 if (tb[NHA_GROUP])
228 print_nh_group(fp, tb[NHA_GROUP]);
229
230 if (tb[NHA_ENCAP])
231 lwt_print_encap(fp, tb[NHA_ENCAP_TYPE], tb[NHA_ENCAP]);
232
233 if (tb[NHA_GATEWAY])
234 print_rta_gateway(fp, nhm->nh_family, tb[NHA_GATEWAY]);
235
236 if (tb[NHA_OIF])
237 print_rta_if(fp, tb[NHA_OIF], "dev");
238
239 if (nhm->nh_scope != RT_SCOPE_UNIVERSE || show_details > 0) {
240 print_string(PRINT_ANY, "scope", "scope %s ",
241 rtnl_rtscope_n2a(nhm->nh_scope, b1, sizeof(b1)));
242 }
243
244 if (tb[NHA_BLACKHOLE])
245 print_null(PRINT_ANY, "blackhole", "blackhole", NULL);
246
247 if (nhm->nh_protocol != RTPROT_UNSPEC || show_details > 0) {
248 print_string(PRINT_ANY, "protocol", "proto %s ",
249 rtnl_rtprot_n2a(nhm->nh_protocol, b1, sizeof(b1)));
250 }
251
252 if (tb[NHA_OIF])
253 print_rt_flags(fp, nhm->nh_flags);
254
255 print_string(PRINT_FP, NULL, "%s", "\n");
256 close_json_object();
257 fflush(fp);
258
259 return 0;
260 }
261
262 static int add_nh_group_attr(struct nlmsghdr *n, int maxlen, char *argv)
263 {
264 struct nexthop_grp *grps;
265 int count = 0, i;
266 char *sep, *wsep;
267
268 if (*argv != '\0')
269 count = 1;
270
271 /* separator is '/' */
272 sep = strchr(argv, '/');
273 while (sep) {
274 count++;
275 sep = strchr(sep + 1, '/');
276 }
277
278 if (count == 0)
279 return -1;
280
281 grps = calloc(count, sizeof(*grps));
282 if (!grps)
283 return -1;
284
285 for (i = 0; i < count; ++i) {
286 sep = strchr(argv, '/');
287 if (sep)
288 *sep = '\0';
289
290 wsep = strchr(argv, ',');
291 if (wsep)
292 *wsep = '\0';
293
294 if (get_unsigned(&grps[i].id, argv, 0))
295 return -1;
296 if (wsep) {
297 unsigned int w;
298
299 wsep++;
300 if (get_unsigned(&w, wsep, 0) || w == 0 || w > 256)
301 invarg("\"weight\" is invalid\n", wsep);
302 grps[i].weight = w - 1;
303 }
304
305 if (!sep)
306 break;
307
308 argv = sep + 1;
309 }
310
311 return addattr_l(n, maxlen, NHA_GROUP, grps, count * sizeof(*grps));
312 }
313
314 static int ipnh_modify(int cmd, unsigned int flags, int argc, char **argv)
315 {
316 struct {
317 struct nlmsghdr n;
318 struct nhmsg nhm;
319 char buf[1024];
320 } req = {
321 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
322 .n.nlmsg_flags = NLM_F_REQUEST | flags,
323 .n.nlmsg_type = cmd,
324 .nhm.nh_family = preferred_family,
325 };
326 __u32 nh_flags = 0;
327
328 while (argc > 0) {
329 if (!strcmp(*argv, "id")) {
330 __u32 id;
331
332 NEXT_ARG();
333 if (get_unsigned(&id, *argv, 0))
334 invarg("invalid id value", *argv);
335 addattr32(&req.n, sizeof(req), NHA_ID, id);
336 } else if (!strcmp(*argv, "dev")) {
337 int ifindex;
338
339 NEXT_ARG();
340 ifindex = ll_name_to_index(*argv);
341 if (!ifindex)
342 invarg("Device does not exist\n", *argv);
343 addattr32(&req.n, sizeof(req), NHA_OIF, ifindex);
344 if (req.nhm.nh_family == AF_UNSPEC)
345 req.nhm.nh_family = AF_INET;
346 } else if (strcmp(*argv, "via") == 0) {
347 inet_prefix addr;
348 int family;
349
350 NEXT_ARG();
351 family = read_family(*argv);
352 if (family == AF_UNSPEC)
353 family = req.nhm.nh_family;
354 else
355 NEXT_ARG();
356 get_addr(&addr, *argv, family);
357 if (req.nhm.nh_family == AF_UNSPEC)
358 req.nhm.nh_family = addr.family;
359 else if (req.nhm.nh_family != addr.family)
360 invarg("address family mismatch\n", *argv);
361 addattr_l(&req.n, sizeof(req), NHA_GATEWAY,
362 &addr.data, addr.bytelen);
363 } else if (strcmp(*argv, "encap") == 0) {
364 char buf[1024];
365 struct rtattr *rta = (void *)buf;
366
367 rta->rta_type = NHA_ENCAP;
368 rta->rta_len = RTA_LENGTH(0);
369
370 lwt_parse_encap(rta, sizeof(buf), &argc, &argv,
371 NHA_ENCAP, NHA_ENCAP_TYPE);
372
373 if (rta->rta_len > RTA_LENGTH(0)) {
374 addraw_l(&req.n, 1024, RTA_DATA(rta),
375 RTA_PAYLOAD(rta));
376 }
377 } else if (!strcmp(*argv, "blackhole")) {
378 addattr_l(&req.n, sizeof(req), NHA_BLACKHOLE, NULL, 0);
379 if (req.nhm.nh_family == AF_UNSPEC)
380 req.nhm.nh_family = AF_INET;
381 } else if (!strcmp(*argv, "onlink")) {
382 nh_flags |= RTNH_F_ONLINK;
383 } else if (!strcmp(*argv, "group")) {
384 NEXT_ARG();
385
386 if (add_nh_group_attr(&req.n, sizeof(req), *argv))
387 invarg("\"group\" value is invalid\n", *argv);
388 } else if (matches(*argv, "protocol") == 0) {
389 __u32 prot;
390
391 NEXT_ARG();
392 if (rtnl_rtprot_a2n(&prot, *argv))
393 invarg("\"protocol\" value is invalid\n", *argv);
394 req.nhm.nh_protocol = prot;
395 } else if (strcmp(*argv, "help") == 0) {
396 usage();
397 } else {
398 invarg("", *argv);
399 }
400 argc--; argv++;
401 }
402
403 req.nhm.nh_flags = nh_flags;
404
405 if (rtnl_talk(&rth, &req.n, NULL) < 0)
406 return -2;
407
408 return 0;
409 }
410
411 static int ipnh_get_id(__u32 id)
412 {
413 struct {
414 struct nlmsghdr n;
415 struct nhmsg nhm;
416 char buf[1024];
417 } req = {
418 .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
419 .n.nlmsg_flags = NLM_F_REQUEST,
420 .n.nlmsg_type = RTM_GETNEXTHOP,
421 .nhm.nh_family = preferred_family,
422 };
423 struct nlmsghdr *answer;
424
425 addattr32(&req.n, sizeof(req), NHA_ID, id);
426
427 if (rtnl_talk(&rth, &req.n, &answer) < 0)
428 return -2;
429
430 new_json_obj(json);
431
432 if (print_nexthop(answer, (void *)stdout) < 0) {
433 free(answer);
434 return -1;
435 }
436
437 delete_json_obj();
438 fflush(stdout);
439
440 free(answer);
441
442 return 0;
443 }
444
445 static int ipnh_list_flush(int argc, char **argv, int action)
446 {
447 unsigned int all = (argc == 0);
448
449 while (argc > 0) {
450 if (!matches(*argv, "dev")) {
451 NEXT_ARG();
452 filter.ifindex = ll_name_to_index(*argv);
453 if (!filter.ifindex)
454 invarg("Device does not exist\n", *argv);
455 } else if (!matches(*argv, "groups")) {
456 filter.groups = 1;
457 } else if (!matches(*argv, "master")) {
458 NEXT_ARG();
459 filter.master = ll_name_to_index(*argv);
460 if (!filter.master)
461 invarg("Device does not exist\n", *argv);
462 } else if (matches(*argv, "vrf") == 0) {
463 NEXT_ARG();
464 if (!name_is_vrf(*argv))
465 invarg("Invalid VRF\n", *argv);
466 filter.master = ll_name_to_index(*argv);
467 if (!filter.master)
468 invarg("VRF does not exist\n", *argv);
469 } else if (!strcmp(*argv, "id")) {
470 __u32 id;
471
472 NEXT_ARG();
473 if (get_unsigned(&id, *argv, 0))
474 invarg("invalid id value", *argv);
475 return ipnh_get_id(id);
476 } else if (matches(*argv, "help") == 0) {
477 usage();
478 } else {
479 invarg("", *argv);
480 }
481 argc--; argv++;
482 }
483
484 if (action == IPNH_FLUSH)
485 return ipnh_flush(all);
486
487 if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) {
488 perror("Cannot send dump request");
489 return -2;
490 }
491
492 new_json_obj(json);
493
494 if (rtnl_dump_filter(&rth, print_nexthop, stdout) < 0) {
495 fprintf(stderr, "Dump terminated\n");
496 return -2;
497 }
498
499 delete_json_obj();
500 fflush(stdout);
501
502 return 0;
503 }
504
505 static int ipnh_get(int argc, char **argv)
506 {
507 __u32 id = 0;
508
509 while (argc > 0) {
510 if (!strcmp(*argv, "id")) {
511 NEXT_ARG();
512 if (get_unsigned(&id, *argv, 0))
513 invarg("invalid id value", *argv);
514 } else {
515 usage();
516 }
517 argc--; argv++;
518 }
519
520 if (!id) {
521 usage();
522 return -1;
523 }
524
525 return ipnh_get_id(id);
526 }
527
528 int do_ipnh(int argc, char **argv)
529 {
530 if (argc < 1)
531 return ipnh_list_flush(0, NULL, IPNH_LIST);
532
533 if (!matches(*argv, "add"))
534 return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_EXCL,
535 argc-1, argv+1);
536 if (!matches(*argv, "replace"))
537 return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_REPLACE,
538 argc-1, argv+1);
539 if (!matches(*argv, "delete"))
540 return ipnh_modify(RTM_DELNEXTHOP, 0, argc-1, argv+1);
541
542 if (!matches(*argv, "list") ||
543 !matches(*argv, "show") ||
544 !matches(*argv, "lst"))
545 return ipnh_list_flush(argc-1, argv+1, IPNH_LIST);
546
547 if (!matches(*argv, "get"))
548 return ipnh_get(argc-1, argv+1);
549
550 if (!matches(*argv, "flush"))
551 return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH);
552
553 if (!matches(*argv, "help"))
554 usage();
555
556 fprintf(stderr,
557 "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv);
558 exit(-1);
559 }