diff options
author | David Ahern <dsahern@kernel.org> | 2022-05-08 09:50:40 -0600 |
---|---|---|
committer | David Ahern <dsahern@kernel.org> | 2022-05-08 09:51:02 -0600 |
commit | 2c09c7622afb0b6e4be517af4375182512e5df89 (patch) | |
tree | 11c55a60e0e8b808465fdaecf474cee647430398 | |
parent | 837294e452521129718c913fd04e2214998ae9e4 (diff) | |
parent | 40b50f153c52e14b1848c74fd09ffc9e51b75c8a (diff) | |
download | iproute2-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/Makefile | 2 | ||||
-rw-r--r-- | bridge/br_common.h | 2 | ||||
-rw-r--r-- | bridge/bridge.c | 1 | ||||
-rw-r--r-- | bridge/monitor.c | 28 | ||||
-rw-r--r-- | bridge/vni.c | 439 | ||||
-rw-r--r-- | include/libnetlink.h | 9 | ||||
-rw-r--r-- | ip/iplink_vxlan.c | 23 | ||||
-rw-r--r-- | lib/libnetlink.c | 20 | ||||
-rw-r--r-- | man/man8/bridge.8 | 77 | ||||
-rw-r--r-- | man/man8/ip-link.8.in | 9 |
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). |