aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Ahern <dsahern@kernel.org>2022-05-08 09:50:40 -0600
committerDavid Ahern <dsahern@kernel.org>2022-05-08 09:51:02 -0600
commit2c09c7622afb0b6e4be517af4375182512e5df89 (patch)
tree11c55a60e0e8b808465fdaecf474cee647430398
parent837294e452521129718c913fd04e2214998ae9e4 (diff)
parent40b50f153c52e14b1848c74fd09ffc9e51b75c8a (diff)
downloadiproute2-2c09c7622afb0b6e4be517af4375182512e5df89.tar.gz
Merge branch 'bridge-vxlan-vni-filtering' into next
Roopa Prabhu says: ==================== This series adds bridge command to manage recently added vnifilter on a collect metadata vxlan (external) device. Also includes per vni stats support. 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: David Ahern <dsahern@kernel.org>
-rw-r--r--bridge/Makefile2
-rw-r--r--bridge/br_common.h2
-rw-r--r--bridge/bridge.c1
-rw-r--r--bridge/monitor.c28
-rw-r--r--bridge/vni.c439
-rw-r--r--include/libnetlink.h9
-rw-r--r--ip/iplink_vxlan.c23
-rw-r--r--lib/libnetlink.c20
-rw-r--r--man/man8/bridge.877
-rw-r--r--man/man8/ip-link.8.in9
10 files changed, 606 insertions, 4 deletions
diff --git a/bridge/Makefile b/bridge/Makefile
index c6b7d08da..01f8a455b 100644
--- a/bridge/Makefile
+++ b/bridge/Makefile
@@ -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
diff --git a/bridge/br_common.h b/bridge/br_common.h
index 610e83f65..841f0594a 100644
--- a/bridge/br_common.h
+++ b/bridge/br_common.h
@@ -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;
diff --git a/bridge/bridge.c b/bridge/bridge.c
index f3a4f08ff..704be50c7 100644
--- a/bridge/bridge.c
+++ b/bridge/bridge.c
@@ -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 }
diff --git a/bridge/monitor.c b/bridge/monitor.c
index 845e221ab..f17c1906b 100644
--- a/bridge/monitor.c
+++ b/bridge/monitor.c
@@ -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
index 000000000..a0c2792c7
--- /dev/null
+++ b/bridge/vni.c
@@ -0,0 +1,439 @@
+/* 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_vnifilter_entry_stats(struct rtattr *stats_attr)
+{
+ struct rtattr *stb[VNIFILTER_ENTRY_STATS_MAX+1];
+ __u64 stat;
+
+ open_json_object("stats");
+ parse_rtattr_flags(stb, VNIFILTER_ENTRY_STATS_MAX, RTA_DATA(stats_attr),
+ RTA_PAYLOAD(stats_attr), NLA_F_NESTED);
+
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ print_string(PRINT_FP, NULL, "RX: ", "");
+
+ if (stb[VNIFILTER_ENTRY_STATS_RX_BYTES]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_BYTES]);
+ print_lluint(PRINT_ANY, "rx_bytes", "bytes %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_PKTS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_PKTS]);
+ print_lluint(PRINT_ANY, "rx_pkts", "pkts %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_DROPS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_DROPS]);
+ print_lluint(PRINT_ANY, "rx_drops", "drops %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_RX_ERRORS]);
+ print_lluint(PRINT_ANY, "rx_errors", "errors %llu ", stat);
+ }
+
+ print_nl();
+ print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", "");
+ print_string(PRINT_FP, NULL, "TX: ", "");
+
+ if (stb[VNIFILTER_ENTRY_STATS_TX_BYTES]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_BYTES]);
+ print_lluint(PRINT_ANY, "tx_bytes", "bytes %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_PKTS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_PKTS]);
+ print_lluint(PRINT_ANY, "tx_pkts", "pkts %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_DROPS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_DROPS]);
+ print_lluint(PRINT_ANY, "tx_drops", "drops %llu ", stat);
+ }
+ if (stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]) {
+ stat = rta_getattr_u64(stb[VNIFILTER_ENTRY_STATS_TX_ERRORS]);
+ print_lluint(PRINT_ANY, "tx_errors", "errors %llu ", stat);
+ }
+ close_json_object();
+}
+
+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));
+ }
+ }
+
+ if (ttb[VXLAN_VNIFILTER_ENTRY_STATS])
+ print_vnifilter_entry_stats(ttb[VXLAN_VNIFILTER_ENTRY_STATS]);
+
+ 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;
+ __u8 flags = 0;
+ 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)
+ flags = TUNNEL_MSG_FLAG_STATS;
+
+ if (rtnl_tunneldump_req(&rth, PF_BRIDGE, filter_index, flags) < 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);
+}
diff --git a/include/libnetlink.h b/include/libnetlink.h
index 372c37062..a7b0f3523 100644
--- a/include/libnetlink.h
+++ b/include/libnetlink.h
@@ -112,6 +112,10 @@ 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,
+ __u8 flags)
+ __attribute__((warn_unused_result));
+
struct rtnl_ctrl_data {
int nsid;
};
@@ -331,6 +335,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
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
index 9afa3ccad..01522d6eb 100644
--- a/ip/iplink_vxlan.c
+++ b/ip/iplink_vxlan.c
@@ -48,6 +48,7 @@ static void print_explain(FILE *f)
" [ [no]udp6zerocsumrx ]\n"
" [ [no]remcsumtx ] [ [no]remcsumrx ]\n"
" [ [no]external ] [ gbp ] [ gpe ]\n"
+ " [ [no]vnifilter ]\n"
"\n"
"Where: VNI := 0-16777215\n"
" ADDR := { IP_ADDRESS | any }\n"
@@ -81,6 +82,7 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
__u8 learning = 1;
__u16 dstport = 0;
__u8 metadata = 0;
+ __u8 vnifilter = 0;
__u64 attrs = 0;
bool set_op = (n->nlmsg_type == RTM_NEWLINK &&
!(n->nlmsg_flags & NLM_F_CREATE));
@@ -330,6 +332,15 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
} else if (!matches(*argv, "gpe")) {
check_duparg(&attrs, IFLA_VXLAN_GPE, *argv, *argv);
addattr_l(n, 1024, IFLA_VXLAN_GPE, NULL, 0);
+ } else if (!strcmp(*argv, "vnifilter")) {
+ check_duparg(&attrs, IFLA_VXLAN_VNIFILTER,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 1);
+ vnifilter = 1;
+ } else if (!strcmp(*argv, "novnifilter")) {
+ check_duparg(&attrs, IFLA_VXLAN_VNIFILTER,
+ *argv, *argv);
+ addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 0);
} else if (matches(*argv, "help") == 0) {
explain();
return -1;
@@ -341,12 +352,17 @@ static int vxlan_parse_opt(struct link_util *lu, int argc, char **argv,
argc--, argv++;
}
+ if (!metadata && vnifilter) {
+ fprintf(stderr, "vxlan: vnifilter is valid only when 'external' is set\n");
+ return -1;
+ }
+
if (metadata && VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID)) {
fprintf(stderr, "vxlan: both 'external' and vni cannot be specified\n");
return -1;
}
- if (!metadata && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) {
+ if (!metadata && !vnifilter && !VXLAN_ATTRSET(attrs, IFLA_VXLAN_ID) && !set_op) {
fprintf(stderr, "vxlan: missing virtual network identifier\n");
return -1;
}
@@ -420,6 +436,11 @@ static void vxlan_print_opt(struct link_util *lu, FILE *f, struct rtattr *tb[])
print_bool(PRINT_ANY, "external", "external ", true);
}
+ if (tb[IFLA_VXLAN_VNIFILTER] &&
+ rta_getattr_u8(tb[IFLA_VXLAN_VNIFILTER])) {
+ print_bool(PRINT_ANY, "vnifilter", "vnifilter", true);
+ }
+
if (tb[IFLA_VXLAN_ID] &&
RTA_PAYLOAD(tb[IFLA_VXLAN_ID]) >= sizeof(__u32)) {
print_uint(PRINT_ANY, "id", "id %u ", rta_getattr_u32(tb[IFLA_VXLAN_ID]));
diff --git a/lib/libnetlink.c b/lib/libnetlink.c
index 4d33e4dd1..c27627fed 100644
--- a/lib/libnetlink.c
+++ b/lib/libnetlink.c
@@ -1609,3 +1609,23 @@ void nl_print_policy(const struct rtattr *attr, FILE *fp)
}
}
}
+
+int rtnl_tunneldump_req(struct rtnl_handle *rth, int family, int ifindex,
+ __u8 flags)
+{
+ 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.flags = flags,
+ .tmsg.ifindex = ifindex,
+ };
+
+ return send(rth->fd, &req, sizeof(req), 0);
+}
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index 2fa4f3d69..d8923d2eb 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -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
@@ -197,6 +197,25 @@ bridge \- show / manipulate bridge addresses and devices
.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 " ]"
.SH OPTIONS
@@ -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
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index fc214a10c..6f3326450 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -601,6 +601,8 @@ the following additional arguments are supported:
.B gbp
] [
.B gpe
+] [
+.RB [ no ] vnifilter
]
.in +8
@@ -713,6 +715,13 @@ are entered into the VXLAN device forwarding database.
or the internal FDB should be used.
.sp
+.RB [ no ] vnifilter
+- specifies whether the vxlan device is capable of vni filtering. Only works with a vxlan
+device with external flag set. once enabled, bridge vni command is used to manage the
+vni filtering table on the device. The device can only receive packets with vni's configured
+in the vni filtering table.
+
+.sp
.B gbp
- enables the Group Policy extension (VXLAN-GBP).