[][MAC80211][Add Bridger related patches]
[Description]
Add Bridger related patches.
[Release-log]
N/A
Change-Id: I62a66ef99dc14ef2a86598a5bb251c0168e4cdd7
Reviewed-on: https://gerrit.mediatek.inc/c/openwrt/feeds/mtk_openwrt_feeds/+/6295961
diff --git a/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-update-net-bridge-for-bridger.patch b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-update-net-bridge-for-bridger.patch
new file mode 100644
index 0000000..6fe3733
--- /dev/null
+++ b/autobuild_mac80211_release/target/linux/mediatek/patches-5.4/9999-update-net-bridge-for-bridger.patch
@@ -0,0 +1,1823 @@
+diff --git a/include/net/switchdev.h b/include/net/switchdev.h
+index 191dc34..d4d71d9 100644
+--- a/include/net/switchdev.h
++++ b/include/net/switchdev.h
+@@ -77,6 +77,7 @@ struct switchdev_obj {
+ struct switchdev_obj_port_vlan {
+ struct switchdev_obj obj;
+ u16 flags;
++ u16 vid;
+ u16 vid_begin;
+ u16 vid_end;
+ };
+@@ -117,6 +118,7 @@ enum switchdev_notifier_type {
+ struct switchdev_notifier_info {
+ struct net_device *dev;
+ struct netlink_ext_ack *extack;
++ const void *ctx;
+ };
+
+ struct switchdev_notifier_fdb_info {
+diff --git a/net/bridge/Makefile b/net/bridge/Makefile
+index ac9ef33..49da7ae 100644
+--- a/net/bridge/Makefile
++++ b/net/bridge/Makefile
+@@ -20,7 +20,7 @@ obj-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
+
+ bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
+
+-bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o
++bridge-$(CONFIG_BRIDGE_VLAN_FILTERING) += br_vlan.o br_vlan_tunnel.o br_vlan_options.o
+
+ bridge-$(CONFIG_NET_SWITCHDEV) += br_switchdev.o
+
+diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
+index da5ed4c..eeabfbc 100644
+--- a/net/bridge/br_mdb.c
++++ b/net/bridge/br_mdb.c
+@@ -16,7 +16,37 @@
+
+ #include "br_private.h"
+
+-static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++static size_t __br_rports_one_size(void)
++{
++ return nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PORT */
++ nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_TIMER */
++ nla_total_size(sizeof(u8)) + /* MDBA_ROUTER_PATTR_TYPE */
++ nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET_TIMER */
++ nla_total_size(sizeof(u32)) + /* MDBA_ROUTER_PATTR_INET6_TIMER */
++ nla_total_size(sizeof(u32)); /* MDBA_ROUTER_PATTR_VID */
++}
++
++size_t br_rports_size(const struct net_bridge_mcast *brmctx)
++{
++ struct net_bridge_mcast_port *pmctx;
++ size_t size = nla_total_size(0); /* MDBA_ROUTER */
++
++ rcu_read_lock();
++ hlist_for_each_entry_rcu(pmctx, &brmctx->ip4_mc_router_list,
++ ip4_rlist)
++ size += __br_rports_one_size();
++
++#if IS_ENABLED(CONFIG_IPV6)
++ hlist_for_each_entry_rcu(pmctx, &brmctx->ip6_mc_router_list,
++ ip6_rlist)
++ size += __br_rports_one_size();
++#endif
++ rcu_read_unlock();
++
++ return size;
++}
++
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
+ struct net_device *dev)
+ {
+ struct net_bridge *br = netdev_priv(dev);
+diff --git a/net/bridge/br_netlink.c b/net/bridge/br_netlink.c
+index cbcbc19..887e767 100644
+--- a/net/bridge/br_netlink.c
++++ b/net/bridge/br_netlink.c
+@@ -562,7 +562,7 @@ static int br_vlan_info(struct net_bridge *br, struct net_bridge_port *p,
+ return err;
+ }
+
+-static int br_process_vlan_info(struct net_bridge *br,
++int br_process_vlan_info(struct net_bridge *br,
+ struct net_bridge_port *p, int cmd,
+ struct bridge_vlan_info *vinfo_curr,
+ struct bridge_vlan_info **vinfo_last,
+@@ -1578,7 +1578,7 @@ static int br_fill_linkxstats(struct sk_buff *skb,
+ pvid = br_get_pvid(vg);
+ list_for_each_entry(v, &vg->vlan_list, vlist) {
+ struct bridge_vlan_xstats vxi;
+- struct br_vlan_stats stats;
++ struct pcpu_sw_netstats stats;
+
+ if (++vl_idx < *prividx)
+ continue;
+@@ -1652,6 +1652,7 @@ int __init br_netlink_init(void)
+ int err;
+
+ br_mdb_init();
++ br_vlan_rtnl_init();
+ rtnl_af_register(&br_af_ops);
+
+ err = rtnl_link_register(&br_link_ops);
+@@ -1669,6 +1670,7 @@ int __init br_netlink_init(void)
+ void br_netlink_fini(void)
+ {
+ br_mdb_uninit();
++ br_vlan_rtnl_uninit();
+ rtnl_af_unregister(&br_af_ops);
+ rtnl_link_unregister(&br_link_ops);
+ }
+diff --git a/net/bridge/br_netlink_tunnel.c b/net/bridge/br_netlink_tunnel.c
+index afee292..3bbbd66 100644
+--- a/net/bridge/br_netlink_tunnel.c
++++ b/net/bridge/br_netlink_tunnel.c
+@@ -26,7 +26,7 @@ static size_t __get_vlan_tinfo_size(void)
+ nla_total_size(sizeof(u16)); /* IFLA_BRIDGE_VLAN_TUNNEL_FLAGS */
+ }
+
+-static bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
+ struct net_bridge_vlan *v_last)
+ {
+ __be32 tunid_curr = tunnel_id_to_key32(v_curr->tinfo.tunnel_id);
+@@ -193,7 +193,7 @@ static const struct nla_policy vlan_tunnel_policy[IFLA_BRIDGE_VLAN_TUNNEL_MAX +
+ [IFLA_BRIDGE_VLAN_TUNNEL_FLAGS] = { .type = NLA_U16 },
+ };
+
+-static int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
+ u16 vid, u32 tun_id, bool *changed)
+ {
+ int err = 0;
+diff --git a/net/bridge/br_private.h b/net/bridge/br_private.h
+index 4bd9e9b..4620f70 100644
+--- a/net/bridge/br_private.h
++++ b/net/bridge/br_private.h
+@@ -95,6 +95,60 @@ struct br_vlan_stats {
+ struct u64_stats_sync syncp;
+ };
+
++/* net_bridge_mcast_port must be always defined due to forwarding stubs */
++struct net_bridge_mcast_port {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ struct net_bridge_port *port;
++ struct net_bridge_vlan *vlan;
++
++ struct bridge_mcast_own_query ip4_own_query;
++ struct timer_list ip4_mc_router_timer;
++ struct hlist_node ip4_rlist;
++#if IS_ENABLED(CONFIG_IPV6)
++ struct bridge_mcast_own_query ip6_own_query;
++ struct timer_list ip6_mc_router_timer;
++ struct hlist_node ip6_rlist;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++ unsigned char multicast_router;
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
++/* net_bridge_mcast must be always defined due to forwarding stubs */
++struct net_bridge_mcast {
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ struct net_bridge *br;
++ struct net_bridge_vlan *vlan;
++
++ u32 multicast_last_member_count;
++ u32 multicast_startup_query_count;
++
++ u8 multicast_querier;
++ u8 multicast_igmp_version;
++ u8 multicast_router;
++#if IS_ENABLED(CONFIG_IPV6)
++ u8 multicast_mld_version;
++#endif
++ unsigned long multicast_last_member_interval;
++ unsigned long multicast_membership_interval;
++ unsigned long multicast_querier_interval;
++ unsigned long multicast_query_interval;
++ unsigned long multicast_query_response_interval;
++ unsigned long multicast_startup_query_interval;
++ struct hlist_head ip4_mc_router_list;
++ struct timer_list ip4_mc_router_timer;
++ struct bridge_mcast_other_query ip4_other_query;
++ struct bridge_mcast_own_query ip4_own_query;
++ struct bridge_mcast_querier ip4_querier;
++#if IS_ENABLED(CONFIG_IPV6)
++ struct hlist_head ip6_mc_router_list;
++ struct timer_list ip6_mc_router_timer;
++ struct bridge_mcast_other_query ip6_other_query;
++ struct bridge_mcast_own_query ip6_own_query;
++ struct bridge_mcast_querier ip6_querier;
++#endif /* IS_ENABLED(CONFIG_IPV6) */
++#endif /* CONFIG_BRIDGE_IGMP_SNOOPING */
++};
++
+ struct br_tunnel_info {
+ __be64 tunnel_id;
+ struct metadata_dst __rcu *tunnel_dst;
+@@ -104,6 +158,8 @@ struct br_tunnel_info {
+ enum {
+ BR_VLFLAG_PER_PORT_STATS = BIT(0),
+ BR_VLFLAG_ADDED_BY_SWITCHDEV = BIT(1),
++ BR_VLFLAG_MCAST_ENABLED = BIT(2),
++ BR_VLFLAG_GLOBAL_MCAST_ENABLED = BIT(3),
+ };
+
+ /**
+@@ -113,12 +169,16 @@ enum {
+ * @vid: VLAN id
+ * @flags: bridge vlan flags
+ * @priv_flags: private (in-kernel) bridge vlan flags
++ * @state: STP state (e.g. blocking, learning, forwarding)
+ * @stats: per-cpu VLAN statistics
+ * @br: if MASTER flag set, this points to a bridge struct
+ * @port: if MASTER flag unset, this points to a port struct
+ * @refcnt: if MASTER flag set, this is bumped for each port referencing it
+ * @brvlan: if MASTER flag unset, this points to the global per-VLAN context
+ * for this VLAN entry
++ * @br_mcast_ctx: if MASTER flag set, this is the global vlan multicast context
++ * @port_mcast_ctx: if MASTER flag unset, this is the per-port/vlan multicast
++ * context
+ * @vlist: sorted list of VLAN entries
+ * @rcu: used for entry destruction
+ *
+@@ -133,7 +193,8 @@ struct net_bridge_vlan {
+ u16 vid;
+ u16 flags;
+ u16 priv_flags;
+- struct br_vlan_stats __percpu *stats;
++ u8 state;
++ struct pcpu_sw_netstats __percpu *stats;
+ union {
+ struct net_bridge *br;
+ struct net_bridge_port *port;
+@@ -145,6 +206,11 @@ struct net_bridge_vlan {
+
+ struct br_tunnel_info tinfo;
+
++ union {
++ struct net_bridge_mcast br_mcast_ctx;
++ struct net_bridge_mcast_port port_mcast_ctx;
++ };
++
+ struct list_head vlist;
+
+ struct rcu_head rcu;
+@@ -170,6 +236,7 @@ struct net_bridge_vlan_group {
+ struct list_head vlan_list;
+ u16 num_vlans;
+ u16 pvid;
++ u8 pvid_state;
+ };
+
+ struct net_bridge_fdb_key {
+@@ -497,6 +564,67 @@ static inline bool br_vlan_should_use(const struct net_bridge_vlan *v)
+ return true;
+ }
+
++static inline bool br_vlan_valid_id(u16 vid, struct netlink_ext_ack *extack)
++{
++ bool ret = vid > 0 && vid < VLAN_VID_MASK;
++
++ if (!ret)
++ NL_SET_ERR_MSG_MOD(extack, "Vlan id is invalid");
++
++ return ret;
++}
++
++static inline bool br_vlan_valid_range(const struct bridge_vlan_info *cur,
++ const struct bridge_vlan_info *last,
++ struct netlink_ext_ack *extack)
++{
++ /* pvid flag is not allowed in ranges */
++ if (cur->flags & BRIDGE_VLAN_INFO_PVID) {
++ NL_SET_ERR_MSG_MOD(extack, "Pvid isn't allowed in a range");
++ return false;
++ }
++
++ /* when cur is the range end, check if:
++ * - it has range start flag
++ * - range ids are invalid (end is equal to or before start)
++ */
++ if (last) {
++ if (cur->flags & BRIDGE_VLAN_INFO_RANGE_BEGIN) {
++ NL_SET_ERR_MSG_MOD(extack, "Found a new vlan range start while processing one");
++ return false;
++ } else if (!(cur->flags & BRIDGE_VLAN_INFO_RANGE_END)) {
++ NL_SET_ERR_MSG_MOD(extack, "Vlan range end flag is missing");
++ return false;
++ } else if (cur->vid <= last->vid) {
++ NL_SET_ERR_MSG_MOD(extack, "End vlan id is less than or equal to start vlan id");
++ return false;
++ }
++ }
++
++ /* check for required range flags */
++ if (!(cur->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++ BRIDGE_VLAN_INFO_RANGE_END))) {
++ NL_SET_ERR_MSG_MOD(extack, "Both vlan range flags are missing");
++ return false;
++ }
++
++ return true;
++}
++
++static inline u8 br_vlan_multicast_router(const struct net_bridge_vlan *v)
++{
++ u8 mcast_router = MDB_RTR_TYPE_DISABLED;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ if (!br_vlan_is_master(v))
++ mcast_router = v->port_mcast_ctx.multicast_router;
++ else
++ mcast_router = v->br_mcast_ctx.multicast_router;
++#endif
++
++ return mcast_router;
++}
++
+ static inline int br_opt_get(const struct net_bridge *br,
+ enum net_bridge_opts opt)
+ {
+@@ -676,8 +804,10 @@ void br_multicast_flood(struct net_bridge_mdb_entry *mdst,
+ struct sk_buff *skb, bool local_rcv, bool local_orig);
+ int br_multicast_set_router(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_port_router(struct net_bridge_port *p, unsigned long val);
++int br_multicast_set_vlan_router(struct net_bridge_vlan *v, u8 mcast_router);
+ int br_multicast_toggle(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
++
+ int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
+ int br_multicast_set_igmp_version(struct net_bridge *br, unsigned long val);
+ #if IS_ENABLED(CONFIG_IPV6)
+@@ -708,6 +838,17 @@ void br_mdb_init(void);
+ void br_mdb_uninit(void);
+ void br_multicast_host_join(struct net_bridge_mdb_entry *mp, bool notify);
+ void br_multicast_host_leave(struct net_bridge_mdb_entry *mp, bool notify);
++int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
++ struct net_device *dev);
++int br_multicast_dump_querier_state(struct sk_buff *skb,
++ const struct net_bridge_mcast *brmctx,
++ int nest_attr);
++size_t br_multicast_querier_state_size(void);
++size_t br_rports_size(const struct net_bridge_mcast *brmctx);
++void br_multicast_set_query_intvl(struct net_bridge_mcast *brmctx,
++ unsigned long val);
++void br_multicast_set_startup_query_intvl(struct net_bridge_mcast *brmctx,
++ unsigned long val);
+
+ #define mlock_dereference(X, br) \
+ rcu_dereference_protected(X, lockdep_is_held(&br->multicast_lock))
+@@ -760,6 +901,49 @@ static inline int br_multicast_igmp_type(const struct sk_buff *skb)
+ {
+ return BR_INPUT_SKB_CB(skb)->igmp;
+ }
++static inline bool
++br_rports_have_mc_router(const struct net_bridge_mcast *brmctx)
++{
++#if IS_ENABLED(CONFIG_IPV6)
++ return !hlist_empty(&brmctx->ip4_mc_router_list) ||
++ !hlist_empty(&brmctx->ip6_mc_router_list);
++#else
++ return !hlist_empty(&brmctx->ip4_mc_router_list);
++#endif
++}
++
++static inline bool
++br_multicast_ctx_options_equal(const struct net_bridge_mcast *brmctx1,
++ const struct net_bridge_mcast *brmctx2)
++{
++ return brmctx1->multicast_igmp_version ==
++ brmctx2->multicast_igmp_version &&
++ brmctx1->multicast_last_member_count ==
++ brmctx2->multicast_last_member_count &&
++ brmctx1->multicast_startup_query_count ==
++ brmctx2->multicast_startup_query_count &&
++ brmctx1->multicast_last_member_interval ==
++ brmctx2->multicast_last_member_interval &&
++ brmctx1->multicast_membership_interval ==
++ brmctx2->multicast_membership_interval &&
++ brmctx1->multicast_querier_interval ==
++ brmctx2->multicast_querier_interval &&
++ brmctx1->multicast_query_interval ==
++ brmctx2->multicast_query_interval &&
++ brmctx1->multicast_query_response_interval ==
++ brmctx2->multicast_query_response_interval &&
++ brmctx1->multicast_startup_query_interval ==
++ brmctx2->multicast_startup_query_interval &&
++ brmctx1->multicast_querier == brmctx2->multicast_querier &&
++ brmctx1->multicast_router == brmctx2->multicast_router &&
++ !br_rports_have_mc_router(brmctx1) &&
++ !br_rports_have_mc_router(brmctx2) &&
++#if IS_ENABLED(CONFIG_IPV6)
++ brmctx1->multicast_mld_version ==
++ brmctx2->multicast_mld_version &&
++#endif
++ true;
++}
+ #else
+ static inline int br_multicast_rcv(struct net_bridge *br,
+ struct net_bridge_port *port,
+@@ -907,10 +1091,21 @@ void nbp_vlan_flush(struct net_bridge_port *port);
+ int nbp_vlan_init(struct net_bridge_port *port, struct netlink_ext_ack *extack);
+ int nbp_get_num_vlan_infos(struct net_bridge_port *p, u32 filter_mask);
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+- struct br_vlan_stats *stats);
++ struct pcpu_sw_netstats *stats);
+ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event);
+ int br_vlan_bridge_event(struct net_device *dev, unsigned long event,
+ void *ptr);
++void br_vlan_rtnl_init(void);
++void br_vlan_rtnl_uninit(void);
++void br_vlan_notify(const struct net_bridge *br,
++ const struct net_bridge_port *p,
++ u16 vid, u16 vid_range,
++ int cmd);
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++ const void *ctx, bool adding, struct notifier_block *nb,
++ struct netlink_ext_ack *extack);
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *range_end);
+
+ void br_vlan_fill_forward_path_pvid(struct net_bridge *br,
+ struct net_device_path_ctx *ctx,
+@@ -969,6 +1164,10 @@ static inline u16 br_get_pvid(const struct net_bridge_vlan_group *vg)
+ return vg->pvid;
+ }
+
++static inline u16 br_vlan_flags(const struct net_bridge_vlan *v, u16 pvid)
++{
++ return v->vid == pvid ? v->flags | BRIDGE_VLAN_INFO_PVID : v->flags;
++}
+ #else
+ static inline bool br_allowed_ingress(const struct net_bridge *br,
+ struct net_bridge_vlan_group *vg,
+@@ -1111,7 +1310,7 @@ static inline struct net_bridge_vlan_group *nbp_vlan_group_rcu(
+ }
+
+ static inline void br_vlan_get_stats(const struct net_bridge_vlan *v,
+- struct br_vlan_stats *stats)
++ struct pcpu_sw_netstats *stats)
+ {
+ }
+
+@@ -1125,6 +1324,88 @@ static inline int br_vlan_bridge_event(struct net_device *dev,
+ {
+ return 0;
+ }
++
++static inline void br_vlan_rtnl_init(void)
++{
++}
++
++static inline void br_vlan_rtnl_uninit(void)
++{
++}
++
++static inline void br_vlan_notify(const struct net_bridge *br,
++ const struct net_bridge_port *p,
++ u16 vid, u16 vid_range,
++ int cmd)
++{
++}
++
++static inline bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
++ const struct net_bridge_vlan *range_end)
++{
++ return true;
++}
++
++static inline int br_vlan_replay(struct net_device *br_dev,
++ struct net_device *dev, const void *ctx,
++ bool adding, struct notifier_block *nb,
++ struct netlink_ext_ack *extack)
++{
++ return -EOPNOTSUPP;
++}
++#endif
++
++/* br_vlan_options.c */
++#ifdef CONFIG_BRIDGE_VLAN_FILTERING
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *range_end);
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v);
++size_t br_vlan_opts_nl_size(void);
++int br_vlan_process_options(const struct net_bridge *br,
++ struct net_bridge_port *p,
++ struct net_bridge_vlan *range_start,
++ struct net_bridge_vlan *range_end,
++ struct nlattr **tb,
++ struct netlink_ext_ack *extack);
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++ const struct net_bridge_vlan *r_end);
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++ const struct net_bridge_vlan *v_opts);
++
++/* vlan state manipulation helpers using *_ONCE to annotate lock-free access */
++static inline u8 br_vlan_get_state(const struct net_bridge_vlan *v)
++{
++ return READ_ONCE(v->state);
++}
++
++static inline void br_vlan_set_state(struct net_bridge_vlan *v, u8 state)
++{
++ WRITE_ONCE(v->state, state);
++}
++
++static inline u8 br_vlan_get_pvid_state(const struct net_bridge_vlan_group *vg)
++{
++ return READ_ONCE(vg->pvid_state);
++}
++
++static inline void br_vlan_set_pvid_state(struct net_bridge_vlan_group *vg,
++ u8 state)
++{
++ WRITE_ONCE(vg->pvid_state, state);
++}
++
++/* learn_allow is true at ingress and false at egress */
++static inline bool br_vlan_state_allowed(u8 state, bool learn_allow)
++{
++ switch (state) {
++ case BR_STATE_LEARNING:
++ return learn_allow;
++ case BR_STATE_FORWARDING:
++ return true;
++ default:
++ return false;
++ }
++}
+ #endif
+
+ struct nf_br_ops {
+@@ -1196,6 +1477,12 @@ int br_setlink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags,
+ int br_dellink(struct net_device *dev, struct nlmsghdr *nlmsg, u16 flags);
+ int br_getlink(struct sk_buff *skb, u32 pid, u32 seq, struct net_device *dev,
+ u32 filter_mask, int nlflags);
++int br_process_vlan_info(struct net_bridge *br,
++ struct net_bridge_port *p, int cmd,
++ struct bridge_vlan_info *vinfo_curr,
++ struct bridge_vlan_info **vinfo_last,
++ bool *changed,
++ struct netlink_ext_ack *extack);
+
+ #ifdef CONFIG_SYSFS
+ /* br_sysfs_if.c */
+diff --git a/net/bridge/br_private_tunnel.h b/net/bridge/br_private_tunnel.h
+index 2bdef2e..25be963 100644
+--- a/net/bridge/br_private_tunnel.h
++++ b/net/bridge/br_private_tunnel.h
+@@ -42,6 +42,10 @@ int br_handle_ingress_vlan_tunnel(struct sk_buff *skb,
+ struct net_bridge_vlan_group *vg);
+ int br_handle_egress_vlan_tunnel(struct sk_buff *skb,
+ struct net_bridge_vlan *vlan);
++bool vlan_tunid_inrange(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *v_last);
++int br_vlan_tunnel_info(struct net_bridge_port *p, int cmd,
++ u16 vid, u32 tun_id, bool *changed);
+ #else
+ static inline int vlan_tunnel_init(struct net_bridge_vlan_group *vg)
+ {
+diff --git a/net/bridge/br_vlan.c b/net/bridge/br_vlan.c
+index bcfd169..2b5950c 100644
+--- a/net/bridge/br_vlan.c
++++ b/net/bridge/br_vlan.c
+@@ -34,13 +34,15 @@ static struct net_bridge_vlan *br_vlan_lookup(struct rhashtable *tbl, u16 vid)
+ return rhashtable_lookup_fast(tbl, &vid, br_vlan_rht_params);
+ }
+
+-static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg, u16 vid)
++static bool __vlan_add_pvid(struct net_bridge_vlan_group *vg,
++ const struct net_bridge_vlan *v)
+ {
+- if (vg->pvid == vid)
++ if (vg->pvid == v->vid)
+ return false;
+
+ smp_wmb();
+- vg->pvid = vid;
++ br_vlan_set_pvid_state(vg, v->state);
++ vg->pvid = v->vid;
+
+ return true;
+ }
+@@ -69,7 +71,7 @@ static bool __vlan_add_flags(struct net_bridge_vlan *v, u16 flags)
+ vg = nbp_vlan_group(v->port);
+
+ if (flags & BRIDGE_VLAN_INFO_PVID)
+- ret = __vlan_add_pvid(vg, v->vid);
++ ret = __vlan_add_pvid(vg, v);
+ else
+ ret = __vlan_delete_pvid(vg, v->vid);
+
+@@ -257,6 +259,10 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ &changed, extack);
+ if (err)
+ goto out_filt;
++
++ if (changed)
++ br_vlan_notify(br, NULL, v->vid, 0,
++ RTM_NEWVLAN);
+ }
+
+ masterv = br_vlan_get_master(br, v->vid, extack);
+@@ -266,7 +272,7 @@ static int __vlan_add(struct net_bridge_vlan *v, u16 flags,
+ }
+ v->brvlan = masterv;
+ if (br_opt_get(br, BROPT_VLAN_STATS_PER_PORT)) {
+- v->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++ v->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ if (!v->stats) {
+ err = -ENOMEM;
+ goto out_filt;
+@@ -382,13 +388,31 @@ static void __vlan_group_free(struct net_bridge_vlan_group *vg)
+ kfree(vg);
+ }
+
+-static void __vlan_flush(struct net_bridge_vlan_group *vg)
++static void __vlan_flush(const struct net_bridge *br,
++ const struct net_bridge_port *p,
++ struct net_bridge_vlan_group *vg)
+ {
+ struct net_bridge_vlan *vlan, *tmp;
++ u16 v_start = 0, v_end = 0;
+
+ __vlan_delete_pvid(vg, vg->pvid);
+- list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist)
++ list_for_each_entry_safe(vlan, tmp, &vg->vlan_list, vlist) {
++ /* take care of disjoint ranges */
++ if (!v_start) {
++ v_start = vlan->vid;
++ } else if (vlan->vid - v_end != 1) {
++ /* found range end, notify and start next one */
++ br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
++ v_start = vlan->vid;
++ }
++ v_end = vlan->vid;
++
+ __vlan_del(vlan);
++ }
++
++ /* notify about the last/whole vlan range */
++ if (v_start)
++ br_vlan_notify(br, p, v_start, v_end, RTM_DELVLAN);
+ }
+
+ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+@@ -396,7 +420,7 @@ struct sk_buff *br_handle_vlan(struct net_bridge *br,
+ struct net_bridge_vlan_group *vg,
+ struct sk_buff *skb)
+ {
+- struct br_vlan_stats *stats;
++ struct pcpu_sw_netstats *stats;
+ struct net_bridge_vlan *v;
+ u16 vid;
+
+@@ -448,7 +472,7 @@ static bool __allowed_ingress(const struct net_bridge *br,
+ struct net_bridge_vlan_group *vg,
+ struct sk_buff *skb, u16 *vid)
+ {
+- struct br_vlan_stats *stats;
++ struct pcpu_sw_netstats *stats;
+ struct net_bridge_vlan *v;
+ bool tagged;
+
+@@ -666,7 +690,7 @@ int br_vlan_add(struct net_bridge *br, u16 vid, u16 flags, bool *changed,
+ if (!vlan)
+ return -ENOMEM;
+
+- vlan->stats = netdev_alloc_pcpu_stats(struct br_vlan_stats);
++ vlan->stats = netdev_alloc_pcpu_stats(struct pcpu_sw_netstats);
+ if (!vlan->stats) {
+ kfree(vlan);
+ return -ENOMEM;
+@@ -718,7 +742,7 @@ void br_vlan_flush(struct net_bridge *br)
+ ASSERT_RTNL();
+
+ vg = br_vlan_group(br);
+- __vlan_flush(vg);
++ __vlan_flush(br, NULL, vg);
+ RCU_INIT_POINTER(br->vlgrp, NULL);
+ synchronize_rcu();
+ __vlan_group_free(vg);
+@@ -927,12 +951,15 @@ static void br_vlan_disable_default_pvid(struct net_bridge *br)
+ /* Disable default_pvid on all ports where it is still
+ * configured.
+ */
+- if (vlan_default_pvid(br_vlan_group(br), pvid))
+- br_vlan_delete(br, pvid);
++ if (vlan_default_pvid(br_vlan_group(br), pvid)) {
++ if (!br_vlan_delete(br, pvid))
++ br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
++ }
+
+ list_for_each_entry(p, &br->port_list, list) {
+- if (vlan_default_pvid(nbp_vlan_group(p), pvid))
+- nbp_vlan_delete(p, pvid);
++ if (vlan_default_pvid(nbp_vlan_group(p), pvid) &&
++ !nbp_vlan_delete(p, pvid))
++ br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ }
+
+ br->default_pvid = 0;
+@@ -974,7 +1001,10 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ &vlchange, extack);
+ if (err)
+ goto out;
+- br_vlan_delete(br, old_pvid);
++
++ if (br_vlan_delete(br, old_pvid))
++ br_vlan_notify(br, NULL, old_pvid, 0, RTM_DELVLAN);
++ br_vlan_notify(br, NULL, pvid, 0, RTM_NEWVLAN);
+ set_bit(0, changed);
+ }
+
+@@ -994,7 +1024,9 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ &vlchange, extack);
+ if (err)
+ goto err_port;
+- nbp_vlan_delete(p, old_pvid);
++ if (nbp_vlan_delete(p, old_pvid))
++ br_vlan_notify(br, p, old_pvid, 0, RTM_DELVLAN);
++ br_vlan_notify(p->br, p, pvid, 0, RTM_NEWVLAN);
+ set_bit(p->port_no, changed);
+ }
+
+@@ -1009,22 +1041,28 @@ int __br_vlan_set_default_pvid(struct net_bridge *br, u16 pvid,
+ if (!test_bit(p->port_no, changed))
+ continue;
+
+- if (old_pvid)
++ if (old_pvid) {
+ nbp_vlan_add(p, old_pvid,
+ BRIDGE_VLAN_INFO_PVID |
+ BRIDGE_VLAN_INFO_UNTAGGED,
+ &vlchange, NULL);
++ br_vlan_notify(p->br, p, old_pvid, 0, RTM_NEWVLAN);
++ }
+ nbp_vlan_delete(p, pvid);
++ br_vlan_notify(br, p, pvid, 0, RTM_DELVLAN);
+ }
+
+ if (test_bit(0, changed)) {
+- if (old_pvid)
++ if (old_pvid) {
+ br_vlan_add(br, old_pvid,
+ BRIDGE_VLAN_INFO_PVID |
+ BRIDGE_VLAN_INFO_UNTAGGED |
+ BRIDGE_VLAN_INFO_BRENTRY,
+ &vlchange, NULL);
++ br_vlan_notify(br, NULL, old_pvid, 0, RTM_NEWVLAN);
++ }
+ br_vlan_delete(br, pvid);
++ br_vlan_notify(br, NULL, pvid, 0, RTM_DELVLAN);
+ }
+ goto out;
+ }
+@@ -1117,6 +1155,7 @@ int nbp_vlan_init(struct net_bridge_port *p, struct netlink_ext_ack *extack)
+ &changed, extack);
+ if (ret)
+ goto err_vlan_add;
++ br_vlan_notify(p->br, p, p->br->default_pvid, 0, RTM_NEWVLAN);
+ }
+ out:
+ return ret;
+@@ -1198,21 +1237,21 @@ void nbp_vlan_flush(struct net_bridge_port *port)
+ ASSERT_RTNL();
+
+ vg = nbp_vlan_group(port);
+- __vlan_flush(vg);
++ __vlan_flush(port->br, port, vg);
+ RCU_INIT_POINTER(port->vlgrp, NULL);
+ synchronize_rcu();
+ __vlan_group_free(vg);
+ }
+
+ void br_vlan_get_stats(const struct net_bridge_vlan *v,
+- struct br_vlan_stats *stats)
++ struct pcpu_sw_netstats *stats)
+ {
+ int i;
+
+ memset(stats, 0, sizeof(*stats));
+ for_each_possible_cpu(i) {
+ u64 rxpackets, rxbytes, txpackets, txbytes;
+- struct br_vlan_stats *cpu_stats;
++ struct pcpu_sw_netstats *cpu_stats;
+ unsigned int start;
+
+ cpu_stats = per_cpu_ptr(v->stats, i);
+@@ -1526,8 +1565,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ {
+ struct netdev_notifier_changeupper_info *info;
+ struct net_bridge *br = netdev_priv(dev);
+- bool changed;
+- int ret = 0;
++ int vlcmd = 0, ret = 0;
++ bool changed = false;
+
+ switch (event) {
+ case NETDEV_REGISTER:
+@@ -1535,9 +1574,11 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ BRIDGE_VLAN_INFO_PVID |
+ BRIDGE_VLAN_INFO_UNTAGGED |
+ BRIDGE_VLAN_INFO_BRENTRY, &changed, NULL);
++ vlcmd = RTM_NEWVLAN;
+ break;
+ case NETDEV_UNREGISTER:
+- br_vlan_delete(br, br->default_pvid);
++ changed = !br_vlan_delete(br, br->default_pvid);
++ vlcmd = RTM_DELVLAN;
+ break;
+ case NETDEV_CHANGEUPPER:
+ info = ptr;
+@@ -1551,6 +1592,8 @@ int br_vlan_bridge_event(struct net_device *dev, unsigned long event, void *ptr)
+ br_vlan_link_state_change(dev, br);
+ break;
+ }
++ if (changed)
++ br_vlan_notify(br, NULL, br->default_pvid, 0, vlcmd);
+
+ return ret;
+ }
+@@ -1569,3 +1612,608 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
+ break;
+ }
+ }
++
++static bool br_vlan_stats_fill(struct sk_buff *skb,
++ const struct net_bridge_vlan *v)
++{
++ struct pcpu_sw_netstats stats;
++ struct nlattr *nest;
++
++ nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_STATS);
++ if (!nest)
++ return false;
++
++ br_vlan_get_stats(v, &stats);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_BYTES, stats.rx_bytes,
++ BRIDGE_VLANDB_STATS_PAD) ||
++ nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_RX_PACKETS,
++ stats.rx_packets, BRIDGE_VLANDB_STATS_PAD) ||
++ nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_BYTES, stats.tx_bytes,
++ BRIDGE_VLANDB_STATS_PAD) ||
++ nla_put_u64_64bit(skb, BRIDGE_VLANDB_STATS_TX_PACKETS,
++ stats.tx_packets, BRIDGE_VLANDB_STATS_PAD))
++ goto out_err;
++
++ nla_nest_end(skb, nest);
++
++ return true;
++
++out_err:
++ nla_nest_cancel(skb, nest);
++ return false;
++}
++
++/* v_opts is used to dump the options which must be equal in the whole range */
++static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 vid_range,
++ const struct net_bridge_vlan *v_opts,
++ u16 flags,
++ bool dump_stats)
++{
++ struct bridge_vlan_info info;
++ struct nlattr *nest;
++
++ nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY);
++ if (!nest)
++ return false;
++
++ memset(&info, 0, sizeof(info));
++ info.vid = vid;
++ if (flags & BRIDGE_VLAN_INFO_UNTAGGED)
++ info.flags |= BRIDGE_VLAN_INFO_UNTAGGED;
++ if (flags & BRIDGE_VLAN_INFO_PVID)
++ info.flags |= BRIDGE_VLAN_INFO_PVID;
++
++ if (nla_put(skb, BRIDGE_VLANDB_ENTRY_INFO, sizeof(info), &info))
++ goto out_err;
++
++ if (vid_range && vid < vid_range &&
++ !(flags & BRIDGE_VLAN_INFO_PVID) &&
++ nla_put_u16(skb, BRIDGE_VLANDB_ENTRY_RANGE, vid_range))
++ goto out_err;
++
++ if (v_opts) {
++ if (!br_vlan_opts_fill(skb, v_opts))
++ goto out_err;
++
++ if (dump_stats && !br_vlan_stats_fill(skb, v_opts))
++ goto out_err;
++ }
++
++ nla_nest_end(skb, nest);
++
++ return true;
++
++out_err:
++ nla_nest_cancel(skb, nest);
++ return false;
++}
++
++static size_t rtnl_vlan_nlmsg_size(void)
++{
++ return NLMSG_ALIGN(sizeof(struct br_vlan_msg))
++ + nla_total_size(0) /* BRIDGE_VLANDB_ENTRY */
++ + nla_total_size(sizeof(u16)) /* BRIDGE_VLANDB_ENTRY_RANGE */
++ + nla_total_size(sizeof(struct bridge_vlan_info)) /* BRIDGE_VLANDB_ENTRY_INFO */
++ + br_vlan_opts_nl_size(); /* bridge vlan options */
++}
++
++void br_vlan_notify(const struct net_bridge *br,
++ const struct net_bridge_port *p,
++ u16 vid, u16 vid_range,
++ int cmd)
++{
++ struct net_bridge_vlan_group *vg;
++ struct net_bridge_vlan *v = NULL;
++ struct br_vlan_msg *bvm;
++ struct nlmsghdr *nlh;
++ struct sk_buff *skb;
++ int err = -ENOBUFS;
++ struct net *net;
++ u16 flags = 0;
++ int ifindex;
++
++ /* right now notifications are done only with rtnl held */
++ ASSERT_RTNL();
++
++ if (p) {
++ ifindex = p->dev->ifindex;
++ vg = nbp_vlan_group(p);
++ net = dev_net(p->dev);
++ } else {
++ ifindex = br->dev->ifindex;
++ vg = br_vlan_group(br);
++ net = dev_net(br->dev);
++ }
++
++ skb = nlmsg_new(rtnl_vlan_nlmsg_size(), GFP_KERNEL);
++ if (!skb)
++ goto out_err;
++
++ err = -EMSGSIZE;
++ nlh = nlmsg_put(skb, 0, 0, cmd, sizeof(*bvm), 0);
++ if (!nlh)
++ goto out_err;
++ bvm = nlmsg_data(nlh);
++ memset(bvm, 0, sizeof(*bvm));
++ bvm->family = AF_BRIDGE;
++ bvm->ifindex = ifindex;
++
++ switch (cmd) {
++ case RTM_NEWVLAN:
++ /* need to find the vlan due to flags/options */
++ v = br_vlan_find(vg, vid);
++ if (!v || !br_vlan_should_use(v))
++ goto out_kfree;
++
++ flags = v->flags;
++ if (br_get_pvid(vg) == v->vid)
++ flags |= BRIDGE_VLAN_INFO_PVID;
++ break;
++ case RTM_DELVLAN:
++ break;
++ default:
++ goto out_kfree;
++ }
++
++ if (!br_vlan_fill_vids(skb, vid, vid_range, v, flags, false))
++ goto out_err;
++
++ nlmsg_end(skb, nlh);
++ rtnl_notify(skb, net, 0, RTNLGRP_BRVLAN, NULL, GFP_KERNEL);
++ return;
++
++out_err:
++ rtnl_set_sk_err(net, RTNLGRP_BRVLAN, err);
++out_kfree:
++ kfree_skb(skb);
++}
++
++static int br_vlan_replay_one(struct notifier_block *nb,
++ struct net_device *dev,
++ struct switchdev_obj_port_vlan *vlan,
++ const void *ctx, unsigned long action,
++ struct netlink_ext_ack *extack)
++{
++ struct switchdev_notifier_port_obj_info obj_info = {
++ .info = {
++ .dev = dev,
++ .extack = extack,
++ .ctx = ctx,
++ },
++ .obj = &vlan->obj,
++ };
++ int err;
++
++ err = nb->notifier_call(nb, action, &obj_info);
++ return notifier_to_errno(err);
++}
++
++int br_vlan_replay(struct net_device *br_dev, struct net_device *dev,
++ const void *ctx, bool adding, struct notifier_block *nb,
++ struct netlink_ext_ack *extack)
++{
++ struct net_bridge_vlan_group *vg;
++ struct net_bridge_vlan *v;
++ struct net_bridge_port *p;
++ struct net_bridge *br;
++ unsigned long action;
++ int err = 0;
++ u16 pvid;
++
++ ASSERT_RTNL();
++
++ if (!nb)
++ return 0;
++
++ if (!netif_is_bridge_master(br_dev))
++ return -EINVAL;
++
++ if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++ return -EINVAL;
++
++ if (netif_is_bridge_master(dev)) {
++ br = netdev_priv(dev);
++ vg = br_vlan_group(br);
++ p = NULL;
++ } else {
++ p = br_port_get_rtnl(dev);
++ if (WARN_ON(!p))
++ return -EINVAL;
++ vg = nbp_vlan_group(p);
++ br = p->br;
++ }
++
++ if (!vg)
++ return 0;
++
++ if (adding)
++ action = SWITCHDEV_PORT_OBJ_ADD;
++ else
++ action = SWITCHDEV_PORT_OBJ_DEL;
++
++ pvid = br_get_pvid(vg);
++
++ list_for_each_entry(v, &vg->vlan_list, vlist) {
++ struct switchdev_obj_port_vlan vlan = {
++ .obj.orig_dev = dev,
++ .obj.id = SWITCHDEV_OBJ_ID_PORT_VLAN,
++ .flags = br_vlan_flags(v, pvid),
++ .vid = v->vid,
++ };
++
++ if (!br_vlan_should_use(v))
++ continue;
++
++ err = br_vlan_replay_one(nb, dev, &vlan, ctx, action, extack);
++ if (err)
++ return err;
++ }
++
++ return err;
++}
++
++/* check if v_curr can enter a range ending in range_end */
++bool br_vlan_can_enter_range(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *range_end)
++{
++ return v_curr->vid - range_end->vid == 1 &&
++ range_end->flags == v_curr->flags &&
++ br_vlan_opts_eq_range(v_curr, range_end);
++}
++
++static int br_vlan_dump_dev(const struct net_device *dev,
++ struct sk_buff *skb,
++ struct netlink_callback *cb,
++ u32 dump_flags)
++{
++ struct net_bridge_vlan *v, *range_start = NULL, *range_end = NULL;
++ bool dump_global = !!(dump_flags & BRIDGE_VLANDB_DUMPF_GLOBAL);
++ bool dump_stats = !!(dump_flags & BRIDGE_VLANDB_DUMPF_STATS);
++ struct net_bridge_vlan_group *vg;
++ int idx = 0, s_idx = cb->args[1];
++ struct nlmsghdr *nlh = NULL;
++ struct net_bridge_port *p;
++ struct br_vlan_msg *bvm;
++ struct net_bridge *br;
++ int err = 0;
++ u16 pvid;
++
++ if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev))
++ return -EINVAL;
++
++ if (netif_is_bridge_master(dev)) {
++ br = netdev_priv(dev);
++ vg = br_vlan_group_rcu(br);
++ p = NULL;
++ } else {
++ /* global options are dumped only for bridge devices */
++ if (dump_global)
++ return 0;
++
++ p = br_port_get_rcu(dev);
++ if (WARN_ON(!p))
++ return -EINVAL;
++ vg = nbp_vlan_group_rcu(p);
++ br = p->br;
++ }
++
++ if (!vg)
++ return 0;
++
++ nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
++ RTM_NEWVLAN, sizeof(*bvm), NLM_F_MULTI);
++ if (!nlh)
++ return -EMSGSIZE;
++ bvm = nlmsg_data(nlh);
++ memset(bvm, 0, sizeof(*bvm));
++ bvm->family = PF_BRIDGE;
++ bvm->ifindex = dev->ifindex;
++ pvid = br_get_pvid(vg);
++
++ /* idx must stay at range's beginning until it is filled in */
++ list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
++ if (!dump_global && !br_vlan_should_use(v))
++ continue;
++ if (idx < s_idx) {
++ idx++;
++ continue;
++ }
++
++ if (!range_start) {
++ range_start = v;
++ range_end = v;
++ continue;
++ }
++
++ if (dump_global) {
++ if (br_vlan_global_opts_can_enter_range(v, range_end))
++ goto update_end;
++ if (!br_vlan_global_opts_fill(skb, range_start->vid,
++ range_end->vid,
++ range_start)) {
++ err = -EMSGSIZE;
++ break;
++ }
++ /* advance number of filled vlans */
++ idx += range_end->vid - range_start->vid + 1;
++
++ range_start = v;
++ } else if (dump_stats || v->vid == pvid ||
++ !br_vlan_can_enter_range(v, range_end)) {
++ u16 vlan_flags = br_vlan_flags(range_start, pvid);
++
++ if (!br_vlan_fill_vids(skb, range_start->vid,
++ range_end->vid, range_start,
++ vlan_flags, dump_stats)) {
++ err = -EMSGSIZE;
++ break;
++ }
++ /* advance number of filled vlans */
++ idx += range_end->vid - range_start->vid + 1;
++
++ range_start = v;
++ }
++update_end:
++ range_end = v;
++ }
++
++ /* err will be 0 and range_start will be set in 3 cases here:
++ * - first vlan (range_start == range_end)
++ * - last vlan (range_start == range_end, not in range)
++ * - last vlan range (range_start != range_end, in range)
++ */
++ if (!err && range_start) {
++ if (dump_global &&
++ !br_vlan_global_opts_fill(skb, range_start->vid,
++ range_end->vid, range_start))
++ err = -EMSGSIZE;
++ else if (!dump_global &&
++ !br_vlan_fill_vids(skb, range_start->vid,
++ range_end->vid, range_start,
++ br_vlan_flags(range_start, pvid),
++ dump_stats))
++ err = -EMSGSIZE;
++ }
++
++ cb->args[1] = err ? idx : 0;
++
++ nlmsg_end(skb, nlh);
++
++ return err;
++}
++
++static const struct nla_policy br_vlan_db_dump_pol[BRIDGE_VLANDB_DUMP_MAX + 1] = {
++ [BRIDGE_VLANDB_DUMP_FLAGS] = { .type = NLA_U32 },
++};
++
++static int br_vlan_rtm_dump(struct sk_buff *skb, struct netlink_callback *cb)
++{
++ struct nlattr *dtb[BRIDGE_VLANDB_DUMP_MAX + 1];
++ int idx = 0, err = 0, s_idx = cb->args[0];
++ struct net *net = sock_net(skb->sk);
++ struct br_vlan_msg *bvm;
++ struct net_device *dev;
++ u32 dump_flags = 0;
++
++ err = nlmsg_parse(cb->nlh, sizeof(*bvm), dtb, BRIDGE_VLANDB_DUMP_MAX,
++ br_vlan_db_dump_pol, cb->extack);
++ if (err < 0)
++ return err;
++
++ bvm = nlmsg_data(cb->nlh);
++ if (dtb[BRIDGE_VLANDB_DUMP_FLAGS])
++ dump_flags = nla_get_u32(dtb[BRIDGE_VLANDB_DUMP_FLAGS]);
++
++ rcu_read_lock();
++ if (bvm->ifindex) {
++ dev = dev_get_by_index_rcu(net, bvm->ifindex);
++ if (!dev) {
++ err = -ENODEV;
++ goto out_err;
++ }
++ err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++ /* if the dump completed without an error we return 0 here */
++ if (err != -EMSGSIZE)
++ goto out_err;
++ } else {
++ for_each_netdev_rcu(net, dev) {
++ if (idx < s_idx)
++ goto skip;
++
++ err = br_vlan_dump_dev(dev, skb, cb, dump_flags);
++ if (err == -EMSGSIZE)
++ break;
++skip:
++ idx++;
++ }
++ }
++ cb->args[0] = idx;
++ rcu_read_unlock();
++
++ return skb->len;
++
++out_err:
++ rcu_read_unlock();
++
++ return err;
++}
++
++static const struct nla_policy br_vlan_db_policy[BRIDGE_VLANDB_ENTRY_MAX + 1] = {
++ [BRIDGE_VLANDB_ENTRY_INFO] =
++ NLA_POLICY_EXACT_LEN(sizeof(struct bridge_vlan_info)),
++ [BRIDGE_VLANDB_ENTRY_RANGE] = { .type = NLA_U16 },
++ [BRIDGE_VLANDB_ENTRY_STATE] = { .type = NLA_U8 },
++ [BRIDGE_VLANDB_ENTRY_TUNNEL_INFO] = { .type = NLA_NESTED },
++ [BRIDGE_VLANDB_ENTRY_MCAST_ROUTER] = { .type = NLA_U8 },
++};
++
++static int br_vlan_rtm_process_one(struct net_device *dev,
++ const struct nlattr *attr,
++ int cmd, struct netlink_ext_ack *extack)
++{
++ struct bridge_vlan_info *vinfo, vrange_end, *vinfo_last = NULL;
++ struct nlattr *tb[BRIDGE_VLANDB_ENTRY_MAX + 1];
++ bool changed = false, skip_processing = false;
++ struct net_bridge_vlan_group *vg;
++ struct net_bridge_port *p = NULL;
++ int err = 0, cmdmap = 0;
++ struct net_bridge *br;
++
++ if (netif_is_bridge_master(dev)) {
++ br = netdev_priv(dev);
++ vg = br_vlan_group(br);
++ } else {
++ p = br_port_get_rtnl(dev);
++ if (WARN_ON(!p))
++ return -ENODEV;
++ br = p->br;
++ vg = nbp_vlan_group(p);
++ }
++
++ if (WARN_ON(!vg))
++ return -ENODEV;
++
++ err = nla_parse_nested(tb, BRIDGE_VLANDB_ENTRY_MAX, attr,
++ br_vlan_db_policy, extack);
++ if (err)
++ return err;
++
++ if (!tb[BRIDGE_VLANDB_ENTRY_INFO]) {
++ NL_SET_ERR_MSG_MOD(extack, "Missing vlan entry info");
++ return -EINVAL;
++ }
++ memset(&vrange_end, 0, sizeof(vrange_end));
++
++ vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++ if (vinfo->flags & (BRIDGE_VLAN_INFO_RANGE_BEGIN |
++ BRIDGE_VLAN_INFO_RANGE_END)) {
++ NL_SET_ERR_MSG_MOD(extack, "Old-style vlan ranges are not allowed when using RTM vlan calls");
++ return -EINVAL;
++ }
++ if (!br_vlan_valid_id(vinfo->vid, extack))
++ return -EINVAL;
++
++ if (tb[BRIDGE_VLANDB_ENTRY_RANGE]) {
++ vrange_end.vid = nla_get_u16(tb[BRIDGE_VLANDB_ENTRY_RANGE]);
++ /* validate user-provided flags without RANGE_BEGIN */
++ vrange_end.flags = BRIDGE_VLAN_INFO_RANGE_END | vinfo->flags;
++ vinfo->flags |= BRIDGE_VLAN_INFO_RANGE_BEGIN;
++
++ /* vinfo_last is the range start, vinfo the range end */
++ vinfo_last = vinfo;
++ vinfo = &vrange_end;
++
++ if (!br_vlan_valid_id(vinfo->vid, extack) ||
++ !br_vlan_valid_range(vinfo, vinfo_last, extack))
++ return -EINVAL;
++ }
++
++ switch (cmd) {
++ case RTM_NEWVLAN:
++ cmdmap = RTM_SETLINK;
++ skip_processing = !!(vinfo->flags & BRIDGE_VLAN_INFO_ONLY_OPTS);
++ break;
++ case RTM_DELVLAN:
++ cmdmap = RTM_DELLINK;
++ break;
++ }
++
++ if (!skip_processing) {
++ struct bridge_vlan_info *tmp_last = vinfo_last;
++
++ /* br_process_vlan_info may overwrite vinfo_last */
++ err = br_process_vlan_info(br, p, cmdmap, vinfo, &tmp_last,
++ &changed, extack);
++
++ /* notify first if anything changed */
++ if (changed)
++ br_ifinfo_notify(cmdmap, br, p);
++
++ if (err)
++ return err;
++ }
++
++ /* deal with options */
++ if (cmd == RTM_NEWVLAN) {
++ struct net_bridge_vlan *range_start, *range_end;
++
++ if (vinfo_last) {
++ range_start = br_vlan_find(vg, vinfo_last->vid);
++ range_end = br_vlan_find(vg, vinfo->vid);
++ } else {
++ range_start = br_vlan_find(vg, vinfo->vid);
++ range_end = range_start;
++ }
++
++ err = br_vlan_process_options(br, p, range_start, range_end,
++ tb, extack);
++ }
++
++ return err;
++}
++
++static int br_vlan_rtm_process(struct sk_buff *skb, struct nlmsghdr *nlh,
++ struct netlink_ext_ack *extack)
++{
++ struct net *net = sock_net(skb->sk);
++ struct br_vlan_msg *bvm;
++ struct net_device *dev;
++ struct nlattr *attr;
++ int err, vlans = 0;
++ int rem;
++
++ /* this should validate the header and check for remaining bytes */
++ err = nlmsg_parse(nlh, sizeof(*bvm), NULL, BRIDGE_VLANDB_MAX, NULL,
++ extack);
++ if (err < 0)
++ return err;
++
++ bvm = nlmsg_data(nlh);
++ dev = __dev_get_by_index(net, bvm->ifindex);
++ if (!dev)
++ return -ENODEV;
++
++ if (!netif_is_bridge_master(dev) && !netif_is_bridge_port(dev)) {
++ NL_SET_ERR_MSG_MOD(extack, "The device is not a valid bridge or bridge port");
++ return -EINVAL;
++ }
++
++ nlmsg_for_each_attr(attr, nlh, sizeof(*bvm), rem) {
++ switch (nla_type(attr)) {
++ case BRIDGE_VLANDB_ENTRY:
++ err = br_vlan_rtm_process_one(dev, attr,
++ nlh->nlmsg_type,
++ extack);
++ break;
++ default:
++ continue;
++ }
++
++ vlans++;
++ if (err)
++ break;
++ }
++ if (!vlans) {
++ NL_SET_ERR_MSG_MOD(extack, "No vlans found to process");
++ err = -EINVAL;
++ }
++
++ return err;
++}
++
++void br_vlan_rtnl_init(void)
++{
++ rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_GETVLAN, NULL,
++ br_vlan_rtm_dump, 0);
++ rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_NEWVLAN,
++ br_vlan_rtm_process, NULL, 0);
++ rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_DELVLAN,
++ br_vlan_rtm_process, NULL, 0);
++}
++
++void br_vlan_rtnl_uninit(void)
++{
++ rtnl_unregister(PF_BRIDGE, RTM_GETVLAN);
++ rtnl_unregister(PF_BRIDGE, RTM_NEWVLAN);
++ rtnl_unregister(PF_BRIDGE, RTM_DELVLAN);
++}
+diff --git a/net/bridge/br_vlan_options.c b/net/bridge/br_vlan_options.c
+new file mode 100644
+index 0000000..5e48c29
+--- /dev/null
++++ b/net/bridge/br_vlan_options.c
+@@ -0,0 +1,346 @@
++// SPDX-License-Identifier: GPL-2.0-only
++// Copyright (c) 2020, Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
++#include <linux/kernel.h>
++#include <linux/netdevice.h>
++#include <linux/rtnetlink.h>
++#include <linux/slab.h>
++#include <net/ip_tunnels.h>
++
++#include "br_private.h"
++#include "br_private_tunnel.h"
++
++static bool __vlan_tun_put(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++ __be32 tid = tunnel_id_to_key32(v->tinfo.tunnel_id);
++ struct nlattr *nest;
++
++ if (!v->tinfo.tunnel_dst)
++ return true;
++
++ nest = nla_nest_start(skb, BRIDGE_VLANDB_ENTRY_TUNNEL_INFO);
++ if (!nest)
++ return false;
++ if (nla_put_u32(skb, BRIDGE_VLANDB_TINFO_ID, be32_to_cpu(tid))) {
++ nla_nest_cancel(skb, nest);
++ return false;
++ }
++ nla_nest_end(skb, nest);
++
++ return true;
++}
++
++static bool __vlan_tun_can_enter_range(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *range_end)
++{
++ return (!v_curr->tinfo.tunnel_dst && !range_end->tinfo.tunnel_dst) ||
++ vlan_tunid_inrange(v_curr, range_end);
++}
++
++/* check if the options' state of v_curr allow it to enter the range */
++bool br_vlan_opts_eq_range(struct net_bridge_vlan *v_curr,
++ struct net_bridge_vlan *range_end)
++{
++ u8 range_mc_rtr = br_vlan_multicast_router(range_end);
++ u8 curr_mc_rtr = br_vlan_multicast_router(v_curr);
++
++ return v_curr->state == range_end->state &&
++ __vlan_tun_can_enter_range(v_curr, range_end) &&
++ curr_mc_rtr == range_mc_rtr;
++}
++
++bool br_vlan_opts_fill(struct sk_buff *skb, const struct net_bridge_vlan *v)
++{
++ if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_STATE, br_vlan_get_state(v)) ||
++ !__vlan_tun_put(skb, v))
++ return false;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ if (nla_put_u8(skb, BRIDGE_VLANDB_ENTRY_MCAST_ROUTER,
++ br_vlan_multicast_router(v)))
++ return false;
++#endif
++
++ return true;
++}
++
++size_t br_vlan_opts_nl_size(void)
++{
++ return nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_STATE */
++ + nla_total_size(0) /* BRIDGE_VLANDB_ENTRY_TUNNEL_INFO */
++ + nla_total_size(sizeof(u32)) /* BRIDGE_VLANDB_TINFO_ID */
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ + nla_total_size(sizeof(u8)) /* BRIDGE_VLANDB_ENTRY_MCAST_ROUTER */
++#endif
++ + 0;
++}
++
++static int br_vlan_modify_state(struct net_bridge_vlan_group *vg,
++ struct net_bridge_vlan *v,
++ u8 state,
++ bool *changed,
++ struct netlink_ext_ack *extack)
++{
++ struct net_bridge *br;
++
++ ASSERT_RTNL();
++
++ if (state > BR_STATE_BLOCKING) {
++ NL_SET_ERR_MSG_MOD(extack, "Invalid vlan state");
++ return -EINVAL;
++ }
++
++ if (br_vlan_is_brentry(v))
++ br = v->br;
++ else
++ br = v->port->br;
++
++ if (br->stp_enabled == BR_KERNEL_STP) {
++ NL_SET_ERR_MSG_MOD(extack, "Can't modify vlan state when using kernel STP");
++ return -EBUSY;
++ }
++
++ if (v->state == state)
++ return 0;
++
++ if (v->vid == br_get_pvid(vg))
++ br_vlan_set_pvid_state(vg, state);
++
++ br_vlan_set_state(v, state);
++ *changed = true;
++
++ return 0;
++}
++
++static const struct nla_policy br_vlandb_tinfo_pol[BRIDGE_VLANDB_TINFO_MAX + 1] = {
++ [BRIDGE_VLANDB_TINFO_ID] = { .type = NLA_U32 },
++ [BRIDGE_VLANDB_TINFO_CMD] = { .type = NLA_U32 },
++};
++
++static int br_vlan_modify_tunnel(struct net_bridge_port *p,
++ struct net_bridge_vlan *v,
++ struct nlattr **tb,
++ bool *changed,
++ struct netlink_ext_ack *extack)
++{
++ struct nlattr *tun_tb[BRIDGE_VLANDB_TINFO_MAX + 1], *attr;
++ struct bridge_vlan_info *vinfo;
++ u32 tun_id = 0;
++ int cmd, err;
++
++ if (!p) {
++ NL_SET_ERR_MSG_MOD(extack, "Can't modify tunnel mapping of non-port vlans");
++ return -EINVAL;
++ }
++ if (!(p->flags & BR_VLAN_TUNNEL)) {
++ NL_SET_ERR_MSG_MOD(extack, "Port doesn't have tunnel flag set");
++ return -EINVAL;
++ }
++
++ attr = tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO];
++ err = nla_parse_nested(tun_tb, BRIDGE_VLANDB_TINFO_MAX, attr,
++ br_vlandb_tinfo_pol, extack);
++ if (err)
++ return err;
++
++ if (!tun_tb[BRIDGE_VLANDB_TINFO_CMD]) {
++ NL_SET_ERR_MSG_MOD(extack, "Missing tunnel command attribute");
++ return -ENOENT;
++ }
++ cmd = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_CMD]);
++ switch (cmd) {
++ case RTM_SETLINK:
++ if (!tun_tb[BRIDGE_VLANDB_TINFO_ID]) {
++ NL_SET_ERR_MSG_MOD(extack, "Missing tunnel id attribute");
++ return -ENOENT;
++ }
++ /* when working on vlan ranges this is the starting tunnel id */
++ tun_id = nla_get_u32(tun_tb[BRIDGE_VLANDB_TINFO_ID]);
++ /* vlan info attr is guaranteed by br_vlan_rtm_process_one */
++ vinfo = nla_data(tb[BRIDGE_VLANDB_ENTRY_INFO]);
++ /* tunnel ids are mapped to each vlan in increasing order,
++ * the starting vlan is in BRIDGE_VLANDB_ENTRY_INFO and v is the
++ * current vlan, so we compute: tun_id + v - vinfo->vid
++ */
++ tun_id += v->vid - vinfo->vid;
++ break;
++ case RTM_DELLINK:
++ break;
++ default:
++ NL_SET_ERR_MSG_MOD(extack, "Unsupported tunnel command");
++ return -EINVAL;
++ }
++
++ return br_vlan_tunnel_info(p, cmd, v->vid, tun_id, changed);
++}
++
++static int br_vlan_process_one_opts(const struct net_bridge *br,
++ struct net_bridge_port *p,
++ struct net_bridge_vlan_group *vg,
++ struct net_bridge_vlan *v,
++ struct nlattr **tb,
++ bool *changed,
++ struct netlink_ext_ack *extack)
++{
++ int err;
++
++ *changed = false;
++ if (tb[BRIDGE_VLANDB_ENTRY_STATE]) {
++ u8 state = nla_get_u8(tb[BRIDGE_VLANDB_ENTRY_STATE]);
++
++ err = br_vlan_modify_state(vg, v, state, changed, extack);
++ if (err)
++ return err;
++ }
++ if (tb[BRIDGE_VLANDB_ENTRY_TUNNEL_INFO]) {
++ err = br_vlan_modify_tunnel(p, v, tb, changed, extack);
++ if (err)
++ return err;
++ }
++
++ return 0;
++}
++
++int br_vlan_process_options(const struct net_bridge *br,
++ struct net_bridge_port *p,
++ struct net_bridge_vlan *range_start,
++ struct net_bridge_vlan *range_end,
++ struct nlattr **tb,
++ struct netlink_ext_ack *extack)
++{
++ struct net_bridge_vlan *v, *curr_start = NULL, *curr_end = NULL;
++ struct net_bridge_vlan_group *vg;
++ int vid, err = 0;
++ u16 pvid;
++
++ if (p)
++ vg = nbp_vlan_group(p);
++ else
++ vg = br_vlan_group(br);
++
++ if (!range_start || !br_vlan_should_use(range_start)) {
++ NL_SET_ERR_MSG_MOD(extack, "Vlan range start doesn't exist, can't process options");
++ return -ENOENT;
++ }
++ if (!range_end || !br_vlan_should_use(range_end)) {
++ NL_SET_ERR_MSG_MOD(extack, "Vlan range end doesn't exist, can't process options");
++ return -ENOENT;
++ }
++
++ pvid = br_get_pvid(vg);
++ for (vid = range_start->vid; vid <= range_end->vid; vid++) {
++ bool changed = false;
++
++ v = br_vlan_find(vg, vid);
++ if (!v || !br_vlan_should_use(v)) {
++ NL_SET_ERR_MSG_MOD(extack, "Vlan in range doesn't exist, can't process options");
++ err = -ENOENT;
++ break;
++ }
++
++ err = br_vlan_process_one_opts(br, p, vg, v, tb, &changed,
++ extack);
++ if (err)
++ break;
++
++ if (changed) {
++ /* vlan options changed, check for range */
++ if (!curr_start) {
++ curr_start = v;
++ curr_end = v;
++ continue;
++ }
++
++ if (v->vid == pvid ||
++ !br_vlan_can_enter_range(v, curr_end)) {
++ br_vlan_notify(br, p, curr_start->vid,
++ curr_end->vid, RTM_NEWVLAN);
++ curr_start = v;
++ }
++ curr_end = v;
++ } else {
++ /* nothing changed and nothing to notify yet */
++ if (!curr_start)
++ continue;
++
++ br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++ RTM_NEWVLAN);
++ curr_start = NULL;
++ curr_end = NULL;
++ }
++ }
++ if (curr_start)
++ br_vlan_notify(br, p, curr_start->vid, curr_end->vid,
++ RTM_NEWVLAN);
++
++ return err;
++}
++
++bool br_vlan_global_opts_can_enter_range(const struct net_bridge_vlan *v_curr,
++ const struct net_bridge_vlan *r_end)
++{
++ return v_curr->vid - r_end->vid == 1 &&
++ ((v_curr->priv_flags ^ r_end->priv_flags) &
++ BR_VLFLAG_GLOBAL_MCAST_ENABLED) == 0 &&
++ br_multicast_ctx_options_equal(&v_curr->br_mcast_ctx,
++ &r_end->br_mcast_ctx);
++}
++
++bool br_vlan_global_opts_fill(struct sk_buff *skb, u16 vid, u16 vid_range,
++ const struct net_bridge_vlan *v_opts)
++{
++ struct nlattr *nest2 __maybe_unused;
++ u64 clockval __maybe_unused;
++ struct nlattr *nest;
++
++ nest = nla_nest_start(skb, BRIDGE_VLANDB_GLOBAL_OPTIONS);
++ if (!nest)
++ return false;
++
++ if (nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_ID, vid))
++ goto out_err;
++
++ if (vid_range && vid < vid_range &&
++ nla_put_u16(skb, BRIDGE_VLANDB_GOPTS_RANGE, vid_range))
++ goto out_err;
++
++#ifdef CONFIG_BRIDGE_IGMP_SNOOPING
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_last_member_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_LAST_MEMBER_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_membership_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_MEMBERSHIP_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_querier_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERIER_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_query_response_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_QUERY_RESPONSE_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++ clockval = jiffies_to_clock_t(v_opts->br_mcast_ctx.multicast_startup_query_interval);
++ if (nla_put_u64_64bit(skb, BRIDGE_VLANDB_GOPTS_MCAST_STARTUP_QUERY_INTVL,
++ clockval, BRIDGE_VLANDB_GOPTS_PAD))
++ goto out_err;
++
++#if IS_ENABLED(CONFIG_IPV6)
++ if (nla_put_u8(skb, BRIDGE_VLANDB_GOPTS_MCAST_MLD_VERSION,
++ v_opts->br_mcast_ctx.multicast_mld_version))
++ goto out_err;
++#endif
++#endif
++
++ nla_nest_end(skb, nest);
++
++ return true;
++
++out_err:
++ nla_nest_cancel(skb, nest);
++ return false;
++}
+diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
+index dbc9b2f..706b207 100644
+--- a/net/core/rtnetlink.c
++++ b/net/core/rtnetlink.c
+@@ -1996,6 +1996,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
+ goto cont;
+ if (idx < s_idx)
+ goto cont;
++
+ err = rtnl_fill_ifinfo(skb, dev, net,
+ RTM_NEWLINK,
+ NETLINK_CB(cb->skb).portid,
+diff --git a/net/dsa/slave.c b/net/dsa/slave.c
+index 2dfaa1e..a60a26c 100644
+--- a/net/dsa/slave.c
++++ b/net/dsa/slave.c
+@@ -1495,8 +1495,19 @@ int dsa_slave_create(struct dsa_port *port)
+ goto out_phy;
+ }
+
++ rtnl_lock();
++
++ ret = netdev_upper_dev_link(master, slave_dev, NULL);
++
++ rtnl_unlock();
++
++ if (ret)
++ goto out_unregister;
++
+ return 0;
+
++out_unregister:
++ unregister_netdev(slave_dev);
+ out_phy:
+ rtnl_lock();
+ phylink_disconnect_phy(p->dp->pl);