Merge git://git.kernel.org/pub/scm/linux/kernel/git/davem/net
[cascardo/linux.git] / net / core / rtnetlink.c
index 65763c2..d69c464 100644 (file)
@@ -808,11 +808,6 @@ static void copy_rtnl_link_stats(struct rtnl_link_stats *a,
        a->rx_nohandler = b->rx_nohandler;
 }
 
-static void copy_rtnl_link_stats64(void *v, const struct rtnl_link_stats64 *b)
-{
-       memcpy(v, b, sizeof(*b));
-}
-
 /* All VF info */
 static inline int rtnl_vfinfo_size(const struct net_device *dev,
                                   u32 ext_filter_mask)
@@ -830,17 +825,17 @@ static inline int rtnl_vfinfo_size(const struct net_device *dev,
                         nla_total_size(sizeof(struct ifla_vf_link_state)) +
                         nla_total_size(sizeof(struct ifla_vf_rss_query_en)) +
                         /* IFLA_VF_STATS_RX_PACKETS */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         /* IFLA_VF_STATS_TX_PACKETS */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         /* IFLA_VF_STATS_RX_BYTES */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         /* IFLA_VF_STATS_TX_BYTES */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         /* IFLA_VF_STATS_BROADCAST */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         /* IFLA_VF_STATS_MULTICAST */
-                        nla_total_size(sizeof(__u64)) +
+                        nla_total_size_64bit(sizeof(__u64)) +
                         nla_total_size(sizeof(struct ifla_vf_trust)));
                return size;
        } else
@@ -881,9 +876,9 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev,
               + nla_total_size(IFNAMSIZ) /* IFLA_IFNAME */
               + nla_total_size(IFALIASZ) /* IFLA_IFALIAS */
               + nla_total_size(IFNAMSIZ) /* IFLA_QDISC */
-              + nla_total_size(sizeof(struct rtnl_link_ifmap))
+              + nla_total_size_64bit(sizeof(struct rtnl_link_ifmap))
               + nla_total_size(sizeof(struct rtnl_link_stats))
-              + nla_total_size(sizeof(struct rtnl_link_stats64))
+              + nla_total_size_64bit(sizeof(struct rtnl_link_stats64))
               + nla_total_size(MAX_ADDR_LEN) /* IFLA_ADDRESS */
               + nla_total_size(MAX_ADDR_LEN) /* IFLA_BROADCAST */
               + nla_total_size(4) /* IFLA_TXQLEN */
@@ -1054,25 +1049,23 @@ static int rtnl_phys_switch_id_fill(struct sk_buff *skb, struct net_device *dev)
 static noinline_for_stack int rtnl_fill_stats(struct sk_buff *skb,
                                              struct net_device *dev)
 {
-       const struct rtnl_link_stats64 *stats;
-       struct rtnl_link_stats64 temp;
+       struct rtnl_link_stats64 *sp;
        struct nlattr *attr;
 
-       stats = dev_get_stats(dev, &temp);
-
-       attr = nla_reserve(skb, IFLA_STATS,
-                          sizeof(struct rtnl_link_stats));
+       attr = nla_reserve_64bit(skb, IFLA_STATS64,
+                                sizeof(struct rtnl_link_stats64), IFLA_PAD);
        if (!attr)
                return -EMSGSIZE;
 
-       copy_rtnl_link_stats(nla_data(attr), stats);
+       sp = nla_data(attr);
+       dev_get_stats(dev, sp);
 
-       attr = nla_reserve(skb, IFLA_STATS64,
-                          sizeof(struct rtnl_link_stats64));
+       attr = nla_reserve(skb, IFLA_STATS,
+                          sizeof(struct rtnl_link_stats));
        if (!attr)
                return -EMSGSIZE;
 
-       copy_rtnl_link_stats64(nla_data(attr), stats);
+       copy_rtnl_link_stats(nla_data(attr), sp);
 
        return 0;
 }
@@ -1160,18 +1153,18 @@ static noinline_for_stack int rtnl_fill_vfinfo(struct sk_buff *skb,
                nla_nest_cancel(skb, vfinfo);
                return -EMSGSIZE;
        }
