diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 14:18:53 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 14:18:53 +0000 |
commit | a0e0018c9a7ef5ce7f6d2c3ae16aecbbd16a8f67 (patch) | |
tree | 8feaf1a1932871b139b3b30be4c09c66489918be /bridge | |
parent | Initial commit. (diff) | |
download | iproute2-a0e0018c9a7ef5ce7f6d2c3ae16aecbbd16a8f67.tar.xz iproute2-a0e0018c9a7ef5ce7f6d2c3ae16aecbbd16a8f67.zip |
Adding upstream version 6.1.0.upstream/6.1.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'bridge')
-rw-r--r-- | bridge/.gitignore | 1 | ||||
-rw-r--r-- | bridge/Makefile | 15 | ||||
-rw-r--r-- | bridge/br_common.h | 34 | ||||
-rw-r--r-- | bridge/bridge.c | 194 | ||||
-rw-r--r-- | bridge/fdb.c | 841 | ||||
-rw-r--r-- | bridge/link.c | 610 | ||||
-rw-r--r-- | bridge/mdb.c | 588 | ||||
-rw-r--r-- | bridge/monitor.c | 171 | ||||
-rw-r--r-- | bridge/vlan.c | 1365 | ||||
-rw-r--r-- | bridge/vni.c | 443 |
10 files changed, 4262 insertions, 0 deletions
diff --git a/bridge/.gitignore b/bridge/.gitignore new file mode 100644 index 0000000..7096907 --- /dev/null +++ b/bridge/.gitignore @@ -0,0 +1 @@ +bridge diff --git a/bridge/Makefile b/bridge/Makefile new file mode 100644 index 0000000..01f8a45 --- /dev/null +++ b/bridge/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +BROBJ = bridge.o fdb.o monitor.o link.o mdb.o vlan.o vni.o + +include ../config.mk + +all: bridge + +bridge: $(BROBJ) $(LIBNETLINK) + $(QUIET_LINK)$(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +install: all + install -m 0755 bridge $(DESTDIR)$(SBINDIR) + +clean: + rm -f $(BROBJ) bridge diff --git a/bridge/br_common.h b/bridge/br_common.h new file mode 100644 index 0000000..da677df --- /dev/null +++ b/bridge/br_common.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#define MDB_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(struct br_mdb_entry)))) + +#define MDB_RTR_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + RTA_ALIGN(sizeof(__u32)))) + +void print_vlan_info(struct rtattr *tb, int ifindex); +int print_linkinfo(struct nlmsghdr *n, void *arg); +int print_mdb_mon(struct nlmsghdr *n, void *arg); +int print_fdb(struct nlmsghdr *n, void *arg); +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); +void print_headers(FILE *fp, const char *label); + +int do_fdb(int argc, char **argv); +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; +extern int show_details; +extern int timestamp; +extern int compress_vlans; +extern int json; +extern struct rtnl_handle rth; diff --git a/bridge/bridge.c b/bridge/bridge.c new file mode 100644 index 0000000..704be50 --- /dev/null +++ b/bridge/bridge.c @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Get/set/delete bridge with netlink + * + * Authors: Stephen Hemminger <shemminger@vyatta.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/socket.h> +#include <string.h> +#include <errno.h> + +#include "version.h" +#include "utils.h" +#include "br_common.h" +#include "namespace.h" +#include "color.h" + +struct rtnl_handle rth = { .fd = -1 }; +int preferred_family = AF_UNSPEC; +int oneline; +int show_stats; +int show_details; +static int color; +int compress_vlans; +int json; +int timestamp; +static const char *batch_file; +int force; + +static void usage(void) __attribute__((noreturn)); + +static void usage(void) +{ + fprintf(stderr, +"Usage: bridge [ OPTIONS ] OBJECT { COMMAND | help }\n" +" bridge [ -force ] -batch filename\n" +"where OBJECT := { link | fdb | mdb | vlan | monitor }\n" +" OPTIONS := { -V[ersion] | -s[tatistics] | -d[etails] |\n" +" -o[neline] | -t[imestamp] | -n[etns] name |\n" +" -c[ompressvlans] -color -p[retty] -j[son] }\n"); + exit(-1); +} + +static int do_help(int argc, char **argv) +{ + usage(); +} + + +static const struct cmd { + const char *cmd; + int (*func)(int argc, char **argv); +} cmds[] = { + { "link", do_link }, + { "fdb", do_fdb }, + { "mdb", do_mdb }, + { "vlan", do_vlan }, + { "vni", do_vni }, + { "monitor", do_monitor }, + { "help", do_help }, + { 0 } +}; + +static int do_cmd(const char *argv0, int argc, char **argv) +{ + const struct cmd *c; + + for (c = cmds; c->cmd; ++c) { + if (matches(argv0, c->cmd) == 0) + return c->func(argc-1, argv+1); + } + + fprintf(stderr, + "Object \"%s\" is unknown, try \"bridge help\".\n", argv0); + return -1; +} + +static int br_batch_cmd(int argc, char *argv[], void *data) +{ + return do_cmd(argv[0], argc, argv); +} + +static int batch(const char *name) +{ + int ret; + + if (rtnl_open(&rth, 0) < 0) { + fprintf(stderr, "Cannot open rtnetlink\n"); + return EXIT_FAILURE; + } + + rtnl_set_strict_dump(&rth); + + ret = do_batch(name, force, br_batch_cmd, NULL); + + rtnl_close(&rth); + return ret; +} + +int +main(int argc, char **argv) +{ + while (argc > 1) { + const char *opt = argv[1]; + + if (strcmp(opt, "--") == 0) { + argc--; argv++; + break; + } + if (opt[0] != '-') + break; + if (opt[1] == '-') + opt++; + + if (matches(opt, "-help") == 0) { + usage(); + } else if (matches(opt, "-Version") == 0) { + printf("bridge utility, %s\n", version); + exit(0); + } else if (matches(opt, "-stats") == 0 || + matches(opt, "-statistics") == 0) { + ++show_stats; + } else if (matches(opt, "-details") == 0) { + ++show_details; + } else if (matches(opt, "-oneline") == 0) { + ++oneline; + } else if (matches(opt, "-timestamp") == 0) { + ++timestamp; + } else if (matches(opt, "-family") == 0) { + argc--; + argv++; + if (argc <= 1) + usage(); + if (strcmp(argv[1], "inet") == 0) + preferred_family = AF_INET; + else if (strcmp(argv[1], "inet6") == 0) + preferred_family = AF_INET6; + else if (strcmp(argv[1], "help") == 0) + usage(); + else + invarg("invalid protocol family", argv[1]); + } else if (strcmp(opt, "-4") == 0) { + preferred_family = AF_INET; + } else if (strcmp(opt, "-6") == 0) { + preferred_family = AF_INET6; + } else if (matches(opt, "-netns") == 0) { + NEXT_ARG(); + if (netns_switch(argv[1])) + exit(-1); + } else if (matches_color(opt, &color)) { + } else if (matches(opt, "-compressvlans") == 0) { + ++compress_vlans; + } else if (matches(opt, "-force") == 0) { + ++force; + } else if (matches(opt, "-json") == 0) { + ++json; + } else if (matches(opt, "-pretty") == 0) { + ++pretty; + } else if (matches(opt, "-batch") == 0) { + argc--; + argv++; + if (argc <= 1) + usage(); + batch_file = argv[1]; + } else { + fprintf(stderr, + "Option \"%s\" is unknown, try \"bridge help\".\n", + opt); + exit(-1); + } + argc--; argv++; + } + + _SL_ = oneline ? "\\" : "\n"; + + check_enable_color(color, json); + + if (batch_file) + return batch(batch_file); + + if (rtnl_open(&rth, 0) < 0) + exit(1); + + rtnl_set_strict_dump(&rth); + + if (argc > 1) + return do_cmd(argv[1], argc-1, argv+1); + + rtnl_close(&rth); + usage(); +} diff --git a/bridge/fdb.c b/bridge/fdb.c new file mode 100644 index 0000000..775feb1 --- /dev/null +++ b/bridge/fdb.c @@ -0,0 +1,841 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Get/set/delete fdb table with netlink + * + * TODO: merge/replace this with ip neighbour + * + * Authors: Stephen Hemminger <shemminger@vyatta.com> + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <netdb.h> +#include <time.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> +#include <linux/neighbour.h> +#include <string.h> +#include <limits.h> +#include <stdbool.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "br_common.h" +#include "rt_names.h" +#include "utils.h" + +static unsigned int filter_index, filter_dynamic, filter_master, + filter_state, filter_vlan; + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge fdb { add | append | del | replace } ADDR dev DEV\n" + " [ self ] [ master ] [ use ] [ router ] [ extern_learn ]\n" + " [ sticky ] [ local | static | dynamic ] [ vlan VID ]\n" + " { [ dst IPADDR ] [ port PORT] [ vni VNI ] | [ nhid NHID ] }\n" + " [ via DEV ] [ src_vni VNI ]\n" + " bridge fdb [ show [ br BRDEV ] [ brport DEV ] [ vlan VID ]\n" + " [ state STATE ] [ dynamic ] ]\n" + " bridge fdb get [ to ] LLADDR [ br BRDEV ] { brport | dev } DEV\n" + " [ vlan VID ] [ vni VNI ] [ self ] [ master ] [ dynamic ]\n" + " bridge fdb flush dev DEV [ brport DEV ] [ vlan VID ]\n" + " [ self ] [ master ] [ [no]permanent | [no]static | [no]dynamic ]\n" + " [ [no]added_by_user ] [ [no]extern_learn ] [ [no]sticky ]\n" + " [ [no]offloaded ]\n"); + exit(-1); +} + +static const char *state_n2a(unsigned int s) +{ + static char buf[32]; + + if (s & NUD_PERMANENT) + return "permanent"; + + if (s & NUD_NOARP) + return "static"; + + if (s & NUD_STALE) + return "stale"; + + if (s & NUD_REACHABLE) + return ""; + + if (is_json_context()) + sprintf(buf, "%#x", s); + else + sprintf(buf, "state=%#x", s); + return buf; +} + +static int state_a2n(unsigned int *s, const char *arg) +{ + if (matches(arg, "permanent") == 0) + *s = NUD_PERMANENT; + else if (matches(arg, "static") == 0 || matches(arg, "temp") == 0) + *s = NUD_NOARP; + else if (matches(arg, "stale") == 0) + *s = NUD_STALE; + else if (matches(arg, "reachable") == 0 || matches(arg, "dynamic") == 0) + *s = NUD_REACHABLE; + else if (strcmp(arg, "all") == 0) + *s = ~0; + else if (get_unsigned(s, arg, 0)) + return -1; + + return 0; +} + +static void fdb_print_flags(FILE *fp, unsigned int flags) +{ + open_json_array(PRINT_JSON, + is_json_context() ? "flags" : ""); + + if (flags & NTF_SELF) + print_string(PRINT_ANY, NULL, "%s ", "self"); + + if (flags & NTF_ROUTER) + print_string(PRINT_ANY, NULL, "%s ", "router"); + + if (flags & NTF_EXT_LEARNED) + print_string(PRINT_ANY, NULL, "%s ", "extern_learn"); + + if (flags & NTF_OFFLOADED) + print_string(PRINT_ANY, NULL, "%s ", "offload"); + + if (flags & NTF_MASTER) + print_string(PRINT_ANY, NULL, "%s ", "master"); + + if (flags & NTF_STICKY) + print_string(PRINT_ANY, NULL, "%s ", "sticky"); + + close_json_array(PRINT_JSON, NULL); +} + +static void fdb_print_stats(FILE *fp, const struct nda_cacheinfo *ci) +{ + static int hz; + + if (!hz) + hz = get_user_hz(); + + if (is_json_context()) { + print_uint(PRINT_JSON, "used", NULL, + ci->ndm_used / hz); + print_uint(PRINT_JSON, "updated", NULL, + ci->ndm_updated / hz); + } else { + fprintf(fp, "used %d/%d ", ci->ndm_used / hz, + ci->ndm_updated / hz); + + } +} + +int print_fdb(struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + struct ndmsg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr *tb[NDA_MAX+1]; + __u16 vid = 0; + + if (n->nlmsg_type != RTM_NEWNEIGH && n->nlmsg_type != RTM_DELNEIGH) { + fprintf(stderr, "Not RTM_NEWNEIGH: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (r->ndm_family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != r->ndm_ifindex) + return 0; + + if (filter_state && !(r->ndm_state & filter_state)) + return 0; + + parse_rtattr(tb, NDA_MAX, NDA_RTA(r), + n->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); + + if (tb[NDA_VLAN]) + vid = rta_getattr_u16(tb[NDA_VLAN]); + + if (filter_vlan && filter_vlan != vid) + return 0; + + if (filter_dynamic && (r->ndm_state & NUD_PERMANENT)) + return 0; + + print_headers(fp, "[NEIGH]"); + + open_json_object(NULL); + if (n->nlmsg_type == RTM_DELNEIGH) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + if (tb[NDA_LLADDR]) { + const char *lladdr; + SPRINT_BUF(b1); + + lladdr = ll_addr_n2a(RTA_DATA(tb[NDA_LLADDR]), + RTA_PAYLOAD(tb[NDA_LLADDR]), + ll_index_to_type(r->ndm_ifindex), + b1, sizeof(b1)); + + print_color_string(PRINT_ANY, COLOR_MAC, + "mac", "%s ", lladdr); + } + + if (!filter_index && r->ndm_ifindex) { + print_string(PRINT_FP, NULL, "dev ", NULL); + + print_color_string(PRINT_ANY, COLOR_IFNAME, + "ifname", "%s ", + ll_index_to_name(r->ndm_ifindex)); + } + + if (tb[NDA_DST]) { + int family = AF_INET; + const char *dst; + + if (RTA_PAYLOAD(tb[NDA_DST]) == sizeof(struct in6_addr)) + family = AF_INET6; + + dst = format_host(family, + RTA_PAYLOAD(tb[NDA_DST]), + RTA_DATA(tb[NDA_DST])); + + print_string(PRINT_FP, NULL, "dst ", NULL); + + print_color_string(PRINT_ANY, + ifa_family_color(family), + "dst", "%s ", dst); + } + + if (vid) + print_uint(PRINT_ANY, + "vlan", "vlan %hu ", vid); + + if (tb[NDA_PORT]) + print_uint(PRINT_ANY, + "port", "port %u ", + rta_getattr_be16(tb[NDA_PORT])); + + if (tb[NDA_VNI]) + print_uint(PRINT_ANY, + "vni", "vni %u ", + rta_getattr_u32(tb[NDA_VNI])); + + if (tb[NDA_SRC_VNI]) + print_uint(PRINT_ANY, + "src_vni", "src_vni %u ", + rta_getattr_u32(tb[NDA_SRC_VNI])); + + if (tb[NDA_IFINDEX]) { + unsigned int ifindex = rta_getattr_u32(tb[NDA_IFINDEX]); + + if (tb[NDA_LINK_NETNSID]) + print_uint(PRINT_ANY, + "viaIfIndex", "via ifindex %u ", + ifindex); + else + print_string(PRINT_ANY, + "viaIf", "via %s ", + ll_index_to_name(ifindex)); + } + + if (tb[NDA_NH_ID]) + print_uint(PRINT_ANY, "nhid", "nhid %u ", + rta_getattr_u32(tb[NDA_NH_ID])); + + if (tb[NDA_LINK_NETNSID]) + print_uint(PRINT_ANY, + "linkNetNsId", "link-netnsid %d ", + rta_getattr_u32(tb[NDA_LINK_NETNSID])); + + if (show_stats && tb[NDA_CACHEINFO]) + fdb_print_stats(fp, RTA_DATA(tb[NDA_CACHEINFO])); + + fdb_print_flags(fp, r->ndm_flags); + + + if (tb[NDA_MASTER]) + print_string(PRINT_ANY, "master", "master %s ", + ll_index_to_name(rta_getattr_u32(tb[NDA_MASTER]))); + + print_string(PRINT_ANY, "state", "%s\n", + state_n2a(r->ndm_state)); + close_json_object(); + fflush(fp); + return 0; +} + +static int fdb_linkdump_filter(struct nlmsghdr *nlh, int reqlen) +{ + int err; + + if (filter_index) { + struct ifinfomsg *ifm = NLMSG_DATA(nlh); + + ifm->ifi_index = filter_index; + } + + if (filter_master) { + err = addattr32(nlh, reqlen, IFLA_MASTER, filter_master); + if (err) + return err; + } + + return 0; +} + +static int fdb_dump_filter(struct nlmsghdr *nlh, int reqlen) +{ + int err; + + if (filter_index) { + struct ndmsg *ndm = NLMSG_DATA(nlh); + + ndm->ndm_ifindex = filter_index; + } + + if (filter_master) { + err = addattr32(nlh, reqlen, NDA_MASTER, filter_master); + if (err) + return err; + } + + return 0; +} + +static int fdb_show(int argc, char **argv) +{ + char *filter_dev = NULL; + char *br = NULL; + int rc; + + while (argc > 0) { + if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + filter_dev = *argv; + } else if (strcmp(*argv, "br") == 0) { + NEXT_ARG(); + br = *argv; + } else if (strcmp(*argv, "vlan") == 0) { + NEXT_ARG(); + if (filter_vlan) + duparg("vlan", *argv); + filter_vlan = atoi(*argv); + } else if (strcmp(*argv, "state") == 0) { + unsigned int state; + + NEXT_ARG(); + if (state_a2n(&state, *argv)) + invarg("invalid state", *argv); + filter_state |= state; + } else if (strcmp(*argv, "dynamic") == 0) { + filter_dynamic = 1; + } else { + if (matches(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (br) { + int br_ifindex = ll_name_to_index(br); + + if (br_ifindex == 0) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", br); + return -1; + } + filter_master = br_ifindex; + } + + /*we'll keep around filter_dev for older kernels */ + if (filter_dev) { + filter_index = ll_name_to_index(filter_dev); + if (!filter_index) + return nodev(filter_dev); + } + + if (rth.flags & RTNL_HANDLE_F_STRICT_CHK) + rc = rtnl_neighdump_req(&rth, PF_BRIDGE, fdb_dump_filter); + else + rc = rtnl_fdb_linkdump_req_filter_fn(&rth, fdb_linkdump_filter); + if (rc < 0) { + perror("Cannot send dump request"); + exit(1); + } + + new_json_obj(json); + if (rtnl_dump_filter(&rth, print_fdb, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + delete_json_obj(); + fflush(stdout); + + return 0; +} + +static int fdb_modify(int cmd, int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ndmsg ndm; + char buf[256]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), + .n.nlmsg_flags = NLM_F_REQUEST | flags, + .n.nlmsg_type = cmd, + .ndm.ndm_family = PF_BRIDGE, + .ndm.ndm_state = NUD_NOARP, + }; + char *addr = NULL; + char *d = NULL; + char abuf[ETH_ALEN]; + int dst_ok = 0; + inet_prefix dst; + unsigned long port = 0; + unsigned long vni = ~0; + unsigned long src_vni = ~0; + unsigned int via = 0; + char *endptr; + short vid = -1; + __u32 nhid = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "dst") == 0) { + NEXT_ARG(); + if (dst_ok) + duparg2("dst", *argv); + get_addr(&dst, *argv, preferred_family); + dst_ok = 1; + } else if (strcmp(*argv, "nhid") == 0) { + NEXT_ARG(); + if (get_u32(&nhid, *argv, 0)) + invarg("\"id\" value is invalid\n", *argv); + } else if (strcmp(*argv, "port") == 0) { + + NEXT_ARG(); + port = strtoul(*argv, &endptr, 0); + if (endptr && *endptr) { + struct servent *pse; + + pse = getservbyname(*argv, "udp"); + if (!pse) + invarg("invalid port\n", *argv); + port = ntohs(pse->s_port); + } else if (port > 0xffff) + invarg("invalid port\n", *argv); + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + vni = strtoul(*argv, &endptr, 0); + if ((endptr && *endptr) || + (vni >> 24) || vni == ULONG_MAX) + invarg("invalid VNI\n", *argv); + } else if (strcmp(*argv, "src_vni") == 0) { + NEXT_ARG(); + src_vni = strtoul(*argv, &endptr, 0); + if ((endptr && *endptr) || + (src_vni >> 24) || src_vni == ULONG_MAX) + invarg("invalid src VNI\n", *argv); + } else if (strcmp(*argv, "via") == 0) { + NEXT_ARG(); + via = ll_name_to_index(*argv); + if (!via) + exit(nodev(*argv)); + } else if (strcmp(*argv, "self") == 0) { + req.ndm.ndm_flags |= NTF_SELF; + } else if (matches(*argv, "master") == 0) { + req.ndm.ndm_flags |= NTF_MASTER; + } else if (matches(*argv, "router") == 0) { + req.ndm.ndm_flags |= NTF_ROUTER; + } else if (matches(*argv, "local") == 0 || + matches(*argv, "permanent") == 0) { + req.ndm.ndm_state |= NUD_PERMANENT; + } else if (matches(*argv, "temp") == 0 || + matches(*argv, "static") == 0) { + req.ndm.ndm_state |= NUD_REACHABLE; + } else if (matches(*argv, "dynamic") == 0) { + req.ndm.ndm_state |= NUD_REACHABLE; + req.ndm.ndm_state &= ~NUD_NOARP; + } else if (matches(*argv, "vlan") == 0) { + if (vid >= 0) + duparg2("vlan", *argv); + NEXT_ARG(); + vid = atoi(*argv); + } else if (matches(*argv, "use") == 0) { + req.ndm.ndm_flags |= NTF_USE; + } else if (matches(*argv, "extern_learn") == 0) { + req.ndm.ndm_flags |= NTF_EXT_LEARNED; + } else if (matches(*argv, "sticky") == 0) { + req.ndm.ndm_flags |= NTF_STICKY; + } else { + if (strcmp(*argv, "to") == 0) + NEXT_ARG(); + + if (matches(*argv, "help") == 0) + usage(); + if (addr) + duparg2("to", *argv); + addr = *argv; + } + argc--; argv++; + } + + if (d == NULL || addr == NULL) { + fprintf(stderr, "Device and address are required arguments.\n"); + return -1; + } + + if (nhid && (dst_ok || port || vni != ~0)) { + fprintf(stderr, "dst, port, vni are mutually exclusive with nhid\n"); + return -1; + } + + /* Assume self */ + if (!(req.ndm.ndm_flags&(NTF_SELF|NTF_MASTER))) + req.ndm.ndm_flags |= NTF_SELF; + + /* Assume permanent */ + if (!(req.ndm.ndm_state&(NUD_PERMANENT|NUD_REACHABLE))) + req.ndm.ndm_state |= NUD_PERMANENT; + + if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + abuf, abuf+1, abuf+2, + abuf+3, abuf+4, abuf+5) != 6) { + fprintf(stderr, "Invalid mac address %s\n", addr); + return -1; + } + + addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN); + if (dst_ok) + addattr_l(&req.n, sizeof(req), NDA_DST, &dst.data, dst.bytelen); + + if (vid >= 0) + addattr16(&req.n, sizeof(req), NDA_VLAN, vid); + if (nhid > 0) + addattr32(&req.n, sizeof(req), NDA_NH_ID, nhid); + + if (port) { + unsigned short dport; + + dport = htons((unsigned short)port); + addattr16(&req.n, sizeof(req), NDA_PORT, dport); + } + if (vni != ~0) + addattr32(&req.n, sizeof(req), NDA_VNI, vni); + if (src_vni != ~0) + addattr32(&req.n, sizeof(req), NDA_SRC_VNI, src_vni); + if (via) + addattr32(&req.n, sizeof(req), NDA_IFINDEX, via); + + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (!req.ndm.ndm_ifindex) + return nodev(d); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int fdb_get(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ndmsg ndm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_GETNEIGH, + .ndm.ndm_family = AF_BRIDGE, + }; + char *d = NULL, *br = NULL; + struct nlmsghdr *answer; + unsigned long vni = ~0; + char abuf[ETH_ALEN]; + int br_ifindex = 0; + char *addr = NULL; + short vlan = -1; + char *endptr; + int ret; + + while (argc > 0) { + if ((strcmp(*argv, "brport") == 0) || strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "br") == 0) { + NEXT_ARG(); + br = *argv; + } else if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "vni") == 0) { + NEXT_ARG(); + vni = strtoul(*argv, &endptr, 0); + if ((endptr && *endptr) || + (vni >> 24) || vni == ULONG_MAX) + invarg("invalid VNI\n", *argv); + } else if (strcmp(*argv, "self") == 0) { + req.ndm.ndm_flags |= NTF_SELF; + } else if (matches(*argv, "master") == 0) { + req.ndm.ndm_flags |= NTF_MASTER; + } else if (matches(*argv, "vlan") == 0) { + if (vlan >= 0) + duparg2("vlan", *argv); + NEXT_ARG(); + vlan = atoi(*argv); + } else if (matches(*argv, "dynamic") == 0) { + filter_dynamic = 1; + } else { + if (strcmp(*argv, "to") == 0) + NEXT_ARG(); + + if (matches(*argv, "help") == 0) + usage(); + if (addr) + duparg2("to", *argv); + addr = *argv; + } + argc--; argv++; + } + + if ((d == NULL && br == NULL) || addr == NULL) { + fprintf(stderr, "Device or master and address are required arguments.\n"); + return -1; + } + + if (sscanf(addr, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx", + abuf, abuf+1, abuf+2, + abuf+3, abuf+4, abuf+5) != 6) { + fprintf(stderr, "Invalid mac address %s\n", addr); + return -1; + } + + addattr_l(&req.n, sizeof(req), NDA_LLADDR, abuf, ETH_ALEN); + + if (vlan >= 0) + addattr16(&req.n, sizeof(req), NDA_VLAN, vlan); + + if (vni != ~0) + addattr32(&req.n, sizeof(req), NDA_VNI, vni); + + if (d) { + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (!req.ndm.ndm_ifindex) { + fprintf(stderr, "Cannot find device \"%s\"\n", d); + return -1; + } + } + + if (br) { + br_ifindex = ll_name_to_index(br); + if (!br_ifindex) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", br); + return -1; + } + addattr32(&req.n, sizeof(req), NDA_MASTER, br_ifindex); + } + + if (rtnl_talk(&rth, &req.n, &answer) < 0) + return -2; + + /* + * Initialize a json_writer and open an array object + * if -json was specified. + */ + new_json_obj(json); + ret = 0; + if (print_fdb(answer, stdout) < 0) { + fprintf(stderr, "An error :-)\n"); + ret = -1; + } + delete_json_obj(); + free(answer); + + return ret; +} + +static int fdb_flush(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ndmsg ndm; + char buf[256]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ndmsg)), + .n.nlmsg_flags = NLM_F_REQUEST | NLM_F_BULK, + .n.nlmsg_type = RTM_DELNEIGH, + .ndm.ndm_family = PF_BRIDGE, + }; + unsigned short ndm_state_mask = 0; + unsigned short ndm_flags_mask = 0; + short vid = -1, port_ifidx = -1; + unsigned short ndm_flags = 0; + unsigned short ndm_state = 0; + char *d = NULL, *port = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "master") == 0) { + ndm_flags |= NTF_MASTER; + } else if (strcmp(*argv, "self") == 0) { + ndm_flags |= NTF_SELF; + } else if (strcmp(*argv, "permanent") == 0) { + ndm_state |= NUD_PERMANENT; + ndm_state_mask |= NUD_PERMANENT; + } else if (strcmp(*argv, "nopermanent") == 0) { + ndm_state &= ~NUD_PERMANENT; + ndm_state_mask |= NUD_PERMANENT; + } else if (strcmp(*argv, "static") == 0) { + ndm_state |= NUD_NOARP; + ndm_state_mask |= NUD_NOARP | NUD_PERMANENT; + } else if (strcmp(*argv, "nostatic") == 0) { + ndm_state &= ~NUD_NOARP; + ndm_state_mask |= NUD_NOARP; + } else if (strcmp(*argv, "dynamic") == 0) { + ndm_state &= ~NUD_NOARP | NUD_PERMANENT; + ndm_state_mask |= NUD_NOARP | NUD_PERMANENT; + } else if (strcmp(*argv, "nodynamic") == 0) { + ndm_state |= NUD_NOARP; + ndm_state_mask |= NUD_NOARP; + } else if (strcmp(*argv, "added_by_user") == 0) { + ndm_flags |= NTF_USE; + ndm_flags_mask |= NTF_USE; + } else if (strcmp(*argv, "noadded_by_user") == 0) { + ndm_flags &= ~NTF_USE; + ndm_flags_mask |= NTF_USE; + } else if (strcmp(*argv, "extern_learn") == 0) { + ndm_flags |= NTF_EXT_LEARNED; + ndm_flags_mask |= NTF_EXT_LEARNED; + } else if (strcmp(*argv, "noextern_learn") == 0) { + ndm_flags &= ~NTF_EXT_LEARNED; + ndm_flags_mask |= NTF_EXT_LEARNED; + } else if (strcmp(*argv, "sticky") == 0) { + ndm_flags |= NTF_STICKY; + ndm_flags_mask |= NTF_STICKY; + } else if (strcmp(*argv, "nosticky") == 0) { + ndm_flags &= ~NTF_STICKY; + ndm_flags_mask |= NTF_STICKY; + } else if (strcmp(*argv, "offloaded") == 0) { + ndm_flags |= NTF_OFFLOADED; + ndm_flags_mask |= NTF_OFFLOADED; + } else if (strcmp(*argv, "nooffloaded") == 0) { + ndm_flags &= ~NTF_OFFLOADED; + ndm_flags_mask |= NTF_OFFLOADED; + } else if (strcmp(*argv, "brport") == 0) { + if (port) + duparg2("brport", *argv); + NEXT_ARG(); + port = *argv; + } else if (strcmp(*argv, "vlan") == 0) { + if (vid >= 0) + duparg2("vlan", *argv); + NEXT_ARG(); + vid = atoi(*argv); + } else { + if (strcmp(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + + if (d == NULL) { + fprintf(stderr, "Device is a required argument.\n"); + return -1; + } + + req.ndm.ndm_ifindex = ll_name_to_index(d); + if (req.ndm.ndm_ifindex == 0) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", d); + return -1; + } + + if (port) { + port_ifidx = ll_name_to_index(port); + if (port_ifidx == 0) { + fprintf(stderr, "Cannot find bridge port device \"%s\"\n", + port); + return -1; + } + } + + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid); + return -1; + } + + /* if self and master were not specified assume self */ + if (!(ndm_flags & (NTF_SELF | NTF_MASTER))) + ndm_flags |= NTF_SELF; + + req.ndm.ndm_flags = ndm_flags; + req.ndm.ndm_state = ndm_state; + if (port_ifidx > -1) + addattr32(&req.n, sizeof(req), NDA_IFINDEX, port_ifidx); + if (vid > -1) + addattr16(&req.n, sizeof(req), NDA_VLAN, vid); + if (ndm_flags_mask) + addattr8(&req.n, sizeof(req), NDA_NDM_FLAGS_MASK, + ndm_flags_mask); + if (ndm_state_mask) + addattr16(&req.n, sizeof(req), NDA_NDM_STATE_MASK, + ndm_state_mask); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +int do_fdb(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1); + if (matches(*argv, "append") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_APPEND, argc-1, argv+1); + if (matches(*argv, "replace") == 0) + return fdb_modify(RTM_NEWNEIGH, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return fdb_modify(RTM_DELNEIGH, 0, argc-1, argv+1); + if (matches(*argv, "get") == 0) + return fdb_get(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return fdb_show(argc-1, argv+1); + if (strcmp(*argv, "flush") == 0) + return fdb_flush(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else + return fdb_show(0, NULL); + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge fdb help\".\n", *argv); + exit(-1); +} diff --git a/bridge/link.c b/bridge/link.c new file mode 100644 index 0000000..fef3a9e --- /dev/null +++ b/bridge/link.c @@ -0,0 +1,610 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <netinet/in.h> +#include <linux/if.h> +#include <linux/if_bridge.h> +#include <string.h> +#include <stdbool.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "utils.h" +#include "br_common.h" + +static unsigned int filter_index; + +static const char *stp_states[] = { + [BR_STATE_DISABLED] = "disabled", + [BR_STATE_LISTENING] = "listening", + [BR_STATE_LEARNING] = "learning", + [BR_STATE_FORWARDING] = "forwarding", + [BR_STATE_BLOCKING] = "blocking", +}; + +static const char *hw_mode[] = { + "VEB", "VEPA" +}; + +static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown) +{ + open_json_array(PRINT_ANY, is_json_context() ? "flags" : "<"); + if (flags & IFF_UP && !(flags & IFF_RUNNING)) + print_string(PRINT_ANY, NULL, + flags ? "%s," : "%s", "NO-CARRIER"); + flags &= ~IFF_RUNNING; + +#define _PF(f) if (flags&IFF_##f) { \ + flags &= ~IFF_##f ; \ + print_string(PRINT_ANY, NULL, flags ? "%s," : "%s", #f); } + _PF(LOOPBACK); + _PF(BROADCAST); + _PF(POINTOPOINT); + _PF(MULTICAST); + _PF(NOARP); + _PF(ALLMULTI); + _PF(PROMISC); + _PF(MASTER); + _PF(SLAVE); + _PF(DEBUG); + _PF(DYNAMIC); + _PF(AUTOMEDIA); + _PF(PORTSEL); + _PF(NOTRAILERS); + _PF(UP); + _PF(LOWER_UP); + _PF(DORMANT); + _PF(ECHO); +#undef _PF + if (flags) + print_hex(PRINT_ANY, NULL, "%x", flags); + if (mdown) + print_string(PRINT_ANY, NULL, ",%s", "M-DOWN"); + close_json_array(PRINT_ANY, "> "); +} + +void print_stp_state(__u8 state) +{ + if (state <= BR_STATE_BLOCKING) + print_string(PRINT_ANY, "state", + "state %s ", stp_states[state]); + else + print_uint(PRINT_ANY, "state", + "state (%d) ", state); +} + +int parse_stp_state(const char *arg) +{ + size_t nstates = ARRAY_SIZE(stp_states); + int state; + + for (state = 0; state < nstates; state++) + if (strcmp(stp_states[state], arg) == 0) + break; + + if (state == nstates) + state = -1; + + return state; +} + +static void print_hwmode(__u16 mode) +{ + if (mode >= ARRAY_SIZE(hw_mode)) + print_0xhex(PRINT_ANY, "hwmode", + "hwmode %#llx ", mode); + else + print_string(PRINT_ANY, "hwmode", + "hwmode %s ", hw_mode[mode]); +} + +static void print_protinfo(FILE *fp, struct rtattr *attr) +{ + if (attr->rta_type & NLA_F_NESTED) { + struct rtattr *prtb[IFLA_BRPORT_MAX + 1]; + + parse_rtattr_nested(prtb, IFLA_BRPORT_MAX, attr); + + if (prtb[IFLA_BRPORT_STATE]) + print_stp_state(rta_getattr_u8(prtb[IFLA_BRPORT_STATE])); + + if (prtb[IFLA_BRPORT_PRIORITY]) + print_uint(PRINT_ANY, "priority", + "priority %u ", + rta_getattr_u16(prtb[IFLA_BRPORT_PRIORITY])); + + if (prtb[IFLA_BRPORT_COST]) + print_uint(PRINT_ANY, "cost", + "cost %u ", + rta_getattr_u32(prtb[IFLA_BRPORT_COST])); + + if (!show_details) + return; + + if (!is_json_context()) + fprintf(fp, "%s ", _SL_); + + if (prtb[IFLA_BRPORT_MODE]) + print_on_off(PRINT_ANY, "hairpin", "hairpin %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MODE])); + if (prtb[IFLA_BRPORT_GUARD]) + print_on_off(PRINT_ANY, "guard", "guard %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_GUARD])); + if (prtb[IFLA_BRPORT_PROTECT]) + print_on_off(PRINT_ANY, "root_block", "root_block %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_PROTECT])); + if (prtb[IFLA_BRPORT_FAST_LEAVE]) + print_on_off(PRINT_ANY, "fastleave", "fastleave %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_FAST_LEAVE])); + if (prtb[IFLA_BRPORT_LEARNING]) + print_on_off(PRINT_ANY, "learning", "learning %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING])); + if (prtb[IFLA_BRPORT_LEARNING_SYNC]) + print_on_off(PRINT_ANY, "learning_sync", "learning_sync %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LEARNING_SYNC])); + if (prtb[IFLA_BRPORT_UNICAST_FLOOD]) + print_on_off(PRINT_ANY, "flood", "flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_UNICAST_FLOOD])); + if (prtb[IFLA_BRPORT_MCAST_FLOOD]) + print_on_off(PRINT_ANY, "mcast_flood", "mcast_flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_FLOOD])); + if (prtb[IFLA_BRPORT_BCAST_FLOOD]) + print_on_off(PRINT_ANY, "bcast_flood", "bcast_flood %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_BCAST_FLOOD])); + if (prtb[IFLA_BRPORT_MULTICAST_ROUTER]) + print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ", + rta_getattr_u8(prtb[IFLA_BRPORT_MULTICAST_ROUTER])); + if (prtb[IFLA_BRPORT_MCAST_TO_UCAST]) + print_on_off(PRINT_ANY, "mcast_to_unicast", "mcast_to_unicast %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_MCAST_TO_UCAST])); + if (prtb[IFLA_BRPORT_NEIGH_SUPPRESS]) + print_on_off(PRINT_ANY, "neigh_suppress", "neigh_suppress %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_NEIGH_SUPPRESS])); + if (prtb[IFLA_BRPORT_VLAN_TUNNEL]) + print_on_off(PRINT_ANY, "vlan_tunnel", "vlan_tunnel %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_VLAN_TUNNEL])); + + if (prtb[IFLA_BRPORT_BACKUP_PORT]) { + int ifidx; + + ifidx = rta_getattr_u32(prtb[IFLA_BRPORT_BACKUP_PORT]); + print_string(PRINT_ANY, + "backup_port", "backup_port %s ", + ll_index_to_name(ifidx)); + } + + if (prtb[IFLA_BRPORT_ISOLATED]) + print_on_off(PRINT_ANY, "isolated", "isolated %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED])); + if (prtb[IFLA_BRPORT_LOCKED]) + print_on_off(PRINT_ANY, "locked", "locked %s ", + rta_getattr_u8(prtb[IFLA_BRPORT_LOCKED])); + } else + print_stp_state(rta_getattr_u8(attr)); +} + + +/* + * This is reported by HW devices that have some bridging + * capabilities. + */ +static void print_af_spec(struct rtattr *attr, int ifindex) +{ + struct rtattr *aftb[IFLA_BRIDGE_MAX+1]; + + parse_rtattr_nested(aftb, IFLA_BRIDGE_MAX, attr); + + if (aftb[IFLA_BRIDGE_MODE]) + print_hwmode(rta_getattr_u16(aftb[IFLA_BRIDGE_MODE])); +} + +int print_linkinfo(struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + struct ifinfomsg *ifi = NLMSG_DATA(n); + struct rtattr *tb[IFLA_MAX+1]; + unsigned int m_flag = 0; + int len = n->nlmsg_len; + const char *name; + + len -= NLMSG_LENGTH(sizeof(*ifi)); + if (len < 0) { + fprintf(stderr, "Message too short!\n"); + return -1; + } + + if (!(ifi->ifi_family == AF_BRIDGE || ifi->ifi_family == AF_UNSPEC)) + return 0; + + if (filter_index && filter_index != ifi->ifi_index) + return 0; + + parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED); + + name = get_ifname_rta(ifi->ifi_index, tb[IFLA_IFNAME]); + if (!name) + return -1; + + print_headers(fp, "[LINK]"); + + open_json_object(NULL); + if (n->nlmsg_type == RTM_DELLINK) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + print_int(PRINT_ANY, "ifindex", "%d: ", ifi->ifi_index); + m_flag = print_name_and_link("%s: ", name, tb); + print_link_flags(fp, ifi->ifi_flags, m_flag); + + if (tb[IFLA_MTU]) + print_int(PRINT_ANY, + "mtu", "mtu %u ", + rta_getattr_u32(tb[IFLA_MTU])); + + if (tb[IFLA_MASTER]) { + int master = rta_getattr_u32(tb[IFLA_MASTER]); + + print_string(PRINT_ANY, "master", "master %s ", + ll_index_to_name(master)); + } + + if (tb[IFLA_PROTINFO]) + print_protinfo(fp, tb[IFLA_PROTINFO]); + + if (tb[IFLA_AF_SPEC]) + print_af_spec(tb[IFLA_AF_SPEC], ifi->ifi_index); + + print_string(PRINT_FP, NULL, "%s", "\n"); + close_json_object(); + fflush(fp); + return 0; +} + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge link set dev DEV [ cost COST ] [ priority PRIO ] [ state STATE ]\n" + " [ guard {on | off} ]\n" + " [ hairpin {on | off} ]\n" + " [ fastleave {on | off} ]\n" + " [ root_block {on | off} ]\n" + " [ learning {on | off} ]\n" + " [ learning_sync {on | off} ]\n" + " [ flood {on | off} ]\n" + " [ mcast_router MULTICAST_ROUTER ]\n" + " [ mcast_flood {on | off} ]\n" + " [ bcast_flood {on | off} ]\n" + " [ mcast_to_unicast {on | off} ]\n" + " [ neigh_suppress {on | off} ]\n" + " [ vlan_tunnel {on | off} ]\n" + " [ isolated {on | off} ]\n" + " [ locked {on | off} ]\n" + " [ hwmode {vepa | veb} ]\n" + " [ backup_port DEVICE ] [ nobackup_port ]\n" + " [ self ] [ master ]\n" + " bridge link show [dev DEV]\n"); + exit(-1); +} + +static int brlink_modify(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ifinfomsg ifm; + char buf[512]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_SETLINK, + .ifm.ifi_family = PF_BRIDGE, + }; + char *d = NULL; + int backup_port_idx = -1; + __s8 neigh_suppress = -1; + __s8 learning = -1; + __s8 learning_sync = -1; + __s8 flood = -1; + __s8 vlan_tunnel = -1; + __s8 mcast_router = -1; + __s8 mcast_flood = -1; + __s8 bcast_flood = -1; + __s8 mcast_to_unicast = -1; + __s8 locked = -1; + __s8 isolated = -1; + __s8 hairpin = -1; + __s8 bpdu_guard = -1; + __s8 fast_leave = -1; + __s8 root_block = -1; + __u32 cost = 0; + __s16 priority = -1; + __s8 state = -1; + __s16 mode = -1; + __u16 flags = 0; + struct rtattr *nest; + int ret; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "guard") == 0) { + NEXT_ARG(); + bpdu_guard = parse_on_off("guard", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "hairpin") == 0) { + NEXT_ARG(); + hairpin = parse_on_off("hairpin", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "fastleave") == 0) { + NEXT_ARG(); + fast_leave = parse_on_off("fastleave", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "root_block") == 0) { + NEXT_ARG(); + root_block = parse_on_off("root_block", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "learning") == 0) { + NEXT_ARG(); + learning = parse_on_off("learning", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "learning_sync") == 0) { + NEXT_ARG(); + learning_sync = parse_on_off("learning_sync", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "flood") == 0) { + NEXT_ARG(); + flood = parse_on_off("flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mcast_router") == 0) { + NEXT_ARG(); + mcast_router = atoi(*argv); + } else if (strcmp(*argv, "mcast_flood") == 0) { + NEXT_ARG(); + mcast_flood = parse_on_off("mcast_flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "bcast_flood") == 0) { + NEXT_ARG(); + bcast_flood = parse_on_off("bcast_flood", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "mcast_to_unicast") == 0) { + NEXT_ARG(); + mcast_to_unicast = parse_on_off("mcast_to_unicast", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "cost") == 0) { + NEXT_ARG(); + cost = atoi(*argv); + } else if (strcmp(*argv, "priority") == 0) { + NEXT_ARG(); + priority = atoi(*argv); + } else if (strcmp(*argv, "state") == 0) { + NEXT_ARG(); + char *endptr; + + state = strtol(*argv, &endptr, 10); + if (!(**argv != '\0' && *endptr == '\0')) { + state = parse_stp_state(*argv); + if (state == -1) { + fprintf(stderr, + "Error: invalid STP port state\n"); + return -1; + } + } + } else if (strcmp(*argv, "hwmode") == 0) { + NEXT_ARG(); + flags = BRIDGE_FLAGS_SELF; + if (strcmp(*argv, "vepa") == 0) + mode = BRIDGE_MODE_VEPA; + else if (strcmp(*argv, "veb") == 0) + mode = BRIDGE_MODE_VEB; + else { + fprintf(stderr, + "Mode argument must be \"vepa\" or \"veb\".\n"); + return -1; + } + } else if (strcmp(*argv, "self") == 0) { + flags |= BRIDGE_FLAGS_SELF; + } else if (strcmp(*argv, "master") == 0) { + flags |= BRIDGE_FLAGS_MASTER; + } else if (strcmp(*argv, "neigh_suppress") == 0) { + NEXT_ARG(); + neigh_suppress = parse_on_off("neigh_suppress", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "vlan_tunnel") == 0) { + NEXT_ARG(); + vlan_tunnel = parse_on_off("vlan_tunnel", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "isolated") == 0) { + NEXT_ARG(); + isolated = parse_on_off("isolated", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "locked") == 0) { + NEXT_ARG(); + locked = parse_on_off("locked", *argv, &ret); + if (ret) + return ret; + } else if (strcmp(*argv, "backup_port") == 0) { + NEXT_ARG(); + backup_port_idx = ll_name_to_index(*argv); + if (!backup_port_idx) { + fprintf(stderr, "Error: device %s does not exist\n", + *argv); + return -1; + } + } else if (strcmp(*argv, "nobackup_port") == 0) { + backup_port_idx = 0; + } else { + usage(); + } + argc--; argv++; + } + if (d == NULL) { + fprintf(stderr, "Device is a required argument.\n"); + return -1; + } + + + req.ifm.ifi_index = ll_name_to_index(d); + if (req.ifm.ifi_index == 0) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", d); + return -1; + } + + /* Nested PROTINFO attribute. Contains: port flags, cost, priority and + * state. + */ + nest = addattr_nest(&req.n, sizeof(req), + IFLA_PROTINFO | NLA_F_NESTED); + /* Flags first */ + if (bpdu_guard >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_GUARD, bpdu_guard); + if (hairpin >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MODE, hairpin); + if (fast_leave >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_FAST_LEAVE, + fast_leave); + if (root_block >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_PROTECT, root_block); + if (flood >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_UNICAST_FLOOD, flood); + if (mcast_router >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MULTICAST_ROUTER, + mcast_router); + if (mcast_flood >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_FLOOD, + mcast_flood); + if (bcast_flood >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_BCAST_FLOOD, + bcast_flood); + if (mcast_to_unicast >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_MCAST_TO_UCAST, + mcast_to_unicast); + if (learning >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING, learning); + if (learning_sync >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_LEARNING_SYNC, + learning_sync); + + if (cost > 0) + addattr32(&req.n, sizeof(req), IFLA_BRPORT_COST, cost); + + if (priority >= 0) + addattr16(&req.n, sizeof(req), IFLA_BRPORT_PRIORITY, priority); + + if (state >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_STATE, state); + + if (neigh_suppress != -1) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_NEIGH_SUPPRESS, + neigh_suppress); + if (vlan_tunnel != -1) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_VLAN_TUNNEL, + vlan_tunnel); + if (isolated != -1) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_ISOLATED, isolated); + + if (locked >= 0) + addattr8(&req.n, sizeof(req), IFLA_BRPORT_LOCKED, locked); + + if (backup_port_idx != -1) + addattr32(&req.n, sizeof(req), IFLA_BRPORT_BACKUP_PORT, + backup_port_idx); + + addattr_nest_end(&req.n, nest); + + /* IFLA_AF_SPEC nested attribute. Contains IFLA_BRIDGE_FLAGS that + * designates master or self operation and IFLA_BRIDGE_MODE + * for hw 'vepa' or 'veb' operation modes. The hwmodes are + * only valid in 'self' mode on some devices so far. + */ + if (mode >= 0 || flags > 0) { + nest = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC); + + if (flags > 0) + addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags); + + if (mode >= 0) + addattr16(&req.n, sizeof(req), IFLA_BRIDGE_MODE, mode); + + addattr_nest_end(&req.n, nest); + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int brlink_show(int argc, char **argv) +{ + char *filter_dev = NULL; + + 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); + } + + if (rtnl_linkdump_req(&rth, PF_BRIDGE) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + new_json_obj(json); + if (rtnl_dump_filter(&rth, print_linkinfo, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + delete_json_obj(); + fflush(stdout); + return 0; +} + +int do_link(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (matches(*argv, "set") == 0 || + matches(*argv, "change") == 0) + return brlink_modify(argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return brlink_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else + return brlink_show(0, NULL); + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge link help\".\n", *argv); + exit(-1); +} diff --git a/bridge/mdb.c b/bridge/mdb.c new file mode 100644 index 0000000..d3afc90 --- /dev/null +++ b/bridge/mdb.c @@ -0,0 +1,588 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Get mdb table with netlink + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> +#include <string.h> +#include <arpa/inet.h> + +#include "libnetlink.h" +#include "utils.h" +#include "br_common.h" +#include "rt_names.h" +#include "json_print.h" + +#ifndef MDBA_RTA +#define MDBA_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_port_msg)))) +#endif + +static unsigned int filter_index, filter_vlan; + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge mdb { add | del } dev DEV port PORT grp GROUP [src SOURCE] [permanent | temp] [vid VID]\n" + " bridge mdb {show} [ dev DEV ] [ vid VID ]\n"); + exit(-1); +} + +static bool is_temp_mcast_rtr(__u8 type) +{ + return type == MDB_RTR_TYPE_TEMP_QUERY || type == MDB_RTR_TYPE_TEMP; +} + +static const char *format_timer(__u32 ticks, int align) +{ + struct timeval tv; + static char tbuf[32]; + + __jiffies_to_tv(&tv, ticks); + if (align) + snprintf(tbuf, sizeof(tbuf), "%4lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); + else + snprintf(tbuf, sizeof(tbuf), "%lu.%.2lu", + (unsigned long)tv.tv_sec, + (unsigned long)tv.tv_usec / 10000); + + return tbuf; +} + +void br_print_router_port_stats(struct rtattr *pattr) +{ + struct rtattr *tb[MDBA_ROUTER_PATTR_MAX + 1]; + + parse_rtattr(tb, MDBA_ROUTER_PATTR_MAX, MDB_RTR_RTA(RTA_DATA(pattr)), + RTA_PAYLOAD(pattr) - RTA_ALIGN(sizeof(uint32_t))); + + if (tb[MDBA_ROUTER_PATTR_TIMER]) { + __u32 timer = rta_getattr_u32(tb[MDBA_ROUTER_PATTR_TIMER]); + + print_string(PRINT_ANY, "timer", " %s", + format_timer(timer, 1)); + } + + if (tb[MDBA_ROUTER_PATTR_TYPE]) { + __u8 type = rta_getattr_u8(tb[MDBA_ROUTER_PATTR_TYPE]); + + print_string(PRINT_ANY, "type", " %s", + is_temp_mcast_rtr(type) ? "temp" : "permanent"); + } +} + +static void br_print_router_ports(FILE *f, struct rtattr *attr, + const char *brifname) +{ + int rem = RTA_PAYLOAD(attr); + struct rtattr *i; + + if (is_json_context()) + open_json_array(PRINT_JSON, brifname); + else if (!show_stats) + fprintf(f, "router ports on %s: ", brifname); + + for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + uint32_t *port_ifindex = RTA_DATA(i); + const char *port_ifname = ll_index_to_name(*port_ifindex); + + if (is_json_context()) { + open_json_object(NULL); + print_string(PRINT_JSON, "port", NULL, port_ifname); + + if (show_stats) + br_print_router_port_stats(i); + close_json_object(); + } else if (show_stats) { + fprintf(f, "router ports on %s: %s", + brifname, port_ifname); + + br_print_router_port_stats(i); + fprintf(f, "\n"); + } else { + fprintf(f, "%s ", port_ifname); + } + } + + if (!show_stats) + print_nl(); + + close_json_array(PRINT_JSON, NULL); +} + +static void print_src_entry(struct rtattr *src_attr, int af, const char *sep) +{ + struct rtattr *stb[MDBA_MDB_SRCATTR_MAX + 1]; + SPRINT_BUF(abuf); + const char *addr; + __u32 timer_val; + + parse_rtattr_nested(stb, MDBA_MDB_SRCATTR_MAX, src_attr); + if (!stb[MDBA_MDB_SRCATTR_ADDRESS] || !stb[MDBA_MDB_SRCATTR_TIMER]) + return; + + addr = inet_ntop(af, RTA_DATA(stb[MDBA_MDB_SRCATTR_ADDRESS]), abuf, + sizeof(abuf)); + if (!addr) + return; + timer_val = rta_getattr_u32(stb[MDBA_MDB_SRCATTR_TIMER]); + + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%s", sep); + print_color_string(PRINT_ANY, ifa_family_color(af), + "address", "%s", addr); + print_string(PRINT_ANY, "timer", "/%s", format_timer(timer_val, 0)); + close_json_object(); +} + +static void print_mdb_entry(FILE *f, int ifindex, const struct br_mdb_entry *e, + struct nlmsghdr *n, struct rtattr **tb) +{ + const void *grp, *src; + const char *addr; + SPRINT_BUF(abuf); + const char *dev; + int af; + + if (filter_vlan && e->vid != filter_vlan) + return; + + if (!e->addr.proto) { + af = AF_PACKET; + grp = &e->addr.u.mac_addr; + } else if (e->addr.proto == htons(ETH_P_IP)) { + af = AF_INET; + grp = &e->addr.u.ip4; + } else { + af = AF_INET6; + grp = &e->addr.u.ip6; + } + dev = ll_index_to_name(ifindex); + + open_json_object(NULL); + + print_int(PRINT_JSON, "index", NULL, ifindex); + print_color_string(PRINT_ANY, COLOR_IFNAME, "dev", "dev %s", dev); + print_string(PRINT_ANY, "port", " port %s", + ll_index_to_name(e->ifindex)); + + /* The ETH_ALEN argument is ignored for all cases but AF_PACKET */ + addr = rt_addr_n2a_r(af, ETH_ALEN, grp, abuf, sizeof(abuf)); + if (!addr) + return; + + print_color_string(PRINT_ANY, ifa_family_color(af), + "grp", " grp %s", addr); + + if (tb && tb[MDBA_MDB_EATTR_SOURCE]) { + src = (const void *)RTA_DATA(tb[MDBA_MDB_EATTR_SOURCE]); + print_color_string(PRINT_ANY, ifa_family_color(af), + "src", " src %s", + inet_ntop(af, src, abuf, sizeof(abuf))); + } + print_string(PRINT_ANY, "state", " %s", + (e->state & MDB_PERMANENT) ? "permanent" : "temp"); + if (show_details && tb) { + if (tb[MDBA_MDB_EATTR_GROUP_MODE]) { + __u8 mode = rta_getattr_u8(tb[MDBA_MDB_EATTR_GROUP_MODE]); + + print_string(PRINT_ANY, "filter_mode", " filter_mode %s", + mode == MCAST_INCLUDE ? "include" : + "exclude"); + } + if (tb[MDBA_MDB_EATTR_SRC_LIST]) { + struct rtattr *i, *attr = tb[MDBA_MDB_EATTR_SRC_LIST]; + const char *sep = " "; + int rem; + + open_json_array(PRINT_ANY, is_json_context() ? + "source_list" : + " source_list"); + rem = RTA_PAYLOAD(attr); + for (i = RTA_DATA(attr); RTA_OK(i, rem); + i = RTA_NEXT(i, rem)) { + print_src_entry(i, af, sep); + sep = ","; + } + close_json_array(PRINT_JSON, NULL); + } + if (tb[MDBA_MDB_EATTR_RTPROT]) { + __u8 rtprot = rta_getattr_u8(tb[MDBA_MDB_EATTR_RTPROT]); + SPRINT_BUF(rtb); + + print_string(PRINT_ANY, "protocol", " proto %s ", + rtnl_rtprot_n2a(rtprot, rtb, sizeof(rtb))); + } + } + + open_json_array(PRINT_JSON, "flags"); + if (e->flags & MDB_FLAGS_OFFLOAD) + print_string(PRINT_ANY, NULL, " %s", "offload"); + if (e->flags & MDB_FLAGS_FAST_LEAVE) + print_string(PRINT_ANY, NULL, " %s", "fast_leave"); + if (e->flags & MDB_FLAGS_STAR_EXCL) + print_string(PRINT_ANY, NULL, " %s", "added_by_star_ex"); + if (e->flags & MDB_FLAGS_BLOCKED) + print_string(PRINT_ANY, NULL, " %s", "blocked"); + close_json_array(PRINT_JSON, NULL); + + if (e->vid) + print_uint(PRINT_ANY, "vid", " vid %u", e->vid); + + if (show_stats && tb && tb[MDBA_MDB_EATTR_TIMER]) { + __u32 timer = rta_getattr_u32(tb[MDBA_MDB_EATTR_TIMER]); + + print_string(PRINT_ANY, "timer", " %s", + format_timer(timer, 1)); + } + + print_nl(); + close_json_object(); +} + +static void br_print_mdb_entry(FILE *f, int ifindex, struct rtattr *attr, + struct nlmsghdr *n) +{ + struct rtattr *etb[MDBA_MDB_EATTR_MAX + 1]; + struct br_mdb_entry *e; + struct rtattr *i; + int rem; + + rem = RTA_PAYLOAD(attr); + for (i = RTA_DATA(attr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + e = RTA_DATA(i); + parse_rtattr_flags(etb, MDBA_MDB_EATTR_MAX, MDB_RTA(RTA_DATA(i)), + RTA_PAYLOAD(i) - RTA_ALIGN(sizeof(*e)), + NLA_F_NESTED); + print_mdb_entry(f, ifindex, e, n, etb); + } +} + +static void print_mdb_entries(FILE *fp, struct nlmsghdr *n, + int ifindex, struct rtattr *mdb) +{ + int rem = RTA_PAYLOAD(mdb); + struct rtattr *i; + + for (i = RTA_DATA(mdb); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) + br_print_mdb_entry(fp, ifindex, i, n); +} + +static void print_router_entries(FILE *fp, struct nlmsghdr *n, + int ifindex, struct rtattr *router) +{ + const char *brifname = ll_index_to_name(ifindex); + + if (n->nlmsg_type == RTM_GETMDB) { + if (show_details) + br_print_router_ports(fp, router, brifname); + } else { + struct rtattr *i = RTA_DATA(router); + uint32_t *port_ifindex = RTA_DATA(i); + const char *port_name = ll_index_to_name(*port_ifindex); + + if (is_json_context()) { + open_json_array(PRINT_JSON, brifname); + open_json_object(NULL); + + print_string(PRINT_JSON, "port", NULL, + port_name); + close_json_object(); + close_json_array(PRINT_JSON, NULL); + } else { + fprintf(fp, "router port dev %s master %s\n", + port_name, brifname); + } + } +} + +static int __parse_mdb_nlmsg(struct nlmsghdr *n, struct rtattr **tb) +{ + struct br_port_msg *r = NLMSG_DATA(n); + int len = n->nlmsg_len; + + if (n->nlmsg_type != RTM_GETMDB && + n->nlmsg_type != RTM_NEWMDB && + n->nlmsg_type != RTM_DELMDB) { + fprintf(stderr, + "Not RTM_GETMDB, RTM_NEWMDB or RTM_DELMDB: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*r)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (filter_index && filter_index != r->ifindex) + return 0; + + parse_rtattr(tb, MDBA_MAX, MDBA_RTA(r), n->nlmsg_len - NLMSG_LENGTH(sizeof(*r))); + + return 1; +} + +static int print_mdbs(struct nlmsghdr *n, void *arg) +{ + struct br_port_msg *r = NLMSG_DATA(n); + struct rtattr *tb[MDBA_MAX+1]; + FILE *fp = arg; + int ret; + + ret = __parse_mdb_nlmsg(n, tb); + if (ret != 1) + return ret; + + if (tb[MDBA_MDB]) + print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]); + + return 0; +} + +static int print_rtrs(struct nlmsghdr *n, void *arg) +{ + struct br_port_msg *r = NLMSG_DATA(n); + struct rtattr *tb[MDBA_MAX+1]; + FILE *fp = arg; + int ret; + + ret = __parse_mdb_nlmsg(n, tb); + if (ret != 1) + return ret; + + if (tb[MDBA_ROUTER]) + print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]); + + return 0; +} + +int print_mdb_mon(struct nlmsghdr *n, void *arg) +{ + struct br_port_msg *r = NLMSG_DATA(n); + struct rtattr *tb[MDBA_MAX+1]; + FILE *fp = arg; + int ret; + + ret = __parse_mdb_nlmsg(n, tb); + if (ret != 1) + return ret; + + print_headers(fp, "[MDB]"); + + if (n->nlmsg_type == RTM_DELMDB) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + if (tb[MDBA_MDB]) + print_mdb_entries(fp, n, r->ifindex, tb[MDBA_MDB]); + + if (tb[MDBA_ROUTER]) + print_router_entries(fp, n, r->ifindex, tb[MDBA_ROUTER]); + + return 0; +} + +static int mdb_show(int argc, char **argv) +{ + char *filter_dev = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (filter_dev) + duparg("dev", *argv); + filter_dev = *argv; + } else if (strcmp(*argv, "vid") == 0) { + NEXT_ARG(); + if (filter_vlan) + duparg("vid", *argv); + filter_vlan = atoi(*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); + open_json_object(NULL); + + /* get mdb entries */ + if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) { + perror("Cannot send dump request"); + return -1; + } + + open_json_array(PRINT_JSON, "mdb"); + if (rtnl_dump_filter(&rth, print_mdbs, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + return -1; + } + close_json_array(PRINT_JSON, NULL); + + /* get router ports */ + if (rtnl_mdbdump_req(&rth, PF_BRIDGE) < 0) { + perror("Cannot send dump request"); + return -1; + } + + open_json_object("router"); + if (rtnl_dump_filter(&rth, print_rtrs, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + return -1; + } + close_json_object(); + + close_json_object(); + delete_json_obj(); + fflush(stdout); + + return 0; +} + +static int mdb_parse_grp(const char *grp, struct br_mdb_entry *e) +{ + if (inet_pton(AF_INET, grp, &e->addr.u.ip4)) { + e->addr.proto = htons(ETH_P_IP); + return 0; + } + if (inet_pton(AF_INET6, grp, &e->addr.u.ip6)) { + e->addr.proto = htons(ETH_P_IPV6); + return 0; + } + if (ll_addr_a2n((char *)e->addr.u.mac_addr, sizeof(e->addr.u.mac_addr), + grp) == ETH_ALEN) { + e->addr.proto = 0; + return 0; + } + + return -1; +} + +static int mdb_modify(int cmd, int flags, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct br_port_msg bpm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_port_msg)), + .n.nlmsg_flags = NLM_F_REQUEST | flags, + .n.nlmsg_type = cmd, + .bpm.family = PF_BRIDGE, + }; + char *d = NULL, *p = NULL, *grp = NULL, *src = NULL; + struct br_mdb_entry entry = {}; + short vid = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "grp") == 0) { + NEXT_ARG(); + grp = *argv; + } else if (strcmp(*argv, "port") == 0) { + NEXT_ARG(); + p = *argv; + } else if (strcmp(*argv, "permanent") == 0) { + if (cmd == RTM_NEWMDB) + entry.state |= MDB_PERMANENT; + } else if (strcmp(*argv, "temp") == 0) { + ;/* nothing */ + } else if (strcmp(*argv, "vid") == 0) { + NEXT_ARG(); + vid = atoi(*argv); + } else if (strcmp(*argv, "src") == 0) { + NEXT_ARG(); + src = *argv; + } else { + if (matches(*argv, "help") == 0) + usage(); + } + argc--; argv++; + } + + if (d == NULL || grp == NULL || p == NULL) { + fprintf(stderr, "Device, group address and port name are required arguments.\n"); + return -1; + } + + req.bpm.ifindex = ll_name_to_index(d); + if (!req.bpm.ifindex) + return nodev(d); + + entry.ifindex = ll_name_to_index(p); + if (!entry.ifindex) + return nodev(p); + + if (mdb_parse_grp(grp, &entry)) { + fprintf(stderr, "Invalid address \"%s\"\n", grp); + return -1; + } + + entry.vid = vid; + addattr_l(&req.n, sizeof(req), MDBA_SET_ENTRY, &entry, sizeof(entry)); + if (src) { + struct rtattr *nest = addattr_nest(&req.n, sizeof(req), + MDBA_SET_ENTRY_ATTRS); + struct in6_addr src_ip6; + __be32 src_ip4; + + nest->rta_type |= NLA_F_NESTED; + if (!inet_pton(AF_INET, src, &src_ip4)) { + if (!inet_pton(AF_INET6, src, &src_ip6)) { + fprintf(stderr, "Invalid source address \"%s\"\n", src); + return -1; + } + addattr_l(&req.n, sizeof(req), MDBE_ATTR_SOURCE, &src_ip6, sizeof(src_ip6)); + } else { + addattr32(&req.n, sizeof(req), MDBE_ATTR_SOURCE, src_ip4); + } + addattr_nest_end(&req.n, nest); + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +int do_mdb(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return mdb_modify(RTM_NEWMDB, NLM_F_CREATE|NLM_F_EXCL, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return mdb_modify(RTM_DELMDB, 0, argc-1, argv+1); + + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return mdb_show(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else + return mdb_show(0, NULL); + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge mdb help\".\n", *argv); + exit(-1); +} diff --git a/bridge/monitor.c b/bridge/monitor.c new file mode 100644 index 0000000..e321516 --- /dev/null +++ b/bridge/monitor.c @@ -0,0 +1,171 @@ +/* + * brmonitor.c "bridge monitor" + * + * 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. + * + * Authors: Stephen Hemminger <shemminger@vyatta.com> + * + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <time.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <linux/neighbour.h> +#include <string.h> + +#include "utils.h" +#include "br_common.h" + + +static void usage(void) __attribute__((noreturn)); +static int prefix_banner; + +static void usage(void) +{ + fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | vni | all]\n"); + exit(-1); +} + +static int accept_msg(struct rtnl_ctrl_data *ctrl, + struct nlmsghdr *n, void *arg) +{ + FILE *fp = arg; + + switch (n->nlmsg_type) { + case RTM_NEWLINK: + case RTM_DELLINK: + return print_linkinfo(n, arg); + + case RTM_NEWNEIGH: + case RTM_DELNEIGH: + return print_fdb(n, arg); + + case RTM_NEWMDB: + case RTM_DELMDB: + return print_mdb_mon(n, arg); + + case NLMSG_TSTAMP: + print_nlmsg_timestamp(fp, n); + return 0; + + case RTM_NEWVLAN: + case RTM_DELVLAN: + return print_vlan_rtm(n, arg, true, false); + + case RTM_NEWTUNNEL: + case RTM_DELTUNNEL: + return print_vnifilter_rtm(n, arg, true); + + default: + return 0; + } +} + +void print_headers(FILE *fp, const char *label) +{ + if (timestamp) + print_timestamp(fp); + + if (prefix_banner) + fprintf(fp, "%s", label); +} + +int do_monitor(int argc, char **argv) +{ + char *file = NULL; + unsigned int groups = ~RTMGRP_TC; + int llink = 0; + int lneigh = 0; + int lmdb = 0; + int lvlan = 0; + int lvni = 0; + + rtnl_close(&rth); + + while (argc > 0) { + if (matches(*argv, "file") == 0) { + NEXT_ARG(); + file = *argv; + } else if (matches(*argv, "link") == 0) { + llink = 1; + groups = 0; + } else if (matches(*argv, "fdb") == 0) { + lneigh = 1; + groups = 0; + } else if (matches(*argv, "mdb") == 0) { + lmdb = 1; + groups = 0; + } 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(); + } else { + fprintf(stderr, "Argument \"%s\" is unknown, try \"bridge monitor help\".\n", *argv); + exit(-1); + } + argc--; argv++; + } + + if (llink) + groups |= nl_mgrp(RTNLGRP_LINK); + + if (lneigh) { + groups |= nl_mgrp(RTNLGRP_NEIGH); + } + + if (lmdb) { + groups |= nl_mgrp(RTNLGRP_MDB); + } + + if (file) { + FILE *fp; + int err; + + fp = fopen(file, "r"); + if (fp == NULL) { + perror("Cannot fopen"); + exit(-1); + } + err = rtnl_from_file(fp, accept_msg, stdout); + fclose(fp); + return err; + } + + if (rtnl_open(&rth, groups) < 0) + exit(1); + + if (lvlan && rtnl_add_nl_group(&rth, RTNLGRP_BRVLAN) < 0) { + fprintf(stderr, "Failed to add bridge vlan group to list\n"); + 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) + exit(2); + + return 0; +} diff --git a/bridge/vlan.c b/bridge/vlan.c new file mode 100644 index 0000000..13df1e8 --- /dev/null +++ b/bridge/vlan.c @@ -0,0 +1,1365 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netinet/in.h> +#include <linux/if_bridge.h> +#include <linux/if_ether.h> +#include <string.h> +#include <errno.h> + +#include "json_print.h" +#include "libnetlink.h" +#include "br_common.h" +#include "utils.h" + +static unsigned int filter_index, filter_vlan; +static int vlan_rtm_cur_ifidx = -1; + +enum vlan_show_subject { + VLAN_SHOW_VLAN, + VLAN_SHOW_TUNNELINFO, +}; + +#define VLAN_ID_LEN 9 + +#define __stringify_1(x...) #x +#define __stringify(x...) __stringify_1(x) + +static void usage(void) +{ + fprintf(stderr, + "Usage: bridge vlan { add | del } vid VLAN_ID dev DEV [ tunnel_info id TUNNEL_ID ]\n" + " [ pvid ] [ untagged ]\n" + " [ self ] [ master ]\n" + " bridge vlan { set } vid VLAN_ID dev DEV [ state STP_STATE ]\n" + " [ mcast_router MULTICAST_ROUTER ]\n" + " bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n" + " bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n" + " bridge vlan global { set } vid VLAN_ID dev DEV\n" + " [ mcast_snooping MULTICAST_SNOOPING ]\n" + " [ mcast_querier MULTICAST_QUERIER ]\n" + " [ mcast_igmp_version IGMP_VERSION ]\n" + " [ mcast_mld_version MLD_VERSION ]\n" + " [ mcast_last_member_count LAST_MEMBER_COUNT ]\n" + " [ mcast_last_member_interval LAST_MEMBER_INTERVAL ]\n" + " [ mcast_startup_query_count STARTUP_QUERY_COUNT ]\n" + " [ mcast_startup_query_interval STARTUP_QUERY_INTERVAL ]\n" + " [ mcast_membership_interval MEMBERSHIP_INTERVAL ]\n" + " [ mcast_querier_interval QUERIER_INTERVAL ]\n" + " [ mcast_query_interval QUERY_INTERVAL ]\n" + " [ mcast_query_response_interval QUERY_RESPONSE_INTERVAL ]\n" + " bridge vlan global { show } [ dev DEV ] [ vid VLAN_ID ]\n"); + exit(-1); +} + +static int parse_tunnel_info(int *argcp, char ***argvp, __u32 *tun_id_start, + __u32 *tun_id_end) +{ + char **argv = *argvp; + int argc = *argcp; + char *t; + + NEXT_ARG(); + if (!matches(*argv, "id")) { + NEXT_ARG(); + t = strchr(*argv, '-'); + if (t) { + *t = '\0'; + if (get_u32(tun_id_start, *argv, 0) || + *tun_id_start >= 1u << 24) + invarg("invalid tun id", *argv); + if (get_u32(tun_id_end, t + 1, 0) || + *tun_id_end >= 1u << 24) + invarg("invalid tun id", *argv); + + } else { + if (get_u32(tun_id_start, *argv, 0) || + *tun_id_start >= 1u << 24) + invarg("invalid tun id", *argv); + } + } else { + invarg("tunnel id expected", *argv); + } + + *argcp = argc; + *argvp = argv; + + return 0; +} + +static int add_tunnel_info(struct nlmsghdr *n, int reqsize, + __u16 vid, __u32 tun_id, __u16 flags) +{ + struct rtattr *tinfo; + + tinfo = addattr_nest(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_INFO); + addattr32(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_ID, tun_id); + addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_VID, vid); + addattr16(n, reqsize, IFLA_BRIDGE_VLAN_TUNNEL_FLAGS, flags); + + addattr_nest_end(n, tinfo); + + return 0; +} + +static int add_tunnel_info_range(struct nlmsghdr *n, int reqsize, + __u16 vid_start, int16_t vid_end, + __u32 tun_id_start, __u32 tun_id_end) +{ + if (vid_end != -1 && (vid_end - vid_start) > 0) { + add_tunnel_info(n, reqsize, vid_start, tun_id_start, + BRIDGE_VLAN_INFO_RANGE_BEGIN); + + add_tunnel_info(n, reqsize, vid_end, tun_id_end, + BRIDGE_VLAN_INFO_RANGE_END); + } else { + add_tunnel_info(n, reqsize, vid_start, tun_id_start, 0); + } + + return 0; +} + +static int add_vlan_info_range(struct nlmsghdr *n, int reqsize, __u16 vid_start, + int16_t vid_end, __u16 flags) +{ + struct bridge_vlan_info vinfo = {}; + + vinfo.flags = flags; + vinfo.vid = vid_start; + if (vid_end != -1) { + /* send vlan range start */ + addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo, + sizeof(vinfo)); + vinfo.flags &= ~BRIDGE_VLAN_INFO_RANGE_BEGIN; + + /* Now send the vlan range end */ + vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_END; + vinfo.vid = vid_end; + addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo, + sizeof(vinfo)); + } else { + addattr_l(n, reqsize, IFLA_BRIDGE_VLAN_INFO, &vinfo, + sizeof(vinfo)); + } + + return 0; +} + +static int vlan_modify(int cmd, int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct ifinfomsg ifm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = cmd, + .ifm.ifi_family = PF_BRIDGE, + }; + char *d = NULL; + short vid = -1; + short vid_end = -1; + struct rtattr *afspec; + struct bridge_vlan_info vinfo = {}; + bool tunnel_info_set = false; + unsigned short flags = 0; + __u32 tun_id_start = 0; + __u32 tun_id_end = 0; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + } else if (strcmp(*argv, "vid") == 0) { + char *p; + + NEXT_ARG(); + p = strchr(*argv, '-'); + if (p) { + *p = '\0'; + p++; + vid = atoi(*argv); + vid_end = atoi(p); + vinfo.flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN; + } else { + vid = atoi(*argv); + } + } else if (strcmp(*argv, "self") == 0) { + flags |= BRIDGE_FLAGS_SELF; + } else if (strcmp(*argv, "master") == 0) { + flags |= BRIDGE_FLAGS_MASTER; + } else if (strcmp(*argv, "pvid") == 0) { + vinfo.flags |= BRIDGE_VLAN_INFO_PVID; + } else if (strcmp(*argv, "untagged") == 0) { + vinfo.flags |= BRIDGE_VLAN_INFO_UNTAGGED; + } else if (strcmp(*argv, "tunnel_info") == 0) { + if (parse_tunnel_info(&argc, &argv, + &tun_id_start, + &tun_id_end)) + return -1; + tunnel_info_set = true; + } else { + if (matches(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + + if (d == NULL || vid == -1) { + fprintf(stderr, "Device and VLAN ID are required arguments.\n"); + return -1; + } + + req.ifm.ifi_index = ll_name_to_index(d); + if (req.ifm.ifi_index == 0) { + fprintf(stderr, "Cannot find bridge device \"%s\"\n", d); + return -1; + } + + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid); + return -1; + } + + if (vinfo.flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) { + if (vid_end == -1 || vid_end >= 4096 || vid >= vid_end) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + if (vinfo.flags & BRIDGE_VLAN_INFO_PVID) { + fprintf(stderr, + "pvid cannot be configured for a vlan range\n"); + return -1; + } + } + + afspec = addattr_nest(&req.n, sizeof(req), IFLA_AF_SPEC); + + if (flags) + addattr16(&req.n, sizeof(req), IFLA_BRIDGE_FLAGS, flags); + + if (tunnel_info_set) + add_tunnel_info_range(&req.n, sizeof(req), vid, vid_end, + tun_id_start, tun_id_end); + else + add_vlan_info_range(&req.n, sizeof(req), vid, vid_end, + vinfo.flags); + + addattr_nest_end(&req.n, afspec); + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int vlan_option_set(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct br_vlan_msg bvm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_NEWVLAN, + .bvm.family = PF_BRIDGE, + }; + struct bridge_vlan_info vinfo = {}; + struct rtattr *afspec; + char *d = NULL; + short vid = -1; + + afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY); + afspec->rta_type |= NLA_F_NESTED; + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + req.bvm.ifindex = ll_name_to_index(d); + if (req.bvm.ifindex == 0) { + fprintf(stderr, + "Cannot find network device \"%s\"\n", + d); + return -1; + } + } else if (strcmp(*argv, "vid") == 0) { + short vid_end = -1; + char *p; + + NEXT_ARG(); + p = strchr(*argv, '-'); + if (p) { + *p = '\0'; + p++; + vid = atoi(*argv); + vid_end = atoi(p); + if (vid >= vid_end || vid_end >= 4096) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + } else { + vid = atoi(*argv); + } + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", + vid); + return -1; + } + + vinfo.flags = BRIDGE_VLAN_INFO_ONLY_OPTS; + vinfo.vid = vid; + addattr_l(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_INFO, + &vinfo, sizeof(vinfo)); + if (vid_end != -1) + addattr16(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_RANGE, vid_end); + } else if (strcmp(*argv, "state") == 0) { + char *endptr; + int state; + + NEXT_ARG(); + state = strtol(*argv, &endptr, 10); + if (!(**argv != '\0' && *endptr == '\0')) + state = parse_stp_state(*argv); + if (state == -1) { + fprintf(stderr, "Error: invalid STP state\n"); + return -1; + } + addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE, + state); + } else if (strcmp(*argv, "mcast_router") == 0) { + __u8 mcast_router; + + NEXT_ARG(); + if (get_u8(&mcast_router, *argv, 0)) + invarg("invalid mcast_router", *argv); + addattr8(&req.n, sizeof(req), + BRIDGE_VLANDB_ENTRY_MCAST_ROUTER, + mcast_router); + } else { + if (matches(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + addattr_nest_end(&req.n, afspec); + + if (d == NULL || vid == -1) { + fprintf(stderr, "Device and VLAN ID are required arguments.\n"); + return -1; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +static int vlan_global_option_set(int argc, char **argv) +{ + struct { + struct nlmsghdr n; + struct br_vlan_msg bvm; + char buf[1024]; + } req = { + .n.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)), + .n.nlmsg_flags = NLM_F_REQUEST, + .n.nlmsg_type = RTM_NEWVLAN, + .bvm.family = PF_BRIDGE, + }; + struct rtattr *afspec; + short vid_end = -1; + char *d = NULL; + short vid = -1; + __u64 val64; + __u32 val32; + __u8 val8; + + afspec = addattr_nest(&req.n, sizeof(req), + BRIDGE_VLANDB_GLOBAL_OPTIONS); + afspec->rta_type |= NLA_F_NESTED; + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + d = *argv; + req.bvm.ifindex = ll_name_to_index(d); + if (req.bvm.ifindex == 0) { + fprintf(stderr, "Cannot find network device \"%s\"\n", + d); + return -1; + } + } else if (strcmp(*argv, "vid") == 0) { + char *p; + + NEXT_ARG(); + p = strchr(*argv, '-'); + if (p) { + *p = '\0'; + p++; + vid = atoi(*argv); + vid_end = atoi(p); + if (vid >= vid_end || vid_end >= 4096) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + } else { + vid = atoi(*argv); + } + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", + vid); + return -1; + } + addattr16(&req.n, sizeof(req), BRIDGE_VLANDB_GOPTS_ID, + vid); + if (vid_end != -1) + addattr16(&req.n, sizeof(req), + BRIDGE_VLANDB_GOPTS_RANGE, vid_end); + } else if (strcmp(*argv, "mcast_snooping") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_snooping", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING, val8); + } else if (strcmp(*argv, "mcast_querier") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_querier", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERIER, val8); + } else if (strcmp(*argv, "mcast_igmp_version") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_igmp_version", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION, val8); + } else if (strcmp(*argv, "mcast_mld_version") == 0) { + NEXT_ARG(); + if (get_u8(&val8, *argv, 0)) + invarg("invalid mcast_mld_version", *argv); + addattr8(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION, val8); + } else if (strcmp(*argv, "mcast_last_member_count") == 0) { + NEXT_ARG(); + if (get_u32(&val32, *argv, 0)) + invarg("invalid mcast_last_member_count", *argv); + addattr32(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT, + val32); + } else if (strcmp(*argv, "mcast_startup_query_count") == 0) { + NEXT_ARG(); + if (get_u32(&val32, *argv, 0)) + invarg("invalid mcast_startup_query_count", + *argv); + addattr32(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT, + val32); + } else if (strcmp(*argv, "mcast_last_member_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_last_member_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL, + val64); + } else if (strcmp(*argv, "mcast_membership_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_membership_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL, + val64); + } else if (strcmp(*argv, "mcast_querier_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_querier_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL, + val64); + } else if (strcmp(*argv, "mcast_query_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_query_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL, + val64); + } else if (strcmp(*argv, "mcast_query_response_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_query_response_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL, + val64); + } else if (strcmp(*argv, "mcast_startup_query_interval") == 0) { + NEXT_ARG(); + if (get_u64(&val64, *argv, 0)) + invarg("invalid mcast_startup_query_interval", + *argv); + addattr64(&req.n, 1024, + BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL, + val64); + } else { + if (strcmp(*argv, "help") == 0) + NEXT_ARG(); + } + argc--; argv++; + } + addattr_nest_end(&req.n, afspec); + + if (d == NULL || vid == -1) { + fprintf(stderr, "Device and VLAN ID are required arguments.\n"); + return -1; + } + + if (rtnl_talk(&rth, &req.n, NULL) < 0) + return -1; + + return 0; +} + +/* In order to use this function for both filtering and non-filtering cases + * we need to make it a tristate: + * return -1 - if filtering we've gone over so don't continue + * return 0 - skip entry and continue (applies to range start or to entries + * which are less than filter_vlan) + * return 1 - print the entry and continue + */ +static int filter_vlan_check(__u16 vid, __u16 flags) +{ + /* if we're filtering we should stop on the first greater entry */ + if (filter_vlan && vid > filter_vlan && + !(flags & BRIDGE_VLAN_INFO_RANGE_END)) + return -1; + if ((flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) || + vid < filter_vlan) + return 0; + + return 1; +} + +static void open_vlan_port(int ifi_index, enum vlan_show_subject subject) +{ + 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, + subject == VLAN_SHOW_VLAN ? "vlans": "tunnels"); +} + +static void close_vlan_port(void) +{ + close_json_array(PRINT_JSON, NULL); + close_json_object(); +} + +static unsigned int print_range(const char *name, __u32 start, __u32 id) +{ + char end[64]; + int width; + + snprintf(end, sizeof(end), "%sEnd", name); + + width = print_uint(PRINT_ANY, name, "%u", start); + if (start != id) + width += print_uint(PRINT_ANY, end, "-%u", id); + + return width; +} + +static void print_vlan_tunnel_info(struct rtattr *tb, int ifindex) +{ + struct rtattr *i, *list = tb; + int rem = RTA_PAYLOAD(list); + __u16 last_vid_start = 0; + __u32 last_tunid_start = 0; + bool opened = false; + + for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + struct rtattr *ttb[IFLA_BRIDGE_VLAN_TUNNEL_MAX+1]; + __u32 tunnel_id = 0; + __u16 tunnel_vid = 0; + __u16 tunnel_flags = 0; + unsigned int width; + int vcheck_ret; + + if (i->rta_type != IFLA_BRIDGE_VLAN_TUNNEL_INFO) + continue; + + parse_rtattr(ttb, IFLA_BRIDGE_VLAN_TUNNEL_MAX, + RTA_DATA(i), RTA_PAYLOAD(i)); + + if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID]) + tunnel_vid = + rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_VID]); + else + continue; + + if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID]) + tunnel_id = + rta_getattr_u32(ttb[IFLA_BRIDGE_VLAN_TUNNEL_ID]); + + if (ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]) + tunnel_flags = + rta_getattr_u16(ttb[IFLA_BRIDGE_VLAN_TUNNEL_FLAGS]); + + if (!(tunnel_flags & BRIDGE_VLAN_INFO_RANGE_END)) { + last_vid_start = tunnel_vid; + last_tunid_start = tunnel_id; + } + + vcheck_ret = filter_vlan_check(tunnel_vid, tunnel_flags); + if (vcheck_ret == -1) + break; + else if (vcheck_ret == 0) + continue; + + if (!opened) { + open_vlan_port(ifindex, VLAN_SHOW_TUNNELINFO); + opened = true; + } else { + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s ", ""); + } + + open_json_object(NULL); + width = print_range("vlan", last_vid_start, tunnel_vid); + if (width <= VLAN_ID_LEN) { + char buf[VLAN_ID_LEN + 1]; + + snprintf(buf, sizeof(buf), "%-*s", + VLAN_ID_LEN - width, ""); + print_string(PRINT_FP, NULL, "%s ", buf); + } else { + fprintf(stderr, "BUG: vlan range too wide, %u\n", + width); + } + print_range("tunid", last_tunid_start, tunnel_id); + close_json_object(); + print_nl(); + } + + if (opened) + close_vlan_port(); +} + +static int print_vlan(struct nlmsghdr *n, void *arg) +{ + enum vlan_show_subject *subject = arg; + struct ifinfomsg *ifm = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr *tb[IFLA_MAX+1]; + + if (n->nlmsg_type != RTM_NEWLINK) { + fprintf(stderr, "Not RTM_NEWLINK: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*ifm)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (ifm->ifi_family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != ifm->ifi_index) + return 0; + + parse_rtattr(tb, IFLA_MAX, IFLA_RTA(ifm), len); + if (!tb[IFLA_AF_SPEC]) + return 0; + + switch (*subject) { + case VLAN_SHOW_VLAN: + print_vlan_info(tb[IFLA_AF_SPEC], ifm->ifi_index); + break; + case VLAN_SHOW_TUNNELINFO: + print_vlan_tunnel_info(tb[IFLA_AF_SPEC], ifm->ifi_index); + break; + } + + return 0; +} + +static void print_vlan_flags(__u16 flags) +{ + if (flags == 0) + return; + + open_json_array(PRINT_JSON, "flags"); + if (flags & BRIDGE_VLAN_INFO_PVID) + print_string(PRINT_ANY, NULL, " %s", "PVID"); + + if (flags & BRIDGE_VLAN_INFO_UNTAGGED) + print_string(PRINT_ANY, NULL, " %s", "Egress Untagged"); + close_json_array(PRINT_JSON, NULL); +} + +static void __print_one_vlan_stats(const struct bridge_vlan_xstats *vstats) +{ + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_lluint(PRINT_ANY, "rx_bytes", "RX: %llu bytes", + vstats->rx_bytes); + print_lluint(PRINT_ANY, "rx_packets", " %llu packets\n", + vstats->rx_packets); + + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_lluint(PRINT_ANY, "tx_bytes", "TX: %llu bytes", + vstats->tx_bytes); + print_lluint(PRINT_ANY, "tx_packets", " %llu packets\n", + vstats->tx_packets); +} + +static void print_one_vlan_stats(const struct bridge_vlan_xstats *vstats) +{ + open_json_object(NULL); + + print_hu(PRINT_ANY, "vid", "%hu", vstats->vid); + print_vlan_flags(vstats->flags); + print_nl(); + __print_one_vlan_stats(vstats); + + close_json_object(); +} + +static void print_vlan_stats_attr(struct rtattr *attr, int ifindex) +{ + struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1]; + struct rtattr *i, *list; + bool found_vlan = false; + int rem; + + parse_rtattr(brtb, LINK_XSTATS_TYPE_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + if (!brtb[LINK_XSTATS_TYPE_BRIDGE]) + return; + + list = brtb[LINK_XSTATS_TYPE_BRIDGE]; + rem = RTA_PAYLOAD(list); + + for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + const struct bridge_vlan_xstats *vstats = RTA_DATA(i); + + if (i->rta_type != BRIDGE_XSTATS_VLAN) + continue; + + if (filter_vlan && filter_vlan != vstats->vid) + continue; + + /* skip pure port entries, they'll be dumped via the slave stats call */ + if ((vstats->flags & BRIDGE_VLAN_INFO_MASTER) && + !(vstats->flags & BRIDGE_VLAN_INFO_BRENTRY)) + continue; + + /* found vlan stats, first time print the interface name */ + if (!found_vlan) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + found_vlan = true; + } else { + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s ", ""); + } + print_one_vlan_stats(vstats); + } + + /* vlan_port is opened only if there are any vlan stats */ + if (found_vlan) + close_vlan_port(); +} + +static int print_vlan_stats(struct nlmsghdr *n, void *arg) +{ + struct if_stats_msg *ifsm = NLMSG_DATA(n); + struct rtattr *tb[IFLA_STATS_MAX+1]; + int len = n->nlmsg_len; + FILE *fp = arg; + + len -= NLMSG_LENGTH(sizeof(*ifsm)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (filter_index && filter_index != ifsm->ifindex) + return 0; + + parse_rtattr(tb, IFLA_STATS_MAX, IFLA_STATS_RTA(ifsm), len); + + /* We have to check if any of the two attrs are usable */ + if (tb[IFLA_STATS_LINK_XSTATS]) + print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS], + ifsm->ifindex); + + if (tb[IFLA_STATS_LINK_XSTATS_SLAVE]) + print_vlan_stats_attr(tb[IFLA_STATS_LINK_XSTATS_SLAVE], + ifsm->ifindex); + + fflush(fp); + return 0; +} + +static void print_vlan_router_ports(struct rtattr *rattr) +{ + int rem = RTA_PAYLOAD(rattr); + struct rtattr *i; + + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + open_json_array(PRINT_ANY, is_json_context() ? "router_ports" : + "router ports: "); + for (i = RTA_DATA(rattr); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + uint32_t *port_ifindex = RTA_DATA(i); + const char *port_ifname = ll_index_to_name(*port_ifindex); + + open_json_object(NULL); + if (show_stats && i != RTA_DATA(rattr)) { + print_nl(); + /* start: IFNAMSIZ + 4 + strlen("router ports: ") */ + print_string(PRINT_FP, NULL, + "%-" __stringify(IFNAMSIZ) "s " + " ", + ""); + } + print_string(PRINT_ANY, "port", "%s ", port_ifname); + if (show_stats) + br_print_router_port_stats(i); + close_json_object(); + } + close_json_array(PRINT_JSON, NULL); + print_nl(); +} + +static void print_vlan_global_opts(struct rtattr *a, int ifindex) +{ + struct rtattr *vtb[BRIDGE_VLANDB_GOPTS_MAX + 1], *vattr; + __u16 vid, vrange = 0; + + if ((a->rta_type & NLA_TYPE_MASK) != BRIDGE_VLANDB_GLOBAL_OPTIONS) + return; + + parse_rtattr_flags(vtb, BRIDGE_VLANDB_GOPTS_MAX, RTA_DATA(a), + RTA_PAYLOAD(a), NLA_F_NESTED); + vid = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_ID]); + if (vtb[BRIDGE_VLANDB_GOPTS_RANGE]) + vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_GOPTS_RANGE]); + else + vrange = vid; + + if (filter_vlan && (filter_vlan < vid || filter_vlan > vrange)) + return; + + if (vlan_rtm_cur_ifidx != ifindex) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + open_json_object(NULL); + vlan_rtm_cur_ifidx = ifindex; + } else { + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + print_range("vlan", vid, vrange); + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_SNOOPING]; + print_uint(PRINT_ANY, "mcast_snooping", "mcast_snooping %u ", + rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER]; + print_uint(PRINT_ANY, "mcast_querier", "mcast_querier %u ", + rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]; + print_uint(PRINT_ANY, "mcast_igmp_version", + "mcast_igmp_version %u ", rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]; + print_uint(PRINT_ANY, "mcast_mld_version", + "mcast_mld_version %u ", rta_getattr_u8(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_CNT]; + print_uint(PRINT_ANY, "mcast_last_member_count", + "mcast_last_member_count %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL]; + print_lluint(PRINT_ANY, "mcast_last_member_interval", + "mcast_last_member_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_CNT]; + print_uint(PRINT_ANY, "mcast_startup_query_count", + "mcast_startup_query_count %u ", + rta_getattr_u32(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL]; + print_lluint(PRINT_ANY, "mcast_startup_query_interval", + "mcast_startup_query_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL]; + print_lluint(PRINT_ANY, "mcast_membership_interval", + "mcast_membership_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL]; + print_lluint(PRINT_ANY, "mcast_querier_interval", + "mcast_querier_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL]; + print_lluint(PRINT_ANY, "mcast_query_interval", + "mcast_query_interval %llu ", + rta_getattr_u64(vattr)); + } + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]) { + vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL]; + print_lluint(PRINT_ANY, "mcast_query_response_interval", + "mcast_query_response_interval %llu ", + rta_getattr_u64(vattr)); + } + print_nl(); + if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]) { + vattr = RTA_DATA(vtb[BRIDGE_VLANDB_GOPTS_MCAST_ROUTER_PORTS]); + print_vlan_router_ports(vattr); + } + close_json_object(); +} + +static void print_vlan_opts(struct rtattr *a, int ifindex) +{ + struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *vattr; + struct bridge_vlan_xstats vstats; + struct bridge_vlan_info *vinfo; + __u16 vrange = 0; + __u8 state = 0; + + if ((a->rta_type & NLA_TYPE_MASK) != BRIDGE_VLANDB_ENTRY) + return; + + parse_rtattr_flags(vtb, BRIDGE_VLANDB_ENTRY_MAX, RTA_DATA(a), + RTA_PAYLOAD(a), NLA_F_NESTED); + vinfo = RTA_DATA(vtb[BRIDGE_VLANDB_ENTRY_INFO]); + + memset(&vstats, 0, sizeof(vstats)); + if (vtb[BRIDGE_VLANDB_ENTRY_RANGE]) + vrange = rta_getattr_u16(vtb[BRIDGE_VLANDB_ENTRY_RANGE]); + else + vrange = vinfo->vid; + + if (filter_vlan && (filter_vlan < vinfo->vid || filter_vlan > vrange)) + return; + + if (vtb[BRIDGE_VLANDB_ENTRY_STATE]) + state = rta_getattr_u8(vtb[BRIDGE_VLANDB_ENTRY_STATE]); + + if (vtb[BRIDGE_VLANDB_ENTRY_STATS]) { + struct rtattr *stb[BRIDGE_VLANDB_STATS_MAX+1]; + struct rtattr *attr; + + attr = vtb[BRIDGE_VLANDB_ENTRY_STATS]; + parse_rtattr(stb, BRIDGE_VLANDB_STATS_MAX, RTA_DATA(attr), + RTA_PAYLOAD(attr)); + + if (stb[BRIDGE_VLANDB_STATS_RX_BYTES]) { + attr = stb[BRIDGE_VLANDB_STATS_RX_BYTES]; + vstats.rx_bytes = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_RX_PACKETS]) { + attr = stb[BRIDGE_VLANDB_STATS_RX_PACKETS]; + vstats.rx_packets = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_TX_PACKETS]) { + attr = stb[BRIDGE_VLANDB_STATS_TX_PACKETS]; + vstats.tx_packets = rta_getattr_u64(attr); + } + if (stb[BRIDGE_VLANDB_STATS_TX_BYTES]) { + attr = stb[BRIDGE_VLANDB_STATS_TX_BYTES]; + vstats.tx_bytes = rta_getattr_u64(attr); + } + } + + if (vlan_rtm_cur_ifidx != ifindex) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + open_json_object(NULL); + vlan_rtm_cur_ifidx = ifindex; + } else { + open_json_object(NULL); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + } + print_range("vlan", vinfo->vid, vrange); + print_vlan_flags(vinfo->flags); + print_nl(); + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + print_stp_state(state); + if (vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]) { + vattr = vtb[BRIDGE_VLANDB_ENTRY_MCAST_ROUTER]; + print_uint(PRINT_ANY, "mcast_router", "mcast_router %u ", + rta_getattr_u8(vattr)); + } + print_nl(); + if (show_stats) + __print_one_vlan_stats(&vstats); + close_json_object(); +} + +int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor, bool global_only) +{ + struct br_vlan_msg *bvm = NLMSG_DATA(n); + int len = n->nlmsg_len; + struct rtattr *a; + FILE *fp = arg; + int rem; + + if (n->nlmsg_type != RTM_NEWVLAN && n->nlmsg_type != RTM_DELVLAN && + n->nlmsg_type != RTM_GETVLAN) { + fprintf(stderr, "Unknown vlan rtm message: %08x %08x %08x\n", + n->nlmsg_len, n->nlmsg_type, n->nlmsg_flags); + return 0; + } + + len -= NLMSG_LENGTH(sizeof(*bvm)); + if (len < 0) { + fprintf(stderr, "BUG: wrong nlmsg len %d\n", len); + return -1; + } + + if (bvm->family != AF_BRIDGE) + return 0; + + if (filter_index && filter_index != bvm->ifindex) + return 0; + + print_headers(fp, "[VLAN]"); + + if (n->nlmsg_type == RTM_DELVLAN) + print_bool(PRINT_ANY, "deleted", "Deleted ", true); + + if (monitor) + vlan_rtm_cur_ifidx = -1; + + if (vlan_rtm_cur_ifidx != -1 && vlan_rtm_cur_ifidx != bvm->ifindex) { + close_vlan_port(); + vlan_rtm_cur_ifidx = -1; + } + + rem = len; + for (a = BRVLAN_RTA(bvm); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) { + unsigned short rta_type = a->rta_type & NLA_TYPE_MASK; + + /* skip unknown attributes */ + if (rta_type > BRIDGE_VLANDB_MAX || + (global_only && rta_type != BRIDGE_VLANDB_GLOBAL_OPTIONS)) + continue; + + switch (rta_type) { + case BRIDGE_VLANDB_ENTRY: + print_vlan_opts(a, bvm->ifindex); + break; + case BRIDGE_VLANDB_GLOBAL_OPTIONS: + print_vlan_global_opts(a, bvm->ifindex); + break; + } + } + + return 0; +} + +static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg) +{ + return print_vlan_rtm(n, arg, false, false); +} + +static int print_vlan_rtm_global_filter(struct nlmsghdr *n, void *arg) +{ + return print_vlan_rtm(n, arg, false, true); +} + +static int vlan_show(int argc, char **argv, int subject) +{ + 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; + } else if (strcmp(*argv, "vid") == 0) { + NEXT_ARG(); + if (filter_vlan) + duparg("vid", *argv); + filter_vlan = atoi(*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_details is true then use the new bridge vlan dump format */ + if (show_details && subject == VLAN_SHOW_VLAN) { + __u32 dump_flags = show_stats ? BRIDGE_VLANDB_DUMPF_STATS : 0; + + if (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan_rtm_filter, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + if (vlan_rtm_cur_ifidx != -1) + close_vlan_port(); + + goto out; + } + + if (!show_stats) { + if (rtnl_linkdump_req_filter(&rth, PF_BRIDGE, + (compress_vlans ? + RTEXT_FILTER_BRVLAN_COMPRESSED : + RTEXT_FILTER_BRVLAN)) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + if (subject == VLAN_SHOW_TUNNELINFO) + printf(" tunnel-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + } else { + __u32 filt_mask; + + filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS); + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) + printf("%-" __stringify(IFNAMSIZ) "s vlan-id\n", + "port"); + + if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + filt_mask = IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_XSTATS_SLAVE); + if (rtnl_statsdump_req_filter(&rth, AF_UNSPEC, filt_mask, + NULL, NULL) < 0) { + perror("Cannot send slave dump request"); + exit(1); + } + + if (rtnl_dump_filter(&rth, print_vlan_stats, stdout) < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + } + +out: + delete_json_obj(); + fflush(stdout); + return 0; +} + +static int vlan_global_show(int argc, char **argv) +{ + __u32 dump_flags = BRIDGE_VLANDB_DUMPF_GLOBAL; + int ret = 0, subject = VLAN_SHOW_VLAN; + char *filter_dev = NULL; + + while (argc > 0) { + if (strcmp(*argv, "dev") == 0) { + NEXT_ARG(); + if (filter_dev) + duparg("dev", *argv); + filter_dev = *argv; + } else if (strcmp(*argv, "vid") == 0) { + NEXT_ARG(); + if (filter_vlan) + duparg("vid", *argv); + filter_vlan = atoi(*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 (rtnl_brvlandump_req(&rth, PF_BRIDGE, dump_flags) < 0) { + perror("Cannot send dump request"); + exit(1); + } + + if (!is_json_context()) { + printf("%-" __stringify(IFNAMSIZ) "s %-" + __stringify(VLAN_ID_LEN) "s", "port", + "vlan-id"); + printf("\n"); + } + + ret = rtnl_dump_filter(&rth, print_vlan_rtm_global_filter, &subject); + if (ret < 0) { + fprintf(stderr, "Dump terminated\n"); + exit(1); + } + + if (vlan_rtm_cur_ifidx != -1) + close_vlan_port(); + + delete_json_obj(); + fflush(stdout); + return 0; +} + +void print_vlan_info(struct rtattr *tb, int ifindex) +{ + struct rtattr *i, *list = tb; + int rem = RTA_PAYLOAD(list); + __u16 last_vid_start = 0; + bool opened = false; + + for (i = RTA_DATA(list); RTA_OK(i, rem); i = RTA_NEXT(i, rem)) { + struct bridge_vlan_info *vinfo; + int vcheck_ret; + + if (i->rta_type != IFLA_BRIDGE_VLAN_INFO) + continue; + + vinfo = RTA_DATA(i); + + if (!(vinfo->flags & BRIDGE_VLAN_INFO_RANGE_END)) + last_vid_start = vinfo->vid; + vcheck_ret = filter_vlan_check(vinfo->vid, vinfo->flags); + if (vcheck_ret == -1) + break; + else if (vcheck_ret == 0) + continue; + + if (!opened) { + open_vlan_port(ifindex, VLAN_SHOW_VLAN); + opened = true; + } else { + print_string(PRINT_FP, NULL, "%-" + __stringify(IFNAMSIZ) "s ", ""); + } + + open_json_object(NULL); + print_range("vlan", last_vid_start, vinfo->vid); + + print_vlan_flags(vinfo->flags); + close_json_object(); + print_nl(); + } + + if (opened) + close_vlan_port(); +} + +static int vlan_global(int argc, char **argv) +{ + if (argc > 0) { + if (strcmp(*argv, "show") == 0 || + strcmp(*argv, "lst") == 0 || + strcmp(*argv, "list") == 0) + return vlan_global_show(argc-1, argv+1); + else if (strcmp(*argv, "set") == 0) + return vlan_global_option_set(argc-1, argv+1); + else + usage(); + } else { + return vlan_global_show(0, NULL); + } + + return 0; +} + +int do_vlan(int argc, char **argv) +{ + ll_init_map(&rth); + timestamp = 0; + + if (argc > 0) { + if (matches(*argv, "add") == 0) + return vlan_modify(RTM_SETLINK, argc-1, argv+1); + if (matches(*argv, "delete") == 0) + return vlan_modify(RTM_DELLINK, argc-1, argv+1); + if (matches(*argv, "show") == 0 || + matches(*argv, "lst") == 0 || + matches(*argv, "list") == 0) + return vlan_show(argc-1, argv+1, VLAN_SHOW_VLAN); + if (matches(*argv, "tunnelshow") == 0) { + return vlan_show(argc-1, argv+1, VLAN_SHOW_TUNNELINFO); + } + if (matches(*argv, "set") == 0) + return vlan_option_set(argc-1, argv+1); + if (strcmp(*argv, "global") == 0) + return vlan_global(argc-1, argv+1); + if (matches(*argv, "help") == 0) + usage(); + } else { + return vlan_show(0, NULL, VLAN_SHOW_VLAN); + } + + fprintf(stderr, "Command \"%s\" is unknown, try \"bridge vlan help\".\n", *argv); + exit(-1); +} diff --git a/bridge/vni.c b/bridge/vni.c new file mode 100644 index 0000000..e776797 --- /dev/null +++ b/bridge/vni.c @@ -0,0 +1,443 @@ +/* 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; + FILE *fp = arg; + 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; + + print_headers(fp, "[TUNNEL]"); + + 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); + timestamp = 0; + + 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); +} |