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)
        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);
 }
 
        { RTM_NEWNSID,          NETLINK_ROUTE_SOCKET__NLMSG_WRITE },
        { RTM_DELNSID,          NETLINK_ROUTE_SOCKET__NLMSG_READ  },
        { RTM_GETNSID,          NETLINK_ROUTE_SOCKET__NLMSG_READ  },
+       { RTM_NEWSTATS,         NETLINK_ROUTE_SOCKET__NLMSG_READ },
+       { RTM_GETSTATS,         NETLINK_ROUTE_SOCKET__NLMSG_READ  },
 };
 
 static struct nlmsg_perm nlmsg_tcpdiag_perms[] =
        switch (sclass) {
        case SECCLASS_NETLINK_ROUTE_SOCKET:
                /* RTM_MAX always point to RTM_SETxxxx, ie RTM_NEWxxx + 3 */
-               BUILD_BUG_ON(RTM_MAX != (RTM_NEWNSID + 3));
+               BUILD_BUG_ON(RTM_MAX != (RTM_NEWSTATS + 3));
                err = nlmsg_perm(nlmsg_type, perm, nlmsg_route_perms,
                                 sizeof(nlmsg_route_perms));
                break;