From: Petr Machata <m...@pmachata.org>

From: Ido Schimmel <ido...@nvidia.com>

Add ability to dump multiple nexthop buckets and get a specific one.
Example:

 # ip nexthop add id 10 group 1/2 type resilient buckets 8
 # ip nexthop
 id 1 via 192.0.2.2 dev dummy10 scope link
 id 2 via 192.0.2.19 dev dummy20 scope link
 id 10 group 1/2 type resilient buckets 8 idle_timer 120 unbalanced_timer 0 
unbalanced_time 0
 # ip nexthop bucket
 id 10 index 0 idle_time 28.1 nhid 2
 id 10 index 1 idle_time 28.1 nhid 2
 id 10 index 2 idle_time 28.1 nhid 2
 id 10 index 3 idle_time 28.1 nhid 2
 id 10 index 4 idle_time 28.1 nhid 1
 id 10 index 5 idle_time 28.1 nhid 1
 id 10 index 6 idle_time 28.1 nhid 1
 id 10 index 7 idle_time 28.1 nhid 1
 # ip nexthop bucket show nhid 1
 id 10 index 4 idle_time 53.59 nhid 1
 id 10 index 5 idle_time 53.59 nhid 1
 id 10 index 6 idle_time 53.59 nhid 1
 id 10 index 7 idle_time 53.59 nhid 1
 # ip nexthop bucket get id 10 index 5
 id 10 index 5 idle_time 81 nhid 1
 # ip -j -p nexthop bucket get id 10 index 5
 [ {
         "id": 10,
         "bucket": {
             "index": 5,
             "idle_time": 104.89,
             "nhid": 1
         },
         "flags": [ ]
     } ]

Signed-off-by: Ido Schimmel <ido...@nvidia.com>
---
 include/libnetlink.h  |   3 +
 ip/ip_common.h        |   1 +
 ip/ipmonitor.c        |   6 +
 ip/ipnexthop.c        | 254 ++++++++++++++++++++++++++++++++++++++++++
 lib/libnetlink.c      |  26 +++++
 man/man8/ip-nexthop.8 |  45 ++++++++
 6 files changed, 335 insertions(+)

diff --git a/include/libnetlink.h b/include/libnetlink.h
index b9073a6a13ad..e8ed5d7fb495 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -97,6 +97,9 @@ int rtnl_dump_request_n(struct rtnl_handle *rth, struct 
nlmsghdr *n)
 int rtnl_nexthopdump_req(struct rtnl_handle *rth, int family,
                         req_filter_fn_t filter_fn)
        __attribute__((warn_unused_result));
+int rtnl_nexthop_bucket_dump_req(struct rtnl_handle *rth, int family,
+                                req_filter_fn_t filter_fn)
+       __attribute__((warn_unused_result));
 
 struct rtnl_ctrl_data {
        int     nsid;
diff --git a/ip/ip_common.h b/ip/ip_common.h
index 9a31e837563f..55a5521c4275 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -53,6 +53,7 @@ int print_rule(struct nlmsghdr *n, void *arg);
 int print_netconf(struct rtnl_ctrl_data *ctrl,
                  struct nlmsghdr *n, void *arg);
 int print_nexthop(struct nlmsghdr *n, void *arg);
+int print_nexthop_bucket(struct nlmsghdr *n, void *arg);
 void netns_map_init(void);
 void netns_nsid_socket_init(void);
 int print_nsid(struct nlmsghdr *n, void *arg);
diff --git a/ip/ipmonitor.c b/ip/ipmonitor.c
index 99f5fda8ba1f..d7f31cf5d1b5 100644
--- a/ip/ipmonitor.c
+++ b/ip/ipmonitor.c
@@ -90,6 +90,12 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl,
                print_nexthop(n, arg);
                return 0;
 
+       case RTM_NEWNEXTHOPBUCKET:
+       case RTM_DELNEXTHOPBUCKET:
+               print_headers(fp, "[NEXTHOPBUCKET]", ctrl);
+               print_nexthop_bucket(n, arg);
+               return 0;
+
        case RTM_NEWLINK:
        case RTM_DELLINK:
                ll_remember_index(n, NULL);
diff --git a/ip/ipnexthop.c b/ip/ipnexthop.c
index 1d50bf7529c4..0263307c49df 100644
--- a/ip/ipnexthop.c
+++ b/ip/ipnexthop.c
@@ -21,6 +21,8 @@ static struct {
        unsigned int master;
        unsigned int proto;
        unsigned int fdb;
+       unsigned int id;
+       unsigned int nhid;
 } filter;
 
 enum {
@@ -39,8 +41,11 @@ static void usage(void)
                "Usage: ip nexthop { list | flush } [ protocol ID ] SELECTOR\n"
                "       ip nexthop { add | replace } id ID NH [ protocol ID ]\n"
                "       ip nexthop { get | del } id ID\n"
+               "       ip nexthop bucket list BUCKET_SELECTOR\n"
+               "       ip nexthop bucket get id ID index INDEX\n"
                "SELECTOR := [ id ID ] [ dev DEV ] [ vrf NAME ] [ master DEV 
]\n"
                "            [ groups ] [ fdb ]\n"
+               "BUCKET_SELECTOR := SELECTOR | [ nhid ID ]\n"
                "NH := { blackhole | [ via ADDRESS ] [ dev DEV ] [ onlink ]\n"
                "        [ encap ENCAPTYPE ENCAPHDR ] |\n"
                "        group GROUP [ fdb ] [ type TYPE [ TYPE_ARGS ] ] }\n"
@@ -85,6 +90,36 @@ static int nh_dump_filter(struct nlmsghdr *nlh, int reqlen)
        return 0;
 }
 
