rtnetlink: use netnsid to query interface
[linux-2.6-block.git] / net / core / rtnetlink.c
index de24d394c69e4d9a2b6d45f7d2f0b80378b3d528..8a8c51937edff6bf59a6813dd8de88fef9962bb7 100644 (file)
@@ -921,7 +921,8 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev,
               + nla_total_size(4)  /* IFLA_EVENT */
               + nla_total_size(4)  /* IFLA_NEW_NETNSID */
               + nla_total_size(1); /* IFLA_PROTO_DOWN */
-
+              + nla_total_size(4)  /* IFLA_IF_NETNSID */
+              + 0;
 }
 
 static int rtnl_vf_ports_fill(struct sk_buff *skb, struct net_device *dev)
@@ -1370,13 +1371,14 @@ static noinline_for_stack int nla_put_ifalias(struct sk_buff *skb,
 }
 
 static int rtnl_fill_link_netnsid(struct sk_buff *skb,
-                                 const struct net_device *dev)
+                                 const struct net_device *dev,
+                                 struct net *src_net)
 {
        if (dev->rtnl_link_ops && dev->rtnl_link_ops->get_link_net) {
                struct net *link_net = dev->rtnl_link_ops->get_link_net(dev);
 
                if (!net_eq(dev_net(dev), link_net)) {
-                       int id = peernet2id_alloc(dev_net(dev), link_net);
+                       int id = peernet2id_alloc(src_net, link_net);
 
                        if (nla_put_s32(skb, IFLA_LINK_NETNSID, id))
                                return -EMSGSIZE;
@@ -1427,10 +1429,11 @@ static int rtnl_fill_link_af(struct sk_buff *skb,
        return 0;
 }
 
-static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
+static int rtnl_fill_ifinfo(struct sk_buff *skb,
+                           struct net_device *dev, struct net *src_net,
                            int type, u32 pid, u32 seq, u32 change,
                            unsigned int flags, u32 ext_filter_mask,
-                           u32 event, int *new_nsid)
+                           u32 event, int *new_nsid, int tgt_netnsid)
 {
        struct ifinfomsg *ifm;
        struct nlmsghdr *nlh;
@@ -1448,6 +1451,9 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
        ifm->ifi_flags = dev_get_flags(dev);
        ifm->ifi_change = change;
 
+       if (tgt_netnsid >= 0 && nla_put_s32(skb, IFLA_IF_NETNSID, tgt_netnsid))
+               goto nla_put_failure;
+
        if (nla_put_string(skb, IFLA_IFNAME, dev->name) ||
            nla_put_u32(skb, IFLA_TXQLEN, dev->tx_queue_len) ||
            nla_put_u8(skb, IFLA_OPERSTATE,
@@ -1513,7 +1519,7 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
                        goto nla_put_failure;
        }
 
-       if (rtnl_fill_link_netnsid(skb, dev))
+       if (rtnl_fill_link_netnsid(skb, dev, src_net))
                goto nla_put_failure;
 
        if (new_nsid &&
@@ -1571,6 +1577,7 @@ static const struct nla_policy ifla_policy[IFLA_MAX+1] = {
        [IFLA_XDP]              = { .type = NLA_NESTED },
        [IFLA_EVENT]            = { .type = NLA_U32 },
        [IFLA_GROUP]            = { .type = NLA_U32 },
+       [IFLA_IF_NETNSID]       = { .type = NLA_S32 },
 };
 
 static const struct nla_policy ifla_info_policy[IFLA_INFO_MAX+1] = {
@@ -1674,9 +1681,28 @@ static bool link_dump_filtered(struct net_device *dev,
        return false;
 }
 
+static struct net *get_target_net(struct sk_buff *skb, int netnsid)
+{
+       struct net *net;
+
+       net = get_net_ns_by_id(sock_net(skb->sk), netnsid);
+       if (!net)
+               return ERR_PTR(-EINVAL);
+
+       /* For now, the caller is required to have CAP_NET_ADMIN in
+        * the user namespace owning the target net ns.
+        */
+       if (!netlink_ns_capable(skb, net->user_ns, CAP_NET_ADMIN)) {
+               put_net(net);
+               return ERR_PTR(-EACCES);
+       }
+       return net;
+}
+
 static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
+       struct net *tgt_net = net;
        int h, s_h;
        int idx = 0, s_idx;
        struct net_device *dev;
@@ -1686,6 +1712,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
        const struct rtnl_link_ops *kind_ops = NULL;
        unsigned int flags = NLM_F_MULTI;
        int master_idx = 0;
+       int netnsid = -1;
        int err;
        int hdrlen;
 
@@ -1704,6 +1731,15 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 
        if (nlmsg_parse(cb->nlh, hdrlen, tb, IFLA_MAX,
                        ifla_policy, NULL) >= 0) {
+               if (tb[IFLA_IF_NETNSID]) {
+                       netnsid = nla_get_s32(tb[IFLA_IF_NETNSID]);
+                       tgt_net = get_target_net(skb, netnsid);
+                       if (IS_ERR(tgt_net)) {
+                               tgt_net = net;
+                               netnsid = -1;
+                       }
+               }
+
                if (tb[IFLA_EXT_MASK])
                        ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
 
@@ -1719,17 +1755,19 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
 
        for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
                idx = 0;
-               head = &net->dev_index_head[h];
+               head = &tgt_net->dev_index_head[h];
                hlist_for_each_entry(dev, head, index_hlist) {
                        if (link_dump_filtered(dev, master_idx, kind_ops))
                                goto cont;
                        if (idx < s_idx)
                                goto cont;
-                       err = rtnl_fill_ifinfo(skb, dev, RTM_NEWLINK,
+                       err = rtnl_fill_ifinfo(skb, dev, net,
+                                              RTM_NEWLINK,
                                               NETLINK_CB(cb->skb).portid,
                                               cb->nlh->nlmsg_seq, 0,
                                               flags,
-                                              ext_filter_mask, 0, NULL);
+                                              ext_filter_mask, 0, NULL,
+                                              netnsid);
 
                        if (err < 0) {
                                if (likely(skb->len))
@@ -1748,6 +1786,8 @@ out_err:
        cb->args[0] = h;
        cb->seq = net->dev_base_seq;
        nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+       if (netnsid >= 0)
+               put_net(tgt_net);
 
        return err;
 }
@@ -2360,6 +2400,9 @@ static int rtnl_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (err < 0)
                goto errout;
 
+       if (tb[IFLA_IF_NETNSID])
+               return -EOPNOTSUPP;
+
        if (tb[IFLA_IFNAME])
                nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
        else
@@ -2454,6 +2497,9 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (err < 0)
                return err;
 
+       if (tb[IFLA_IF_NETNSID])
+               return -EOPNOTSUPP;
+
        if (tb[IFLA_IFNAME])
                nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
 
@@ -2585,6 +2631,9 @@ replay:
        if (err < 0)
                return err;
 
+       if (tb[IFLA_IF_NETNSID])
+               return -EOPNOTSUPP;
+
        if (tb[IFLA_IFNAME])
                nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
        else
@@ -2818,11 +2867,13 @@ static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr *nlh,
                        struct netlink_ext_ack *extack)
 {
        struct net *net = sock_net(skb->sk);
+       struct net *tgt_net = net;
        struct ifinfomsg *ifm;
        char ifname[IFNAMSIZ];
        struct nlattr *tb[IFLA_MAX+1];
        struct net_device *dev = NULL;
        struct sk_buff *nskb;
+       int netnsid = -1;
        int err;
        u32 ext_filter_mask = 0;
 
@@ -2830,35 +2881,50 @@ static int rtnl_getlink(struct sk_buff *skb, struct nlmsghdr *nlh,
        if (err < 0)
                return err;
 
+       if (tb[IFLA_IF_NETNSID]) {
+               netnsid = nla_get_s32(tb[IFLA_IF_NETNSID]);
+               tgt_net = get_target_net(skb, netnsid);
+               if (IS_ERR(tgt_net))
+                       return PTR_ERR(tgt_net);
+       }
+
        if (tb[IFLA_IFNAME])
                nla_strlcpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
 
        if (tb[IFLA_EXT_MASK])
                ext_filter_mask = nla_get_u32(tb[IFLA_EXT_MASK]);
 
+       err = -EINVAL;
        ifm = nlmsg_data(nlh);
        if (ifm->ifi_index > 0)
-               dev = __dev_get_by_index(net, ifm->ifi_index);
+               dev = __dev_get_by_index(tgt_net, ifm->ifi_index);
        else if (tb[IFLA_IFNAME])
-               dev = __dev_get_by_name(net, ifname);
+               dev = __dev_get_by_name(tgt_net, ifname);
        else
-               return -EINVAL;
+               goto out;
 
+       err = -ENODEV;
        if (dev == NULL)
-               return -ENODEV;
+               goto out;
 
+       err = -ENOBUFS;
        nskb = nlmsg_new(if_nlmsg_size(dev, ext_filter_mask), GFP_KERNEL);
        if (nskb == NULL)
-               return -ENOBUFS;
+               goto out;
 
-       err = rtnl_fill_ifinfo(nskb, dev, RTM_NEWLINK, NETLINK_CB(skb).portid,
-                              nlh->nlmsg_seq, 0, 0, ext_filter_mask, 0, NULL);
+       err = rtnl_fill_ifinfo(nskb, dev, net,
+                              RTM_NEWLINK, NETLINK_CB(skb).portid,
+                              nlh->nlmsg_seq, 0, 0, ext_filter_mask,
+                              0, NULL, netnsid);
        if (err < 0) {
                /* -EMSGSIZE implies BUG in if_nlmsg_size */
                WARN_ON(err == -EMSGSIZE);
                kfree_skb(nskb);
        } else
                err = rtnl_unicast(nskb, net, NETLINK_CB(skb).portid);
+out:
+       if (netnsid >= 0)
+               put_net(tgt_net);
 
        return err;
 }
@@ -2948,8 +3014,9 @@ struct sk_buff *rtmsg_ifinfo_build_skb(int type, struct net_device *dev,
        if (skb == NULL)
                goto errout;
 
-       err = rtnl_fill_ifinfo(skb, dev, type, 0, 0, change, 0, 0, event,
-                              new_nsid);
+       err = rtnl_fill_ifinfo(skb, dev, dev_net(dev),
+                              type, 0, 0, change, 0, 0, event,
+                              new_nsid, -1);
        if (err < 0) {
                /* -EMSGSIZE implies BUG in if_nlmsg_size() */
                WARN_ON(err == -EMSGSIZE);