Merge remote-tracking branch 'main/main' into next

Signed-off-by: David Ahern <dsahern@kernel.org>
diff --git a/MAINTAINERS b/MAINTAINERS
index c9ea3ea..82043c1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -31,6 +31,8 @@
 L: bridge@lists.linux-foundation.org (moderated for non-subscribers)
 F: bridge/*
 F: ip/iplink_bridge*
+F: lib/bridge*
+F: include/bridge*
 
 Data Center Bridging - dcb
 M: Petr Machata <me@pmachata.org>
diff --git a/bridge/mdb.c b/bridge/mdb.c
index 196363a..7249097 100644
--- a/bridge/mdb.c
+++ b/bridge/mdb.c
@@ -256,6 +256,8 @@
 		print_string(PRINT_ANY, NULL, " %s", "added_by_star_ex");
 	if (e->flags & MDB_FLAGS_BLOCKED)
 		print_string(PRINT_ANY, NULL, " %s", "blocked");
+	if (e->flags & MDB_FLAGS_OFFLOAD_FAILED)
+		print_string(PRINT_ANY, NULL, " %s", "offload_failed");
 	close_json_array(PRINT_JSON, NULL);
 
 	if (e->vid)
diff --git a/bridge/vlan.c b/bridge/vlan.c
index ea4aff9..3c24020 100644
--- a/bridge/vlan.c
+++ b/bridge/vlan.c
@@ -15,6 +15,7 @@
 #include "json_print.h"
 #include "libnetlink.h"
 #include "br_common.h"
+#include "bridge.h"
 #include "utils.h"
 
 static unsigned int filter_index, filter_vlan;
@@ -705,47 +706,6 @@
 	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, "%-" textify(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, "%-" textify(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];
@@ -783,7 +743,7 @@
 			print_string(PRINT_FP, NULL,
 				     "%-" textify(IFNAMSIZ) "s  ", "");
 		}
-		print_one_vlan_stats(vstats);
+		bridge_print_vlan_stats(vstats);
 	}
 
 	/* vlan_port is opened only if there are any vlan stats */
@@ -892,6 +852,11 @@
 		print_uint(PRINT_ANY, "mcast_querier", "mcast_querier %u ",
 			   rta_getattr_u8(vattr));
 	}
+	if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE]) {
+		struct rtattr *attr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_STATE];
+
+		bridge_print_mcast_querier_state(attr);
+	}
 	if (vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION]) {
 		vattr = vtb[BRIDGE_VLANDB_GOPTS_MCAST_IGMP_VERSION];
 		print_uint(PRINT_ANY, "mcast_igmp_version",
@@ -1025,7 +990,7 @@
 		print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s  ", "");
 	}
 	print_range("vlan", vinfo->vid, vrange);
-	print_vlan_flags(vinfo->flags);
+	bridge_print_vlan_flags(vinfo->flags);
 	print_nl();
 	print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s    ", "");
 	print_stp_state(state);
@@ -1051,7 +1016,7 @@
 	}
 	print_nl();
 	if (show_stats)
-		__print_one_vlan_stats(&vstats);
+		bridge_print_vlan_stats_only(&vstats);
 	close_json_object();
 }
 
@@ -1334,7 +1299,7 @@
 		open_json_object(NULL);
 		print_range("vlan", last_vid_start, vinfo->vid);
 
-		print_vlan_flags(vinfo->flags);
+		bridge_print_vlan_flags(vinfo->flags);
 		close_json_object();
 		print_nl();
 	}
diff --git a/devlink/devlink.c b/devlink/devlink.c
index d14f3f4..171b853 100644
--- a/devlink/devlink.c
+++ b/devlink/devlink.c
@@ -310,6 +310,7 @@
 #define DL_OPT_PORT_FN_RATE_TX_WEIGHT	BIT(56)
 #define DL_OPT_PORT_FN_CAPS	BIT(57)
 #define DL_OPT_PORT_FN_MAX_IO_EQS	BIT(58)
+#define DL_OPT_PORT_FN_RATE_TC_BWS	BIT(59)
 
 struct dl_opts {
 	uint64_t present; /* flags of present items */
@@ -372,6 +373,7 @@
 	uint32_t rate_tx_weight;
 	char *rate_node_name;
 	const char *rate_parent_node;
+	uint32_t rate_tc_bw[DEVLINK_RATE_TCS_MAX];
 	uint32_t linecard_index;
 	const char *linecard_type;
 	bool selftests_opt[DEVLINK_ATTR_SELFTEST_ID_MAX + 1];
@@ -1699,6 +1701,84 @@
 	return err;
 }
 
+static int
+parse_tc_bw_arg(const char *tc_bw_str, int *tc_index, uint32_t *tc_bw)
+{
+	char *index, *value, *endptr;
+	char *input = NULL;
+	int err;
+
+	input = strdup(tc_bw_str);
+	if (!input)
+		return -ENOMEM;
+
+	err = str_split_by_char(input, &index, &value, ':');
+	if (err) {
+		pr_err("Invalid format in token: %s\n", input);
+		goto out;
+	}
+
+	*tc_index = strtoul(index, &endptr, 10);
+	if (endptr && *endptr) {
+		pr_err("Invalid traffic class index: %s\n", index);
+		err = -EINVAL;
+		goto out;
+	}
+
+	*tc_bw = strtoul(value, &endptr, 10);
+	if (endptr && *endptr) {
+		pr_err("Invalid bandwidth value: %s\n", value);
+		err = -EINVAL;
+		goto out;
+	}
+
+out:
+	free(input);
+	return err;
+}
+
+static int parse_tc_bw_args(struct dl *dl, uint32_t *tc_bw)
+{
+	bool parsed_indices[DEVLINK_RATE_TCS_MAX] = {};
+	const char *tc_bw_str;
+	int index, err, i;
+	uint32_t bw;
+
+	memset(tc_bw, 0, sizeof(uint32_t) * DEVLINK_RATE_TCS_MAX);
+
+	for (i = 0; i < DEVLINK_RATE_TCS_MAX; i++) {
+		err = dl_argv_str(dl, &tc_bw_str);
+		if (err) {
+			fprintf(stderr,
+				"Error parsing tc-bw: example usage: tc-bw 0:60 1:10 2:0 3:0 4:30 5:0 6:0 7:0\n");
+			return err;
+		}
+
+		err = parse_tc_bw_arg(tc_bw_str, &index, &bw);
+		if (err)
+			return err;
+
+		if (index < 0 || index >= DEVLINK_RATE_TCS_MAX) {
+			fprintf(stderr,
+				"Error parsing tc-bw: invalid index: %d, use values between 0 and %d\n",
+				index, DEVLINK_RATE_TC_INDEX_MAX);
+			return -EINVAL;
+		}
+
+		if (parsed_indices[index]) {
+			fprintf(stderr,
+				"Error parsing tc-bw: duplicate index : %d\n",
+				index);
+			return -EINVAL;
+		}
+
+		tc_bw[index] = bw;
+		parsed_indices[index] = true;
+	}
+
+	return 0;
+}
+
 static int dl_argv_parse(struct dl *dl, uint64_t o_required,
 			 uint64_t o_optional)
 {
@@ -2237,6 +2317,13 @@
 			dl_arg_inc(dl);
 			opts->rate_parent_node = "";
 			o_found |= DL_OPT_PORT_FN_RATE_PARENT;
+		} else if (dl_argv_match(dl, "tc-bw") &&
+			   (o_all & DL_OPT_PORT_FN_RATE_TC_BWS)) {
+			dl_arg_inc(dl);
+			err = parse_tc_bw_args(dl, opts->rate_tc_bw);
+			if (err)
+				return err;
+			o_found |= DL_OPT_PORT_FN_RATE_TC_BWS;
 		} else if (dl_argv_match(dl, "lc") &&
 			   (o_all & DL_OPT_LINECARD)) {
 			dl_arg_inc(dl);
@@ -2678,6 +2765,20 @@
 	if (opts->present & DL_OPT_PORT_FN_RATE_PARENT)
 		mnl_attr_put_strz(nlh, DEVLINK_ATTR_RATE_PARENT_NODE_NAME,
 				  opts->rate_parent_node);
+	if (opts->present & DL_OPT_PORT_FN_RATE_TC_BWS) {
+		struct nlattr *nla_tc_bw_entry;
+		int i;
+
+		for (i = 0; i < DEVLINK_RATE_TCS_MAX; i++) {
+			nla_tc_bw_entry =
+				mnl_attr_nest_start(nlh,
+						    DEVLINK_ATTR_RATE_TC_BWS);
+			mnl_attr_put_u8(nlh, DEVLINK_RATE_TC_ATTR_INDEX, i);
+			mnl_attr_put_u32(nlh, DEVLINK_RATE_TC_ATTR_BW,
+					 opts->rate_tc_bw[i]);
+			mnl_attr_nest_end(nlh, nla_tc_bw_entry);
+		}
+	}
 	if (opts->present & DL_OPT_LINECARD)
 		mnl_attr_put_u32(nlh, DEVLINK_ATTR_LINECARD_INDEX,
 				 opts->linecard_index);
@@ -5366,7 +5467,78 @@
 	}
 }
 