-       if (nla_put_u64(skb, IFLA_VF_STATS_RX_PACKETS,
-                       vf_stats.rx_packets) ||
-           nla_put_u64(skb, IFLA_VF_STATS_TX_PACKETS,
-                       vf_stats.tx_packets) ||
-           nla_put_u64(skb, IFLA_VF_STATS_RX_BYTES,
-                       vf_stats.rx_bytes) ||
-           nla_put_u64(skb, IFLA_VF_STATS_TX_BYTES,
-                       vf_stats.tx_bytes) ||
-           nla_put_u64(skb, IFLA_VF_STATS_BROADCAST,
-                       vf_stats.broadcast) ||
-           nla_put_u64(skb, IFLA_VF_STATS_MULTICAST,
-                       vf_stats.multicast))
+       if (nla_put_u64_64bit(skb, IFLA_VF_STATS_RX_PACKETS,
+                             vf_stats.rx_packets, IFLA_VF_STATS_PAD) ||
+           nla_put_u64_64bit(skb, IFLA_VF_STATS_TX_PACKETS,
+                             vf_stats.tx_packets, IFLA_VF_STATS_PAD) ||
+           nla_put_u64_64bit(skb, IFLA_VF_STATS_RX_BYTES,
+                             vf_stats.rx_bytes, IFLA_VF_STATS_PAD) ||
+           nla_put_u64_64bit(skb, IFLA_VF_STATS_TX_BYTES,
+                             vf_stats.tx_bytes, IFLA_VF_STATS_PAD) ||
+           nla_put_u64_64bit(skb, IFLA_VF_STATS_BROADCAST,
+                             vf_stats.broadcast, IFLA_VF_STATS_PAD) ||
+           nla_put_u64_64bit(skb, IFLA_VF_STATS_MULTICAST,
+                             vf_stats.multicast, IFLA_VF_STATS_PAD))
                return -EMSGSIZE;
        nla_nest_end(skb, vfstats);
        nla_nest_end(skb, vf);
@@ -1190,7 +1183,7 @@ static int rtnl_fill_link_ifmap(struct sk_buff *skb, struct net_device *dev)
        map.dma         = dev->dma;
        map.port        = dev->if_port;
 
-       if (nla_put(skb, IFLA_MAP, sizeof(map), &map))
+       if (nla_put_64bit(skb, IFLA_MAP, sizeof(map), &map, IFLA_PAD))
                return -EMSGSIZE;
 
        return 0;
@@ -3453,6 +3446,202 @@ out:
        return err;
 }
 
