]> git.ipfire.org Git - thirdparty/iproute2.git/commitdiff
bridge: vxlan device vnifilter support
authorRoopa Prabhu <roopa@nvidia.com>
Sun, 8 May 2022 04:53:38 +0000 (04:53 +0000)
committerDavid Ahern <dsahern@kernel.org>
Sun, 8 May 2022 15:50:32 +0000 (09:50 -0600)
This patch adds bridge command to manage
recently added vnifilter on a collect metadata
vxlan device.

examples:
$bridge vni add dev vxlan0 vni 400

$bridge vni add dev vxlan0 vni 200 group 239.1.1.101

$bridge vni del dev vxlan0 vni 400

$bridge vni show

$bridge -s vni show

Signed-off-by: Roopa Prabhu <roopa@nvidia.com>
Signed-off-by: David Ahern <dsahern@kernel.org>
bridge/Makefile
bridge/br_common.h
bridge/bridge.c
bridge/monitor.c
bridge/vni.c [new file with mode: 0644]
include/libnetlink.h
lib/libnetlink.c
man/man8/bridge.8

index c6b7d08dade4d454d83798602107bfebb64f4dfb..01f8a455b97b7cbb68879440a8dbab4bf2fd5e08 100644 (file)
@@ -1,5 +1,5 @@
 # SPDX-License-Identifier: GPL-2.0
-BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o
+BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o vni.o
 
 include ../config.mk
 
index 610e83f65603ac0d47424d4899c0ef076d96bdaa..841f0594a86045adbf35598f0525bb498d9d2258 100644 (file)
@@ -14,6 +14,7 @@ void print_stp_state(__u8 state);
 int parse_stp_state(const char *arg);
 int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor,
                   bool global_only);
+int print_vnifilter_rtm(struct nlmsghdr *n, void *arg, bool monitor);
 void br_print_router_port_stats(struct rtattr *pattr);
 
 int do_fdb(int argc, char **argv);
@@ -21,6 +22,7 @@ int do_mdb(int argc, char **argv);
 int do_monitor(int argc, char **argv);
 int do_vlan(int argc, char **argv);
 int do_link(int argc, char **argv);
+int do_vni(int argc, char **argv);
 
 extern int preferred_family;
 extern int show_stats;
index f3a4f08ff5f378b145a406fc4aa3b61a3c4a09ca..704be50c70b30e6d1e19b18b7b85c0981e6224ff 100644 (file)
@@ -58,6 +58,7 @@ static const struct cmd {
        { "fdb",        do_fdb },
        { "mdb",        do_mdb },
        { "vlan",       do_vlan },
+       { "vni",        do_vni },
        { "monitor",    do_monitor },
        { "help",       do_help },
        { 0 }
index 845e221abb49c4fc4125cc2e86710c15e5f143b1..f17c1906b045db0eec1116778cb9b1040331d495 100644 (file)
@@ -31,10 +31,20 @@ static int prefix_banner;
 
 static void usage(void)
 {
-       fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | all]\n");
+       fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | vni | all]\n");
        exit(-1);
 }
 
+static int print_tunnel_rtm(struct nlmsghdr *n, void *arg, bool monitor)
+{
+       struct tunnel_msg *tmsg = NLMSG_DATA(n);
+
+       if (tmsg->family == PF_BRIDGE)
+               return print_vnifilter_rtm(n, arg, monitor);
+
+       return 0;
+}
+
 static int accept_msg(struct rtnl_ctrl_data *ctrl,
                      struct nlmsghdr *n, void *arg)
 {
@@ -73,6 +83,12 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl,
                        fprintf(fp, "[VLAN]");
                return print_vlan_rtm(n, arg, true, false);
 
+       case RTM_NEWTUNNEL:
+       case RTM_DELTUNNEL:
+               if (prefix_banner)
+                       fprintf(fp, "[TUNNEL]");
+               return print_tunnel_rtm(n, arg, true);
+
        default:
                return 0;
        }
@@ -86,6 +102,7 @@ int do_monitor(int argc, char **argv)
        int lneigh = 0;
        int lmdb = 0;
        int lvlan = 0;
+       int lvni = 0;
 
        rtnl_close(&rth);
 
@@ -105,9 +122,13 @@ int do_monitor(int argc, char **argv)
                } else if (matches(*argv, "vlan") == 0) {
                        lvlan = 1;
                        groups = 0;
+               } else if (strcmp(*argv, "vni") == 0) {
+                       lvni = 1;
+                       groups = 0;
                } else if (strcmp(*argv, "all") == 0) {
                        groups = ~RTMGRP_TC;
                        lvlan = 1;
+                       lvni = 1;
                        prefix_banner = 1;
                } else if (matches(*argv, "help") == 0) {
                        usage();
@@ -151,6 +172,11 @@ int do_monitor(int argc, char **argv)
                exit(1);
        }
 