-static void pr_out_port_fn_rate(struct dl *dl, struct nlattr **tb)
+static const enum mnl_attr_data_type
+rate_tc_bws_policy[DEVLINK_RATE_TC_ATTR_BW + 1] = {
+	[DEVLINK_RATE_TC_ATTR_INDEX] = MNL_TYPE_U8,
+	[DEVLINK_RATE_TC_ATTR_BW] = MNL_TYPE_U32,
+};
+
+static int rate_tc_bw_attr_cb(const struct nlattr *attr, void *data)
+{
+	const struct nlattr **tb = data;
+	int type;
+
+	if (mnl_attr_type_valid(attr, DEVLINK_RATE_TC_ATTR_MAX) < 0)
+		return MNL_CB_OK;
+
+	type = mnl_attr_get_type(attr);
+
+	if (mnl_attr_validate(attr, rate_tc_bws_policy[type]) < 0)
+		return MNL_CB_ERROR;
+
+	tb[type] = attr;
+	return MNL_CB_OK;
+}
+
+static int
+parse_rate_tc_bw(struct nlattr *nla_tc_bw, uint8_t *tc_index, uint32_t *tc_bw)
+{
+	struct nlattr *tb_tc_bw[DEVLINK_RATE_TC_ATTR_MAX + 1] = {};
+
+	if (mnl_attr_parse_nested(nla_tc_bw, rate_tc_bw_attr_cb, tb_tc_bw) != MNL_CB_OK)
+		return MNL_CB_ERROR;
+
+	if (!tb_tc_bw[DEVLINK_RATE_TC_ATTR_INDEX] ||
+	    !tb_tc_bw[DEVLINK_RATE_TC_ATTR_BW])
+		return MNL_CB_ERROR;
+
+	*tc_index = mnl_attr_get_u8(tb_tc_bw[DEVLINK_RATE_TC_ATTR_INDEX]);
+	*tc_bw = mnl_attr_get_u32(tb_tc_bw[DEVLINK_RATE_TC_ATTR_BW]);
+
+	return MNL_CB_OK;
+}
+
+static void pr_out_port_fn_rate_tc_bw(struct dl *dl, const struct nlmsghdr *nlh)
+{
+	struct nlattr *nla_tc_bw;
+
+	mnl_attr_for_each(nla_tc_bw, nlh, sizeof(struct genlmsghdr)) {
+		uint8_t tc_index;
+		uint32_t tc_bw;
+
+		if (mnl_attr_get_type(nla_tc_bw) != DEVLINK_ATTR_RATE_TC_BWS)
+			continue;
+
+		if (parse_rate_tc_bw(nla_tc_bw, &tc_index, &tc_bw) != MNL_CB_OK)
+			continue;
+
+		if (tc_bw) {
+			char buf[32];
+
+			if (dl->json_output) {
+				snprintf(buf, sizeof(buf), "tc_%u", tc_index);
+				print_uint(PRINT_JSON, buf, "%u", tc_bw);
+			} else {
+				snprintf(buf, sizeof(buf), " tc_%u bw %u",
+					 tc_index, tc_bw);
+				print_string(PRINT_ANY, NULL, "%s", buf);
+			}
+		}
+	}
+}
+
+static void pr_out_port_fn_rate(struct dl *dl, const struct nlmsghdr *nlh,
+				struct nlattr **tb)
 {
 
 	if (!tb[DEVLINK_ATTR_RATE_NODE_NAME])
@@ -5412,6 +5584,7 @@
 			print_uint(PRINT_ANY, "tx_weight",
 				   " tx_weight %u", weight);
 	}
+
 	if (tb[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]) {
 		const char *parent =
 			mnl_attr_get_str(tb[DEVLINK_ATTR_RATE_PARENT_NODE_NAME]);
@@ -5419,6 +5592,9 @@
 		print_string(PRINT_ANY, "parent", " parent %s", parent);
 	}
 
+	if (tb[DEVLINK_ATTR_RATE_TC_BWS])
+		pr_out_port_fn_rate_tc_bw(dl, nlh);
+
 	pr_out_port_handle_end(dl);
 }
 
@@ -5434,7 +5610,7 @@
 	    !tb[DEVLINK_ATTR_RATE_NODE_NAME]) {
 		return MNL_CB_ERROR;
 	}
-	pr_out_port_fn_rate(dl, tb);
+	pr_out_port_fn_rate(dl, nlh, tb);
 	return MNL_CB_OK;
 }
 
@@ -5443,12 +5619,13 @@
 	pr_err("Usage: devlink port function rate help\n");
 	pr_err("       devlink port function rate show [ DEV/{ PORT_INDEX | NODE_NAME } ]\n");
 	pr_err("       devlink port function rate add DEV/NODE_NAME\n");
-	pr_err("               [ tx_share VAL ][ tx_max VAL ][ tx_priority N ][ tx_weight N ][ { parent NODE_NAME | noparent } ]\n");
+	pr_err("               [ tx_share VAL ][ tx_max VAL ][ tx_priority N ][ tx_weight N ][ tc-bw INDEX:N ... INDEX:N ][ { parent NODE_NAME | noparent } ]\n");
 	pr_err("       devlink port function rate del DEV/NODE_NAME\n");
 	pr_err("       devlink port function rate set DEV/{ PORT_INDEX | NODE_NAME }\n");
-	pr_err("               [ tx_share VAL ][ tx_max VAL ][ tx_priority N ][ tx_weight N ][ { parent NODE_NAME | noparent } ]\n\n");
+	pr_err("               [ tx_share VAL ][ tx_max VAL ][ tx_priority N ][ tx_weight N ][ tc-bw INDEX:N ... INDEX:N ][ { parent NODE_NAME | noparent } ]\n\n");
 	pr_err("       VAL - float or integer value in units of bits or bytes per second (bit|bps)\n");
 	pr_err("       N - integer representing priority/weight of the node among siblings\n");
+	pr_err("       INDEX - integer representing traffic class index in the tc-bw option, ranging from 0 to 7\n");
 	pr_err("       and SI (k-, m-, g-, t-) or IEC (ki-, mi-, gi-, ti-) case-insensitive prefix.\n");
 	pr_err("       Bare number, means bits per second, is possible.\n\n");
 	pr_err("       For details refer to devlink-rate(8) man page.\n");
@@ -5503,7 +5680,8 @@
 			    DL_OPT_PORT_FN_RATE_TX_SHARE | DL_OPT_PORT_FN_RATE_TX_MAX |
 			    DL_OPT_PORT_FN_RATE_TX_PRIORITY |
 			    DL_OPT_PORT_FN_RATE_TX_WEIGHT |
-			    DL_OPT_PORT_FN_RATE_PARENT);
+			    DL_OPT_PORT_FN_RATE_PARENT |
+			    DL_OPT_PORT_FN_RATE_TC_BWS);
 	if (err)
 		return err;
 
@@ -5538,6 +5716,25 @@
 	return mnlu_gen_socket_sndrcv(&dl->nlg, nlh, NULL, NULL);
 }
 
+static void parse_tc_bw_entries(const struct nlmsghdr *nlh,
+				struct dl_opts *opts)
+{
+	struct nlattr *nla_tc_bw;
+
+	mnl_attr_for_each(nla_tc_bw, nlh, sizeof(struct genlmsghdr)) {
+		uint8_t tc_index;
+		uint32_t tc_bw;
+
+		if (mnl_attr_get_type(nla_tc_bw) != DEVLINK_ATTR_RATE_TC_BWS)
+			continue;
+
+		if (parse_rate_tc_bw(nla_tc_bw, &tc_index, &tc_bw) != MNL_CB_OK)
+			continue;
+
+		opts->rate_tc_bw[tc_index] = tc_bw;
+	}
+}
+
 static int port_fn_get_rates_cb(const struct nlmsghdr *nlh, void *data)
 {
 	struct dl_opts *opts = data;
@@ -5563,6 +5760,10 @@
 	if (tb[DEVLINK_ATTR_RATE_TX_WEIGHT])
 		opts->rate_tx_weight =
 			mnl_attr_get_u32(tb[DEVLINK_ATTR_RATE_TX_WEIGHT]);
+
+	if (tb[DEVLINK_ATTR_RATE_TC_BWS])
+		parse_tc_bw_entries(nlh, opts);
+
 	return MNL_CB_OK;
 }
 
@@ -5578,7 +5779,8 @@
 				DL_OPT_PORT_FN_RATE_TX_MAX |
 				DL_OPT_PORT_FN_RATE_TX_PRIORITY |
 				DL_OPT_PORT_FN_RATE_TX_WEIGHT |
-				DL_OPT_PORT_FN_RATE_PARENT);
+				DL_OPT_PORT_FN_RATE_PARENT |
+				DL_OPT_PORT_FN_RATE_TC_BWS);
 	if (err)
 		return err;
 
