Merge branch 'main' into next

Conflicts:
	include/uapi/linux/virtio_ids.h

Signed-off-by: David Ahern <dsahern@kernel.org>
diff --git a/bridge/br_common.h b/bridge/br_common.h
index b9adafd..610e83f 100644
--- a/bridge/br_common.h
+++ b/bridge/br_common.h
@@ -12,7 +12,9 @@
 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 print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor,
+		   bool global_only);
+void br_print_router_port_stats(struct rtattr *pattr);
 
 int do_fdb(int argc, char **argv);
 int do_mdb(int argc, char **argv);
diff --git a/bridge/mdb.c b/bridge/mdb.c
index b427d87..7b5863d 100644
--- a/bridge/mdb.c
+++ b/bridge/mdb.c
@@ -59,7 +59,7 @@
 	return tbuf;
 }
 
-static void __print_router_port_stats(FILE *f, struct rtattr *pattr)
+void br_print_router_port_stats(struct rtattr *pattr)
 {
 	struct rtattr *tb[MDBA_ROUTER_PATTR_MAX + 1];
 
@@ -101,13 +101,13 @@
 			print_string(PRINT_JSON, "port", NULL, port_ifname);
 
 			if (show_stats)
-				__print_router_port_stats(f, i);
+				br_print_router_port_stats(i);
 			close_json_object();
 		} else if (show_stats) {
 			fprintf(f, "router ports on %s: %s",
 				brifname, port_ifname);
 
-			__print_router_port_stats(f, i);
+			br_print_router_port_stats(i);
 			fprintf(f, "\n");
 		} else {
 			fprintf(f, "%s ", port_ifname);
diff --git a/bridge/monitor.c b/bridge/monitor.c
index 88f52f5..845e221 100644
--- a/bridge/monitor.c
+++ b/bridge/monitor.c
@@ -71,7 +71,7 @@
 	case RTM_DELVLAN:
 		if (prefix_banner)
 			fprintf(fp, "[VLAN]");
-		return print_vlan_rtm(n, arg, true);
+		return print_vlan_rtm(n, arg, true, false);
 
 	default:
 		return 0;
diff --git a/bridge/vlan.c b/bridge/vlan.c
index 9b6511f..8300f35 100644
--- a/bridge/vlan.c
+++ b/bridge/vlan.c
@@ -9,6 +9,7 @@
 #include <linux/if_bridge.h>
 #include <linux/if_ether.h>
 #include <string.h>
+#include <errno.h>
 
 #include "json_print.h"
 #include "libnetlink.h"
@@ -35,8 +36,23 @@
 		"                                                     [ 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 { 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);
 }
 
@@ -257,15 +273,129 @@
 	};
 	struct bridge_vlan_info vinfo = {};
 	struct rtattr *afspec;
-	short vid_end = -1;
 	char *d = NULL;
 	short vid = -1;
-	int state = -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;
 
@@ -284,53 +414,116 @@
 			} 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");
+			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 (matches(*argv, "help") == 0)
+			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;
 	}
 
-	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;
 
@@ -621,11 +814,224 @@
 	return 0;
 }
 
-int print_vlan_rtm(struct nlmsghdr *n, void *arg, bool monitor)
+static void print_vlan_router_ports(struct rtattr *rattr)
 {
-	struct rtattr *vtb[BRIDGE_VLANDB_ENTRY_MAX + 1], *a;
+	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;
 	int rem;
 
 	if (n->nlmsg_type != RTM_NEWVLAN && n->nlmsg_type != RTM_DELVLAN &&
@@ -660,66 +1066,21 @@
 
 	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;
+		unsigned short rta_type = a->rta_type & NLA_TYPE_MASK;
 
-		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]);
+		/* skip unknown attributes */
+		if (rta_type > BRIDGE_VLANDB_MAX ||
+		    (global_only && rta_type != BRIDGE_VLANDB_GLOBAL_OPTIONS))
+			continue;
 
