]> git.proxmox.com Git - mirror_iproute2.git/blame - ip/ipnexthop.c
Add support for nexthop objects
[mirror_iproute2.git] / ip / ipnexthop.c
CommitLineData
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
17static struct {
18 unsigned int flushed;
19 unsigned int groups;
20 unsigned int ifindex;
21 unsigned int master;
22} filter;
23
24enum {
25 IPNH_LIST,
26 IPNH_FLUSH,
27};
28
29#define RTM_NHA(h) ((struct rtattr *)(((char *)(h)) + \
30 NLMSG_ALIGN(sizeof(struct nhmsg))))
31
32static void usage(void) __attribute__((noreturn));
33
34static 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
50static 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
75static struct rtnl_handle rth_del = { .fd = -1 };
76
77static 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
99static 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
122static 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 }
136again:
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;
154out:
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
164static 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
192int 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
261static 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
313static 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
410static 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
444static 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
504static 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
527int 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}