diff --git a/include/bridge.h b/include/bridge.h
new file mode 100644
index 0000000..8b0942b
--- /dev/null
+++ b/include/bridge.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __BRIDGE_H__
+#define __BRIDGE_H__ 1
+
+#include <linux/if_bridge.h>
+#include <linux/rtnetlink.h>
+
+void bridge_print_vlan_flags(__u16 flags);
+void bridge_print_vlan_stats_only(const struct bridge_vlan_xstats *vstats);
+void bridge_print_vlan_stats(const struct bridge_vlan_xstats *vstats);
+
+void bridge_print_mcast_querier_state(const struct rtattr *vtb);
+
+#endif /* __BRIDGE_H__ */
diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h
index 9a1bdc9..a89df2a 100644
--- a/include/uapi/linux/devlink.h
+++ b/include/uapi/linux/devlink.h
@@ -221,6 +221,11 @@
 				      */
 };
 
+/* IEEE 802.1Qaz standard supported values. */
+
+#define DEVLINK_RATE_TCS_MAX 8
+#define DEVLINK_RATE_TC_INDEX_MAX (DEVLINK_RATE_TCS_MAX - 1)
+
 enum devlink_rate_type {
 	DEVLINK_RATE_TYPE_LEAF,
 	DEVLINK_RATE_TYPE_NODE,
@@ -629,6 +634,8 @@
 
 	DEVLINK_ATTR_REGION_DIRECT,		/* flag */
 
+	DEVLINK_ATTR_RATE_TC_BWS,		/* nested */
+
 	/* Add new attributes above here, update the spec in
 	 * Documentation/netlink/specs/devlink.yaml and re-generate
 	 * net/devlink/netlink_gen.c.
@@ -638,6 +645,15 @@
 	DEVLINK_ATTR_MAX = __DEVLINK_ATTR_MAX - 1
 };
 
+enum devlink_rate_tc_attr {
+	DEVLINK_RATE_TC_ATTR_UNSPEC,
+	DEVLINK_RATE_TC_ATTR_INDEX,		/* u8 */
+	DEVLINK_RATE_TC_ATTR_BW,		/* u32 */
+
+	__DEVLINK_RATE_TC_ATTR_MAX,
+	DEVLINK_RATE_TC_ATTR_MAX = __DEVLINK_RATE_TC_ATTR_MAX - 1
+};
+
 /* Mapping between internal resource described by the field and system
  * structure
  */
diff --git a/include/uapi/linux/if_link.h b/include/uapi/linux/if_link.h
index bb94d88..b450757 100644
--- a/include/uapi/linux/if_link.h
+++ b/include/uapi/linux/if_link.h
@@ -1396,6 +1396,7 @@
 	IFLA_VXLAN_LOCALBYPASS,
 	IFLA_VXLAN_LABEL_POLICY, /* IPv6 flow label policy; ifla_vxlan_label_policy */
 	IFLA_VXLAN_RESERVED_BITS,
+	IFLA_VXLAN_MC_ROUTE,
 	__IFLA_VXLAN_MAX
 };
 #define IFLA_VXLAN_MAX	(__IFLA_VXLAN_MAX - 1)
@@ -1532,6 +1533,7 @@
 	IFLA_BOND_MISSED_MAX,
 	IFLA_BOND_NS_IP6_TARGET,
 	IFLA_BOND_COUPLED_CONTROL,
+	IFLA_BOND_BROADCAST_NEIGH,
 	__IFLA_BOND_MAX,
 };
 
diff --git a/include/uapi/linux/if_tun.h b/include/uapi/linux/if_tun.h
index 8a6d5d4..fa0d4dc 100644
--- a/include/uapi/linux/if_tun.h
+++ b/include/uapi/linux/if_tun.h
@@ -93,6 +93,15 @@
 #define TUN_F_USO4	0x20	/* I can handle USO for IPv4 packets */
 #define TUN_F_USO6	0x40	/* I can handle USO for IPv6 packets */
 