-		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);
-			}
+		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;
 		}
-		if (vlan_rtm_cur_ifidx != bvm->ifindex) {
-			open_vlan_port(bvm->ifindex, VLAN_SHOW_VLAN);
-			open_json_object(NULL);
-			vlan_rtm_cur_ifidx = bvm->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);
-		print_nl();
-		if (show_stats)
-			__print_one_vlan_stats(&vstats);
-		close_json_object();
 	}
 
 	return 0;
@@ -727,7 +1088,12 @@
 
 static int print_vlan_rtm_filter(struct nlmsghdr *n, void *arg)
 {
-	return print_vlan_rtm(n, arg, false);
+	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)
@@ -845,6 +1211,61 @@
 	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;
@@ -889,6 +1310,24 @@
 		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);
@@ -907,6 +1346,8 @@
 		}
 		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 {
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
index 99aa27b..6da2f80 100644
--- a/include/uapi/linux/virtio_ids.h
+++ b/include/uapi/linux/virtio_ids.h
@@ -55,6 +55,7 @@
 #define VIRTIO_ID_FS			26 /* virtio filesystem */
 #define VIRTIO_ID_PMEM			27 /* virtio pmem */
 #define VIRTIO_ID_MAC80211_HWSIM	29 /* virtio mac80211-hwsim */
+#define VIRTIO_ID_SCMI			32 /* virtio SCMI */
 #define VIRTIO_ID_I2C_ADAPTER		34 /* virtio i2c adapter */
 #define VIRTIO_ID_BT			40 /* virtio bluetooth */
 
diff --git a/ip/Makefile b/ip/Makefile
index 2ae9df8..bcc5f81 100644
--- a/ip/Makefile
+++ b/ip/Makefile
@@ -18,7 +18,7 @@
 include ../config.mk
 
 ALLOBJ=$(IPOBJ) $(RTMONOBJ)
-SCRIPTS=ifcfg rtpr routel routef
+SCRIPTS=routel
 TARGETS=ip rtmon
 
 all: $(TARGETS) $(SCRIPTS)
diff --git a/ip/ifcfg b/ip/ifcfg
deleted file mode 100755
index 5b34dec..0000000
--- a/ip/ifcfg
+++ /dev/null
@@ -1,150 +0,0 @@
-#! /bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-CheckForwarding () {
-  local sbase fwd
-  sbase=/proc/sys/net/ipv4/conf
-  fwd=0
-  if [ -d $sbase ]; then
-    for dir in $sbase/*/forwarding; do
-      fwd=$(( fwd + $(cat "$dir") ))
-    done
-  else
-    fwd=2
-  fi
-  return $fwd
-}
-
-RestartRDISC () {
-  killall -HUP rdisc || rdisc -fs
-}
-
-ABCMaskLen () {
-  local class;
-
-  class=${1%%.*}
-  if [ "$1" = "" -o $class -eq 0 -o $class -ge 224 ]; then return 0
-  elif [ $class -ge 224 ]; then return 0
-  elif [ $class -ge 192 ]; then return 24
-  elif [ $class -ge 128 ]; then return 16
-  else return 8; fi
-}
-
-label="label $1"
-ldev="$1"
-dev=${1%:*}
-if [ "$dev" = "" -o "$1" = "help" ]; then
-  echo "Usage: ifcfg DEV [[add|del [ADDR[/LEN]] [PEER] | stop]" 1>&2
-  echo "       add - add new address" 1>&2
-  echo "       del - delete address" 1>&2
-  echo "       stop - completely disable IP" 1>&2
-  exit 1
-fi
-shift
-
-CheckForwarding
-fwd=$?
-if [ $fwd -ne 0 ]; then
-  echo "Forwarding is ON or its state is unknown ($fwd). OK, No RDISC." 1>&2
-fi
-
-
-deleting=0
-case "$1" in
-add) shift ;;
-stop)
-  if [ "$ldev" != "$dev" ]; then
-    echo "Cannot stop alias $ldev" 1>&2
-    exit 1;
-  fi
-  ip -4 addr flush dev $dev $label || exit 1
-  if [ $fwd -eq 0 ]; then RestartRDISC; fi
-  exit 0 ;;
-del*)
-  deleting=1; shift ;;
-*)
-esac
-
-ipaddr=
-pfxlen=
-if [ "$1" != "" ]; then
-  ipaddr=${1%/*}
-  if [ "$1" != "$ipaddr" ]; then
-    pfxlen=${1#*/}
-  fi
-  if [ "$ipaddr" = "" ]; then
-    echo "$1 is bad IP address." 1>&2
-    exit 1
-  fi
-fi
-shift
-
-peer=$1
-if [ "$peer" != "" ]; then
-  if [ "$pfxlen" != "" -a "$pfxlen" != "32" ]; then
-    echo "Peer address with non-trivial netmask." 1>&2
-    exit 1
-  fi
-  pfx="$ipaddr peer $peer"
-else
-  if [ "$ipaddr" = "" ]; then
-    echo "Missing IP address argument." 1>&2
-    exit 1
-  fi
-  if [ "$pfxlen" = "" ]; then
-    ABCMaskLen $ipaddr
-    pfxlen=$?
-  fi
-  pfx="$ipaddr/$pfxlen"
-fi
-
-if [ "$ldev" = "$dev" -a "$ipaddr" != "" ]; then
-  label=
-fi
-
-if [ $deleting -ne 0 ]; then
-  ip addr del $pfx dev $dev $label || exit 1
-  if [ $fwd -eq 0 ]; then RestartRDISC; fi
-  exit 0
-fi
-
-
-if ! ip link set up dev $dev ; then
-  echo "Error: cannot enable interface $dev." 1>&2
-  exit 1
-fi
-if [ "$ipaddr" = "" ]; then exit 0; fi
-
-if ! arping -q -c 2 -w 3 -D -I $dev $ipaddr ; then
-  echo "Error: some host already uses address $ipaddr on $dev." 1>&2
-  exit 1
-fi
-
-if ! ip address add $pfx brd + dev $dev $label; then
-  echo "Error: failed to add $pfx on $dev." 1>&2
-  exit 1
-fi
-
-arping -q -A -c 1 -I $dev $ipaddr
-noarp=$?
-( sleep 2 ;
-  arping -q -U -c 1 -I $dev $ipaddr ) >/dev/null 2>&1 </dev/null &
-
-ip route add unreachable 224.0.0.0/24 >/dev/null 2>&1
-ip route add unreachable 255.255.255.255 >/dev/null 2>&1
-if [ "`ip link ls $dev | grep -c MULTICAST`" -ge 1 ]; then
-  ip route add 224.0.0.0/4 dev $dev scope global >/dev/null 2>&1
-fi
-
-if [ $fwd -eq 0 ]; then
-  if [ $noarp -eq 0 ]; then
-    ip ro append default dev $dev metric 30000 scope global
-  elif [ "$peer" != "" ]; then
-    if ping -q -c 2 -w 4 $peer ; then
-      ip ro append default via $peer dev $dev metric 30001
-    fi
-  fi
-  RestartRDISC
-fi
-
-exit 0
diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c
index d12fd05..c2e63f6 100644
--- a/ip/iplink_bridge.c
+++ b/ip/iplink_bridge.c
@@ -43,6 +43,7 @@
 		"		  [ vlan_stats_enabled VLAN_STATS_ENABLED ]\n"
 		"		  [ vlan_stats_per_port VLAN_STATS_PER_PORT ]\n"
 		"		  [ mcast_snooping MULTICAST_SNOOPING ]\n"
