bridge: export multicast database via netlink
Cong Wang [Fri, 7 Dec 2012 00:04:48 +0000 (00:04 +0000)]
V5: fix two bugs pointed out by Thomas
    remove seq check for now, mark it as TODO

V4: remove some useless #include
    some coding style fix

V3: drop debugging printk's
    update selinux perm table as well

V2: drop patch 1/2, export ifindex directly
    Redesign netlink attributes
    Improve netlink seq check
    Handle IPv6 addr as well

This patch exports bridge multicast database via netlink
message type RTM_GETMDB. Similar to fdb, but currently bridge-specific.
We may need to support modify multicast database too (RTM_{ADD,DEL}MDB).

(Thanks to Thomas for patient reviews)

Cc: Herbert Xu <herbert@gondor.apana.org.au>
Cc: Stephen Hemminger <shemminger@vyatta.com>
Cc: "David S. Miller" <davem@davemloft.net>
Cc: Thomas Graf <tgraf@suug.ch>
Cc: Jesper Dangaard Brouer <brouer@redhat.com>
Signed-off-by: Cong Wang <amwang@redhat.com>
Acked-by: Thomas Graf <tgraf@suug.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>

include/uapi/linux/if_bridge.h
include/uapi/linux/rtnetlink.h
net/bridge/Makefile
net/bridge/br_mdb.c [new file with mode: 0644]
net/bridge/br_multicast.c
net/bridge/br_private.h
security/selinux/nlmsgtab.c

index b388579..9a0f6ff 100644 (file)
@@ -116,4 +116,59 @@ enum {
        __IFLA_BRIDGE_MAX,
 };
 #define IFLA_BRIDGE_MAX (__IFLA_BRIDGE_MAX - 1)
+
+/* Bridge multicast database attributes
+ * [MDBA_MDB] = {
+ *     [MDBA_MDB_ENTRY] = {
+ *         [MDBA_MDB_ENTRY_INFO]
+ *     }
+ * }
+ * [MDBA_ROUTER] = {
+ *    [MDBA_ROUTER_PORT]
+ * }
+ */
+enum {
+       MDBA_UNSPEC,
+       MDBA_MDB,
+       MDBA_ROUTER,
+       __MDBA_MAX,
+};
+#define MDBA_MAX (__MDBA_MAX - 1)
+
+enum {
+       MDBA_MDB_UNSPEC,
+       MDBA_MDB_ENTRY,
+       __MDBA_MDB_MAX,
+};
+#define MDBA_MDB_MAX (__MDBA_MDB_MAX - 1)
+
+enum {
+       MDBA_MDB_ENTRY_UNSPEC,
+       MDBA_MDB_ENTRY_INFO,
+       __MDBA_MDB_ENTRY_MAX,
+};
+#define MDBA_MDB_ENTRY_MAX (__MDBA_MDB_ENTRY_MAX - 1)
+
+enum {
+       MDBA_ROUTER_UNSPEC,
+       MDBA_ROUTER_PORT,
+       __MDBA_ROUTER_MAX,
+};
+#define MDBA_ROUTER_MAX (__MDBA_ROUTER_MAX - 1)
+
+struct br_port_msg {
+       __u32 ifindex;
+};
+
+struct br_mdb_entry {
+       __u32 ifindex;
+       struct {
+               union {
+                       __be32  ip4;
+                       struct in6_addr ip6;
+               } u;
+               __be16          proto;
+       } addr;
+};
+
 #endif /* _UAPI_LINUX_IF_BRIDGE_H */
index 33d29ce..354a1e7 100644 (file)
@@ -125,6 +125,9 @@ enum {
        RTM_GETNETCONF = 82,
 #define RTM_GETNETCONF RTM_GETNETCONF
 
+       RTM_GETMDB = 86,
+#define RTM_GETMDB RTM_GETMDB
+
        __RTM_MAX,
 #define RTM_MAX                (((__RTM_MAX + 3) & ~3) - 1)
 };
index d0359ea..e859098 100644 (file)
@@ -12,6 +12,6 @@ bridge-$(CONFIG_SYSFS) += br_sysfs_if.o br_sysfs_br.o
 
 bridge-$(CONFIG_BRIDGE_NETFILTER) += br_netfilter.o
 
-bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o
+bridge-$(CONFIG_BRIDGE_IGMP_SNOOPING) += br_multicast.o br_mdb.o
 
 obj-$(CONFIG_BRIDGE_NF_EBTABLES) += netfilter/
