net: bridge: vlan: add rtm definitions and dump support
authorNikolay Aleksandrov <nikolay@cumulusnetworks.com>
Tue, 14 Jan 2020 17:56:09 +0000 (19:56 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 15 Jan 2020 12:48:17 +0000 (13:48 +0100)
This patch adds vlan rtm definitions:
 - NEWVLAN: to be used for creating vlans, setting options and
   notifications
 - DELVLAN: to be used for deleting vlans
 - GETVLAN: used for dumping vlan information

Dumping vlans which can span multiple messages is added now with basic
information (vid and flags). We use nlmsg_parse() to validate the header
length in order to be able to extend the message with filtering
attributes later.

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

index 4a58e3d..4da04f7 100644 (file)
@@ -165,6 +165,34 @@ struct bridge_stp_xstats {
        __u64 tx_tcn;
 };
 
+/* Bridge vlan RTM header */
+struct br_vlan_msg {
+       __u8 family;
+       __u8 reserved1;
+       __u16 reserved2;
+       __u32 ifindex;
+};
+
+/* Bridge vlan RTM attributes
+ * [BRIDGE_VLANDB_ENTRY] = {
+ *     [BRIDGE_VLANDB_ENTRY_INFO]
+ *     ...
+ * }
+ */
+enum {
+       BRIDGE_VLANDB_UNSPEC,
+       BRIDGE_VLANDB_ENTRY,
+       __BRIDGE_VLANDB_MAX,
+};
+#define BRIDGE_VLANDB_MAX (__BRIDGE_VLANDB_MAX - 1)
+
+enum {
+       BRIDGE_VLANDB_ENTRY_UNSPEC,
+       BRIDGE_VLANDB_ENTRY_INFO,
+       __BRIDGE_VLANDB_ENTRY_MAX,
+};
+#define BRIDGE_VLANDB_ENTRY_MAX (__BRIDGE_VLANDB_ENTRY_MAX - 1)
+
 /* Bridge multicast database attributes
  * [MDBA_MDB] = {
  *     [MDBA_MDB_ENTRY] = {
index cd43321..b333cff 100644 (file)
@@ -171,6 +171,13 @@ enum {
        RTM_GETLINKPROP,
 #define RTM_GETLINKPROP        RTM_GETLINKPROP
 
+       RTM_NEWVLAN = 112,
+#define RTM_NEWNVLAN   RTM_NEWVLAN
+       RTM_DELVLAN,
+#define RTM_DELVLAN    RTM_DELVLAN
+       RTM_GETVLAN,
+#define RTM_GETVLAN    RTM_GETVLAN
+
        __RTM_MAX,
 #define RTM_MAX                (((__RTM_MAX + 3) & ~3) - 1)
 };
index 40942ce..75a7ecf 100644 (file)
@@ -1657,6 +1657,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);
@@ -1674,6 +1675,7 @@ out_af:
 void br_netlink_fini(void)
 {
        br_mdb_uninit();
+       br_vlan_rtnl_uninit();
        rtnl_af_unregister(&br_af_ops);
        rtnl_link_unregister(&br_link_ops);
 }
index a7dddc5..1c00411 100644 (file)
@@ -958,6 +958,8 @@ void br_vlan_get_stats(const struct net_bridge_vlan *v,
 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);
 
 static inline struct net_bridge_vlan_group *br_vlan_group(
                                        const struct net_bridge *br)
@@ -1009,6 +1011,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,
@@ -1152,6 +1158,14 @@ 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)
+{
+}
 #endif
 
 struct nf_br_ops {
index bb98984..5f2ac4f 100644 (file)
@@ -1505,3 +1505,151 @@ void br_vlan_port_event(struct net_bridge_port *p, unsigned long event)
                break;
        }
 }
+
+static bool br_vlan_fill_vids(struct sk_buff *skb, u16 vid, u16 flags)
+{
+       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;
+
+       nla_nest_end(skb, nest);
+
+       return true;
+
+out_err:
+       nla_nest_cancel(skb, nest);
+       return false;
+}
+
+static int br_vlan_dump_dev(const struct net_device *dev,
+                           struct sk_buff *skb,
+                           struct netlink_callback *cb)
+{
+       struct net_bridge_vlan_group *vg;
+       int idx = 0, s_idx = cb->args[1];
+       struct nlmsghdr *nlh = NULL;
+       struct net_bridge_vlan *v;
+       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 {
+               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);
+
+       list_for_each_entry_rcu(v, &vg->vlan_list, vlist) {
+               if (!br_vlan_should_use(v))
+                       continue;
+               if (idx < s_idx)
+                       goto skip;
+               if (!br_vlan_fill_vids(skb, v->vid, br_vlan_flags(v, pvid))) {
+                       err = -EMSGSIZE;
+                       break;
+               }
+skip:
+               idx++;
+       }
+       if (err)
+               cb->args[1] = idx;
+       else
+               cb->args[1] = 0;
+       nlmsg_end(skb, nlh);
+
+       return err;
+}
+
+static int br_vlan_rtm_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       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;
+
+       err = nlmsg_parse(cb->nlh, sizeof(*bvm), NULL, 0, NULL, cb->extack);
+       if (err < 0)
+               return err;
+
+       bvm = nlmsg_data(cb->nlh);
+
+       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);
+               if (err && 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);
+                       if (err == -EMSGSIZE)
+                               break;
+skip:
+                       idx++;
+               }
+       }
+       cb->args[0] = idx;
+       rcu_read_unlock();
+
+       return skb->len;
+
+out_err:
+       rcu_read_unlock();
+
+       return err;
+}
+
+void br_vlan_rtnl_init(void)
+{
+       rtnl_register_module(THIS_MODULE, PF_BRIDGE, RTM_GETVLAN, NULL,
+                            br_vlan_rtm_dump, 0);
+}
+
+void br_vlan_rtnl_uninit(void)
+{
+       rtnl_unregister(PF_BRIDGE, RTM_GETVLAN);
+}
index c97fdae..b692319 100644 (file)
@@ -85,6 +85,9 @@ static const struct nlmsg_perm nlmsg_route_perms[] =
        { RTM_GETNEXTHOP,       NETLINK_ROUTE_SOCKET__NLMSG_READ  },
        { RTM_NEWLINKPROP,      NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
        { RTM_DELLINKPROP,      NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+       { RTM_NEWVLAN,          NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+       { RTM_DELVLAN,          NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+       { RTM_GETVLAN,          NETLINK_ROUTE_SOCKET__NLMSG_READ  },
 };
 
 static const struct nlmsg_perm nlmsg_tcpdiag_perms[] =
@@ -168,7 +171,7 @@ int selinux_nlmsg_lookup(u16 sclass, u16 nlmsg_type, u32 *perm)
                 * structures at the top of this file with the new mappings
                 * before updating the BUILD_BUG_ON() macro!
                 */
-               BUILD_BUG_ON(RTM_MAX != (RTM_NEWLINKPROP + 3));
+               BUILD_BUG_ON(RTM_MAX != (RTM_NEWVLAN + 3));
                err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
                                 sizeof(nlmsg_route_perms));
                break;