+/* I can handle TSO/USO for UDP tunneled packets */
+#define TUN_F_UDP_TUNNEL_GSO		0x080
+
+/*
+ * I can handle TSO/USO for UDP tunneled packets requiring csum offload for
+ * the outer header
+ */
+#define TUN_F_UDP_TUNNEL_GSO_CSUM	0x100
+
 /* Protocol info prepended to the packets (when IFF_NO_PI is not set) */
 #define TUN_PKT_STRIP	0x0001
 struct tun_pi {
diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h
index 776fb41..6713015 100644
--- a/include/uapi/linux/in6.h
+++ b/include/uapi/linux/in6.h
@@ -152,7 +152,6 @@
 /*
  *	IPV6 socket options
  */
-#if __UAPI_DEF_IPV6_OPTIONS
 #define IPV6_ADDRFORM		1
 #define IPV6_2292PKTINFO	2
 #define IPV6_2292HOPOPTS	3
@@ -169,8 +168,10 @@
 #define IPV6_MULTICAST_IF	17
 #define IPV6_MULTICAST_HOPS	18
 #define IPV6_MULTICAST_LOOP	19
+#if __UAPI_DEF_IPV6_OPTIONS
 #define IPV6_ADD_MEMBERSHIP	20
 #define IPV6_DROP_MEMBERSHIP	21
+#endif
 #define IPV6_ROUTER_ALERT	22
 #define IPV6_MTU_DISCOVER	23
 #define IPV6_MTU		24
@@ -203,7 +204,6 @@
 #define IPV6_IPSEC_POLICY	34
 #define IPV6_XFRM_POLICY	35
 #define IPV6_HDRINCL		36
-#endif
 
 /*
  * Multicast:
diff --git a/include/uapi/linux/neighbour.h b/include/uapi/linux/neighbour.h
index 5e67a7e..1401f57 100644
--- a/include/uapi/linux/neighbour.h
+++ b/include/uapi/linux/neighbour.h
@@ -54,6 +54,7 @@
 /* Extended flags under NDA_FLAGS_EXT: */
 #define NTF_EXT_MANAGED		(1 << 0)
 #define NTF_EXT_LOCKED		(1 << 1)
+#define NTF_EXT_EXT_VALIDATED	(1 << 2)
 
 /*
  *	Neighbor Cache Entry States.
@@ -92,6 +93,10 @@
  * bridge in response to a host trying to communicate via a locked bridge port
  * with MAB enabled. Their purpose is to notify user space that a host requires
  * authentication.
+ *
+ * NTF_EXT_EXT_VALIDATED flagged neighbor entries were externally validated by
+ * a user space control plane. The kernel will not remove or invalidate them,
+ * but it can probe them and notify user space when they become reachable.
  */
 
 struct nda_cacheinfo {
diff --git a/include/uapi/linux/netconf.h b/include/uapi/linux/netconf.h
index 229e885..546014b 100644
--- a/include/uapi/linux/netconf.h
+++ b/include/uapi/linux/netconf.h
@@ -19,6 +19,7 @@
 	NETCONFA_IGNORE_ROUTES_WITH_LINKDOWN,
 	NETCONFA_INPUT,
 	NETCONFA_BC_FORWARDING,
+	NETCONFA_FORCE_FORWARDING,
 	__NETCONFA_MAX
 };
 #define NETCONFA_MAX	(__NETCONFA_MAX - 1)
diff --git a/include/uapi/linux/pkt_sched.h b/include/uapi/linux/pkt_sched.h
index 958d940..15d1a37 100644
--- a/include/uapi/linux/pkt_sched.h
+++ b/include/uapi/linux/pkt_sched.h
@@ -1211,4 +1211,72 @@
 
 #define TCA_ETS_MAX (__TCA_ETS_MAX - 1)
 
+/* DUALPI2 */
+enum tc_dualpi2_drop_overload {
+	TC_DUALPI2_DROP_OVERLOAD_OVERFLOW = 0,
+	TC_DUALPI2_DROP_OVERLOAD_DROP = 1,
+	__TCA_DUALPI2_DROP_OVERLOAD_MAX,
+};
+#define TCA_DUALPI2_DROP_OVERLOAD_MAX (__TCA_DUALPI2_DROP_OVERLOAD_MAX - 1)
+
+enum tc_dualpi2_drop_early {
+	TC_DUALPI2_DROP_EARLY_DROP_DEQUEUE = 0,
+	TC_DUALPI2_DROP_EARLY_DROP_ENQUEUE = 1,
+	__TCA_DUALPI2_DROP_EARLY_MAX,
+};
+#define TCA_DUALPI2_DROP_EARLY_MAX (__TCA_DUALPI2_DROP_EARLY_MAX - 1)
+
+enum tc_dualpi2_ecn_mask {
+	TC_DUALPI2_ECN_MASK_L4S_ECT = 1,
+	TC_DUALPI2_ECN_MASK_CLA_ECT = 2,
+	TC_DUALPI2_ECN_MASK_ANY_ECT = 3,
+	__TCA_DUALPI2_ECN_MASK_MAX,
+};
+#define TCA_DUALPI2_ECN_MASK_MAX (__TCA_DUALPI2_ECN_MASK_MAX - 1)
+
+enum tc_dualpi2_split_gso {
+	TC_DUALPI2_SPLIT_GSO_NO_SPLIT_GSO = 0,
+	TC_DUALPI2_SPLIT_GSO_SPLIT_GSO = 1,
+	__TCA_DUALPI2_SPLIT_GSO_MAX,
+};
+#define TCA_DUALPI2_SPLIT_GSO_MAX (__TCA_DUALPI2_SPLIT_GSO_MAX - 1)
+
+enum {
+	TCA_DUALPI2_UNSPEC,
+	TCA_DUALPI2_LIMIT,		/* Packets */
+	TCA_DUALPI2_MEMORY_LIMIT,	/* Bytes */
+	TCA_DUALPI2_TARGET,		/* us */
+	TCA_DUALPI2_TUPDATE,		/* us */
+	TCA_DUALPI2_ALPHA,		/* Hz scaled up by 256 */
+	TCA_DUALPI2_BETA,		/* Hz scaled up by 256 */
+	TCA_DUALPI2_STEP_THRESH_PKTS,	/* Step threshold in packets */
+	TCA_DUALPI2_STEP_THRESH_US,	/* Step threshold in microseconds */
+	TCA_DUALPI2_MIN_QLEN_STEP,	/* Minimum qlen to apply STEP_THRESH */
+	TCA_DUALPI2_COUPLING,		/* Coupling factor between queues */
+	TCA_DUALPI2_DROP_OVERLOAD,	/* Whether to drop on overload */
+	TCA_DUALPI2_DROP_EARLY,		/* Whether to drop on enqueue */
+	TCA_DUALPI2_C_PROTECTION,	/* Percentage */
+	TCA_DUALPI2_ECN_MASK,		/* L4S queue classification mask */
+	TCA_DUALPI2_SPLIT_GSO,		/* Split GSO packets at enqueue */
+	TCA_DUALPI2_PAD,
+	__TCA_DUALPI2_MAX
+};
+
+#define TCA_DUALPI2_MAX   (__TCA_DUALPI2_MAX - 1)
+
+struct tc_dualpi2_xstats {
+	__u32 prob;		/* current probability */
+	__u32 delay_c;		/* current delay in C queue */
+	__u32 delay_l;		/* current delay in L queue */
+	__u32 packets_in_c;	/* number of packets enqueued in C queue */
+	__u32 packets_in_l;	/* number of packets enqueued in L queue */
+	__u32 maxq;		/* maximum queue size */
+	__u32 ecn_mark;		/* packets marked with ecn*/
+	__u32 step_marks;	/* ECN marks due to the step AQM */
+	__s32 credit;		/* current c_protection credit */
+	__u32 memory_used;	/* Memory used by both queues */
+	__u32 max_memory_used;	/* Maximum used memory */
+	__u32 memory_limit;	/* Memory limit of both queues */
+};
+
 #endif
diff --git a/include/uapi/linux/snmp.h b/include/uapi/linux/snmp.h
index 1d234d7..49f5640 100644
--- a/include/uapi/linux/snmp.h
+++ b/include/uapi/linux/snmp.h
@@ -186,6 +186,7 @@
 	LINUX_MIB_TIMEWAITKILLED,		/* TimeWaitKilled */
 	LINUX_MIB_PAWSACTIVEREJECTED,		/* PAWSActiveRejected */
 	LINUX_MIB_PAWSESTABREJECTED,		/* PAWSEstabRejected */
+	LINUX_MIB_BEYOND_WINDOW,		/* BeyondWindow */
 	LINUX_MIB_TSECRREJECTED,		/* TSEcrRejected */
 	LINUX_MIB_PAWS_OLD_ACK,			/* PAWSOldAck */
 	LINUX_MIB_PAWS_TW_REJECTED,		/* PAWSTimewait */
diff --git a/include/uapi/linux/virtio_net.h b/include/uapi/linux/virtio_net.h
index c0a434a..05748dd 100644
--- a/include/uapi/linux/virtio_net.h
+++ b/include/uapi/linux/virtio_net.h
@@ -70,6 +70,28 @@
 					 * with the same MAC.
 					 */
 #define VIRTIO_NET_F_SPEED_DUPLEX 63	/* Device set linkspeed and duplex */
+#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO 65 /* Driver can receive
+					      * GSO-over-UDP-tunnel packets
+					      */
+#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM 66 /* Driver handles
+						   * GSO-over-UDP-tunnel
+						   * packets with partial csum
+						   * for the outer header
+						   */
+#define VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO 67 /* Device can receive
+					     * GSO-over-UDP-tunnel packets
+					     */
+#define VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO_CSUM 68 /* Device handles
+						  * GSO-over-UDP-tunnel
+						  * packets with partial csum
+						  * for the outer header
+						  */
+
+/* Offloads bits corresponding to VIRTIO_NET_F_HOST_UDP_TUNNEL_GSO{,_CSUM}
+ * features
+ */
+#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_MAPPED	46
+#define VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO_CSUM_MAPPED	47
 
 #ifndef VIRTIO_NET_NO_LEGACY
 #define VIRTIO_NET_F_GSO	6	/* Host handles pkts w/ any GSO type */
@@ -131,12 +153,17 @@
 #define VIRTIO_NET_HDR_F_NEEDS_CSUM	1	/* Use csum_start, csum_offset */
 #define VIRTIO_NET_HDR_F_DATA_VALID	2	/* Csum is valid */
 #define VIRTIO_NET_HDR_F_RSC_INFO	4	/* rsc info in csum_ fields */
+#define VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM 8	/* UDP tunnel csum offload */
 	__u8 flags;
 #define VIRTIO_NET_HDR_GSO_NONE		0	/* Not a GSO frame */
 #define VIRTIO_NET_HDR_GSO_TCPV4	1	/* GSO frame, IPv4 TCP (TSO) */
 #define VIRTIO_NET_HDR_GSO_UDP		3	/* GSO frame, IPv4 UDP (UFO) */
 #define VIRTIO_NET_HDR_GSO_TCPV6	4	/* GSO frame, IPv6 TCP */
 #define VIRTIO_NET_HDR_GSO_UDP_L4	5	/* GSO frame, IPv4& IPv6 UDP (USO) */
+#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 0x20 /* UDPv4 tunnel present */
+#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6 0x40 /* UDPv6 tunnel present */
+#define VIRTIO_NET_HDR_GSO_UDP_TUNNEL (VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 | \
+				       VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV6)
 #define VIRTIO_NET_HDR_GSO_ECN		0x80	/* TCP has ECN set */
 	__u8 gso_type;
 	__virtio16 hdr_len;	/* Ethernet + IP + tcp/udp hdrs */
@@ -181,6 +208,12 @@
 	__le16 padding;
 };
 
