diff options
Diffstat (limited to 'net/core/rtnetlink.c')
-rw-r--r-- | net/core/rtnetlink.c | 44 |
1 files changed, 31 insertions, 13 deletions
diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c index 5503dfe6a050..de529a20cd18 100644 --- a/net/core/rtnetlink.c +++ b/net/core/rtnetlink.c @@ -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; |