netns: notify netns id events
[cascardo/linux.git] / net / core / net_namespace.c
index 5221f97..b3b5f22 100644 (file)
@@ -148,9 +148,11 @@ static void ops_free_list(const struct pernet_operations *ops,
        }
 }
 
+static void rtnl_net_notifyid(struct net *net, struct net *peer, int cmd,
+                             int id);
 static int alloc_netid(struct net *net, struct net *peer, int reqid)
 {
-       int min = 0, max = 0;
+       int min = 0, max = 0, id;
 
        ASSERT_RTNL();
 
@@ -159,7 +161,11 @@ static int alloc_netid(struct net *net, struct net *peer, int reqid)
                max = reqid + 1;
        }
 
-       return idr_alloc(&net->netns_ids, peer, min, max, GFP_KERNEL);
+       id = idr_alloc(&net->netns_ids, peer, min, max, GFP_KERNEL);
+       if (id >= 0)
+               rtnl_net_notifyid(net, peer, RTM_NEWNSID, id);
+
+       return id;
 }
 
 /* This function is used by idr_for_each(). If net is equal to peer, the
@@ -198,8 +204,10 @@ static int __peernet2id(struct net *net, struct net *peer, bool alloc)
  */
 int peernet2id(struct net *net, struct net *peer)
 {
-       int id = __peernet2id(net, peer, true);
+       bool alloc = atomic_read(&peer->count) == 0 ? false : true;
+       int id;
 
+       id = __peernet2id(net, peer, alloc);
        return id >= 0 ? id : NETNSA_NSID_NOT_ASSIGNED;
 }
 EXPORT_SYMBOL(peernet2id);
@@ -236,10 +244,6 @@ static __net_init int setup_net(struct net *net, struct user_namespace *user_ns)
        net->user_ns = user_ns;
        idr_init(&net->netns_ids);
 
-#ifdef NETNS_REFCNT_DEBUG
-       atomic_set(&net->use_count, 0);
-#endif
-
        list_for_each_entry(ops, &pernet_list, list) {
                error = ops_init(ops, net);
                if (error < 0)
@@ -294,13 +298,6 @@ out_free:
 
 static void net_free(struct net *net)
 {
-#ifdef NETNS_REFCNT_DEBUG
-       if (unlikely(atomic_read(&net->use_count) != 0)) {
-               pr_emerg("network namespace not free! Usage: %d\n",
-                        atomic_read(&net->use_count));
-               return;
-       }
-#endif
        kfree(rcu_access_pointer(net->gen));
        kmem_cache_free(net_cachep, net);
 }
@@ -349,7 +346,7 @@ static LIST_HEAD(cleanup_list);  /* Must hold cleanup_list_lock to touch */
 static void cleanup_net(struct work_struct *work)
 {
        const struct pernet_operations *ops;
-       struct net *net, *tmp, *peer;
+       struct net *net, *tmp;
        struct list_head net_kill_list;
        LIST_HEAD(net_exit_list);
 
@@ -365,6 +362,16 @@ static void cleanup_net(struct work_struct *work)
        list_for_each_entry(net, &net_kill_list, cleanup_list) {
                list_del_rcu(&net->list);
                list_add_tail(&net->exit_list, &net_exit_list);
+               for_each_net(tmp) {
+                       int id = __peernet2id(tmp, net, false);
+
+                       if (id >= 0) {
+                               rtnl_net_notifyid(tmp, net, RTM_DELNSID, id);
+                               idr_remove(&tmp->netns_ids, id);
+                       }
+               }
+               idr_destroy(&net->netns_ids);
+
        }
        rtnl_unlock();
 
@@ -390,26 +397,12 @@ static void cleanup_net(struct work_struct *work)
         */
        rcu_barrier();
 
-       rtnl_lock();
        /* Finally it is safe to free my network namespace structure */
        list_for_each_entry_safe(net, tmp, &net_exit_list, exit_list) {
-               /* Unreference net from all peers (no need to loop over
-                * net_exit_list because idr_destroy() will be called for each
-                * element of this list.
-                */
-               for_each_net(peer) {
-                       int id = __peernet2id(peer, net, false);
-
-                       if (id >= 0)
-                               idr_remove(&peer->netns_ids, id);
-               }
-               idr_destroy(&net->netns_ids);
-
                list_del_init(&net->exit_list);
                put_user_ns(net->user_ns);
                net_drop_ns(net);
        }
-       rtnl_unlock();
 }
 static DECLARE_WORK(net_cleanup_work, cleanup_net);
 
@@ -546,7 +539,8 @@ static int rtnl_net_get_size(void)
 }
 
 static int rtnl_net_fill(struct sk_buff *skb, u32 portid, u32 seq, int flags,
-                        int cmd, struct net *net, struct net *peer)
+                        int cmd, struct net *net, struct net *peer,
+                        int nsid)
 {
        struct nlmsghdr *nlh;
        struct rtgenmsg *rth;
@@ -561,9 +555,13 @@ static int rtnl_net_fill(struct sk_buff *skb, u32 portid, u32 seq, int flags,
        rth = nlmsg_data(nlh);
        rth->rtgen_family = AF_UNSPEC;
 
-       id = __peernet2id(net, peer, false);
-       if  (id < 0)
-               id = NETNSA_NSID_NOT_ASSIGNED;
+       if (nsid >= 0) {
+               id = nsid;
+       } else {
+               id = __peernet2id(net, peer, false);
+               if  (id < 0)
+                       id = NETNSA_NSID_NOT_ASSIGNED;
+       }
        if (nla_put_s32(skb, NETNSA_NSID, id))
                goto nla_put_failure;
 
@@ -580,8 +578,8 @@ static int rtnl_net_getid(struct sk_buff *skb, struct nlmsghdr *nlh)
        struct net *net = sock_net(skb->sk);
        struct nlattr *tb[NETNSA_MAX + 1];
        struct sk_buff *msg;
-       int err = -ENOBUFS;
        struct net *peer;
+       int err;
 
        err = nlmsg_parse(nlh, sizeof(struct rtgenmsg), tb, NETNSA_MAX,
                          rtnl_net_policy);
@@ -604,7 +602,7 @@ static int rtnl_net_getid(struct sk_buff *skb, struct nlmsghdr *nlh)
        }
 
        err = rtnl_net_fill(msg, NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0,
-                           RTM_GETNSID, net, peer);
+                           RTM_GETNSID, net, peer, -1);
        if (err < 0)
                goto err_out;
 
@@ -618,6 +616,29 @@ out:
        return err;
 }
 
+static void rtnl_net_notifyid(struct net *net, struct net *peer, int cmd,
+                             int id)
+{
+       struct sk_buff *msg;
+       int err = -ENOMEM;
+
+       msg = nlmsg_new(rtnl_net_get_size(), GFP_KERNEL);
+       if (!msg)
+               goto out;
+
+       err = rtnl_net_fill(msg, 0, 0, 0, cmd, net, peer, id);
+       if (err < 0)
+               goto err_out;
+
+       rtnl_notify(msg, net, 0, RTNLGRP_NSID, NULL, 0);
+       return;
+
+err_out:
+       nlmsg_free(msg);
+out:
+       rtnl_set_sk_err(net, RTNLGRP_NSID, err);
+}
+
 static int __init net_ns_init(void)
 {
        struct net_generic *ng;