+struct virtio_net_hdr_v1_hash_tunnel {
+	struct virtio_net_hdr_v1_hash hash_hdr;
+	__le16 outer_th_offset;
+	__le16 inner_nh_offset;
+};
+
 #ifndef VIRTIO_NET_NO_LEGACY
 /* This header comes first in the scatter-gather list.
  * For legacy virtio, if VIRTIO_F_ANY_LAYOUT is not negotiated, it must
diff --git a/ip/ip_common.h b/ip/ip_common.h
index 37de09d..3f55ea3 100644
--- a/ip/ip_common.h
+++ b/ip/ip_common.h
@@ -195,7 +195,6 @@
 	const struct ipstats_stat_desc desc;
 	int xstats_at;
 	int link_type_at;
-	int inner_max;
 	int inner_at;
 	void (*show_cb)(const struct rtattr *at);
 };
diff --git a/ip/ipaddress.c b/ip/ipaddress.c
index 70b3d51..bbe48a4 100644
--- a/ip/ipaddress.c
+++ b/ip/ipaddress.c
@@ -959,12 +959,25 @@
 	}
 }
 
+static int get_rtattr(struct nlmsghdr *n, struct rtattr **tb)
+{
+	int len = n->nlmsg_len;
+	struct ifinfomsg *ifi = NLMSG_DATA(n);
+
+	len -= NLMSG_LENGTH(sizeof(*ifi));
+	if (len < 0)
+		return -1;
+
+	parse_rtattr_flags(tb, IFLA_MAX, IFLA_RTA(ifi), len, NLA_F_NESTED);
+
+	return 0;
+}
+
 int print_linkinfo(struct nlmsghdr *n, void *arg)
 {
 	FILE *fp = (FILE *)arg;
 	struct ifinfomsg *ifi = NLMSG_DATA(n);
 	struct rtattr *tb[IFLA_MAX+1];
-	int len = n->nlmsg_len;
 	const char *name;
 	unsigned int m_flag = 0;
 	SPRINT_BUF(b1);
@@ -973,8 +986,7 @@
 	if (n->nlmsg_type != RTM_NEWLINK && n->nlmsg_type != RTM_DELLINK)
 		return 0;
 
-	len -= NLMSG_LENGTH(sizeof(*ifi));
-	if (len < 0)
+	if (get_rtattr(n, tb) < 0)
 		return -1;
 
 	if (filter.ifindex && ifi->ifi_index != filter.ifindex)
@@ -984,8 +996,6 @@
 	if (filter.down && ifi->ifi_flags&IFF_UP)
 		return -1;
 
-	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;
@@ -1182,6 +1192,11 @@
 				   "max_mtu", "maxmtu %u ",
 				   rta_getattr_u32(tb[IFLA_MAX_MTU]));
 
+		if (tb[IFLA_NETNS_IMMUTABLE] &&
+		    rta_getattr_u8(tb[IFLA_NETNS_IMMUTABLE]))
+			print_bool(PRINT_ANY, "netns-immutable", "netns-immutable ",
+				   true);
+
 		if (tb[IFLA_LINKINFO])
 			print_linktype(fp, tb[IFLA_LINKINFO]);
 
@@ -2116,6 +2131,28 @@
 	return 0;
 }
 
+static void group_filter(struct nlmsg_chain *linfo)
+{
+	struct nlmsg_list *l, **lp;
+
+	lp = &linfo->head;
+	while ((l = *lp) != NULL) {
+		struct nlmsghdr *n = &l->h;
+		struct rtattr *tb[IFLA_MAX+1];
+
+		if (get_rtattr(n, tb) < 0)
+			return;
+
+		if (tb[IFLA_GROUP]) {
+			if (rta_getattr_u32(tb[IFLA_GROUP]) != filter.group) {
+				*lp = l->next;
+				free(l);
+			} else
+				lp = &l->next;
+		}
+	}
+}
+
 static int ipaddr_list_flush_or_save(int argc, char **argv, int action)
 {
 	struct nlmsg_chain linfo = { NULL, NULL};
@@ -2294,6 +2331,9 @@
 		ipaddr_filter(&linfo, ainfo);
 	}
 
+	if (filter.group != -1)
+		group_filter(&linfo);
+
 	for (l = linfo.head; l; l = l->next) {
 		struct nlmsghdr *n = &l->h;
 		struct ifinfomsg *ifi = NLMSG_DATA(n);
diff --git a/ip/iplink_bond.c b/ip/iplink_bond.c
index 62dd907..d6960f6 100644
--- a/ip/iplink_bond.c
+++ b/ip/iplink_bond.c
@@ -940,7 +940,6 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("802.3ad"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS,
 	.link_type_at = LINK_XSTATS_TYPE_BOND,
-	.inner_max = BOND_XSTATS_MAX,
 	.inner_at = BOND_XSTATS_3AD,
 	.show_cb = &bond_print_3ad_stats,
 };
@@ -962,7 +961,6 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("802.3ad"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
 	.link_type_at = LINK_XSTATS_TYPE_BOND,
-	.inner_max = BOND_XSTATS_MAX,
 	.inner_at = BOND_XSTATS_3AD,
 	.show_cb = &bond_print_3ad_stats,
 };
diff --git a/ip/iplink_bridge.c b/ip/iplink_bridge.c
index 1fe8955..76e6908 100644
--- a/ip/iplink_bridge.c
+++ b/ip/iplink_bridge.c
@@ -14,6 +14,7 @@
 #include <linux/if_bridge.h>
 #include <net/if.h>
 
+#include "bridge.h"
 #include "rt_names.h"
 #include "utils.h"
 #include "ip_common.h"
@@ -62,6 +63,7 @@
 		"		  [ nf_call_iptables NF_CALL_IPTABLES ]\n"
 		"		  [ nf_call_ip6tables NF_CALL_IP6TABLES ]\n"
 		"		  [ nf_call_arptables NF_CALL_ARPTABLES ]\n"
+		"		  [ mdb_offload_fail_notification MDB_OFFLOAD_FAIL_NOTIFICATION ]\n"
 		"\n"
 		"Where: VLAN_PROTOCOL := { 802.1Q | 802.1ad }\n"
 	);
@@ -413,6 +415,18 @@
 
 			addattr8(n, 1024, IFLA_BR_NF_CALL_ARPTABLES,
 				 nf_call_arpt);
+		} else if (strcmp(*argv, "mdb_offload_fail_notification") == 0) {
+			__u32 mofn_bit = 1 << BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION;
+			__u8 mofn;
+
+			NEXT_ARG();
+			if (get_u8(&mofn, *argv, 0))
+				invarg("invalid mdb_offload_fail_notification", *argv);
+			bm.optmask |= 1 << BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION;
+			if (mofn)
+				bm.optval |= mofn_bit;
+			else
+				bm.optval &= ~mofn_bit;
 		} else if (matches(*argv, "help") == 0) {
 			explain();
 			return -1;
@@ -620,6 +634,7 @@
 			   rta_getattr_u8(tb[IFLA_BR_MCAST_SNOOPING]));
 
 	if (tb[IFLA_BR_MULTI_BOOLOPT]) {
+		__u32 mofn_bit = 1 << BR_BOOLOPT_MDB_OFFLOAD_FAIL_NOTIFICATION;
 		__u32 mcvl_bit = 1 << BR_BOOLOPT_MCAST_VLAN_SNOOPING;
 		__u32 no_ll_learn_bit = 1 << BR_BOOLOPT_NO_LL_LEARN;
 		__u32 mst_bit = 1 << BR_BOOLOPT_MST_ENABLE;
@@ -641,6 +656,11 @@
 				   "mst_enabled",
 				   "mst_enabled %u ",
 				   !!(bm->optval & mst_bit));
+		if (bm->optmask & mofn_bit)
+			print_uint(PRINT_ANY,
+				   "mdb_offload_fail_notification",
+				   "mdb_offload_fail_notification %u ",
+				   !!(bm->optval & mofn_bit));
 	}
 
 	if (tb[IFLA_BR_MCAST_ROUTER])
@@ -662,62 +682,9 @@
 			   rta_getattr_u8(tb[IFLA_BR_MCAST_QUERIER]));
 
 	if (tb[IFLA_BR_MCAST_QUERIER_STATE]) {
-		struct rtattr *bqtb[BRIDGE_QUERIER_MAX + 1];
-		SPRINT_BUF(other_time);
+		struct rtattr *vtb = tb[IFLA_BR_MCAST_QUERIER_STATE];
 
-		parse_rtattr_nested(bqtb, BRIDGE_QUERIER_MAX, tb[IFLA_BR_MCAST_QUERIER_STATE]);
-		memset(other_time, 0, sizeof(other_time));
-
-		open_json_object("mcast_querier_state_ipv4");
-		if (bqtb[BRIDGE_QUERIER_IP_ADDRESS]) {
-			print_string(PRINT_FP,
-				NULL,
-				"%s ",
-				"mcast_querier_ipv4_addr");
-			print_color_string(PRINT_ANY,
-				COLOR_INET,
-				"mcast_querier_ipv4_addr",
-				"%s ",
-				format_host_rta(AF_INET, bqtb[BRIDGE_QUERIER_IP_ADDRESS]));
-		}
-		if (bqtb[BRIDGE_QUERIER_IP_PORT])
-			print_uint(PRINT_ANY,
-				"mcast_querier_ipv4_port",
-				"mcast_querier_ipv4_port %u ",
-				rta_getattr_u32(bqtb[BRIDGE_QUERIER_IP_PORT]));
-		if (bqtb[BRIDGE_QUERIER_IP_OTHER_TIMER])
-			print_string(PRINT_ANY,
-				"mcast_querier_ipv4_other_timer",
-				"mcast_querier_ipv4_other_timer %s ",
-				sprint_time64(
-					rta_getattr_u64(bqtb[BRIDGE_QUERIER_IP_OTHER_TIMER]),
-									other_time));
-		close_json_object();
-		open_json_object("mcast_querier_state_ipv6");
-		if (bqtb[BRIDGE_QUERIER_IPV6_ADDRESS]) {
-			print_string(PRINT_FP,
-				NULL,
-				"%s ",
-				"mcast_querier_ipv6_addr");
-			print_color_string(PRINT_ANY,
-				COLOR_INET6,
-				"mcast_querier_ipv6_addr",
-				"%s ",
-				format_host_rta(AF_INET6, bqtb[BRIDGE_QUERIER_IPV6_ADDRESS]));
-		}
-		if (bqtb[BRIDGE_QUERIER_IPV6_PORT])
-			print_uint(PRINT_ANY,
-				"mcast_querier_ipv6_port",
-				"mcast_querier_ipv6_port %u ",
-				rta_getattr_u32(bqtb[BRIDGE_QUERIER_IPV6_PORT]));
-		if (bqtb[BRIDGE_QUERIER_IPV6_OTHER_TIMER])
-			print_string(PRINT_ANY,
-				"mcast_querier_ipv6_other_timer",
-				"mcast_querier_ipv6_other_timer %s ",
-				sprint_time64(
-					rta_getattr_u64(bqtb[BRIDGE_QUERIER_IPV6_OTHER_TIMER]),
-									other_time));
-		close_json_object();
+		bridge_print_mcast_querier_state(vtb);
 	}
 
 	if (tb[IFLA_BR_MCAST_HASH_ELASTICITY])
@@ -959,6 +926,26 @@
 	close_json_object();
 }
 
+static void bridge_print_stats_vlan(const struct rtattr *attr)
+{
+	const struct bridge_vlan_xstats *vstats = RTA_DATA(attr);
+
+	print_string(PRINT_FP, NULL, "%-" textify(IFNAMSIZ) "s  ", "");
+	bridge_print_vlan_stats(vstats);
+}
+
+static int bridge_stat_desc_show_xstats(struct ipstats_stat_show_attrs *attrs,
+					const struct ipstats_stat_desc *desc)
+{
+	int ret;
+
+	open_json_array(PRINT_JSON, "vlans");
+	ret = ipstats_stat_desc_show_xstats(attrs, desc);
+	close_json_array(PRINT_JSON, "vlans");
+
+	return ret;
+}
+
 static void bridge_print_stats_attr(struct rtattr *attr, int ifindex)
 {
 	struct rtattr *brtb[LINK_XSTATS_TYPE_MAX+1];
@@ -1056,7 +1043,6 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("stp"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS,
 	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
-	.inner_max = BRIDGE_XSTATS_MAX,
 	.inner_at = BRIDGE_XSTATS_STP,
 	.show_cb = &bridge_print_stats_stp,
 };
@@ -1066,15 +1052,31 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("mcast"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS,
 	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
-	.inner_max = BRIDGE_XSTATS_MAX,
 	.inner_at = BRIDGE_XSTATS_MCAST,
 	.show_cb = &bridge_print_stats_mcast,
 };
 
+#define IPSTATS_STAT_DESC_BRIDGE_VLAN {			\
+		.name = "vlan",				\
+		.kind = IPSTATS_STAT_DESC_KIND_LEAF,	\
+		.show = &bridge_stat_desc_show_xstats,	\
+		.pack = &ipstats_stat_desc_pack_xstats,	\
+	}
+
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_bridge_vlan = {
+	.desc = IPSTATS_STAT_DESC_BRIDGE_VLAN,
+	.xstats_at = IFLA_STATS_LINK_XSTATS,
+	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+	.inner_at = BRIDGE_XSTATS_VLAN,
+	.show_cb = &bridge_print_stats_vlan,
+};
+
 static const struct ipstats_stat_desc *
 ipstats_stat_desc_xstats_bridge_subs[] = {
 	&ipstats_stat_desc_xstats_bridge_stp.desc,
 	&ipstats_stat_desc_xstats_bridge_mcast.desc,
+	&ipstats_stat_desc_xstats_bridge_vlan.desc,
 };
 
 const struct ipstats_stat_desc ipstats_stat_desc_xstats_bridge_group = {
@@ -1089,7 +1091,6 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("stp"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
 	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
-	.inner_max = BRIDGE_XSTATS_MAX,
 	.inner_at = BRIDGE_XSTATS_STP,
 	.show_cb = &bridge_print_stats_stp,
 };
@@ -1099,15 +1100,24 @@
 	.desc = IPSTATS_STAT_DESC_XSTATS_LEAF("mcast"),
 	.xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
 	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
-	.inner_max = BRIDGE_XSTATS_MAX,
 	.inner_at = BRIDGE_XSTATS_MCAST,
 	.show_cb = &bridge_print_stats_mcast,
 };
 
+static const struct ipstats_stat_desc_xstats
+ipstats_stat_desc_xstats_slave_bridge_vlan = {
+	.desc = IPSTATS_STAT_DESC_BRIDGE_VLAN,
+	.xstats_at = IFLA_STATS_LINK_XSTATS_SLAVE,
+	.link_type_at = LINK_XSTATS_TYPE_BRIDGE,
+	.inner_at = BRIDGE_XSTATS_VLAN,
+	.show_cb = &bridge_print_stats_vlan,
+};
+
 static const struct ipstats_stat_desc *
 ipstats_stat_desc_xstats_slave_bridge_subs[] = {
 	&ipstats_stat_desc_xstats_slave_bridge_stp.desc,
 	&ipstats_stat_desc_xstats_slave_bridge_mcast.desc,
+	&ipstats_stat_desc_xstats_slave_bridge_vlan.desc,
 };
 
 const struct ipstats_stat_desc ipstats_stat_desc_xstats_slave_bridge_group = {
diff --git a/ip/iplink_vxlan.c b/ip/iplink_vxlan.c
index 9649a8e..a6e9539 100644
--- a/ip/iplink_vxlan.c
+++ b/ip/iplink_vxlan.c
@@ -37,6 +37,7 @@
 	{ "remcsum_tx", IFLA_VXLAN_REMCSUM_TX,		false },
 	{ "remcsum_rx", IFLA_VXLAN_REMCSUM_RX,		false },
 	{ "localbypass", IFLA_VXLAN_LOCALBYPASS,	true },
+	{ "mcroute",	IFLA_VXLAN_MC_ROUTE,		false },
 };
 
 static void print_explain(FILE *f)
@@ -67,6 +68,7 @@
 		"		[ [no]localbypass ]\n"
 		"		[ [no]external ] [ gbp ] [ gpe ]\n"
 		"		[ [no]vnifilter ]\n"
+		"		[ [no]mcroute ]\n"
 		"\n"
 		"Where:	VNI	:= 0-16777215\n"
 		"	ADDR	:= { IP_ADDRESS | any }\n"
@@ -378,6 +380,14 @@
 			check_duparg(&attrs, IFLA_VXLAN_VNIFILTER,
 				     *argv, *argv);
 			addattr8(n, 1024, IFLA_VXLAN_VNIFILTER, 0);
+		} else if (!strcmp(*argv, "mcroute")) {
+			check_duparg(&attrs, IFLA_VXLAN_MC_ROUTE,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_MC_ROUTE, 1);
+		} else if (!strcmp(*argv, "nomcroute")) {
+			check_duparg(&attrs, IFLA_VXLAN_MC_ROUTE,
+				     *argv, *argv);
+			addattr8(n, 1024, IFLA_VXLAN_MC_ROUTE, 0);
 		} else if (matches(*argv, "help") == 0) {
 			explain();
 			return -1;
diff --git a/ip/ipneigh.c b/ip/ipneigh.c
index bd7f44e..e678545 100644
--- a/ip/ipneigh.c
+++ b/ip/ipneigh.c
@@ -47,7 +47,7 @@
 		"Usage: ip neigh { add | del | change | replace }\n"
 		"                { ADDR [ lladdr LLADDR ] [ nud STATE ] proxy ADDR }\n"
 		"                [ dev DEV ] [ router ] [ use ] [ managed ] [ extern_learn ]\n"
-		"                [ protocol PROTO ]\n"
+		"                [ extern_valid ] [ protocol PROTO ]\n"
 		"\n"
 		"	ip neigh { show | flush } [ proxy ] [ to PREFIX ] [ dev DEV ] [ nud STATE ]\n"
 		"				  [ vrf NAME ] [ nomaster ]\n"
@@ -152,6 +152,8 @@
 			req.ndm.ndm_state = NUD_NONE;
 		} else if (matches(*argv, "extern_learn") == 0) {
 			req.ndm.ndm_flags |= NTF_EXT_LEARNED;
+		} else if (strcmp(*argv, "extern_valid") == 0) {
+			ext_flags |= NTF_EXT_EXT_VALIDATED;
 		} else if (strcmp(*argv, "dev") == 0) {
 			NEXT_ARG();
 			dev = *argv;
@@ -446,6 +448,8 @@
 		print_null(PRINT_ANY, "extern_learn", "%s ", "extern_learn");
 	if (r->ndm_flags & NTF_OFFLOADED)
 		print_null(PRINT_ANY, "offload", "%s ", "offload");
+	if (ext_flags & NTF_EXT_EXT_VALIDATED)
+		print_null(PRINT_ANY, "extern_valid", "%s ", "extern_valid");
 
 	if (show_stats) {
 		if (tb[NDA_CACHEINFO])
diff --git a/ip/ipntable.c b/ip/ipntable.c
index 4ce02a3..54db9b6 100644
--- a/ip/ipntable.c
+++ b/ip/ipntable.c
@@ -40,7 +40,8 @@
 		"PARMS := [ base_reachable MSEC ] [ retrans MSEC ] [ gc_stale MSEC ]\n"
 		"         [ delay_probe MSEC ] [ queue LEN ]\n"
 		"         [ app_probes VAL ] [ ucast_probes VAL ] [ mcast_probes VAL ]\n"
-		"         [ anycast_delay MSEC ] [ proxy_delay MSEC ] [ proxy_queue LEN ]\n"
+		"         [ mcast_reprobes VAL ] [ anycast_delay MSEC ]\n"
+		"         [ proxy_delay MSEC ] [ proxy_queue LEN ]\n"
 		"         [ locktime MSEC ]\n"
 		);
 
@@ -223,6 +224,17 @@
 			rta_addattr32(parms_rta, sizeof(parms_buf),
 				      NDTPA_MCAST_PROBES, mprobe);
 			parms_change = 1;
+		} else if (strcmp(*argv, "mcast_reprobes") == 0) {
+			__u32 mreprobe;
+
+			NEXT_ARG();
+
+			if (get_u32(&mreprobe, *argv, 0))
+				invarg("\"mcast_reprobes\" value is invalid", *argv);
+
+			rta_addattr32(parms_rta, sizeof(parms_buf),
+				      NDTPA_MCAST_REPROBES, mreprobe);
+			parms_change = 1;
 		} else if (strcmp(*argv, "anycast_delay") == 0) {
 			__u64 anycast_delay;
 
@@ -440,6 +452,13 @@
 			   "mcast_probes %u ", mprobe);
 	}
 
+	if (tpb[NDTPA_MCAST_REPROBES]) {
+		__u32 mreprobe = rta_getattr_u32(tpb[NDTPA_MCAST_REPROBES]);
+
+		print_uint(PRINT_ANY, "mcast_reprobes",
+			   "mcast_reprobes %u ", mreprobe);
+	}
+
 	print_string(PRINT_FP, NULL, "%s    ", _SL_);
 
 	if (tpb[NDTPA_ANYCAST_DELAY]) {
diff --git a/ip/iproute.c b/ip/iproute.c
index 0e2c171..c253889 100644
--- a/ip/iproute.c
+++ b/ip/iproute.c
@@ -1134,6 +1134,27 @@
 	return 0;
 }
 
+static unsigned int parse_features(int *argcp, char ***argvp)
+{
+	unsigned int features = 0;
+	char **argv = *argvp;
+	int argc = *argcp;
+
+	while (++argv, --argc > 0) {
+		if (strcmp(*argv, "ecn") == 0) {
+			features |= RTAX_FEATURE_ECN;
+		} else if (strcmp(*argv, "tcp_usec_ts") == 0) {
+			features |= RTAX_FEATURE_TCP_USEC_TS;
+		} else {
+			break;
+		}
+	}
+
+	*argcp = argc;
+	*argvp = argv;
+	return features;
+}
+
 static int iproute_modify(int cmd, unsigned int flags, int argc, char **argv)
 {
 	struct {
@@ -1374,17 +1395,14 @@
 		} else if (matches(*argv, "features") == 0) {
 			unsigned int features = 0;
 
-			while (argc > 0) {
-				NEXT_ARG();
+			features = parse_features(&argc, &argv);
+			if (!features)
+				invarg("\"features\" value not valid\n", *argv);
 
-				if (strcmp(*argv, "ecn") == 0)
-					features |= RTAX_FEATURE_ECN;
-				else if (strcmp(*argv, "tcp_usec_ts") == 0)
-					features |= RTAX_FEATURE_TCP_USEC_TS;
-				else
-					invarg("\"features\" value not valid\n", *argv);
-				break;
-			}
+			/* parse_features stops at the first feature it can't
+			 * parse, rewind one argument back.
+			 */
+			PREV_ARG();
 
 			rta_addattr32(mxrta, sizeof(mxbuf),
 				      RTAX_FEATURES, features);