+       if (lvni && rtnl_add_nl_group(&rth, RTNLGRP_TUNNEL) < 0) {
+               fprintf(stderr, "Failed to add bridge vni group to list\n");
+               exit(1);
+       }
+
        ll_init_map(&rth);
 
        if (rtnl_listen(&rth, accept_msg, stdout) < 0)
diff --git a/bridge/vni.c b/bridge/vni.c
new file mode 100644 (file)
index 0000000..79dff00
--- /dev/null
@@ -0,0 +1,380 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Command to manage vnifiltering on a vxlan device
+ *
+ * Authors:     Roopa Prabhu <roopa@nvidia.com>
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <linux/if_link.h>
+#include <linux/if_bridge.h>
+#include <linux/if_ether.h>
+
+#include "json_print.h"
+#include "libnetlink.h"
+#include "br_common.h"
+#include "utils.h"
+
+static unsigned int filter_index;
+
+#define VXLAN_ID_LEN 15
+
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+static void usage(void)
+{
+       fprintf(stderr,
+               "Usage: bridge vni { add | del } vni VNI\n"
+               "               [ { group | remote } IP_ADDRESS ]\n"
+               "               [ dev DEV ]\n"
+               "       bridge vni { show }\n"
+               "\n"
+               "Where: VNI     := 0-16777215\n"
+              );
+       exit(-1);
+}
+
+static int parse_vni_filter(const char *argv, struct nlmsghdr *n, int reqsize,
+                           inet_prefix *group)
+{
+       char *vnilist = strdupa(argv);
+       char *vni = strtok(vnilist, ",");
+       int group_type = AF_UNSPEC;
+       struct rtattr *nlvlist_e;
+       char *v;
+       int i;
+
+       if (group && is_addrtype_inet(group))
+               group_type = (group->family == AF_INET) ?  VXLAN_VNIFILTER_ENTRY_GROUP :
+                                                    VXLAN_VNIFILTER_ENTRY_GROUP6;
+
+       for (i = 0; vni; i++) {
+               __u32 vni_start = 0, vni_end = 0;
+
+               v = strchr(vni, '-');
+               if (v) {
+                       *v = '\0';
+                       v++;
+                       vni_start = atoi(vni);
+                       vni_end = atoi(v);
+               } else {
+                       vni_start = atoi(vni);
+               }
+               nlvlist_e = addattr_nest(n, reqsize, VXLAN_VNIFILTER_ENTRY |
+                                        NLA_F_NESTED);
+               addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_START, vni_start);
+               if (vni_end)
+                       addattr32(n, 1024, VXLAN_VNIFILTER_ENTRY_END, vni_end);
+               if (group)
+                       addattr_l(n, 1024, group_type, group->data, group->bytelen);
+               addattr_nest_end(n, nlvlist_e);
+               vni = strtok(NULL, ",");
+       }
+
+       return 0;
+}
+
+static int vni_modify(int cmd, int argc, char **argv)
+{
+       struct {
+               struct nlmsghdr n;
+               struct tunnel_msg       tmsg;
+               char                    buf[1024];
+       } req = {
+               .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)),
+               .n.nlmsg_flags = NLM_F_REQUEST,
+               .n.nlmsg_type = cmd,
+               .tmsg.family = PF_BRIDGE,
+       };
+       bool group_present = false;
+       inet_prefix daddr;
+       char *vni = NULL;
+       char *d = NULL;
+
+       while (argc > 0) {
+               if (strcmp(*argv, "dev") == 0) {
+                       NEXT_ARG();
+                       d = *argv;
+               } else if (strcmp(*argv, "vni") == 0) {
+                       NEXT_ARG();
+                       if (vni)
+                               invarg("duplicate vni", *argv);
+                       vni = *argv;
+               } else if (strcmp(*argv, "group") == 0) {
+                       if (group_present)
+                               invarg("duplicate group", *argv);
+                       if (is_addrtype_inet_not_multi(&daddr)) {
+                               fprintf(stderr, "vxlan: both group and remote");
+                               fprintf(stderr, " cannot be specified\n");
+                               return -1;
+                       }
+                       NEXT_ARG();
+                       get_addr(&daddr, *argv, AF_UNSPEC);
+                       if (!is_addrtype_inet_multi(&daddr))
+                               invarg("invalid group address", *argv);
+                       group_present = true;
+               } else if (strcmp(*argv, "remote") == 0) {
+                       if (group_present)
+                               invarg("duplicate group", *argv);
+                       NEXT_ARG();
+                       get_addr(&daddr, *argv, AF_UNSPEC);
+                       group_present = true;
+               } else {
+                       if (strcmp(*argv, "help") == 0)
+                               usage();
+               }
+               argc--; argv++;
+       }
+
+       if (d == NULL || vni == NULL) {
+               fprintf(stderr, "Device and VNI ID are required arguments.\n");
+               return -1;
+       }
+
+       if (!vni && group_present) {
+               fprintf(stderr, "Group can only be specified with a vni\n");
+               return -1;
+       }
+
+       if (vni)
+               parse_vni_filter(vni, &req.n, sizeof(req),
+                                (group_present ? &daddr : NULL));
+
+       req.tmsg.ifindex = ll_name_to_index(d);
+       if (req.tmsg.ifindex == 0) {
+               fprintf(stderr, "Cannot find vxlan device \"%s\"\n", d);
+               return -1;
+       }
+
+       if (rtnl_talk(&rth, &req.n, NULL) < 0)
+               return -1;
+
+       return 0;
+}
+
+static void open_vni_port(int ifi_index, const char *fmt)
+{
+       open_json_object(NULL);
+       print_color_string(PRINT_ANY, COLOR_IFNAME, "ifname",
+                          "%-" __stringify(IFNAMSIZ) "s  ",
+                          ll_index_to_name(ifi_index));
+       open_json_array(PRINT_JSON, "vnis");
+}
+
+static void close_vni_port(void)
+{
+       close_json_array(PRINT_JSON, NULL);
+       close_json_object();
+}
+
+static void print_range(const char *name, __u32 start, __u32 id)
+{
+       char end[64];
+
+       snprintf(end, sizeof(end), "%sEnd", name);
+
+       print_uint(PRINT_ANY, name, " %u", start);
+       if (start != id)
+               print_uint(PRINT_ANY, end, "-%-14u ", id);
+
+}
+
+static void print_vni(struct rtattr *t, int ifindex)
+{
+       struct rtattr *ttb[VXLAN_VNIFILTER_ENTRY_MAX+1];
+       __u32 vni_start = 0;
+       __u32 vni_end = 0;
+
+       parse_rtattr_flags(ttb, VXLAN_VNIFILTER_ENTRY_MAX, RTA_DATA(t),
+                          RTA_PAYLOAD(t), NLA_F_NESTED);
+
+       if (ttb[VXLAN_VNIFILTER_ENTRY_START])
+               vni_start = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_START]);
+
+       if (ttb[VXLAN_VNIFILTER_ENTRY_END])
+               vni_end = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_END]);
+
+       if (vni_end)
+               print_range("vni", vni_start, vni_end);
+       else
+               print_uint(PRINT_ANY, "vni", " %-14u", vni_start);
+
+       if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP]) {
+               __be32 addr = rta_getattr_u32(ttb[VXLAN_VNIFILTER_ENTRY_GROUP]);
+
+               if (addr) {
+                       if (IN_MULTICAST(ntohl(addr)))
+                               print_string(PRINT_ANY,
+                                            "group",
+                                            " %s",
+                                            format_host(AF_INET, 4, &addr));
+                       else
+                               print_string(PRINT_ANY,
+                                            "remote",
+                                            " %s",
+                                            format_host(AF_INET, 4, &addr));
+               }
+       } else if (ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]) {
+               struct in6_addr addr;
+
+               memcpy(&addr, RTA_DATA(ttb[VXLAN_VNIFILTER_ENTRY_GROUP6]), sizeof(struct in6_addr));
+               if (!IN6_IS_ADDR_UNSPECIFIED(&addr)) {
+                       if (IN6_IS_ADDR_MULTICAST(&addr))
+                               print_string(PRINT_ANY,
+                                            "group",
+                                            " %s",
+                                            format_host(AF_INET6,
+                                                        sizeof(struct in6_addr),
+                                                        &addr));
+                       else
+                               print_string(PRINT_ANY,
+                                            "remote",
+                                            " %s",
+                                            format_host(AF_INET6,
+                                                        sizeof(struct in6_addr),
+                                                        &addr));
+               }
+       }
+       close_json_object();
+       print_string(PRINT_FP, NULL, "%s", _SL_);
+}
+
+int print_vnifilter_rtm(struct nlmsghdr *n, void *arg, bool monitor)
+{
+       struct tunnel_msg *tmsg = NLMSG_DATA(n);
+       int len = n->nlmsg_len;
+       bool first = true;
+       struct rtattr *t;
+       int rem;
+
+       if (n->nlmsg_type != RTM_NEWTUNNEL &&
+           n->nlmsg_type != RTM_DELTUNNEL &&
+           n->nlmsg_type != RTM_GETTUNNEL) {
+               fprintf(stderr, "Unknown vni tunnel rtm msg: %08x %08x %08x\n",
+                       n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags);
+               return 0;
+       }
+
+       len -= NLMSG_LENGTH(sizeof(*tmsg));
+       if (len < 0) {
+               fprintf(stderr, "BUG: wrong nlmsg len %d\n", len);
+               return -1;
+       }
+
+       if (tmsg->family != AF_BRIDGE)
+               return 0;
+
+       if (filter_index && filter_index != tmsg->ifindex)
+               return 0;
+
+       if (n->nlmsg_type == RTM_DELTUNNEL)
+               print_bool(PRINT_ANY, "deleted", "Deleted ", true);
+
+       rem = len;
+       for (t = TUNNEL_RTA(tmsg); RTA_OK(t, rem); t = RTA_NEXT(t, rem)) {
+               unsigned short rta_type = t->rta_type & NLA_TYPE_MASK;
+
+               if (rta_type != VXLAN_VNIFILTER_ENTRY)
+                       continue;
+               if (first) {
+                       open_vni_port(tmsg->ifindex, "%s");
+                       open_json_object(NULL);
+                       first = false;
+               } else {
+                       open_json_object(NULL);
+                       print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s  ", "");
+               }
+
+               print_vni(t, tmsg->ifindex);
+       }
+       close_vni_port();
+
+       print_string(PRINT_FP, NULL, "%s", _SL_);
+
+       fflush(stdout);
+       return 0;
+}
+
+static int print_vnifilter_rtm_filter(struct nlmsghdr *n, void *arg)
+{
+       return print_vnifilter_rtm(n, arg, false);
+}
+
+static int vni_show(int argc, char **argv)
+{
+       char *filter_dev = NULL;
+       int ret = 0;
+
+       while (argc > 0) {
+               if (strcmp(*argv, "dev") == 0) {
+                       NEXT_ARG();
+                       if (filter_dev)
+                               duparg("dev", *argv);
+                       filter_dev = *argv;
+               }
+               argc--; argv++;
+       }
+
+       if (filter_dev) {
+               filter_index = ll_name_to_index(filter_dev);
+               if (!filter_index)
+                       return nodev(filter_dev);
+       }
+
+       new_json_obj(json);
+
+       if (!show_stats) {
+               if (rtnl_tunneldump_req(&rth, PF_BRIDGE, filter_index) < 0) {
+                       perror("Cannot send dump request");
+                       exit(1);
+               }
+
+               if (!is_json_context()) {
+                       printf("%-" __stringify(IFNAMSIZ) "s  %-"
+                              __stringify(VXLAN_ID_LEN) "s  %-"
+                              __stringify(15) "s",
+                              "dev", "vni", "group/remote");
+                       printf("\n");
+               }
+
+               ret = rtnl_dump_filter(&rth, print_vnifilter_rtm_filter, NULL);
+               if (ret < 0) {
+                       fprintf(stderr, "Dump ternminated\n");
+                       exit(1);
+               }
+       }
+
+       delete_json_obj();
+       fflush(stdout);
+       return 0;
+}
+
+int do_vni(int argc, char **argv)
+{
+       ll_init_map(&rth);
+
+       if (argc > 0) {
+               if (strcmp(*argv, "add") == 0)
+                       return vni_modify(RTM_NEWTUNNEL, argc-1, argv+1);
+               if (strcmp(*argv, "delete") == 0)
+                       return vni_modify(RTM_DELTUNNEL, argc-1, argv+1);
+               if (strcmp(*argv, "show") == 0 ||
+                   strcmp(*argv, "lst") == 0 ||
+                   strcmp(*argv, "list") == 0)
+                       return vni_show(argc-1, argv+1);
+               if (strcmp(*argv, "help") == 0)
+                       usage();
+       } else {
+               return vni_show(0, NULL);
+       }
+
+       fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vni help\".\n", *argv);
+       exit(-1);
+}
index 372c370629b13dc9d9f67135e9a9b16cb3ae4e72..a1ec91ec469ddc55a973489cf07b18c6b72df963 100644 (file)
@@ -112,6 +112,9 @@ int rtnl_nexthop_bucket_dump_req(struct rtnl_handle *rth, int family,
                                 req_filter_fn_t filter_fn)
        __attribute__((warn_unused_result));
 
