From: David Ahern <dsah...@gmail.com> Signed-off-by: David Ahern <dsah...@gmail.com> --- include/uapi/linux/nexthop.h | 56 ++++ include/uapi/linux/rtnetlink.h | 8 + ip/Makefile | 3 +- ip/ip.c | 3 +- ip/ip_common.h | 7 +- ip/ipmonitor.c | 6 + ip/ipnexthop.c | 652 +++++++++++++++++++++++++++++++++++++++++ ip/iproute.c | 19 +- 8 files changed, 747 insertions(+), 7 deletions(-) create mode 100644 include/uapi/linux/nexthop.h create mode 100644 ip/ipnexthop.c
diff --git a/include/uapi/linux/nexthop.h b/include/uapi/linux/nexthop.h new file mode 100644 index 000000000000..335182e8229a --- /dev/null +++ b/include/uapi/linux/nexthop.h @@ -0,0 +1,56 @@ +#ifndef __LINUX_NEXTHOP_H +#define __LINUX_NEXTHOP_H + +#include <linux/types.h> + +struct nhmsg { + unsigned char nh_family; + unsigned char nh_scope; /* one of RT_SCOPE */ + unsigned char nh_protocol; /* Routing protocol that installed nh */ + unsigned char resvd; + unsigned int nh_flags; /* RTNH_F flags */ +}; + +struct nexthop_grp { + __u32 id; + __u32 weight; +}; + +enum { + NEXTHOP_GRP_TYPE_MPATH, /* default type if not specified */ + __NEXTHOP_GRP_TYPE_MAX, +}; + +#define NEXTHOP_GRP_TYPE_MAX (__NEXTHOP_GRP_TYPE_MAX - 1) + + +/* NHA_ID 32-bit id for nexthop. id must be greater than 0. + * id == 0 means assign an unused id. + */ +enum { + NHA_UNSPEC, + NHA_ID, /* u32 */ + NHA_GROUP, /* array of nexthop_grp */ + NHA_GROUP_TYPE, /* u16 one of NEXTHOP_GRP_TYPE; + * default is NEXTHOP_GRP_TYPE_MPATH */ + + /* if NHA_GROUP attribute is added, no other attributes can be set */ + + NHA_BLACKHOLE, /* flag; nexthop used to blackhole packets */ + NHA_OIF, /* u32 */ + NHA_FLOW, /* u32 */ + + NHA_TABLE_ID, /* u32 - table id to validate gateway */ + NHA_GATEWAY, /* be32 (IPv4) or in6_addr (IPv6) gw address */ + + /* Dump control attributes */ + NHA_GROUPS, /* flag; only return nexthop groups in dump */ + NHA_MASTER, /* u32; only return nexthops with given master dev */ + + NHA_SADDR, /* return only: IPv4 or IPv6 source address */ + + __NHA_MAX, +}; + +#define NHA_MAX (__NHA_MAX - 1) +#endif diff --git a/include/uapi/linux/rtnetlink.h b/include/uapi/linux/rtnetlink.h index 8c1d600bfa33..158114245b6c 100644 --- a/include/uapi/linux/rtnetlink.h +++ b/include/uapi/linux/rtnetlink.h @@ -157,6 +157,13 @@ enum { RTM_GETCHAIN, #define RTM_GETCHAIN RTM_GETCHAIN + RTM_NEWNEXTHOP = 104, +#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP + RTM_DELNEXTHOP, +#define RTM_DELNEXTHOP RTM_DELNEXTHOP + RTM_GETNEXTHOP, +#define RTM_GETNEXTHOP RTM_GETNEXTHOP + __RTM_MAX, #define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1) }; @@ -342,6 +349,7 @@ enum rtattr_type_t { RTA_IP_PROTO, RTA_SPORT, RTA_DPORT, + RTA_NH_ID, __RTA_MAX }; diff --git a/ip/Makefile b/ip/Makefile index a88f93665ee6..7df818dbe23a 100644 --- a/ip/Makefile +++ b/ip/Makefile @@ -10,7 +10,8 @@ IPOBJ=ip.o ipaddress.o ipaddrlabel.o iproute.o iprule.o ipnetns.o \ link_iptnl.o link_gre6.o iplink_bond.o iplink_bond_slave.o iplink_hsr.o \ iplink_bridge.o iplink_bridge_slave.o ipfou.o iplink_ipvlan.o \ iplink_geneve.o iplink_vrf.o iproute_lwtunnel.o ipmacsec.o ipila.o \ - ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o + ipvrf.o iplink_xstats.o ipseg6.o iplink_netdevsim.o iplink_rmnet.o \ + ipnexthop.o RTMONOBJ=rtmon.o diff --git a/ip/ip.c b/ip/ip.c index 58c643df8a36..963ef140c7c4 100644 --- a/ip/ip.c +++ b/ip/ip.c @@ -51,7 +51,7 @@ static void usage(void) "where OBJECT := { link | address | addrlabel | route | rule | neigh | ntable |\n" " tunnel | tuntap | maddress | mroute | mrule | monitor | xfrm |\n" " netns | l2tp | fou | macsec | tcp_metrics | token | netconf | ila |\n" -" vrf | sr }\n" +" vrf | sr | nexthop }\n" " OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] | -r[esolve] |\n" " -h[uman-readable] | -iec | -j[son] | -p[retty] |\n" " -f[amily] { inet | inet6 | ipx | dnet | mpls | bridge | link } |\n" @@ -101,6 +101,7 @@ static const struct cmd { { "netconf", do_ipnetconf }, { "vrf", do_ipvrf}, { "sr", do_seg6 }, + { "nexthop", do_ipnh }, { "help", do_help }, { 0 } }; diff --git a/ip/ip_common.h b/ip/ip_common.h index 200be5e23dd1..2971c1586c4e 100644 --- a/ip/ip_common.h +++ b/ip/ip_common.h @@ -56,6 +56,8 @@ int print_rule(const struct sockaddr_nl *who, int print_netconf(const struct sockaddr_nl *who, struct rtnl_ctrl_data *ctrl, struct nlmsghdr *n, void *arg); +int print_nexthop(const struct sockaddr_nl *who, + struct nlmsghdr *n, void *arg); void netns_map_init(void); void netns_nsid_socket_init(void); int print_nsid(const struct sockaddr_nl *who, @@ -90,6 +92,7 @@ int do_ipvrf(int argc, char **argv); void vrf_reset(void); int netns_identify_pid(const char *pidstr, char *name, int len); int do_seg6(int argc, char **argv); +int do_ipnh(int argc, char **argv); int iplink_get(unsigned int flags, char *name, __u32 filt_mask); int iplink_ifla_xstats(int argc, char **argv); @@ -165,5 +168,7 @@ int name_is_vrf(const char *name); #endif void print_num(FILE *fp, unsigned int width, uint64_t count); - +void print_rta_flow(FILE *fp, const struct rtattr *rta); +void print_rt_flags(FILE *fp, unsigned int flags); +void print_rta_if(FILE *fp, const struct rtattr *rta, const char *prefix); #endif /* _IP_COMMON_H_ */ diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c index a93b62cd6624..de129626683b 100644 --- a/ip/ipmonitor.c +++ b/ip/ipmonitor.c @@ -84,6 +84,12 @@ static int accept_msg(const struct sockaddr_nl *who, } } + case RTM_NEWNEXTHOP: + case RTM_DELNEXTHOP: + print_headers(fp, "[NEXTHOP]", ctrl); + print_nexthop(who, n, arg); + return 0; + case RTM_NEWLINK: case RTM_DELLINK: ll_remember_index(who, n, NULL); diff --git a/ip/ipnexthop.c b/ip/ipnexthop.c new file mode 100644 index 000000000000..9fa4b7292426 --- /dev/null +++ b/ip/ipnexthop.c @@ -0,0 +1,652 @@ +/* + * ip nexthop + * + * Copyright (C) 2017 Cumulus Networks + * Copyright (c) 2017 David Ahern <d...@cumulusnetworks.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/ip.h> +#include <errno.h> +#include <linux/nexthop.h> +#include <libmnl/libmnl.h> +#include <rt_names.h> + +#include "utils.h" +#include "ip_common.h" + +static struct +{ + unsigned int flushed; + unsigned int groups; + char *flushb; + int flushp; + int flushe; +} filter; + +enum { + IPNH_LIST, + IPNH_FLUSH, +}; + +#define RTM_NHA(h) ((struct rtattr *)(((char *)(h)) + \ + NLMSG_ALIGN(sizeof(struct nhmsg)))) + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, "Usage: ip nexthop { list | flush } SELECTOR\n"); + fprintf(stderr, " ip nexthop get [ id ID ]\n"); + fprintf(stderr, " ip nexthop { add | del | change | replace } NH\n"); + fprintf(stderr, "SELECTOR := [ id ID ] [ dev DEV ] [ table TABLE ] [ vrf NAME ]\n"); + fprintf(stderr, "NH := [ encap ENCAPTYPE ENCAPHDR ] [ via [ FAMILY ] ADDRESS ]\n"); + fprintf(stderr, " [ id ID ] [ dev STRING ] [ weight NUMBER ]\n"); + fprintf(stderr, " [ table TABLE ] [ vrf VRF ] NHFLAGS\n"); + fprintf(stderr, "NHFLAGS := [ onlink | pervasive ]\n"); + fprintf(stderr, "ENCAPTYPE := [ mpls | ip | ip6 ]\n"); + fprintf(stderr, "ENCAPHDR := [ MPLSLABEL ]\n"); + exit(-1); +} + +static int delete_nexthop(__u32 id) +{ + struct { + struct nlmsghdr n; + struct nhmsg nhm; + char buf[64]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_DELNEXTHOP, + .nhm.nh_family = AF_UNSPEC, + }; + + req.n.nlmsg_seq = ++rth.seq; + + addattr32(&req.n, sizeof(req), NHA_ID, id); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + filter.flushed++; + return 0; +} + +struct nh_entry { + __u32 id; + unsigned int group; + struct nh_entry *next; +}; + +struct nh_entry *first, *last; + +static int flush_nexthop(const struct sockaddr_nl *who, + struct nlmsghdr *nlh, void *arg) +{ + struct nhmsg *nhm = NLMSG_DATA(nlh); + struct rtattr *tb[NHA_MAX+1]; + struct nh_entry *nh; + __u32 id = 0; + int len; + + len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*nhm)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len); + if (tb[NHA_ID]) + id = rta_getattr_u32(tb[NHA_ID]); + + if (!id) + return 0; + + nh = malloc(sizeof(*nh)); + if (!nh) + return -1; + + nh->id = id; + nh->group = tb[NHA_GROUP] != NULL; + nh->next = NULL; + if (!first) + first = nh; + else + last->next = nh; + + last = nh; + return 0; +} + +static int ipnh_flush(void *req, __u32 len, unsigned int all) +{ + struct nh_entry *nh; + + if (send(rth.fd, req, len, 0) < 0) { + perror("Cannot send dump request"); + return -2; + } + + if (rtnl_dump_filter(&rth, flush_nexthop, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + return -2; + } + + /* if deleting all, then remove groups first */ + if (all) { + nh = first; + while (nh) { + if (nh->group) + delete_nexthop(nh->id); + nh = nh->next; + } + } + + nh = first; + while (nh) { + if (!all || !nh->group) + delete_nexthop(nh->id); + nh = nh->next; + } + + if (!filter.flushed) + printf("Nothing to flush\n"); + else + printf("Flushed %d nexthops\n", filter.flushed); + + return 0; +} + +static char *nh_group_type_to_str(__u16 group_type, char *buf, size_t len) +{ + static const char *typestr[NEXTHOP_GRP_TYPE_MAX + 1] = { + "multipath", /* NEXTHOP_GRP_TYPE_MPATH */ + }; + + if (group_type < ARRAY_SIZE(typestr)) + snprintf(buf, len-1, "%s", typestr[group_type]); + else + snprintf(buf, len-1, "<%u>", group_type); + + buf[len-1] = '\0'; + + return buf; +} + +static void print_nh_group(FILE *fp, const struct rtattr *grps_attr, + const struct rtattr *gtype) +{ + struct nexthop_grp *nhg = RTA_DATA(grps_attr); + int num = RTA_PAYLOAD(grps_attr) / sizeof(*nhg); + __u16 group_type = NEXTHOP_GRP_TYPE_MPATH; + int i; + + SPRINT_BUF(b1); + + if (!num || num * sizeof(*nhg) != RTA_PAYLOAD(grps_attr)) { + fprintf(fp, "<invalid nexthop group>"); + return; + } + + if (gtype) + group_type = rta_getattr_u16(gtype); + + if (is_json_context()) { + open_json_array(PRINT_JSON, "group"); + for (i = 0; i < num; ++i) { + open_json_object(NULL); + print_uint(PRINT_ANY, "id", "id %u ", nhg[i].id); + print_uint(PRINT_ANY, "weight", "weight %u ", nhg[i].weight); + close_json_object(); + } + close_json_array(PRINT_JSON, NULL); + print_string(PRINT_ANY, "type", "type %s ", + nh_group_type_to_str(group_type, b1, sizeof(b1))); + } else { + fprintf(fp, "group "); + for (i = 0; i < num; ++i) { + if (i) + fprintf(fp, "/"); + fprintf(fp, "%u", nhg[i].id); + if (num > 1 && nhg[i].weight > 1) + fprintf(fp, ",%u", nhg[i].weight); + } + } +} + +static void print_nh_gateway(FILE *fp, const struct nhmsg *nhm, + const struct rtattr *rta) +{ + const char *gateway = format_host_rta(nhm->nh_family, rta); + + if (is_json_context()) + print_string(PRINT_JSON, "gateway", NULL, gateway); + else { + fprintf(fp, "via "); + print_color_string(PRINT_FP, ifa_family_color(nhm->nh_family), + NULL, "%s ", gateway); + } +} + +int print_nexthop(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) +{ + struct nhmsg *nhm = NLMSG_DATA(n); + struct rtattr *tb[NHA_MAX+1]; + FILE *fp = (FILE *)arg; + int len; + + SPRINT_BUF(b1); + + if (n->nlmsg_type != RTM_DELNEXTHOP && + n->nlmsg_type != RTM_NEWNEXTHOP) { + fprintf(stderr, "Not a nexthop: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return -1; + } + + len = n->nlmsg_len - NLMSG_SPACE(sizeof(*nhm)); + if (len < 0) { + close_json_object(); + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + parse_rtattr(tb, NHA_MAX, RTM_NHA(nhm), len); + + open_json_object(NULL); + + if (n->nlmsg_type == RTM_DELROUTE) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + if (tb[NHA_ID]) + print_uint(PRINT_ANY, "id", "id %u ", + rta_getattr_u32(tb[NHA_ID])); + + if (tb[NHA_GROUP]) + print_nh_group(fp, tb[NHA_GROUP], tb[NHA_GROUP_TYPE]); + + if (tb[NHA_GATEWAY]) + print_nh_gateway(fp, nhm, tb[NHA_GATEWAY]); + + if (tb[NHA_SADDR]) { + const char *psrc; + + psrc = rt_addr_n2a_rta(nhm->nh_family, tb[NHA_SADDR]); + if (is_json_context()) + print_string(PRINT_JSON, "src", NULL, psrc); + else { + fprintf(fp, "src "); + print_color_string(PRINT_FP, + ifa_family_color(nhm->nh_family), + NULL, "%s ", psrc); + } + } + + if (tb[NHA_OIF]) + print_rta_if(fp, tb[NHA_OIF], "dev"); + + if (tb[NHA_BLACKHOLE]) + print_null(PRINT_ANY, "blackhole", "blackhole", NULL); + + if (nhm->nh_protocol != RTPROT_UNSPEC || show_details > 0) { + print_string(PRINT_ANY, "protocol", "proto %s ", + rtnl_rtprot_n2a(nhm->nh_protocol, b1, sizeof(b1))); + } + + if (nhm->nh_scope != RT_SCOPE_UNIVERSE || show_details > 0) { + print_string(PRINT_ANY, "scope", "scope %s ", + rtnl_rtscope_n2a(nhm->nh_scope, b1, sizeof(b1))); + } + + if (tb[NHA_OIF]) + print_rt_flags(fp, nhm->nh_flags); + + if (tb[NHA_FLOW]) + print_rta_flow(fp, tb[NHA_FLOW]); + + print_string(PRINT_FP, NULL, "%s", "\n"); + close_json_object(); + fflush(fp); + + return 0; +} + +static int add_nh_group_attr(struct nlmsghdr *n, int maxlen, char *argv) +{ + struct nexthop_grp *grps; + int count = 0, i; + char *sep, *wsep; + + if (*argv != '\0') + count = 1; + + /* separator is '/' */ + sep = strchr(argv, '/'); + while (sep) { + count++; + sep = strchr(sep + 1, '/'); + } + + if (count == 0) + return -1; + + grps = calloc(count, sizeof(*grps)); + if (!grps) + return -1; + + for (i = 0; i < count; ++i) { + sep = strchr(argv, '/'); + if (sep) + *sep = '\0'; + + wsep = strchr(argv, ','); + if (wsep) + *wsep = '\0'; + + if (get_unsigned(&grps[i].id, argv, 0)) + return -1; + if (wsep) { + wsep++; + if (get_unsigned(&grps[i].weight, wsep, 0)) + return -1; + } + + if (!sep) + break; + + argv = sep + 1; + } + + return addattr_l(n, maxlen, NHA_GROUP, grps, count * sizeof(*grps)); +} + +static int ipnh_modify(int cmd, unsigned int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct nhmsg nhm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), + .n.nlmsg_flags = NLM_F_REQUEST | flags, + .n.nlmsg_type = cmd, + .nhm.nh_family = AF_UNSPEC, + }; + __u32 nh_flags = 0; + __u16 gtype = NEXTHOP_GRP_TYPE_MAX + 1; + + while (argc > 0) { + if (!strcmp(*argv, "id")) { + __u32 id; + + NEXT_ARG(); + if (get_unsigned(&id, *argv, 0)) + invarg("invalid id value", *argv); + addattr32(&req.n, sizeof(req), NHA_ID, id); + } else if (!strcmp(*argv, "dev")) { + int ifindex; + + NEXT_ARG(); + ifindex = ll_name_to_index(*argv); + if (!ifindex) + invarg("Device does not exist\n", *argv); + addattr32(&req.n, sizeof(req), NHA_OIF, ifindex); + } else if (strcmp(*argv, "via") == 0) { + inet_prefix addr; + int family; + + NEXT_ARG(); + family = read_family(*argv); + if (family == AF_UNSPEC) + family = req.nhm.nh_family; + else + NEXT_ARG(); + get_addr(&addr, *argv, family); + if (req.nhm.nh_family == AF_UNSPEC) + req.nhm.nh_family = addr.family; + else if (req.nhm.nh_family != addr.family) + invarg("address family mismatch\n", *argv); + addattr_l(&req.n, sizeof(req), NHA_GATEWAY, + &addr.data, addr.bytelen); + } else if (!strcmp(*argv, "blackhole")) { + addattr_l(&req.n, sizeof(req), NHA_BLACKHOLE, NULL, 0); + } else if (!strcmp(*argv, "onlink")) { + nh_flags |= RTNH_F_ONLINK; + } else if (!strcmp(*argv, "realms")) { + __u32 realm; + + NEXT_ARG(); + if (get_rt_realms_or_raw(&realm, *argv)) + invarg("\"realm\" value is invalid\n", *argv); + addattr32(&req.n, sizeof(req), NHA_FLOW, realm); + } else if (!strcmp(*argv, "group")) { + NEXT_ARG(); + + if (add_nh_group_attr(&req.n, sizeof(req), *argv)) + invarg("\"group\" value is invalid\n", *argv); + } else if (!strcmp(*argv, "multipath") || + !strcmp(*argv, "mpath")) { + gtype = NEXTHOP_GRP_TYPE_MPATH; + } else if (!strcmp(*argv, "table")) { + __u32 tb_id; + + NEXT_ARG(); + if (get_unsigned(&tb_id, *argv, 0)) + invarg("invalid table value", *argv); + addattr32(&req.n, sizeof(req), NHA_TABLE_ID, tb_id); + } else if (strcmp(*argv, "vrf") == 0) { + __u32 tb_id; + + NEXT_ARG(); + tb_id = ipvrf_get_table(*argv); + if (tb_id == 0) + invarg("Invalid VRF", *argv); + addattr32(&req.n, sizeof(req), NHA_TABLE_ID, tb_id); + } else if (strcmp(*argv, "help") == 0) { + usage(); + } else { + invarg("", *argv); + } + argc--; argv++; + } + + if (gtype <= NEXTHOP_GRP_TYPE_MAX) + addattr16(&req.n, sizeof(req), NHA_GROUP_TYPE, gtype); + + req.nhm.nh_flags = nh_flags; + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -2; + + return 0; +} + +static int ipnh_get_id(__u32 id) +{ + struct { + struct nlmsghdr n; + struct nhmsg nhm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETNEXTHOP, + .nhm.nh_family = preferred_family, + }; + struct nlmsghdr *answer; + + addattr32(&req.n, sizeof(req), NHA_ID, id); + + if (rtnl_talk(&rth, &req.n, &answer) < 0) + return -2; + + new_json_obj(json); + + if (print_nexthop(NULL, answer, (void *)stdout) < 0) { + free(answer); + return -1; + } + + delete_json_obj(); + fflush(stdout); + + free(answer); + + return 0; +} + +static int ipnh_list_flush(int argc, char **argv, int action) +{ + struct { + struct nlmsghdr n; + struct nhmsg nhm; + char buf[256]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)), + .n.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETNEXTHOP, + .n.nlmsg_seq = ++rth.seq, + .nhm.nh_family = preferred_family, + }; + unsigned int master = 0; + unsigned int all = (argc == 0); + + rth.dump = req.n.nlmsg_seq; + + while (argc > 0) { + if (!matches(*argv, "dev")) { + unsigned int ifindex; + + NEXT_ARG(); + ifindex = ll_name_to_index(*argv); + if (!ifindex) + invarg("Device does not exist\n", *argv); + addattr32(&req.n, sizeof(req), NHA_OIF, ifindex); + } else if (!matches(*argv, "groups")) { + addattr_l(&req.n, sizeof(req), NHA_GROUPS, NULL, 0); + } else if (!matches(*argv, "master")) { + NEXT_ARG(); + master = ll_name_to_index(*argv); + if (!master) + invarg("Device does not exist\n", *argv); + } else if (matches(*argv, "vrf") == 0) { + NEXT_ARG(); + master = ll_name_to_index(*argv); + if (!master) + invarg("VRF does not exist\n", *argv); + if (!name_is_vrf(*argv)) + invarg("Invalid VRF\n", *argv); + } else if (!strcmp(*argv, "id")) { + __u32 id; + + NEXT_ARG(); + if (get_unsigned(&id, *argv, 0)) + invarg("invalid id value", *argv); + return ipnh_get_id(id); + } else if (matches(*argv, "help") == 0) { + usage(); + } else { + invarg("", *argv); + } + argc--; argv++; + } + + if (master) + addattr32(&req.n, sizeof(req), NHA_MASTER, master); + + if (action == IPNH_FLUSH) + return ipnh_flush(&req, req.n.nlmsg_len, all); + + if (send(rth.fd, &req, req.n.nlmsg_len, 0) < 0) { + perror("Cannot send dump request"); + return -2; + } + + new_json_obj(json); + + if (rtnl_dump_filter(&rth, print_nexthop, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + return -2; + } + + delete_json_obj(); + fflush(stdout); + + return 0; +} + +static int ipnh_get(int argc, char **argv) +{ + __u32 id = 0; + + while (argc > 0) { + if (!strcmp(*argv, "id")) { + NEXT_ARG(); + if (get_unsigned(&id, *argv, 0)) + invarg("invalid id value", *argv); + } + if (matches(*argv, "help") == 0) + usage(); + + argc--; argv++; + } + + if (!id) { + usage(); + return -1; + } + + return ipnh_get_id(id); +} + +int do_ipnh(int argc, char **argv) +{ + if (argc < 1) + return ipnh_list_flush(0, NULL, IPNH_LIST); + + if (!matches(*argv, "add")) + return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_EXCL, + argc-1, argv+1); + if (!matches(*argv, "change") || !strcmp(*argv, "chg")) + return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_REPLACE, + argc-1, argv+1); + if (!matches(*argv, "replace")) + return ipnh_modify(RTM_NEWNEXTHOP, NLM_F_CREATE|NLM_F_REPLACE, + argc-1, argv+1); + if (!matches(*argv, "delete")) + return ipnh_modify(RTM_DELNEXTHOP, 0, argc-1, argv+1); + + if (!matches(*argv, "list") || + !matches(*argv, "show") || + !matches(*argv, "lst")) + return ipnh_list_flush(argc-1, argv+1, IPNH_LIST); + + if (!matches(*argv, "get")) + return ipnh_get(argc-1, argv+1); + + if (!matches(*argv, "flush")) + return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH); + + if (!matches(*argv, "help")) + usage(); + + fprintf(stderr, + "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv); + exit(-1); +} diff --git a/ip/iproute.c b/ip/iproute.c index 30833414a3f7..0af72c2eccca 100644 --- a/ip/iproute.c +++ b/ip/iproute.c @@ -349,7 +349,7 @@ static void print_rtax_features(FILE *fp, unsigned int features) "features", "0x%x ", of); } -static void print_rt_flags(FILE *fp, unsigned int flags) +void print_rt_flags(FILE *fp, unsigned int flags) { open_json_array(PRINT_JSON, is_json_context() ? "flags" : ""); @@ -394,8 +394,8 @@ static void print_rt_pref(FILE *fp, unsigned int pref) } } -static void print_rta_if(FILE *fp, const struct rtattr *rta, - const char *prefix) +void print_rta_if(FILE *fp, const struct rtattr *rta, + const char *prefix) { const char *ifname = ll_index_to_name(rta_getattr_u32(rta)); @@ -492,7 +492,7 @@ static void print_rta_cacheinfo(FILE *fp, const struct rta_cacheinfo *ci) } } -static void print_rta_flow(FILE *fp, const struct rtattr *rta) +void print_rta_flow(FILE *fp, const struct rtattr *rta) { __u32 to = rta_getattr_u32(rta); __u32 from = to >> 16; @@ -823,6 +823,10 @@ int print_route(const struct sockaddr_nl *who, struct nlmsghdr *n, void *arg) rtnl_dsfield_n2a(r->rtm_tos, b1, sizeof(b1))); } + if (tb[RTA_NH_ID]) + print_uint(PRINT_ANY, "nhid", "nhid %u ", + rta_getattr_u32(tb[RTA_NH_ID])); + if (tb[RTA_GATEWAY] && filter.rvia.bitlen != host_len) print_rta_gateway(fp, r, tb[RTA_GATEWAY]); @@ -1351,6 +1355,13 @@ static int iproute_modify(int cmd, unsigned int flags, int argc, char **argv) } else if (strcmp(*argv, "nexthop") == 0) { nhs_ok = 1; break; + } else if (!strcmp(*argv, "nhid")) { + __u32 id; + + NEXT_ARG(); + if (get_u32(&id, *argv, 0)) + invarg("\"id\" value is invalid\n", *argv); + addattr32(&req.n, sizeof(req), RTA_NH_ID, id); } else if (matches(*argv, "protocol") == 0) { __u32 prot; -- 2.11.0