+static bool stats_attr_valid(unsigned int mask, int attrid, int idxattr)
+{
+       return (mask & IFLA_STATS_FILTER_BIT(attrid)) &&
+              (!idxattr || idxattr == attrid);
+}
+
+static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
+                              int type, u32 pid, u32 seq, u32 change,
+                              unsigned int flags, unsigned int filter_mask,
+                              int *idxattr, int *prividx)
+{
+       struct if_stats_msg *ifsm;
+       struct nlmsghdr *nlh;
+       struct nlattr *attr;
+       int s_prividx = *prividx;
+
+       ASSERT_RTNL();
+
+       nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifsm), flags);
+       if (!nlh)
+               return -EMSGSIZE;
+
+       ifsm = nlmsg_data(nlh);
+       ifsm->ifindex = dev->ifindex;
+       ifsm->filter_mask = filter_mask;
+
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_64, *idxattr)) {
+               struct rtnl_link_stats64 *sp;
+
+               attr = nla_reserve_64bit(skb, IFLA_STATS_LINK_64,
+                                        sizeof(struct rtnl_link_stats64),
+                                        IFLA_STATS_UNSPEC);
+               if (!attr)
+                       goto nla_put_failure;
+
+               sp = nla_data(attr);
+               dev_get_stats(dev, sp);
+       }
+
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_XSTATS, *idxattr)) {
+               const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
+
+               if (ops && ops->fill_linkxstats) {
+                       int err;
+
+                       *idxattr = IFLA_STATS_LINK_XSTATS;
+                       attr = nla_nest_start(skb,
+                                             IFLA_STATS_LINK_XSTATS);
+                       if (!attr)
+                               goto nla_put_failure;
+
+                       err = ops->fill_linkxstats(skb, dev, prividx);
+                       nla_nest_end(skb, attr);
+                       if (err)
+                               goto nla_put_failure;
+                       *idxattr = 0;
+               }
+       }
+
+       nlmsg_end(skb, nlh);
+
+       return 0;
+
+nla_put_failure:
+       /* not a multi message or no progress mean a real error */
+       if (!(flags & NLM_F_MULTI) || s_prividx == *prividx)
+               nlmsg_cancel(skb, nlh);
+       else
+               nlmsg_end(skb, nlh);
+
+       return -EMSGSIZE;
+}
+
+static const struct nla_policy ifla_stats_policy[IFLA_STATS_MAX + 1] = {
+       [IFLA_STATS_LINK_64]    = { .len = sizeof(struct rtnl_link_stats64) },
+};
+
+static size_t if_nlmsg_stats_size(const struct net_device *dev,
+                                 u32 filter_mask)
+{
+       size_t size = 0;
+
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_64, 0))
+               size += nla_total_size_64bit(sizeof(struct rtnl_link_stats64));
+
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_XSTATS, 0)) {
+               const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
+
+               if (ops && ops->get_linkxstats_size) {
+                       size += nla_total_size(ops->get_linkxstats_size(dev));
+                       /* for IFLA_STATS_LINK_XSTATS */
+                       size += nla_total_size(0);
+               }
+       }
+
+       return size;
+}
+
+static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
+{
+       struct net *net = sock_net(skb->sk);
+       struct net_device *dev = NULL;
+       int idxattr = 0, prividx = 0;
+       struct if_stats_msg *ifsm;
+       struct sk_buff *nskb;
+       u32 filter_mask;
+       int err;
+
+       ifsm = nlmsg_data(nlh);
+       if (ifsm->ifindex > 0)
+               dev = __dev_get_by_index(net, ifsm->ifindex);
+       else
+               return -EINVAL;
+
+       if (!dev)
+               return -ENODEV;
+
+       filter_mask = ifsm->filter_mask;
+       if (!filter_mask)
+               return -EINVAL;
+
+       nskb = nlmsg_new(if_nlmsg_stats_size(dev, filter_mask), GFP_KERNEL);
+       if (!nskb)
+               return -ENOBUFS;
+
+       err = rtnl_fill_statsinfo(nskb, dev, RTM_NEWSTATS,
+                                 NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0,
+                                 0, filter_mask, &idxattr, &prividx);
+       if (err < 0) {
+               /* -EMSGSIZE implies BUG in if_nlmsg_stats_size */
+               WARN_ON(err == -EMSGSIZE);
+               kfree_skb(nskb);
+       } else {
+               err = rtnl_unicast(nskb, net, NETLINK_CB(skb).portid);
+       }
+
+       return err;
+}
+
+static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       int h, s_h, err, s_idx, s_idxattr, s_prividx;
+       struct net *net = sock_net(skb->sk);
+       unsigned int flags = NLM_F_MULTI;
+       struct if_stats_msg *ifsm;
+       struct hlist_head *head;
+       struct net_device *dev;
+       u32 filter_mask = 0;
+       int idx = 0;
+
+       s_h = cb->args[0];
+       s_idx = cb->args[1];
+       s_idxattr = cb->args[2];
+       s_prividx = cb->args[3];
+
+       cb->seq = net->dev_base_seq;
+
+       ifsm = nlmsg_data(cb->nlh);
+       filter_mask = ifsm->filter_mask;
+       if (!filter_mask)
+               return -EINVAL;
+
+       for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
+               idx = 0;
+               head = &net->dev_index_head[h];
+               hlist_for_each_entry(dev, head, index_hlist) {
+                       if (idx < s_idx)
+                               goto cont;
+                       err = rtnl_fill_statsinfo(skb, dev, RTM_NEWSTATS,
+                                                 NETLINK_CB(cb->skb).portid,
+                                                 cb->nlh->nlmsg_seq, 0,
+                                                 flags, filter_mask,
+                                                 &s_idxattr, &s_prividx);
+                       /* If we ran out of room on the first message,
+                        * we're in trouble
+                        */
+                       WARN_ON((err == -EMSGSIZE) && (skb->len == 0));
+
+                       if (err < 0)
+                               goto out;
+                       s_prividx = 0;
+                       s_idxattr = 0;
+                       nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+cont:
+                       idx++;
+               }
+       }
+out:
+       cb->args[3] = s_prividx;
+       cb->args[2] = s_idxattr;
+       cb->args[1] = idx;
+       cb->args[0] = h;
+
+       return skb->len;
+}
+
 /* Process one rtnetlink message. */
 
 static int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh)
@@ -3602,4 +3791,7 @@ void __init rtnetlink_init(void)
        rtnl_register(PF_BRIDGE, RTM_GETLINK, NULL, rtnl_bridge_getlink, NULL);
        rtnl_register(PF_BRIDGE, RTM_DELLINK, rtnl_bridge_dellink, NULL, NULL);
        rtnl_register(PF_BRIDGE, RTM_SETLINK, rtnl_bridge_setlink, NULL, NULL);
+
+       rtnl_register(PF_UNSPEC, RTM_GETSTATS, rtnl_stats_get, rtnl_stats_dump,
+                     NULL);
 }