net: bridge: vlan: add rtnetlink group and notify support
authorNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Tue, 14 Jan 2020 17:56:13 +0000 (19:56 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 15 Jan 2020 12:48:18 +0000 (13:48 +0100)
Add a new rtnetlink group for bridge vlan notifications - RTNLGRP_BRVLAN
and add support for sending vlan notifications (both single and ranges).
No functional changes intended, the notification support will be used by
later patches.

Signed-off-by: Nikolay Aleksandrov <nikolay@cumulusnetworks.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
include/uapi/linux/rtnetlink.h
net/bridge/br_private.h
net/bridge/br_vlan.c

index b333cff14071f77d05c2d4e767887f5a8e135e17..4a8c5b7451570d9e5cce44830e208334675583de 100644 (file)
@@ -730,6 +730,8 @@ enum rtnetlink_groups {
 #define RTNLGRP_IPV6_MROUTE_R  RTNLGRP_IPV6_MROUTE_R
        RTNLGRP_NEXTHOP,
 #define RTNLGRP_NEXTHOP                RTNLGRP_NEXTHOP
+       RTNLGRP_BRVLAN,
+#define RTNLGRP_BRVLAN         RTNLGRP_BRVLAN
        __RTNLGRP_MAX
 };
 #define RTNLGRP_MAX    (__RTNLGRP_MAX - 1)
index ee3871dea68fa20cdf387abd8eee4545ac03b2af..ba162c8197da4a8bea3b0ca4fac1affe5a0376a3 100644 (file)
@@ -960,6 +960,10 @@ 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);
 
 static inline struct net_bridge_vlan_group *br_vlan_group(
                                        const struct net_bridge *br)
@@ -1166,6 +1170,13 @@ 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)
+{
+}
 #endif
 
 struct nf_br_ops {
index 9d64a86f2cbdfcd5004c04ae8103cde893765c37..5d52a26045471807e007de41ccd01dd27d38a090 100644 (file)
@@ -1540,6 +1540,85 @@ out_err:
        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 */
+}
+
+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;
+       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, flags))
+               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);
+}
+
 /* check if v_curr can enter a range ending in range_end */
 static bool br_vlan_can_enter_range(const struct net_bridge_vlan *v_curr,
                                    const struct net_bridge_vlan *range_end)