+		"		  [ mcast_vlan_snooping MULTICAST_VLAN_SNOOPING ]\n"
 		"		  [ mcast_router MULTICAST_ROUTER ]\n"
 		"		  [ mcast_query_use_ifaddr MCAST_QUERY_USE_IFADDR ]\n"
 		"		  [ mcast_querier MULTICAST_QUERIER ]\n"
@@ -83,6 +84,7 @@
 static int bridge_parse_opt(struct link_util *lu, int argc, char **argv,
 			    struct nlmsghdr *n)
 {
+	struct br_boolopt_multi bm = {};
 	__u32 val;
 
 	while (argc > 0) {
@@ -200,6 +202,18 @@
 				invarg("invalid mcast_snooping", *argv);
 
 			addattr8(n, 1024, IFLA_BR_MCAST_SNOOPING, mcast_snoop);
+		} else if (strcmp(*argv, "mcast_vlan_snooping") == 0) {
+			__u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+			__u8 mcast_vlan_snooping;
+
+			NEXT_ARG();
+			if (get_u8(&mcast_vlan_snooping, *argv, 0))
+				invarg("invalid mcast_vlan_snooping", *argv);
+			bm.optmask |= 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+			if (mcast_vlan_snooping)
+				bm.optval |= mcvl_bit;
+			else
+				bm.optval &= ~mcvl_bit;
 		} else if (matches(*argv, "mcast_query_use_ifaddr") == 0) {
 			__u8 mcast_qui;
 
@@ -379,6 +393,9 @@
 		argc--, argv++;
 	}
 
+	if (bm.optmask)
+		addattr_l(n, 1024, IFLA_BR_MULTI_BOOLOPT,
+			  &bm, sizeof(bm));
 	return 0;
 }
 