diff --git a/net/bridge/br_mdb.c b/net/bridge/br_mdb.c
new file mode 100644 (file)
index 0000000..edc0d73
--- /dev/null
@@ -0,0 +1,163 @@
+#include <linux/err.h>
+#include <linux/igmp.h>
+#include <linux/kernel.h>
+#include <linux/netdevice.h>
+#include <linux/rculist.h>
+#include <linux/skbuff.h>
+#include <net/ip.h>
+#include <net/netlink.h>
+#if IS_ENABLED(CONFIG_IPV6)
+#include <net/ipv6.h>
+#endif
+
+#include "br_private.h"
+
+static int br_rports_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
+                              struct net_device *dev)
+{
+       struct net_bridge *br = netdev_priv(dev);
+       struct net_bridge_port *p;
+       struct hlist_node *n;
+       struct nlattr *nest;
+
+       if (!br->multicast_router || hlist_empty(&br->router_list))
+               return 0;
+
+       nest = nla_nest_start(skb, MDBA_ROUTER);
+       if (nest == NULL)
+               return -EMSGSIZE;
+
+       hlist_for_each_entry_rcu(p, n, &br->router_list, rlist) {
+               if (p && nla_put_u32(skb, MDBA_ROUTER_PORT, p->dev->ifindex))
+                       goto fail;
+       }
+
+       nla_nest_end(skb, nest);
+       return 0;
+fail:
+       nla_nest_cancel(skb, nest);
+       return -EMSGSIZE;
+}
+
+static int br_mdb_fill_info(struct sk_buff *skb, struct netlink_callback *cb,
+                           struct net_device *dev)
+{
+       struct net_bridge *br = netdev_priv(dev);
+       struct net_bridge_mdb_htable *mdb;
+       struct nlattr *nest, *nest2;
+       int i, err = 0;
+       int idx = 0, s_idx = cb->args[1];
+
+       if (br->multicast_disabled)
+               return 0;
+
+       mdb = rcu_dereference(br->mdb);
+       if (!mdb)
+               return 0;
+
+       nest = nla_nest_start(skb, MDBA_MDB);
+       if (nest == NULL)
+               return -EMSGSIZE;
+
+       for (i = 0; i < mdb->max; i++) {
+               struct hlist_node *h;
+               struct net_bridge_mdb_entry *mp;
+               struct net_bridge_port_group *p, **pp;
+               struct net_bridge_port *port;
+
+               hlist_for_each_entry_rcu(mp, h, &mdb->mhash[i], hlist[mdb->ver]) {
+                       if (idx < s_idx)
+                               goto skip;
+
+                       nest2 = nla_nest_start(skb, MDBA_MDB_ENTRY);
+                       if (nest2 == NULL) {
+                               err = -EMSGSIZE;
+                               goto out;
+                       }
+
+                       for (pp = &mp->ports;
+                            (p = rcu_dereference(*pp)) != NULL;
+                             pp = &p->next) {
+                               port = p->port;
+                               if (port) {
+                                       struct br_mdb_entry e;
+                                       e.ifindex = port->dev->ifindex;
+                                       e.addr.u.ip4 = p->addr.u.ip4;
+#if IS_ENABLED(CONFIG_IPV6)
+                                       e.addr.u.ip6 = p->addr.u.ip6;
+#endif
+                                       e.addr.proto = p->addr.proto;
+                                       if (nla_put(skb, MDBA_MDB_ENTRY_INFO, sizeof(e), &e)) {
+                                               nla_nest_cancel(skb, nest2);
+                                               err = -EMSGSIZE;
+                                               goto out;
+                                       }
+                               }
+                       }
+                       nla_nest_end(skb, nest2);
+               skip:
+                       idx++;
+               }
+       }
+
+out:
+       cb->args[1] = idx;
+       nla_nest_end(skb, nest);
+       return err;
+}
+
+static int br_mdb_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct net_device *dev;
+       struct net *net = sock_net(skb->sk);
+       struct nlmsghdr *nlh = NULL;
+       int idx = 0, s_idx;
+
+       s_idx = cb->args[0];
+
+       rcu_read_lock();
+
+       /* TODO: in case of rehashing, we need to check
+        * consistency for dumping.
+        */
+       cb->seq = net->dev_base_seq;
+
+       for_each_netdev_rcu(net, dev) {
+               if (dev->priv_flags & IFF_EBRIDGE) {
+                       struct br_port_msg *bpm;
+
+                       if (idx < s_idx)
+                               goto skip;
+
+                       nlh = nlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+                                       cb->nlh->nlmsg_seq, RTM_GETMDB,
+                                       sizeof(*bpm), NLM_F_MULTI);
+                       if (nlh == NULL)
+                               break;
+
+                       bpm = nlmsg_data(nlh);
+                       bpm->ifindex = dev->ifindex;
+                       if (br_mdb_fill_info(skb, cb, dev) < 0)
+                               goto out;
+                       if (br_rports_fill_info(skb, cb, dev) < 0)
+                               goto out;
+
+                       cb->args[1] = 0;
+                       nlmsg_end(skb, nlh);
+               skip:
+                       idx++;
+               }
+       }
+
+out:
+       if (nlh)
+               nlmsg_end(skb, nlh);
+       rcu_read_unlock();
+       cb->args[0] = idx;
+       return skb->len;
+}
+
+void br_mdb_init(void)
+{
+       rtnl_register(PF_BRIDGE, RTM_GETMDB, NULL, br_mdb_dump, NULL);
+}
index a2a7a1a..68e375a 100644 (file)
@@ -1605,6 +1605,7 @@ void br_multicast_init(struct net_bridge *br)
                    br_multicast_querier_expired, (unsigned long)br);
        setup_timer(&br->multicast_query_timer, br_multicast_query_expired,
                    (unsigned long)br);
+       br_mdb_init();
 }
 
 void br_multicast_open(struct net_bridge *br)
index cd86222..ae0a6ec 100644 (file)
@@ -433,6 +433,7 @@ extern int br_multicast_set_port_router(struct net_bridge_port *p,
 extern int br_multicast_toggle(struct net_bridge *br, unsigned long val);
 extern int br_multicast_set_querier(struct net_bridge *br, unsigned long val);
 extern int br_multicast_set_hash_max(struct net_bridge *br, unsigned long val);
+extern void br_mdb_init(void);
 
 static inline bool br_multicast_is_router(struct net_bridge *br)
 {
index d309e7f..163aaa7 100644 (file)
@@ -67,6 +67,7 @@ static struct nlmsg_perm nlmsg_route_perms[] =
        { RTM_GETADDRLABEL,     NETLINK_ROUTE_SOCKET__NLMSG_READ  },
        { RTM_GETDCB,           NETLINK_ROUTE_SOCKET__NLMSG_READ  },
        { RTM_SETDCB,           NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
+       { RTM_GETMDB,           NETLINK_ROUTE_SOCKET__NLMSG_READ  },
 };
 
 static struct nlmsg_perm nlmsg_tcpdiag_perms[] =