]>
Commit | Line | Data |
---|---|---|
63df8e85 DA |
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 | close_json_array(PRINT_JSON, NULL); | |
190 | } | |
191 | ||
192 | int print_nexthop(struct nlmsghdr *n, void *arg) | |
193 | { | |
194 | struct nhmsg *nhm = NLMSG_DATA(n); | |
195 | struct rtattr *tb[NHA_MAX+1]; | |
196 | FILE *fp = (FILE *)arg; | |
197 | int len; | |
198 | ||
199 | SPRINT_BUF(b1); | |
200 | ||
201 | if (n->nlmsg_type != RTM_DELNEXTHOP && | |
202 | n->nlmsg_type != RTM_NEWNEXTHOP) { | |
203 | fprintf(stderr, "Not a nexthop: %08x %08x %08x\n", | |
204 | n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); | |
205 | return -1; | |
206 | } | |
207 | ||
208 | len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm)); | |
209 | if (len < 0) { | |
210 | close_json_object(); | |
211 | fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); | |
212 | return -1; | |
213 | } | |
214 | ||
215 | parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len); | |
216 | ||
217 | open_json_object(NULL); | |
218 | ||
219 | if (n->nlmsg_type == RTM_DELROUTE) | |
220 | print_bool(PRINT_ANY, "deleted", "Deleted ", true); | |
221 | ||
222 | if (tb[NHA_ID]) | |
223 | print_uint(PRINT_ANY, "id", "id %u ", | |
224 | rta_getattr_u32(tb[NHA_ID])); | |
225 | ||
226 | if (tb[NHA_GROUP]) | |
227 | print_nh_group(fp, tb[NHA_GROUP]); | |
228 | ||
229 | if (tb[NHA_ENCAP]) | |
230 | lwt_print_encap(fp, tb[NHA_ENCAP_TYPE], tb[NHA_ENCAP]); | |
231 | ||
232 | if (tb[NHA_GATEWAY]) | |
233 | print_rta_gateway(fp, nhm->nh_family, tb[NHA_GATEWAY]); | |
234 | ||
235 | if (tb[NHA_OIF]) | |
236 | print_rta_if(fp, tb[NHA_OIF], "dev"); | |
237 | ||
238 | if (nhm->nh_scope != RT_SCOPE_UNIVERSE || show_details > 0) { | |
239 | print_string(PRINT_ANY, "scope", "scope %s ", | |
240 | rtnl_rtscope_n2a(nhm->nh_scope, b1, sizeof(b1))); | |
241 | } | |
242 | ||
243 | if (tb[NHA_BLACKHOLE]) | |
244 | print_null(PRINT_ANY, "blackhole", "blackhole", NULL); | |
245 | ||
246 | if (nhm->nh_protocol != RTPROT_UNSPEC || show_details > 0) { | |
247 | print_string(PRINT_ANY, "protocol", "proto %s ", | |
248 | rtnl_rtprot_n2a(nhm->nh_protocol, b1, sizeof(b1))); | |
249 | } | |
250 | ||
251 | if (tb[NHA_OIF]) | |
252 | print_rt_flags(fp, nhm->nh_flags); | |
253 | ||
254 | print_string(PRINT_FP, NULL, "%s", "\n"); | |
255 | close_json_object(); | |
256 | fflush(fp); | |
257 | ||
258 | return 0; | |
259 | } | |
260 | ||
261 | static int add_nh_group_attr(struct nlmsghdr *n, int maxlen, char *argv) | |
262 | { | |
263 | struct nexthop_grp *grps; | |
264 | int count = 0, i; | |
265 | char *sep, *wsep; | |
266 | ||
267 | if (*argv != '\0') | |
268 | count = 1; | |
269 | ||
270 | /* separator is '/' */ | |
271 | sep = strchr(argv, '/'); | |
272 | while (sep) { | |
273 | count++; | |
274 | sep = strchr(sep + 1, '/'); | |
275 | } | |
276 | ||
277 | if (count == 0) | |
278 | return -1; | |
279 | ||
280 | grps = calloc(count, sizeof(*grps)); | |
281 | if (!grps) | |
282 | return -1; | |
283 | ||
284 | for (i = 0; i < count; ++i) { | |
285 | sep = strchr(argv, '/'); | |
286 | if (sep) | |
287 | *sep = '\0'; | |
288 | ||
289 | wsep = strchr(argv, ','); | |
290 | if (wsep) | |
291 | *wsep = '\0'; | |
292 | ||
293 | if (get_unsigned(&grps[i].id, argv, 0)) | |
294 | return -1; | |
295 | if (wsep) { | |
296 | unsigned int w; | |
297 | ||
298 | wsep++; | |
299 | if (get_unsigned(&w, wsep, 0) || w == 0 || w > 256) | |
300 | invarg("\"weight\" is invalid\n", wsep); | |
301 | grps[i].weight = w - 1; | |
302 | } | |
303 | ||
304 | if (!sep) | |
305 | break; | |
306 | ||
307 | argv = sep + 1; | |
308 | } | |
309 | ||
310 | return addattr_l(n, maxlen, NHA_GROUP, grps, count * sizeof(*grps)); | |
311 | } | |
312 | ||
313 | static int ipnh_modify(int cmd, unsigned int flags, int argc, char **argv) | |
314 | { | |
315 | struct { | |
316 | struct nlmsghdr n; | |
317 | struct nhmsg nhm; | |
318 | char buf[1024]; | |
319 | } req = { | |
320 | .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), | |
321 | .n.nlmsg_flags = NLM_F_REQUEST | flags, | |
322 | .n.nlmsg_type = cmd, | |
323 | .nhm.nh_family = preferred_family, | |
324 | }; | |
325 | __u32 nh_flags = 0; | |
326 | ||
327 | while (argc > 0) { | |
328 | if (!strcmp(*argv, "id")) { | |
329 | __u32 id; | |
330 | ||
331 | NEXT_ARG(); | |
332 | if (get_unsigned(&id, *argv, 0)) | |
333 | invarg("invalid id value", *argv); | |
334 | addattr32(&req.n, sizeof(req), NHA_ID, id); | |
335 | } else if (!strcmp(*argv, "dev")) { | |
336 | int ifindex; | |
337 | ||
338 | NEXT_ARG(); | |
339 | ifindex = ll_name_to_index(*argv); | |
340 | if (!ifindex) | |
341 | invarg("Device does not exist\n", *argv); | |
342 | addattr32(&req.n, sizeof(req), NHA_OIF, ifindex); | |
343 | if (req.nhm.nh_family == AF_UNSPEC) | |
344 | req.nhm.nh_family = AF_INET; | |
345 | } else if (strcmp(*argv, "via") == 0) { | |
346 | inet_prefix addr; | |
347 | int family; | |
348 | ||
349 | NEXT_ARG(); | |
350 | family = read_family(*argv); | |
351 | if (family == AF_UNSPEC) | |
352 | family = req.nhm.nh_family; | |
353 | else | |
354 | NEXT_ARG(); | |
355 | get_addr(&addr, *argv, family); | |
356 | if (req.nhm.nh_family == AF_UNSPEC) | |
357 | req.nhm.nh_family = addr.family; | |
358 | else if (req.nhm.nh_family != addr.family) | |
359 | invarg("address family mismatch\n", *argv); | |
360 | addattr_l(&req.n, sizeof(req), NHA_GATEWAY, | |
361 | &addr.data, addr.bytelen); | |
362 | } else if (strcmp(*argv, "encap") == 0) { | |
363 | char buf[1024]; | |
364 | struct rtattr *rta = (void *)buf; | |
365 | ||
366 | rta->rta_type = NHA_ENCAP; | |
367 | rta->rta_len = RTA_LENGTH(0); | |
368 | ||
369 | lwt_parse_encap(rta, sizeof(buf), &argc, &argv, | |
370 | NHA_ENCAP, NHA_ENCAP_TYPE); | |
371 | ||
372 | if (rta->rta_len > RTA_LENGTH(0)) { | |
373 | addraw_l(&req.n, 1024, RTA_DATA(rta), | |
374 | RTA_PAYLOAD(rta)); | |
375 | } | |
376 | } else if (!strcmp(*argv, "blackhole")) { | |
377 | addattr_l(&req.n, sizeof(req), NHA_BLACKHOLE, NULL, 0); | |
378 | if (req.nhm.nh_family == AF_UNSPEC) | |
379 | req.nhm.nh_family = AF_INET; | |
380 | } else if (!strcmp(*argv, "onlink")) { | |
381 | nh_flags |= RTNH_F_ONLINK; | |
382 | } else if (!strcmp(*argv, "group")) { | |
383 | NEXT_ARG(); | |
384 | ||
385 | if (add_nh_group_attr(&req.n, sizeof(req), *argv)) | |
386 | invarg("\"group\" value is invalid\n", *argv); | |
387 | } else if (matches(*argv, "protocol") == 0) { | |
388 | __u32 prot; | |
389 | ||
390 | NEXT_ARG(); | |
391 | if (rtnl_rtprot_a2n(&prot, *argv)) | |
392 | invarg("\"protocol\" value is invalid\n", *argv); | |
393 | req.nhm.nh_protocol = prot; | |
394 | } else if (strcmp(*argv, "help") == 0) { | |
395 | usage(); | |
396 | } else { | |
397 | invarg("", *argv); | |
398 | } | |
399 | argc--; argv++; | |
400 | } | |
401 | ||
402 | req.nhm.nh_flags = nh_flags; | |
403 | ||
404 | if (rtnl_talk(&rth, &req.n, NULL) < 0) | |
405 | return -2; | |
406 | ||
407 | return 0; | |
408 | } | |
409 | ||
410 | static int ipnh_get_id(__u32 id) | |
411 | { | |
412 | struct { | |
413 | struct nlmsghdr n; | |
414 | struct nhmsg nhm; | |
415 | char buf[1024]; | |
416 | } req = { | |
417 | .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), | |
418 | .n.nlmsg_flags = NLM_F_REQUEST, | |
419 | .n.nlmsg_type = RTM_GETNEXTHOP, | |
420 | .nhm.nh_family = preferred_family, | |
421 | }; | |
422 | struct nlmsghdr *answer; | |
423 | ||
424 | addattr32(&req.n, sizeof(req), NHA_ID, id); | |
425 | ||
426 | if (rtnl_talk(&rth, &req.n, &answer) < 0) | |
427 | return -2; | |
428 | ||
429 | new_json_obj(json); | |
430 | ||
431 | if (print_nexthop(answer, (void *)stdout) < 0) { | |
432 | free(answer); | |
433 | return -1; | |
434 | } | |
435 | ||
436 | delete_json_obj(); | |
437 | fflush(stdout); | |
438 | ||
439 | free(answer); | |
440 | ||
441 | return 0; | |
442 | } | |
443 | ||
444 | static int ipnh_list_flush(int argc, char **argv, int action) | |
445 | { | |
446 | unsigned int all = (argc == 0); | |
447 | ||
448 | while (argc > 0) { | |
449 | if (!matches(*argv, "dev")) { | |
450 | NEXT_ARG(); | |
451 | filter.ifindex = ll_name_to_index(*argv); | |
452 | if (!filter.ifindex) | |
453 | invarg("Device does not exist\n", *argv); | |
454 | } else if (!matches(*argv, "groups")) { | |
455 | filter.groups = 1; | |
456 | } else if (!matches(*argv, "master")) { | |
457 | NEXT_ARG(); | |
458 | filter.master = ll_name_to_index(*argv); | |
459 | if (!filter.master) | |
460 | invarg("Device does not exist\n", *argv); | |
461 | } else if (matches(*argv, "vrf") == 0) { | |
462 | NEXT_ARG(); | |
463 | if (!name_is_vrf(*argv)) | |
464 | invarg("Invalid VRF\n", *argv); | |
465 | filter.master = ll_name_to_index(*argv); | |
466 | if (!filter.master) | |
467 | invarg("VRF does not exist\n", *argv); | |
468 | } else if (!strcmp(*argv, "id")) { | |
469 | __u32 id; | |
470 | ||
471 | NEXT_ARG(); | |
472 | if (get_unsigned(&id, *argv, 0)) | |
473 | invarg("invalid id value", *argv); | |
474 | return ipnh_get_id(id); | |
475 | } else if (matches(*argv, "help") == 0) { | |
476 | usage(); | |
477 | } else { | |
478 | invarg("", *argv); | |
479 | } | |
480 | argc--; argv++; | |
481 | } | |
482 | ||
483 | if (action == IPNH_FLUSH) | |
484 | return ipnh_flush(all); | |
485 | ||
486 | if (rtnl_nexthopdump_req(&rth, preferred_family, nh_dump_filter) < 0) { | |
487 | perror("Cannot send dump request"); | |
488 | return -2; | |
489 | } | |
490 | ||
491 | new_json_obj(json); | |
492 | ||
493 | if (rtnl_dump_filter(&rth, print_nexthop, stdout) < 0) { | |
494 | fprintf(stderr, "Dump terminated\n"); | |
495 | return -2; | |
496 | } | |
497 | ||
498 | delete_json_obj(); | |
499 | fflush(stdout); | |
500 | ||
501 | return 0; | |
502 | } | |
503 | ||
504 | static int ipnh_get(int argc, char **argv) | |
505 | { | |
506 | __u32 id = 0; | |
507 | ||
508 | while (argc > 0) { | |
509 | if (!strcmp(*argv, "id")) { | |
510 | NEXT_ARG(); | |
511 | if (get_unsigned(&id, *argv, 0)) | |
512 | invarg("invalid id value", *argv); | |
513 | } else { | |
514 | usage(); | |
515 | } | |
516 | argc--; argv++; | |
517 | } | |
518 | ||
519 | if (!id) { | |
520 | usage(); | |
521 | return -1; | |
522 | } | |
523 | ||
524 | return ipnh_get_id(id); | |
525 | } | |
526 | ||
527 | int do_ipnh(int argc, char **argv) | |
528 | { | |
529 | if (argc < 1) | |
530 | return ipnh_list_flush(0, NULL, IPNH_LIST); | |
531 | ||
532 | if (!matches(*argv, "add")) | |
533 | return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_EXCL, | |
534 | argc-1, argv+1); | |
535 | if (!matches(*argv, "replace")) | |
536 | return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_REPLACE, | |
537 | argc-1, argv+1); | |
538 | if (!matches(*argv, "delete")) | |
539 | return ipnh_modify(RTM_DELNEXTHOP, 0, argc-1, argv+1); | |
540 | ||
541 | if (!matches(*argv, "list") || | |
542 | !matches(*argv, "show") || | |
543 | !matches(*argv, "lst")) | |
544 | return ipnh_list_flush(argc-1, argv+1, IPNH_LIST); | |
545 | ||
546 | if (!matches(*argv, "get")) | |
547 | return ipnh_get(argc-1, argv+1); | |
548 | ||
549 | if (!matches(*argv, "flush")) | |
550 | return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH); | |
551 | ||
552 | if (!matches(*argv, "help")) | |
553 | usage(); | |
554 | ||
555 | fprintf(stderr, | |
556 | "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv); | |
557 | exit(-1); | |
558 | } |