@@ -559,6 +576,18 @@
 			   "mcast_snooping %u ",
 			   rta_getattr_u8(tb[IFLA_BR_MCAST_SNOOPING]));
 
+	if (tb[IFLA_BR_MULTI_BOOLOPT]) {
+		__u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
+		struct br_boolopt_multi *bm;
+
+		bm = RTA_DATA(tb[IFLA_BR_MULTI_BOOLOPT]);
+		if (bm->optmask & mcvl_bit)
+			print_uint(PRINT_ANY,
+				   "mcast_vlan_snooping",
+				   "mcast_vlan_snooping %u ",
+				    !!(bm->optval & mcvl_bit));
+	}
+
 	if (tb[IFLA_BR_MCAST_ROUTER])
 		print_uint(PRINT_ANY,
 			   "mcast_router",
diff --git a/ip/routef b/ip/routef
deleted file mode 100755
index c251e7b..0000000
--- a/ip/routef
+++ /dev/null
@@ -1,10 +0,0 @@
-#! /bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-if [ -z "$*" ] ; then
-	exec ip -4 ro flush  scope global  type unicast
-else
-	echo "Usage: routef"
-	echo
-	echo "This script will flush the IPv4 routing table"
-fi
diff --git a/ip/routel b/ip/routel
index 7056886..09a9012 100755
--- a/ip/routel
+++ b/ip/routel
@@ -1,72 +1,62 @@
-#!/bin/sh
+#! /usr/bin/env python3
 # SPDX-License-Identifier: GPL-2.0
-
 #
-# Script created by: Stephen R. van den Berg <srb@cuci.nl>, 1999/04/18
-# Donated to the public domain.
-#
-# This script transforms the output of "ip" into more readable text.
-# "ip" is the Linux-advanced-routing configuration tool part of the
-# iproute package.
-#
+# This is simple script to process JSON output from ip route
+# command and format it.  Based on earlier shell script version.
+"""Script to parse ip route output into more readable text."""
 
-test "X-h" = "X$1" && echo "Usage: $0 [tablenr [raw ip args...]]" && exit 64
+import sys
+import json
+import getopt
+import subprocess
 
-test -z "$*" && set 0
 
-ip route list table "$@" |
- while read network rest
- do set xx $rest
-    shift
-    proto=""
-    via=""
-    dev=""
-    scope=""
-    src=""
-    table=""
-    case $network in
-       broadcast|local|unreachable) via=$network
-          network=$1
-          shift
-          ;;
-    esac
-    while test $# != 0
-    do
-       case "$1" in
-          proto|via|dev|scope|src|table)
-             key=$1
-             val=$2
-             eval "$key='$val'"
-             shift 2
-             ;;
-          dead|onlink|pervasive|offload|notify|linkdown|unresolved)
-             shift
-             ;;
-          *)
-             # avoid infinite loop on unknown keyword without value at line end
-             shift
-             shift
-             ;;
-       esac
-    done
-    echo "$network	$via	$src	$proto	$scope	$dev	$table"
- done | awk -F '	' '
-BEGIN {
-   format="%15s%-3s %15s %15s %8s %8s%7s %s\n";
-   printf(format,"target","","gateway","source","proto","scope","dev","tbl");
- }
- { network=$1;
-   mask="";
-   if(match(network,"/"))
-    { mask=" "substr(network,RSTART+1);
-      network=substr(network,0,RSTART);
-    }
-   via=$2;
-   src=$3;
-   proto=$4;
-   scope=$5;
-   dev=$6;
-   table=$7;
-   printf(format,network,mask,via,src,proto,scope,dev,table);
- }
-'
+def usage():
+    '''Print usage and exit'''
+    print("Usage: {} [tablenr [raw ip args...]]".format(sys.argv[0]))
+    sys.exit(64)
+
+
+def main():
+    '''Process the arguments'''
+    family = 'inet'
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "h46f:", ["help", "family="])
+    except getopt.GetoptError as err:
+        print(err)
+        usage()
+
+    for opt, arg in opts:
+        if opt in ["-h", "--help"]:
+            usage()
+        elif opt == '-6':
+            family = 'inet6'
+        elif opt == "-4":
+            family = 'inet'
+        elif opt in ["-f", "--family"]:
+            family = arg
+        else:
+            assert False, "unhandled option"
+
+    if not args:
+        args = ['0']
+
+    cmd = ['ip', '-f', family, '-j', 'route', 'list', 'table'] + args
+    process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+    tbl = json.load(process.stdout)
+    if family == 'inet':
+        fmt = '{:15} {:15} {:15} {:8} {:8}{:<16} {}'
+    else:
+        fmt = '{:32} {:32} {:32} {:8} {:8}{:<16} {}'
+
+    # ip route json keys
+    keys = ['dst', 'gateway', 'prefsrc', 'protocol', 'scope', 'dev', 'table']
+    print(fmt.format(*map(lambda x: x.capitalize(), keys)))
+
+    for record in tbl:
+        fields = [record[k] if k in record else '' for k in keys]
+        print(fmt.format(*fields))
+
+
+if __name__ == "__main__":
+    main()
diff --git a/ip/rtpr b/ip/rtpr
deleted file mode 100755
index 7e48674..0000000
--- a/ip/rtpr
+++ /dev/null
@@ -1,5 +0,0 @@
-#! /bin/sh
-# SPDX-License-Identifier: GPL-2.0
-
-exec tr "[\\\\]" "[
-]"
diff --git a/man/man8/bridge.8 b/man/man8/bridge.8
index db83a2a..81ce9e6 100644
--- a/man/man8/bridge.8
+++ b/man/man8/bridge.8
@@ -145,7 +145,9 @@
 .B vid
 .IR VID " [ "
 .B state