+int rtnl_tunneldump_req(struct rtnl_handle *rth, int family, int ifindex)
+       __attribute__((warn_unused_result));
+
 struct rtnl_ctrl_data {
        int     nsid;
 };
@@ -331,6 +334,11 @@ int rtnl_from_file(FILE *, rtnl_listen_filter_t handler,
        ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_vlan_msg))))
 #endif
 
+#ifndef TUNNEL_RTA
+#define TUNNEL_RTA(r) \
+       ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct tunnel_msg))))
+#endif
+
 /* User defined nlmsg_type which is used mostly for logging netlink
  * messages from dump file */
 #define NLMSG_TSTAMP   15
index 4d33e4dd17f559ef5c6d2ce9cdb31fdb4c801826..b3c3d0ba312cfa9e6662589d383351fadb3367e0 100644 (file)
@@ -1609,3 +1609,21 @@ void nl_print_policy(const struct rtattr *attr, FILE *fp)
                }
        }
 }
+
+int rtnl_tunneldump_req(struct rtnl_handle *rth, int family, int ifindex)
+{
+       struct {
+               struct nlmsghdr nlh;
+               struct tunnel_msg tmsg;
+               char buf[256];
+       } req = {
+               .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct tunnel_msg)),
+               .nlh.nlmsg_type = RTM_GETTUNNEL,
+               .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST,
+               .nlh.nlmsg_seq = rth->dump = ++rth->seq,
+               .tmsg.family = family,
+               .tmsg.ifindex = ifindex,
+       };
+
+       return send(rth->fd, &req, sizeof(req), 0);
+}
index 2fa4f3d6966fa71c79facf42ef28daa05816e29b..d8923d2eb07625c31f41daf250b068ee17166a00 100644 (file)
@@ -13,7 +13,7 @@ bridge \- show / manipulate bridge addresses and devices
 
 .ti -8
 .IR OBJECT " := { "
