net: rtnetlink: allow rtnl_fill_statsinfo to save private state counter
[cascardo/linux.git] / net / core / rtnetlink.c
index 5503dfe..de529a2 100644 (file)
@@ -3444,13 +3444,21 @@ 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)
+                              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();
 
@@ -3462,7 +3470,7 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
        ifsm->ifindex = dev->ifindex;
        ifsm->filter_mask = filter_mask;
 
-       if (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64)) {
+       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,
@@ -3480,7 +3488,11 @@ static int rtnl_fill_statsinfo(struct sk_buff *skb, struct net_device *dev,
        return 0;
 
 nla_put_failure:
-       nlmsg_cancel(skb, nlh);
+       /* 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;
 }
@@ -3494,7 +3506,7 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev,
 {
        size_t size = 0;
 
-       if (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64))
+       if (stats_attr_valid(filter_mask, IFLA_STATS_LINK_64, 0))
                size += nla_total_size_64bit(sizeof(struct rtnl_link_stats64));
 
        return size;
@@ -3503,8 +3515,9 @@ static size_t if_nlmsg_stats_size(const struct net_device *dev,
 static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
 {
        struct net *net = sock_net(skb->sk);
-       struct if_stats_msg *ifsm;
        struct net_device *dev = NULL;
+       int idxattr = 0, prividx = 0;
+       struct if_stats_msg *ifsm;
        struct sk_buff *nskb;
        u32 filter_mask;
        int err;
@@ -3528,7 +3541,7 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
 
        err = rtnl_fill_statsinfo(nskb, dev, RTM_NEWSTATS,
                                  NETLINK_CB(skb).portid, nlh->nlmsg_seq, 0,
-                                 0, filter_mask);
+                                 0, filter_mask, &idxattr, &prividx);
        if (err < 0) {
                /* -EMSGSIZE implies BUG in if_nlmsg_stats_size */
                WARN_ON(err == -EMSGSIZE);
@@ -3542,18 +3555,19 @@ static int rtnl_stats_get(struct sk_buff *skb, struct nlmsghdr *nlh)
 
 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;
-       int h, s_h;
-       int idx = 0, s_idx;
-       struct net_device *dev;
        struct hlist_head *head;
-       unsigned int flags = NLM_F_MULTI;
+       struct net_device *dev;
        u32 filter_mask = 0;
-       int err;
+       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;
 
@@ -3571,7 +3585,8 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
                        err = rtnl_fill_statsinfo(skb, dev, RTM_NEWSTATS,
                                                  NETLINK_CB(cb->skb).portid,
                                                  cb->nlh->nlmsg_seq, 0,
-                                                 flags, filter_mask);
+                                                 flags, filter_mask,
+                                                 &s_idxattr, &s_prividx);
                        /* If we ran out of room on the first message,
                         * we're in trouble
                         */
@@ -3579,13 +3594,16 @@ static int rtnl_stats_dump(struct sk_buff *skb, struct netlink_callback *cb)
 
                        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;