-.IR STP_STATE " ] "
+.IR STP_STATE " ] [ "
+.B mcast_router
+.IR MULTICAST_ROUTER " ]"
 
 .ti -8
 .BR "bridge vlan" " [ " show " | " tunnelshow " ] [ "
@@ -153,6 +155,44 @@
 .IR DEV " ]"
 
 .ti -8
+.BR "bridge vlan global set"
+.B dev
+.I DEV
+.B vid
+.IR VID " [ "
+.B mcast_snooping
+.IR MULTICAST_SNOOPING " ] [ "
+.B mcast_querier
+.IR MULTICAST_QUERIER " ] [ "
+.B mcast_igmp_version
+.IR IGMP_VERSION " ] [ "
+.B mcast_mld_version
+.IR MLD_VERSION " ] [ "
+.B mcast_last_member_count
+.IR LAST_MEMBER_COUNT " ] [ "
+.B mcast_last_member_interval
+.IR LAST_MEMBER_INTERVAL " ] [ "
+.B mcast_startup_query_count
+.IR STARTUP_QUERY_COUNT " ] [ "
+.B mcast_startup_query_interval
+.IR STARTUP_QUERY_INTERVAL " ] [ "
+.B mcast_membership_interval
+.IR MEMBERSHIP_INTERVAL " ] [ "
+.B mcast_querier_interval
+.IR QUERIER_INTERVAL " ] [ "
+.B mcast_query_interval
+.IR QUERY_INTERVAL " ] [ "
+.B mcast_query_response_interval
+.IR QUERY_RESPONSE_INTERVAL " ]"
+
+.ti -8
+.BR "bridge vlan global" " [ " show " ] [ "
+.B dev
+.IR DEV " ] [ "
+.B vid
+.IR VID " ]"
+
+.ti -8
 .BR "bridge monitor" " [ " all " | " neigh " | " link " | " mdb " | " vlan " ]"
 
 .SH OPTIONS
