diff --git a/bridge/br_common.h b/bridge/br_common.h index b5798da3..b9adafd9 100644 --- a/bridge/br_common.h +++ b/bridge/br_common.h @@ -10,6 +10,9 @@ 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); int do_fdb(int argc, char **argv); int do_mdb(int argc, char **argv); diff --git a/bridge/link.c b/bridge/link.c index d88c469d..205a2fe7 100644 --- a/bridge/link.c +++ b/bridge/link.c @@ -19,7 +19,7 @@ static unsigned int filter_index; -static const char *port_states[] = { +static const char *stp_states[] = { [BR_STATE_DISABLED] = "disabled", [BR_STATE_LISTENING] = "listening", [BR_STATE_LEARNING] = "learning", @@ -68,16 +68,31 @@ static void print_link_flags(FILE *fp, unsigned int flags, unsigned int mdown) close_json_array(PRINT_ANY, "> "); } -static void print_portstate(__u8 state) +void print_stp_state(__u8 state) { if (state <= BR_STATE_BLOCKING) print_string(PRINT_ANY, "state", - "state %s ", port_states[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)) @@ -96,7 +111,7 @@ static void print_protinfo(FILE *fp, struct rtattr *attr) parse_rtattr_nested(prtb, IFLA_BRPORT_MAX, attr); if (prtb[IFLA_BRPORT_STATE]) - print_portstate(rta_getattr_u8(prtb[IFLA_BRPORT_STATE])); + print_stp_state(rta_getattr_u8(prtb[IFLA_BRPORT_STATE])); if (prtb[IFLA_BRPORT_PRIORITY]) print_uint(PRINT_ANY, "priority", @@ -161,7 +176,7 @@ static void print_protinfo(FILE *fp, struct rtattr *attr) print_on_off(PRINT_ANY, "isolated", "isolated %s ", rta_getattr_u8(prtb[IFLA_BRPORT_ISOLATED])); } else - print_portstate(rta_getattr_u8(attr)); + print_stp_state(rta_getattr_u8(attr)); } @@ -359,14 +374,11 @@ static int brlink_modify(int argc, char **argv) } else if (strcmp(*argv, "state") == 0) { NEXT_ARG(); char *endptr; - size_t nstates = ARRAY_SIZE(port_states); state = strtol(*argv, &endptr, 10); if (!(**argv != '\0' && *endptr == '\0')) { - for (state = 0; state < nstates; state++) - if (strcasecmp(port_states[state], *argv) == 0) - break; - if (state == nstates) { + state = parse_stp_state(*argv); + if (state == -1) { fprintf(stderr, "Error: invalid STP port state\n"); return -1; diff --git a/bridge/mdb.c b/bridge/mdb.c index ef89258b..b427d878 100644 --- a/bridge/mdb.c +++ b/bridge/mdb.c @@ -16,9 +16,9 @@ #include #include "libnetlink.h" +#include "utils.h" #include "br_common.h" #include "rt_names.h" -#include "utils.h" #include "json_print.h" #ifndef MDBA_RTA diff --git a/bridge/monitor.c b/bridge/monitor.c index 08439a60..88f52f52 100644 --- a/bridge/monitor.c +++ b/bridge/monitor.c @@ -31,7 +31,7 @@ static int prefix_banner; static void usage(void) { - fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | all]\n"); + fprintf(stderr, "Usage: bridge monitor [file | link | fdb | mdb | vlan | all]\n"); exit(-1); } @@ -67,6 +67,12 @@ static int accept_msg(struct rtnl_ctrl_data *ctrl, print_nlmsg_timestamp(fp, n); return 0; + case RTM_NEWVLAN: + case RTM_DELVLAN: + if (prefix_banner) + fprintf(fp, "[VLAN]"); + return print_vlan_rtm(n, arg, true); + default: return 0; } @@ -79,6 +85,7 @@ int do_monitor(int argc, char **argv) int llink = 0; int lneigh = 0; int lmdb = 0; + int lvlan = 0; rtnl_close(&rth); @@ -95,8 +102,12 @@ int do_monitor(int argc, char **argv) } else if (matches(*argv, "mdb") == 0) { lmdb = 1; groups = 0; + } else if (matches(*argv, "vlan") == 0) { + lvlan = 1; + groups = 0; } else if (strcmp(*argv, "all") == 0) { groups = ~RTMGRP_TC; + lvlan = 1; prefix_banner = 1; } else if (matches(*argv, "help") == 0) { usage(); @@ -134,6 +145,12 @@ int do_monitor(int argc, char **argv) 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); + } + ll_init_map(&rth); if (rtnl_listen(&rth, accept_msg, stdout) < 0) diff --git a/bridge/vlan.c b/bridge/vlan.c index 0d142bc9..9bb9e28d 100644 --- a/bridge/vlan.c +++ b/bridge/vlan.c @@ -16,6 +16,7 @@ #include "utils.h" static unsigned int filter_index, filter_vlan; +static int vlan_rtm_cur_ifidx = -1; enum vlan_show_subject { VLAN_SHOW_VLAN, @@ -33,6 +34,7 @@ static void usage(void) "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" " bridge vlan { show } [ dev DEV ] [ vid VLAN_ID ]\n" " bridge vlan { tunnelshow } [ dev DEV ] [ vid VLAN_ID ]\n"); exit(-1); @@ -241,6 +243,100 @@ static int vlan_modify(int cmd, int argc, char **argv) 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; + short vid_end = -1; + char *d = NULL; + short vid = -1; + int state = -1; + + 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); + if (vid >= vid_end || vid_end >= 4096) { + fprintf(stderr, "Invalid VLAN range \"%hu-%hu\"\n", + vid, vid_end); + return -1; + } + } else { + vid = atoi(*argv); + } + } else if (strcmp(*argv, "state") == 0) { + char *endptr; + + 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; + } + } 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.bvm.ifindex = ll_name_to_index(d); + if (req.bvm.ifindex == 0) { + fprintf(stderr, "Cannot find network device \"%s\"\n", d); + return -1; + } + + if (vid >= 4096) { + fprintf(stderr, "Invalid VLAN ID \"%hu\"\n", vid); + return -1; + } + afspec = addattr_nest(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY); + afspec->rta_type |= NLA_F_NESTED; + + 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); + if (state >= 0) + addattr8(&req.n, sizeof(req), BRIDGE_VLANDB_ENTRY_STATE, state); + addattr_nest_end(&req.n, afspec); + + 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 @@ -422,14 +518,8 @@ static void print_vlan_flags(__u16 flags) close_json_array(PRINT_JSON, NULL); } -static void print_one_vlan_stats(const struct bridge_vlan_xstats *vstats) +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_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); print_lluint(PRINT_ANY, "rx_bytes", "RX: %llu bytes", vstats->rx_bytes); @@ -441,6 +531,16 @@ static void print_one_vlan_stats(const struct bridge_vlan_xstats *vstats) 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(); } @@ -521,6 +621,116 @@ static int print_vlan_stats(struct nlmsghdr *n, void *arg) return 0; } +int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor) +{ + struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *a; + struct br_vlan_msg *bvm = NLMSG_DATA(n); + int len = n->nlmsg_len; + bool newport = false; + 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; + + 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) { + if (vlan_rtm_cur_ifidx != -1) + close_vlan_port(); + open_vlan_port(bvm->ifindex, VLAN_SHOW_VLAN); + vlan_rtm_cur_ifidx = bvm->ifindex; + newport = true; + } + + rem = len; + for (a = BRVLAN_RTA(bvm); RTA_OK(a, rem); a = RTA_NEXT(a, rem)) { + struct bridge_vlan_xstats vstats; + struct bridge_vlan_info *vinfo; + __u32 vrange = 0; + __u8 state = 0; + + 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 (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); + } + } + open_json_object(NULL); + if (!newport) + print_string(PRINT_FP, NULL, "%-" __stringify(IFNAMSIZ) "s ", ""); + else + newport = false; + 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); + print_nl(); + if (show_stats) + __print_one_vlan_stats(&vstats); + close_json_object(); + } + + return 0; +} + +static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg) +{ + return print_vlan_rtm(n, arg, false); +} + static int vlan_show(int argc, char **argv, int subject) { char *filter_dev = NULL; @@ -549,6 +759,34 @@ static int vlan_show(int argc, char **argv, int subject) 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 ? @@ -602,6 +840,7 @@ static int vlan_show(int argc, char **argv, int subject) } } +out: delete_json_obj(); fflush(stdout); return 0; @@ -667,6 +906,8 @@ int do_vlan(int argc, char **argv) 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 (matches(*argv, "help") == 0) usage(); } else { diff --git a/include/libnetlink.h b/include/libnetlink.h index e8ed5d7f..6bff6bae 100644 --- a/include/libnetlink.h +++ b/include/libnetlink.h @@ -69,6 +69,8 @@ int rtnl_neightbldump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); int rtnl_mdbdump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); +int rtnl_brvlandump_req(struct rtnl_handle *rth, int family, __u32 dump_flags) + __attribute__((warn_unused_result)); int rtnl_netconfdump_req(struct rtnl_handle *rth, int family) __attribute__((warn_unused_result)); @@ -283,6 +285,11 @@ int rtnl_from_file(FILE *, rtnl_listen_filter_t handler, ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct if_stats_msg)))) #endif +#ifndef BRVLAN_RTA +#define BRVLAN_RTA(r) \ + ((struct rtattr *)(((char *)(r)) + NLMSG_ALIGN(sizeof(struct br_vlan_msg)))) +#endif + /* User defined nlmsg_type which is used mostly for logging netlink * messages from dump file */ #define NLMSG_TSTAMP 15 diff --git a/lib/libnetlink.c b/lib/libnetlink.c index 6885087b..2f2cc1fe 100644 --- a/lib/libnetlink.c +++ b/lib/libnetlink.c @@ -450,6 +450,25 @@ int rtnl_mdbdump_req(struct rtnl_handle *rth, int family) return send(rth->fd, &req, sizeof(req), 0); } +int rtnl_brvlandump_req(struct rtnl_handle *rth, int family, __u32 dump_flags) +{ + struct { + struct nlmsghdr nlh; + struct br_vlan_msg bvm; + char buf[256]; + } req = { + .nlh.nlmsg_len = NLMSG_LENGTH(sizeof(struct br_vlan_msg)), + .nlh.nlmsg_type = RTM_GETVLAN, + .nlh.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST, + .nlh.nlmsg_seq = rth->dump = ++rth->seq, + .bvm.family = family, + }; + + addattr32(&req.nlh, sizeof(req), BRIDGE_VLANDB_DUMP_FLAGS, dump_flags); + + return send(rth->fd, &req, sizeof(req), 0); +} + int rtnl_netconfdump_req(struct rtnl_handle *rth, int family) { struct { diff --git a/man/man8/bridge.8 b/man/man8/bridge.8 index 9d8663bd..eec7df43 100644 --- a/man/man8/bridge.8 +++ b/man/man8/bridge.8 @@ -138,13 +138,22 @@ bridge \- show / manipulate bridge addresses and devices .BR pvid " ] [ " untagged " ] [ " .BR self " ] [ " master " ] " +.ti -8 +.BR "bridge vlan set" +.B dev +.I DEV +.B vid +.IR VID " [ " +.B state +.IR STP_STATE " ] " + .ti -8 .BR "bridge vlan" " [ " show " | " tunnelshow " ] [ " .B dev .IR DEV " ]" .ti -8 -.BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " ]" +.BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " | " vlan " ]" .SH OPTIONS @@ -162,7 +171,7 @@ As a rule, the information is statistics or some time values. .TP .BR "\-d" , " \-details" -print detailed information about MDB router ports. +print detailed information about bridge vlan filter entries or MDB router ports. .TP .BR "\-n" , " \-net" , " \-netns " @@ -813,10 +822,70 @@ The .BR "pvid " and " untagged" flags are ignored. +.SS bridge vlan set - change vlan filter entry's options + +This command changes vlan filter entry's options. + +.TP +.BI dev " NAME" +the interface with which this vlan is associated. + +.TP +.BI vid " VID" +the VLAN ID that identifies the vlan. + +.TP +.BI state " STP_STATE " +the operation state of the vlan. One may enter STP state name (case insensitive), or one of the +numbers below. Negative inputs are ignored, and unrecognized names return an +error. Note that the state is set only for the vlan of the specified device, e.g. if it is +a bridge port then the state will be set only for the vlan of the port. + +.B 0 +- vlan is in STP +.B DISABLED +state. Make this vlan completely inactive for STP. This is also called +BPDU filter and could be used to disable STP on an untrusted vlan. +.sp + +.B 1 +- vlan is in STP +.B LISTENING +state. Only valid if STP is enabled on the bridge. In this +state the vlan listens for STP BPDUs and drops all other traffic frames. +.sp + +.B 2 +- vlan is in STP +.B LEARNING +state. Only valid if STP is enabled on the bridge. In this +state the vlan will accept traffic only for the purpose of updating MAC +address tables. +.sp + +.B 3 +- vlan is in STP +.B FORWARDING +state. This is the default vlan state. +.sp + +.B 4 +- vlan is in STP +.B BLOCKING +state. Only valid if STP is enabled on the bridge. This state +is used during the STP election process. In this state, the vlan will only process +STP BPDUs. +.sp + .SS bridge vlan show - list vlan configuration. This command displays the current VLAN filter table. +.PP +With the +.B -details +option, the command becomes verbose. It displays the per-vlan options. + .PP With the .B -statistics @@ -842,7 +911,7 @@ command is the first in the command line and then the object list follows: .I OBJECT-LIST is the list of object types that we want to monitor. It may contain -.BR link ", " fdb ", and " mdb "." +.BR link ", " fdb ", " vlan " and " mdb "." If no .B file argument is given,