]> nv-tegra.nvidia Code Review - linux-2.6.git/blobdiff - net/bridge/br_netlink.c
net: dont hold rtnl mutex during netlink dump callbacks
[linux-2.6.git] / net / bridge / br_netlink.c
index fcffb3fb1177aa57352f4fdbcc0247944d1959ac..ffb0dc4cc0e80691c1264e2344064b89d01c2f2f 100644 (file)
  */
 
 #include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/etherdevice.h>
 #include <net/rtnetlink.h>
 #include <net/net_namespace.h>
 #include <net/sock.h>
+
 #include "br_private.h"
 
 static inline size_t br_nlmsg_size(void)
@@ -41,8 +44,8 @@ static int br_fill_ifinfo(struct sk_buff *skb, const struct net_bridge_port *por
        struct nlmsghdr *nlh;
        u8 operstate = netif_running(dev) ? dev->operstate : IF_OPER_DOWN;
 
-       pr_debug("br_fill_info event %d port %s master %s\n",
-                event, dev->name, br->dev->name);
+       br_debug(br, "br_fill_info event %d port %s master %s\n",
+                    event, dev->name, br->dev->name);
 
        nlh = nlmsg_put(skb, pid, seq, event, sizeof(*hdr), flags);
        if (nlh == NULL)
@@ -86,7 +89,9 @@ void br_ifinfo_notify(int event, struct net_bridge_port *port)
        struct sk_buff *skb;
        int err = -ENOBUFS;
 
-       pr_debug("bridge notify event=%d\n", event);
+       br_debug(port->br, "port %u(%s) event %d\n",
+                (unsigned)port->port_no, port->dev->name, event);
+
        skb = nlmsg_new(br_nlmsg_size(), GFP_ATOMIC);
        if (skb == NULL)
                goto errout;
@@ -115,19 +120,23 @@ static int br_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
        int idx;
 
        idx = 0;
-       for_each_netdev(net, dev) {
+       rcu_read_lock();
+       for_each_netdev_rcu(net, dev) {
+               struct net_bridge_port *port = br_port_get_rcu(dev);
+
                /* not a bridge port */
-               if (dev->br_port == NULL || idx < cb->args[0])
+               if (!port || idx < cb->args[0])
                        goto skip;
 
-               if (br_fill_ifinfo(skb, dev->br_port, NETLINK_CB(cb->skb).pid,
+               if (br_fill_ifinfo(skb, port,
+                                  NETLINK_CB(cb->skb).pid,
                                   cb->nlh->nlmsg_seq, RTM_NEWLINK,
                                   NLM_F_MULTI) < 0)
                        break;
 skip:
                ++idx;
        }
-
+       rcu_read_unlock();
        cb->args[0] = idx;
 
        return skb->len;
@@ -165,7 +174,7 @@ static int br_rtm_setlink(struct sk_buff *skb,  struct nlmsghdr *nlh, void *arg)
        if (!dev)
                return -ENODEV;
 
-       p = dev->br_port;
+       p = br_port_get_rtnl(dev);
        if (!p)
                return -EINVAL;
 
@@ -182,20 +191,61 @@ static int br_rtm_setlink(struct sk_buff *skb,  struct nlmsghdr *nlh, void *arg)
        return 0;
 }
 
+static int br_validate(struct nlattr *tb[], struct nlattr *data[])
+{
+       if (tb[IFLA_ADDRESS]) {
+               if (nla_len(tb[IFLA_ADDRESS]) != ETH_ALEN)
+                       return -EINVAL;
+               if (!is_valid_ether_addr(nla_data(tb[IFLA_ADDRESS])))
+                       return -EADDRNOTAVAIL;
+       }
+
+       return 0;
+}
+
+static struct rtnl_link_ops br_link_ops __read_mostly = {
+       .kind           = "bridge",
+       .priv_size      = sizeof(struct net_bridge),
+       .setup          = br_dev_setup,
+       .validate       = br_validate,
+};
 
 int __init br_netlink_init(void)
 {
-       if (__rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, br_dump_ifinfo))
-               return -ENOBUFS;
+       int err;
 
-       /* Only the first call to __rtnl_register can fail */
-       __rtnl_register(PF_BRIDGE, RTM_SETLINK, br_rtm_setlink, NULL);
+       err = rtnl_link_register(&br_link_ops);
+       if (err < 0)
+               goto err1;
+
+       err = __rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, br_dump_ifinfo);
+       if (err)
+               goto err2;
+       err = __rtnl_register(PF_BRIDGE, RTM_SETLINK, br_rtm_setlink, NULL);
+       if (err)
+               goto err3;
+       err = __rtnl_register(PF_BRIDGE, RTM_NEWNEIGH, br_fdb_add, NULL);
+       if (err)
+               goto err3;
+       err = __rtnl_register(PF_BRIDGE, RTM_DELNEIGH, br_fdb_delete, NULL);
+       if (err)
+               goto err3;
+       err = __rtnl_register(PF_BRIDGE, RTM_GETNEIGH, NULL, br_fdb_dump);
+       if (err)
+               goto err3;
 
        return 0;
+
+err3:
+       rtnl_unregister_all(PF_BRIDGE);
+err2:
+       rtnl_link_unregister(&br_link_ops);
+err1:
+       return err;
 }
 
 void __exit br_netlink_fini(void)
 {
+       rtnl_link_unregister(&br_link_ops);
        rtnl_unregister_all(PF_BRIDGE);
 }
-