diff --git a/ip/ipstats.c b/ip/ipstats.c
index cb9d9cb..f0f8dcd 100644
--- a/ip/ipstats.c
+++ b/ip/ipstats.c
@@ -1,5 +1,4 @@
 // SPDX-License-Identifier: GPL-2.0+
-#include <alloca.h>
 #include <assert.h>
 #include <errno.h>
 #include <stdio.h>
@@ -590,7 +589,7 @@
 {
 	struct ipstats_stat_desc_xstats *xdesc;
 	const struct rtattr *at;
-	struct rtattr **tb;
+	const struct rtattr *i;
 	int err;
 
 	xdesc = container_of(desc, struct ipstats_stat_desc_xstats, desc);
@@ -600,15 +599,13 @@
 	if (at == NULL)
 		return err;
 
-	tb = alloca(sizeof(*tb) * (xdesc->inner_max + 1));
-	err = parse_rtattr_nested(tb, xdesc->inner_max, at);
-	if (err != 0)
-		return err;
-
-	if (tb[xdesc->inner_at] != NULL) {
-		print_nl();
-		xdesc->show_cb(tb[xdesc->inner_at]);
+	rtattr_for_each_nested(i, at) {
+		if (i->rta_type == xdesc->inner_at) {
+			print_nl();
+			xdesc->show_cb(i);
+		}
 	}
+
 	return 0;
 }
 
diff --git a/lib/Makefile b/lib/Makefile
index aa7bbd2..0ba6294 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -5,7 +5,8 @@
 
 UTILOBJ = utils.o utils_math.o rt_names.o ll_map.o ll_types.o ll_proto.o ll_addr.o \
 	inet_proto.o namespace.o json_writer.o json_print.o json_print_math.o \
-	names.o color.o bpf_legacy.o bpf_glue.o exec.o fs.o cg_map.o ppp_proto.o
+	names.o color.o bpf_legacy.o bpf_glue.o exec.o fs.o cg_map.o \
+	ppp_proto.o bridge.o
 
 ifeq ($(HAVE_ELF),y)
 ifeq ($(HAVE_LIBBPF),y)
diff --git a/lib/bridge.c b/lib/bridge.c
new file mode 100644
index 0000000..5386aa0
--- /dev/null
+++ b/lib/bridge.c
@@ -0,0 +1,103 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <net/if.h>
+
+#include "bridge.h"
+#include "utils.h"
+
+void bridge_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);
+}
+
+void bridge_print_vlan_stats_only(const struct bridge_vlan_xstats *vstats)
+{
+	print_string(PRINT_FP, NULL, "%-" textify(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, "%-" textify(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);
+}
+
+void bridge_print_vlan_stats(const struct bridge_vlan_xstats *vstats)
+{
+	open_json_object(NULL);
+
+	print_hu(PRINT_ANY, "vid", "%hu", vstats->vid);
+	bridge_print_vlan_flags(vstats->flags);
+	print_nl();
+	bridge_print_vlan_stats_only(vstats);
+
+	close_json_object();
+}
+
+void bridge_print_mcast_querier_state(const struct rtattr *vtb)
+{
+	struct rtattr *bqtb[BRIDGE_QUERIER_MAX + 1];
+	const char *querier_ip;
+	SPRINT_BUF(other_time);
+	__u64 tval;
+
+	parse_rtattr_nested(bqtb, BRIDGE_QUERIER_MAX, vtb);
+	memset(other_time, 0, sizeof(other_time));
+
+	open_json_object("mcast_querier_state_ipv4");
+	if (bqtb[BRIDGE_QUERIER_IP_ADDRESS]) {
+		querier_ip = format_host_rta(AF_INET,
+					     bqtb[BRIDGE_QUERIER_IP_ADDRESS]);
+		print_string(PRINT_FP, NULL, "%s ",
+			     "mcast_querier_ipv4_addr");
+		print_color_string(PRINT_ANY, COLOR_INET,
+				   "mcast_querier_ipv4_addr", "%s ",
+				   querier_ip);
+	}
+	if (bqtb[BRIDGE_QUERIER_IP_PORT])
+		print_uint(PRINT_ANY, "mcast_querier_ipv4_port",
+			   "mcast_querier_ipv4_port %u ",
+			   rta_getattr_u32(bqtb[BRIDGE_QUERIER_IP_PORT]));
+	if (bqtb[BRIDGE_QUERIER_IP_OTHER_TIMER]) {
+		tval = rta_getattr_u64(bqtb[BRIDGE_QUERIER_IP_OTHER_TIMER]);
+		print_string(PRINT_ANY,
+			     "mcast_querier_ipv4_other_timer",
+			     "mcast_querier_ipv4_other_timer %s ",
+			     sprint_time64(tval, other_time));
+	}
+	close_json_object();
+	open_json_object("mcast_querier_state_ipv6");
+	if (bqtb[BRIDGE_QUERIER_IPV6_ADDRESS]) {
+		querier_ip = format_host_rta(AF_INET6,
+					     bqtb[BRIDGE_QUERIER_IPV6_ADDRESS]);
+		print_string(PRINT_FP, NULL, "%s ",
+			     "mcast_querier_ipv6_addr");
+		print_color_string(PRINT_ANY, COLOR_INET6,
+				   "mcast_querier_ipv6_addr", "%s ",
+				   querier_ip);
+	}
+	if (bqtb[BRIDGE_QUERIER_IPV6_PORT])
+		print_uint(PRINT_ANY, "mcast_querier_ipv6_port",
+			   "mcast_querier_ipv6_port %u ",
+			   rta_getattr_u32(bqtb[BRIDGE_QUERIER_IPV6_PORT]));
+	if (bqtb[BRIDGE_QUERIER_IPV6_OTHER_TIMER]) {
+		tval = rta_getattr_u64(bqtb[BRIDGE_QUERIER_IPV6_OTHER_TIMER]);
+		print_string(PRINT_ANY,
+			     "mcast_querier_ipv6_other_timer",
+			     "mcast_querier_ipv6_other_timer %s ",
+			     sprint_time64(tval, other_time));
+	}
+	close_json_object();
+}
diff --git a/lib/color.c b/lib/color.c
index 3c6db08..aa11235 100644
--- a/lib/color.c
+++ b/lib/color.c
@@ -24,7 +24,7 @@
 	C_BOLD_RED,
 	C_BOLD_GREEN,
 	C_BOLD_YELLOW,
-	C_BOLD_BLUE,
+	C_BOLD_LIGHT_BLUE,
 	C_BOLD_MAGENTA,
 	C_BOLD_CYAN,
 	C_BOLD_WHITE,
@@ -42,7 +42,7 @@
 	"\e[1;31m",
 	"\e[1;32m",
 	"\e[1;33m",
-	"\e[1;34m",
+	"\e[1;94m",
 	"\e[1;35m",
 	"\e[1;36m",
 	"\e[1;37m",
@@ -66,13 +66,17 @@
 	C_BOLD_CYAN,
 	C_BOLD_YELLOW,
 	C_BOLD_MAGENTA,
-	C_BOLD_BLUE,
+	C_BOLD_LIGHT_BLUE,
 	C_BOLD_GREEN,
 	C_BOLD_RED,
 	C_CLEAR
 };
 
-static int is_dark_bg;
+/*
+ * Assume dark background until we know otherwise. The dark-background
+ * colours work better on a light background than vice versa.
+ */
+static int is_dark_bg = 1;
 static int color_is_enabled;
 
 static void enable_color(void)
@@ -138,12 +142,12 @@
 	/*
 	 * COLORFGBG environment variable usually contains either two or three
 	 * values separated by semicolons; we want the last value in either case.
-	 * If this value is 0-6 or 8, background is dark.
+	 * If this value is 0-6 or 8, background is dark; otherwise it's light.
 	 */
 	if (p && (p = strrchr(p, ';')) != NULL
-		&& ((p[1] >= '0' && p[1] <= '6') || p[1] == '8')
-		&& p[2] == '\0')
-		is_dark_bg = 1;
+		&& !(((p[1] >= '0' && p[1] <= '6') || p[1] == '8')
+		     && p[2] == '\0'))
+		is_dark_bg = 0;
 }
 
 __attribute__((format(printf, 3, 4)))
