|
@@ -3449,6 +3449,161 @@ out:
|
|
|
return err;
|
|
|
}
|
|
|
|
|
|
+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)
|
|
|
+{
|
|
|
+ struct if_stats_msg *ifsm;
|
|
|
+ struct nlmsghdr *nlh;
|
|
|
+ struct nlattr *attr;
|
|
|
+
|
|
|
+ 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 (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64)) {
|
|
|
+ struct rtnl_link_stats64 *sp;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ /* if necessary, add a zero length NOP attribute so that
|
|
|
+ * IFLA_STATS_LINK_64 will be 64-bit aligned
|
|
|
+ */
|
|
|
+ err = nla_align_64bit(skb, IFLA_STATS_UNSPEC);
|
|
|
+ if (err)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ attr = nla_reserve(skb, IFLA_STATS_LINK_64,
|
|
|
+ sizeof(struct rtnl_link_stats64));
|
|
|
+ if (!attr)
|
|
|
+ goto nla_put_failure;
|
|
|
+
|
|
|
+ sp = nla_data(attr);
|
|
|
+ dev_get_stats(dev, sp);
|
|
|
+ }
|
|
|
+
|
|
|
+ nlmsg_end(skb, nlh);
|
|
|
+
|
|
|
+ return 0;
|
|
|
+
|
|
|
+nla_put_failure:
|
|
|
+ nlmsg_cancel(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 (filter_mask & IFLA_STATS_FILTER_BIT(IFLA_STATS_LINK_64))
|
|
|
+ size += nla_total_size_64bit(sizeof(struct rtnl_link_stats64));
|
|
|
+
|
|
|
+ return size;
|
|
|
+}
|
|
|
+
|
|
|
+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;
|
|
|
+ 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);
|
|
|
+ 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)
|
|
|
+{
|
|
|
+ struct net *net = sock_net(skb->sk);
|
|
|
+ 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;
|
|
|
+ u32 filter_mask = 0;
|
|
|
+ int err;
|
|
|
+
|
|
|
+ s_h = cb->args[0];
|
|
|
+ s_idx = cb->args[1];
|
|
|
+
|
|
|
+ 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);
|
|
|
+ /* 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;
|
|
|
+
|
|
|
+ nl_dump_check_consistent(cb, nlmsg_hdr(skb));
|
|
|
+cont:
|
|
|
+ idx++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+out:
|
|
|
+ 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)
|
|
@@ -3598,4 +3753,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);
|
|
|
}
|