+static int nh_dump_bucket_filter(struct nlmsghdr *nlh, int reqlen)
+{
+       struct rtattr *nest;
+       int err = 0;
+
+       err = nh_dump_filter(nlh, reqlen);
+       if (err)
+               return err;
+
+       if (filter.id) {
+               err = addattr32(nlh, reqlen, NHA_ID, filter.id);
+               if (err)
+                       return err;
+       }
+
+       if (filter.nhid) {
+               nest = addattr_nest(nlh, reqlen, NHA_RES_BUCKET);
+               nest->rta_type |= NLA_F_NESTED;
+
+               err = addattr32(nlh, reqlen, NHA_RES_BUCKET_NH_ID,
+                               filter.nhid);
+               if (err)
+                       return err;
+
+               addattr_nest_end(nlh, nest);
+       }
+
+       return err;
+}
+
 static struct rtnl_handle rth_del = { .fd = -1 };
 
 static int delete_nexthop(__u32 id)
@@ -266,6 +301,33 @@ static void print_nh_res_group(FILE *fp, const struct 
rtattr *res_grp_attr)
        close_json_object();
 }
 
+static void print_nh_res_bucket(FILE *fp, const struct rtattr *res_bucket_attr)
+{
+       struct rtattr *tb[NHA_RES_BUCKET_MAX + 1];
+
+       parse_rtattr_nested(tb, NHA_RES_BUCKET_MAX, res_bucket_attr);
+
+       open_json_object("bucket");
+
+       if (tb[NHA_RES_BUCKET_INDEX])
+               print_uint(PRINT_ANY, "index", "index %u ",
+                          rta_getattr_u16(tb[NHA_RES_BUCKET_INDEX]));
+
+       if (tb[NHA_RES_BUCKET_IDLE_TIME]) {
+               struct rtattr *rta = tb[NHA_RES_BUCKET_IDLE_TIME];
+               struct timeval tv;
+
+               __jiffies_to_tv(&tv, rta_getattr_u64(rta));
+               print_tv(PRINT_ANY, "idle_time", "idle_time %g ", &tv);
+       }
+
+       if (tb[NHA_RES_BUCKET_NH_ID])
+               print_uint(PRINT_ANY, "nhid", "nhid %u ",
+                          rta_getattr_u32(tb[NHA_RES_BUCKET_NH_ID]));
+
+       close_json_object();
+}
+
 int print_nexthop(struct nlmsghdr *n, void *arg)
 {
        struct nhmsg *nhm = NLMSG_DATA(n);
@@ -346,6 +408,50 @@ int print_nexthop(struct nlmsghdr *n, void *arg)
        return 0;
 }
 