@@ -877,6 +917,31 @@
 STP BPDUs.
 .sp
 
+.TP
+.BI mcast_router " MULTICAST_ROUTER "
+configure this vlan and interface's multicast router mode, note that only modes
+0 - 2 are available for bridge devices.
+A vlan and interface with a multicast router will receive all multicast traffic.
+.I MULTICAST_ROUTER
+may be either
+.sp
+.B 0
+- to disable multicast router.
+.sp
+
+.B 1
+- to let the system detect the presence of routers (default).
+.sp
+
+.B 2
+- to permanently enable multicast traffic forwarding on this vlan and interface.
+.sp
+
+.B 3
+- to temporarily mark this vlan and port as having a multicast router, i.e.
+enable multicast traffic forwarding. This mode is available only for ports.
+.sp
+
 .SS bridge vlan show - list vlan configuration.
 
 This command displays the current VLAN filter table.
@@ -895,6 +960,98 @@
 
 This command displays the current vlan tunnel info mapping.
 
+.SS bridge vlan global set - change vlan filter entry's global options
+
+This command changes vlan filter entry's global options.
+
+.TP
+.BI dev " NAME"
+the interface with which this vlan is associated. Only bridge devices are
+supported for global options.
+
+.TP
+.BI vid " VID"
+the VLAN ID that identifies the vlan.
+
+.TP
+.BI mcast_snooping " MULTICAST_SNOOPING "
+turn multicast snooping for VLAN entry with VLAN ID on
+.RI ( MULTICAST_SNOOPING " > 0) "
+or off
+.RI ( MULTICAST_SNOOPING " == 0). Default is on. "
+
+.TP
+.BI mcast_querier " MULTICAST_QUERIER "
+enable
+.RI ( MULTICAST_QUERIER " > 0) "
+or disable
+.RI ( MULTICAST_QUERIER " == 0) "
+IGMP/MLD querier, ie sending of multicast queries by the bridge. Default is disabled.
+
+.TP
+.BI mcast_igmp_version " IGMP_VERSION "
+set the IGMP version. Default is 2.
+
+.TP
+.BI mcast_mld_version " MLD_VERSION "
+set the MLD version. Default is 1.
+
+.TP
+.BI mcast_last_member_count " LAST_MEMBER_COUNT "
+set multicast last member count, ie the number of queries the bridge
+will send before stopping forwarding a multicast group after a "leave"
+message has been received. Default is 2.
+
+.TP
+.BI mcast_last_member_interval " LAST_MEMBER_INTERVAL "
+interval between queries to find remaining members of a group,
+after a "leave" message is received.
+
+.TP
+.BI mcast_startup_query_count " STARTUP_QUERY_COUNT "
+set the number of queries to send during startup phase. Default is 2.
+
+.TP
+.BI mcast_startup_query_interval " STARTUP_QUERY_INTERVAL "
+interval between queries in the startup phase.
+
+.TP
+.BI mcast_membership_interval " MEMBERSHIP_INTERVAL "
+delay after which the bridge will leave a group,
+if no membership reports for this group are received.
+
+.TP
+.BI mcast_querier_interval " QUERIER_INTERVAL "
+interval between queries sent by other routers. If no queries are seen
+after this delay has passed, the bridge will start to send its own queries
+(as if
+.BI mcast_querier
+was enabled).
+
+.TP
+.BI mcast_query_interval " QUERY_INTERVAL "
+interval between queries sent by the bridge after the end of the
+startup phase.
+
+.TP
+.BI mcast_query_response_interval " QUERY_RESPONSE_INTERVAL "
+set the Max Response Time/Maximum Response Delay for IGMP/MLD
+queries sent by the bridge.
+
+.SS bridge vlan global show - list global vlan options.
+
+This command displays the global VLAN options for each VLAN entry.
+
+.TP
+.BI dev " DEV"
+the interface only whose VLAN global options should be listed. Default is to list
+all bridge interfaces.
+
+.TP
+.BI vid " VID"
+the VLAN ID only whose global options should be listed. Default is to list
+all vlans.
+
 .SH bridge monitor - state monitoring
 
 The
diff --git a/man/man8/ifcfg.8 b/man/man8/ifcfg.8
deleted file mode 100644
index 1a3786c..0000000
--- a/man/man8/ifcfg.8
+++ /dev/null
@@ -1,48 +0,0 @@
-.TH IFCFG 8 "September 24 2009" "iproute2" "Linux"
-.SH NAME
-ifcfg \- simplistic script which replaces ifconfig IP management
-.SH SYNOPSIS
-.ad l
-.in +8
-.ti -8
-.B ifcfg
-.RI "[ " DEVICE " ] [ " command " ] " ADDRESS " [ " PEER " ] "
-.sp
-
-.SH DESCRIPTION
-This manual page documents briefly the
-.B ifcfg
-command.
-.PP
-This is a simplistic script replacing one option of
-.B ifconfig
-, namely, IP address management. It not only adds
-addresses, but also carries out Duplicate Address Detection RFC-DHCP,
-sends unsolicited ARP to update the caches of other hosts sharing
-the interface, adds some control routes and restarts Router Discovery
-when it is necessary.
-
-.SH IFCONFIG - COMMAND SYNTAX
-
-.SS
-.TP
-.B DEVICE
-- it may have alias, suffix, separated by colon.
-
-.TP
-.B command
-- add, delete or stop.
-
-.TP
-.B ADDRESS
-- optionally followed by prefix length.
-
-.TP
-.B peer
-- optional peer address for pointpoint interfaces.
-
-.SH NOTES
-This script is not suitable for use with IPv6.
-
-.SH SEE ALSO
-.RB "IP Command reference " ip-cref.ps
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index 6714ef6..41efc6d 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -1492,6 +1492,8 @@
 ] [
 .BI mcast_snooping " MULTICAST_SNOOPING "
 ] [
+.BI mcast_vlan_snooping " MULTICAST_VLAN_SNOOPING "
+] [
 .BI mcast_router " MULTICAST_ROUTER "
 ] [
 .BI mcast_query_use_ifaddr " MCAST_QUERY_USE_IFADDR "
@@ -1614,6 +1616,12 @@
 or off
 .RI ( MULTICAST_SNOOPING " == 0). "
 
+.BI mcast_vlan_snooping " MULTICAST_VLAN_SNOOPING "
+- turn multicast VLAN snooping on
+.RI ( MULTICAST_VLAN_SNOOPING " > 0) "
+or off
+.RI ( MULTICAST_VLAN_SNOOPING " == 0). "
+
 .BI mcast_router " MULTICAST_ROUTER "
 - set bridge's multicast router if IGMP snooping is enabled.
 .I MULTICAST_ROUTER
diff --git a/man/man8/routef.8 b/man/man8/routef.8
deleted file mode 100644
index c2a7059..0000000
--- a/man/man8/routef.8
+++ /dev/null
@@ -1 +0,0 @@
-.so man8/routel.8
diff --git a/man/man8/routel.8 b/man/man8/routel.8
index 2270eac..b1668e7 100644
--- a/man/man8/routel.8
+++ b/man/man8/routel.8
@@ -1,25 +1,31 @@
-.TH "ROUTEL" "8" "3 Jan, 2008" "iproute2" "Linux"
+.TH ROUTEL 8 "1 Sept, 2021" "iproute2" "Linux"
 .SH "NAME"
-.LP
 routel \- list routes with pretty output format
-.br
-routef \- flush routes
-.SH "SYNTAX"
-.LP
-routel [\fItablenr\fP [\fIraw ip args...\fP]]
-.br
-routef
+.SH SYNOPSIS
+.B routel
+.RI "[ " OPTIONS " ]"
+.RI "[ " tablenr
+[ \fIip route options...\fR ] ]
+.P
+.ti 8
+.IR OPTIONS " := {"
+\fB-h\fR | \fB--help\fR |
+[{\fB-f\fR | \fB--family\fR }
+{\fBinet\fR | \fBinet6\fR } |
+\fB-4\fR | \fB-6\fR }
+
 .SH "DESCRIPTION"
 .LP
-These programs are a set of helper scripts you can use instead of raw iproute2 commands.
-.br
-The routel script will list routes in a format that some might consider easier to interpret then the ip route list equivalent.
-.br
-The routef script does not take any arguments and will simply flush the routing table down the drain. Beware! This means deleting all routes which will make your network unusable!
+The routel script will list routes in a format that some might consider
+easier to interpret then the
+.B ip
+route list equivalent.
 
 .SH "AUTHORS"
 .LP
-The routel script was written by Stephen R. van den Berg <srb@cuci.nl>, 1999/04/18 and donated to the public domain.
+Rewritten by Stephen Hemminger <stephen@networkplumber.org>.
+.br
+Original script by Stephen R. van den Berg <srb@cuci.nl>.
 .br
 This manual page was written by Andreas Henriksson  <andreas@fatal.se>, for the Debian GNU/Linux system.
 .SH "SEE ALSO"
diff --git a/man/man8/rtpr.8 b/man/man8/rtpr.8
deleted file mode 100644
index 87f291a..0000000
--- a/man/man8/rtpr.8
+++ /dev/null
@@ -1,25 +0,0 @@
-.TH RTPR 8 "18 September, 2015"
-
-.SH NAME
-rtpr \- replace backslashes with newlines.
-
-.SH DESCRIPTION
-.B rtpr
-is a trivial shell script which converts backslashes in standard input to newlines. It's sole purpose is to be fed with input from
-.B ip
-when executed with it's
-.B --oneline
-flag.
-
-.SH EXAMPLES
-.TP
-ip --oneline address show | rtpr
-Undo oneline converted
-.B ip-address
-output.
-
-.SH SEE ALSO
-.BR ip (8)
-
-.SH AUTHORS
-Stephen Hemminger <shemming@brocade.com>