diff --git a/man/man8/devlink-rate.8 b/man/man8/devlink-rate.8
index f09ac4a..47e2ebc 100644
--- a/man/man8/devlink-rate.8
+++ b/man/man8/devlink-rate.8
@@ -28,6 +28,7 @@
 .RB [ " tx_max \fIVALUE " ]
 .RB [ " tx_priority \fIN " ]
 .RB [ " tx_weight \fIN " ]
+.RB [ " tc-bw \fIINDEX:N " ]
 .RB "[ {" " parent \fINODE_NAME " | " noparent " "} ]"
 
 .ti -8
@@ -36,6 +37,7 @@
 .RB [ " tx_max \fIVALUE " ]
 .RB [ " tx_priority \fIN " ]
 .RB [ " tx_weight \fIN " ]
+.RB [ " tc-bw \fIINDEX:N " ]
 .RB "[ {" " parent \fINODE_NAME " | " noparent " "} ]"
 
 .ti -8
@@ -101,6 +103,12 @@
 siblings. Values are relative like a percentage points, they basically tell
 how much BW should node take relative to it's siblings.
 .PP
+.BI tc-bw " INDEX:N"
+- allows the user to assign relative bandwidth shares to specific traffic
+classes using the IEEE 802.1Qaz standard. The values determine how bandwidth
+is distributed between traffic classes in proportion to one another.
+If not specified, the default bandwidth allocation is applied.
+.PP
 .TP 8
 .I VALUE
 These parameter accept a floating point number, possibly followed by either a