-.BR link " | " fdb " | " mdb " | " vlan " | " monitor " }"
+.BR link " | " fdb " | " mdb " | " vlan " | " vni " | " monitor " }"
 .sp
 
 .ti -8
@@ -196,6 +196,25 @@ bridge \- show / manipulate bridge addresses and devices
 .B vid
 .IR VID " ]"
 
+.ti -8
+.BR "bridge vlan" " show " [ "
+.B dev
+.IR DEV " ]"
+
+.ti -8
+.BR "bridge vni" " { " add " | " del " } "
+.B dev
+.I DEV
+.B vni
+.IR VNI " [ { "
+.B group | remote "} "
+.IR IPADDR " ] "
+
+.ti -8
+.BR "bridge vni" " show " [ "
+.B dev
+.IR DEV " ]"
+
 .ti -8
 .BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " | " vlan " ]"
 
@@ -303,6 +322,10 @@ the output.
 .B vlan
 - VLAN filter list.
 
+.TP
+.B vni
+- VNI filter list.
+
 .SS
 .I COMMAND
 
@@ -1084,6 +1107,58 @@ all bridge interfaces.
 the VLAN ID only whose global options should be listed. Default is to list
 all vlans.
 
+.SH bridge vni - VNI filter list
+
+.B vni
+objects contain known VNI IDs for a dst metadata vxlan link.
+
+.P
+The corresponding commands display vni filter entries, add new entries,
+and delete old ones.
+
+.SS bridge vni add - add a new vni filter entry
+
+This command creates a new vni filter entry.
+
+.TP
+.BI dev " NAME"
+the interface with which this vni is associated.
+
+.TP
+.BI vni " VNI"
+the VNI ID that identifies the vni.
+
+.TP
+.BI remote " IPADDR"
+specifies the unicast destination IP address to use in outgoing packets
+when the destination link layer address is not known in the VXLAN device
+forwarding database. This parameter cannot be specified with the group.
+
+.TP
+.BI group " IPADDR"
+specifies the multicast IP address to join for this VNI
+
+.SS bridge vni del - delete a new vni filter entry
+
+This command removes an existing vni filter entry.
+
+.PP
+The arguments are the same as with
+.BR "bridge vni add".
+
+.SS bridge vni show - list vni filtering configuration.
+
+This command displays the current vni filter table.
+
+.PP
+With the
+.B -statistics
+option, the command displays per-vni traffic statistics.
+
+.TP
+.BI dev " NAME"
+shows vni filtering table associated with the vxlan device
+
 .SH bridge monitor - state monitoring
 
 The