+int print_nexthop_bucket(struct nlmsghdr *n, void *arg)
+{
+       struct nhmsg *nhm = NLMSG_DATA(n);
+       struct rtattr *tb[NHA_MAX+1];
+       FILE *fp = (FILE *)arg;
+       int len;
+
+       if (n->nlmsg_type != RTM_DELNEXTHOPBUCKET &&
+           n->nlmsg_type != RTM_NEWNEXTHOPBUCKET) {
+               fprintf(stderr, "Not a nexthop bucket: %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_flags(tb, NHA_MAX, RTM_NHA(nhm), len, NLA_F_NESTED);
+
+       open_json_object(NULL);
+
+       if (n->nlmsg_type == RTM_DELNEXTHOP)
+               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_RES_BUCKET])
+               print_nh_res_bucket(fp, tb[NHA_RES_BUCKET]);
+
+       print_rt_flags(fp, nhm->nh_flags);
+
+       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;
@@ -721,6 +827,151 @@ static int ipnh_get(int argc, char **argv)
        return ipnh_get_id(id);
 }
 
+static int ipnh_bucket_list(int argc, char **argv)
+{
+       while (argc > 0) {
+               if (!matches(*argv, "dev")) {
+                       NEXT_ARG();
+                       filter.ifindex = ll_name_to_index(*argv);
+                       if (!filter.ifindex)
+                               invarg("Device does not exist\n", *argv);
+               } else if (!matches(*argv, "master")) {
+                       NEXT_ARG();
+                       filter.master = ll_name_to_index(*argv);
+                       if (!filter.master)
+                               invarg("Device does not exist\n", *argv);
+               } else if (matches(*argv, "vrf") == 0) {
+                       NEXT_ARG();
+                       if (!name_is_vrf(*argv))
+                               invarg("Invalid VRF\n", *argv);
+                       filter.master = ll_name_to_index(*argv);
+                       if (!filter.master)
+                               invarg("VRF does not exist\n", *argv);
+               } else if (!strcmp(*argv, "id")) {
+                       NEXT_ARG();
+                       filter.id = ipnh_parse_id(*argv);
+               } else if (!strcmp(*argv, "nhid")) {
+                       NEXT_ARG();
+                       filter.nhid = ipnh_parse_id(*argv);
+               } else if (matches(*argv, "help") == 0) {
+                       usage();
+               } else {
+                       invarg("", *argv);
+               }
+               argc--; argv++;
+       }
+
+       if (rtnl_nexthop_bucket_dump_req(&rth, preferred_family,
+                                        nh_dump_bucket_filter) < 0) {
+               perror("Cannot send dump request");
+               return -2;
+       }
+
+       new_json_obj(json);
+
+       if (rtnl_dump_filter(&rth, print_nexthop_bucket, stdout) < 0) {
+               fprintf(stderr, "Dump terminated\n");
+               return -2;
+       }
+
+       delete_json_obj();
+       fflush(stdout);
+
+       return 0;
+}
+
+static int ipnh_bucket_get_id(__u32 id, __u16 bucket_index)
+{
+       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_GETNEXTHOPBUCKET,
+               .nhm.nh_family = preferred_family,
+       };
+       struct nlmsghdr *answer;
+       struct rtattr *nest;
+
+       addattr32(&req.n, sizeof(req), NHA_ID, id);
+
+       nest = addattr_nest(&req.n, sizeof(req), NHA_RES_BUCKET);
+       nest->rta_type |= NLA_F_NESTED;
+
+       addattr16(&req.n, sizeof(req), NHA_RES_BUCKET_INDEX, bucket_index);
+
+       addattr_nest_end(&req.n, nest);
+
+       if (rtnl_talk(&rth, &req.n, &answer) < 0)
+               return -2;
+
+       new_json_obj(json);
+
+       if (print_nexthop_bucket(answer, (void *)stdout) < 0) {
+               free(answer);
+               return -1;
+       }
+
+       delete_json_obj();
+       fflush(stdout);
+
+       free(answer);
+
+       return 0;
+}
+
+static int ipnh_bucket_get(int argc, char **argv)
+{
+       bool bucket_valid = false;
+       __u16 bucket_index;
+       __u32 id = 0;
+
+       while (argc > 0) {
+               if (!strcmp(*argv, "id")) {
+                       NEXT_ARG();
+                       id = ipnh_parse_id(*argv);
+               } else if (!strcmp(*argv, "index")) {
+                       NEXT_ARG();
+                       if (get_u16(&bucket_index, *argv, 0))
+                               invarg("invalid bucket index value", *argv);
+                       bucket_valid = true;
+               } else  {
+                       usage();
+               }
+               argc--; argv++;
+       }
+
+       if (!id || !bucket_valid) {
+               usage();
+               return -1;
+       }
+
+       return ipnh_bucket_get_id(id, bucket_index);
+}
+
+static int do_ipnh_bucket(int argc, char **argv)
+{
+       if (argc < 1)
+               return ipnh_bucket_list(0, NULL);
+
+       if (!matches(*argv, "list") ||
+           !matches(*argv, "show") ||
+           !matches(*argv, "lst"))
+               return ipnh_bucket_list(argc-1, argv+1);
+
+       if (!matches(*argv, "get"))
+               return ipnh_bucket_get(argc-1, argv+1);
+
+       if (!matches(*argv, "help"))
+               usage();
+
+       fprintf(stderr,
+               "Command \"%s\" is unknown, try \"ip nexthop help\".\n", *argv);
+       exit(-1);
+}
+
 int do_ipnh(int argc, char **argv)
 {
        if (argc < 1)
@@ -746,6 +997,9 @@ int do_ipnh(int argc, char **argv)
        if (!matches(*argv, "flush"))
                return ipnh_list_flush(argc-1, argv+1, IPNH_FLUSH);
 
+       if (!matches(*argv, "bucket"))
+               return do_ipnh_bucket(argc-1, argv+1);
+
        if (!matches(*argv, "help"))
                usage();
 
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index c958aa57d0cd..6885087b34f9 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -282,6 +282,32 @@ int rtnl_nexthopdump_req(struct rtnl_handle *rth, int 
family,
        return send(rth->fd, &req, sizeof(req), 0);
 }
 
+int rtnl_nexthop_bucket_dump_req(struct rtnl_handle *rth, int family,
+                                req_filter_fn_t filter_fn)
+{
+       struct {
+               struct nlmsghdr nlh;
+               struct nhmsg nhm;
+               char buf[128];
+       } req = {
+               .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct nhmsg)),
+               .nlh.nlmsg_type = RTM_GETNEXTHOPBUCKET,
+               .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+               .nlh.nlmsg_seq = rth->dump = ++rth->seq,
+               .nhm.nh_family = family,
+       };
+
+       if (filter_fn) {
+               int err;
+
+               err = filter_fn(&req.nlh, sizeof(req));
+               if (err)
+                       return err;
+       }
+
+       return send(rth->fd, &req, sizeof(req), 0);
+}
+
 int rtnl_addrdump_req(struct rtnl_handle *rth, int family,
                      req_filter_fn_t filter_fn)
 {
diff --git a/man/man8/ip-nexthop.8 b/man/man8/ip-nexthop.8
index c68fcc0f9cf5..d9ce634fb3d8 100644
--- a/man/man8/ip-nexthop.8
+++ b/man/man8/ip-nexthop.8
@@ -28,6 +28,14 @@ ip-nexthop \- nexthop object management
 .BR "ip nexthop" " { " get " | " del " } id "
 .I  ID
 
+.ti -8
+.BI "ip nexthop bucket list " BUCKET_SELECTOR
+
+.ti -8
+.BR "ip nexthop bucket get " id
+.I  ID
+.RI "index " INDEX
+
 .ti -8
 .IR SELECTOR " := "
 .RB "[ " id
@@ -41,6 +49,12 @@ ip-nexthop \- nexthop object management
 .BR  groups " ] [ "
 .BR  fdb " ]"
 
+.ti -8
+.IR BUCKET_SELECTOR " := "
+.IR SELECTOR
+.RB " | [ " nhid
+.IR ID " ]"
+
 .ti -8
 .IR NH " := { "
 .BR blackhole " | [ "
@@ -229,6 +243,37 @@ as show.
 ip nexthop get id ID
 get a single nexthop by id
 
+.TP
+ip nexthop bucket show
+show the contents of the nexthop bucket table or the nexthop buckets
+selected by some criteria.
+.RS
+.TP
+.BI id " ID "
+.in +0
+show the nexthop buckets that belong to a nexthop group with a given id
+.TP
+.BI nhid " ID "
+.in +0
+show the nexthop buckets that hold a nexthop with a given id
+.TP
+.BI dev " DEV "
+.in +0
+show the nexthop buckets using the given device
+.TP
+.BI vrf " NAME "
+.in +0
+show the nexthop buckets using devices associated with the vrf name
+.TP
+.BI master " DEV "
+.in +0
+show the nexthop buckets using devices enslaved to given master device
+.RE
+
+.TP
+ip nexthop bucket get id ID index INDEX
+get a single nexthop bucket by nexthop group id and bucket index
+
 .SH EXAMPLES
 .PP
 ip nexthop ls
-- 
2.26.2

Reply via email to