@@ -142,6 +150,12 @@
 .RE
 .PP
 .TP 8
+.I INDEX
+These parameters represent the traffic class index in the \fItc-bw\fR option.
+The traffic class is specified as an integer value, ranging from 0 to 7, which
+maps to the defined traffic classes under the IEEE 802.1Qaz standard.
+.PP
+.TP 8
 .I N
 These parameter accept integer meaning weight or priority of a node.
 .PP
diff --git a/man/man8/ip-link.8.in b/man/man8/ip-link.8.in
index d6e05d9..e3297c5 100644
--- a/man/man8/ip-link.8.in
+++ b/man/man8/ip-link.8.in
@@ -663,6 +663,8 @@
 .B gpe
 ] [
 .RB [ no ] vnifilter
+] [
+.RB [ no ] mcroute
 ]
 
 .in +8
@@ -797,6 +799,14 @@
 in the vni filtering table.
 
 .sp
+.RB [ no ] mcroute
+- when the VXLAN tunnel has a multicast remote, whether the underlay packets
+should be sent directly to the physical device (the default), or whether they
+should be multicast-routed. In the latter case, for purposes of matching a
+multicast route, (S,G) are, respectively, local and remote address of the
+tunnel, and iif is the tunnel physical device.
+
+.sp
 .B gbp
 - enables the Group Policy extension (VXLAN-GBP).
 
@@ -1768,6 +1778,8 @@
 .BI nf_call_ip6tables " NF_CALL_IP6TABLES "
 ] [
 .BI nf_call_arptables " NF_CALL_ARPTABLES "
+] [
+.BI mdb_offload_fail_notification " MDB_OFFLOAD_FAIL_NOTIFICATION "
 ]
 
 .in +8
@@ -1992,6 +2004,13 @@
 .RI ( NF_CALL_ARPTABLES " == 0) "
 arptables hooks on the bridge.
 
+.BI mdb_offload_fail_notification " MDB_OFFLOAD_FAIL_NOTIFICATION "
+- turn mdb offload fail notification on
+.RI ( MDB_OFFLOAD_FAIL_NOTIFICATION " > 0) "
+or off
+.RI ( MDB_OFFLOAD_FAIL_NOTIFICATION " == 0). "
+Default is
+.BR 0 .
 
 .in -8
 
diff --git a/man/man8/ip-neighbour.8 b/man/man8/ip-neighbour.8
index 6fed47c..1f890c0 100644
--- a/man/man8/ip-neighbour.8
+++ b/man/man8/ip-neighbour.8
@@ -27,7 +27,8 @@
 .BR router " ] [ "
 .BR use " ] [ "
 .BR managed " ] [ "
-.BR extern_learn " ]"
+.BR extern_learn " ] [ "
+.BR extern_valid " ]"
 
 .ti -8
 .BR "ip neigh" " { " show " | " flush " } [ " proxy " ] [ " to
@@ -116,6 +117,13 @@
 Kernel will not gc such an entry.
 
 .TP
+.BI extern_valid
+this neigh entry was learned and determined to be valid externally. The kernel
+will not remove or invalidate the entry, but it can probe the entry and notify
+user space when the entry becomes reachable. The kernel will return the entry
+to stale state if it did not receive a confirmation after probing the entry.
+
+.TP
 .BI lladdr " LLADDRESS"
 the link layer address of the neighbour.
 .I LLADDRESS
diff --git a/man/man8/ip-ntable.8 b/man/man8/ip-ntable.8
index 4f0f2e5..56108af 100644
--- a/man/man8/ip-ntable.8
+++ b/man/man8/ip-ntable.8
@@ -42,6 +42,8 @@
 .IR VAL " ] ["
 .B mcast_probes
 .IR VAL " ] ["
+.B mcast_reprobes
+.IR VAL " ] ["
 .B anycast_delay
 .IR MSEC " ] ["
 .B proxy_delay
diff --git a/man/man8/ip-stats.8 b/man/man8/ip-stats.8
index 2633645..e9ff49d 100644
--- a/man/man8/ip-stats.8
+++ b/man/man8/ip-stats.8
@@ -152,9 +152,15 @@
 .in 21
 
 .ti 14
-.B subgroup bridge \fR[\fB suite stp \fR] [\fB suite mcast \fR]
-- Statistics for STP and, respectively, IGMP / MLD (under the keyword
-\fBmcast\fR) traffic on bridges and their slaves.
+.B subgroup bridge\fR - Various statistics on bridges and their slaves.
+
+.ti 21
+.BR "suite stp " "- STP statistics"
+.br
+.BR "suite mcast " "- IGMP / MLD statistics"
+.br
+.BR "suite vlan " "- per-VLAN traffic statistics"
+.br
 
 .ti 14
 .B subgroup bond \fR[\fB suite 802.3ad \fR]