RTM_GETMULTICAST dumps IPv4 and IPv6 multicast group memberships, but
the device multicast list (dev->mc) is only available through
/proc/net/dev_mcast, so "ip maddr show" still has to parse procfs for
its link-layer entries.

Handle RTM_GETMULTICAST dumps with ifa_family set to AF_PACKET and
report every entry of dev->mc in the existing ifaddrmsg format:

  - IFA_MULTICAST carries the raw link-layer address
  - IFA_MC_USERS carries the entry reference count
  - IFA_F_PERMANENT marks entries added with SIOCADDMULTI
    (netdev_hw_addr::global_use, "static" in "ip maddr")
  - ifa_scope is RT_SCOPE_LINK

This covers every column of /proc/net/dev_mcast. AF_PACKET is the
family iproute2 already uses for link-layer addresses ("ip -0"), and
AF_UNSPEC keeps its "all families" meaning from RTM_GETADDR.

The default FDB dump also walks dev->mc, but only for Ethernet devices
without an ndo_fdb_dump of their own, so bridge, vxlan or macvlan
devices never show their multicast filter there, and it has no users
count or SIOCADDMULTI bit. Extending it would change "bridge fdb show"
output and add NDA_* attributes, while this dump needs no new uAPI.

There are no legacy users of AF_PACKET requests, so they are always
validated: prefixlen, flags and scope must be zero, no attributes are
accepted, and a non-zero ifa_index restricts the dump to that device.
The dump runs under RCU and netif_addr_lock_bh() and does not need
RTNL.

Signed-off-by: Yuyang Huang <[email protected]>
---
 net/core/rtnetlink.c | 129 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 129 insertions(+)

diff --git a/net/core/rtnetlink.c b/net/core/rtnetlink.c
index 81c5a6104dea..b2febdb6915e 100644
--- a/net/core/rtnetlink.c
+++ b/net/core/rtnetlink.c
@@ -4566,6 +4566,133 @@ static int rtnl_dump_all(struct sk_buff *skb, struct 
netlink_callback *cb)
        return skb->len ? : ret;
 }
 
+static int rtnl_fill_mcaddr(struct sk_buff *skb, const struct net_device *dev,
+                           const struct netdev_hw_addr *ha, u32 portid,
+                           u32 seq, unsigned int flags)
+{
+       struct ifaddrmsg *ifm;
+       struct nlmsghdr *nlh;
+
+       nlh = nlmsg_put(skb, portid, seq, RTM_GETMULTICAST, sizeof(*ifm),
+                       flags);
+       if (!nlh)
+               return -EMSGSIZE;
+
+       ifm = nlmsg_data(nlh);
+       ifm->ifa_family = AF_PACKET;
+       ifm->ifa_prefixlen = 0;
+       ifm->ifa_flags = ha->global_use ? IFA_F_PERMANENT : 0;
+       ifm->ifa_scope = RT_SCOPE_LINK;
+       ifm->ifa_index = dev->ifindex;
+
+       if (nla_put(skb, IFA_MULTICAST, dev->addr_len, ha->addr) ||
+           nla_put_u32(skb, IFA_MC_USERS, ha->refcount)) {
+               nlmsg_cancel(skb, nlh);
+               return -EMSGSIZE;
+       }
+
+       nlmsg_end(skb, nlh);
+       return 0;
+}
+
+static int rtnl_dump_mcaddr_dev(struct net_device *dev, struct sk_buff *skb,
+                               struct netlink_callback *cb, int *s_addr_idx,
+                               unsigned int flags)
+{
+       struct netdev_hw_addr *ha;
+       int addr_idx = 0;
+       int err = 0;
+
+       netif_addr_lock_bh(dev);
+       netdev_for_each_mc_addr(ha, dev) {
+               if (addr_idx < *s_addr_idx) {
+                       addr_idx++;
+                       continue;
+               }
+               err = rtnl_fill_mcaddr(skb, dev, ha, NETLINK_CB(cb->skb).portid,
+                                      cb->nlh->nlmsg_seq, flags);
+               if (err < 0)
+                       break;
+               addr_idx++;
+       }
+       netif_addr_unlock_bh(dev);
+
+       *s_addr_idx = err < 0 ? addr_idx : 0;
+
+       return err;
+}
+
+static int rtnl_valid_dump_mcaddr_req(const struct nlmsghdr *nlh,
+                                     struct netlink_ext_ack *extack,
+                                     int *pifindex)
+{
+       struct ifaddrmsg *ifm;
+
+       ifm = nlmsg_payload(nlh, sizeof(*ifm));
+       if (!ifm) {
+               NL_SET_ERR_MSG(extack,
+                              "Invalid header for multicast dump request");
+               return -EINVAL;
+       }
+
+       if (ifm->ifa_prefixlen || ifm->ifa_flags || ifm->ifa_scope) {
+               NL_SET_ERR_MSG(extack,
+                              "Invalid values in multicast dump header");
+               return -EINVAL;
+       }
+
+       if (nlmsg_attrlen(nlh, sizeof(*ifm))) {
+               NL_SET_ERR_MSG(extack,
+                              "Invalid data after multicast dump header");
+               return -EINVAL;
+       }
+
+       *pifindex = ifm->ifa_index;
+
+       return 0;
+}
+
+static int rtnl_dump_mcaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+       struct net *net = sock_net(skb->sk);
+       unsigned int flags = NLM_F_MULTI;
+       struct {
+               unsigned long ifindex;
+               int addr_idx;
+       } *ctx = (void *)cb->ctx;
+       struct net_device *dev;
+       int ifindex;
+       int err;
+
+       err = rtnl_valid_dump_mcaddr_req(cb->nlh, cb->extack, &ifindex);
+       if (err < 0)
+               return err;
+
+       rcu_read_lock();
+
+       if (ifindex) {
+               cb->answer_flags |= NLM_F_DUMP_FILTERED;
+               flags |= NLM_F_DUMP_FILTERED;
+               dev = dev_get_by_index_rcu(net, ifindex);
+               if (!dev) {
+                       err = -ENODEV;
+                       goto out;
+               }
+               err = rtnl_dump_mcaddr_dev(dev, skb, cb, &ctx->addr_idx, flags);
+               goto out;
+       }
+
+       for_each_netdev_dump(net, dev, ctx->ifindex) {
+               err = rtnl_dump_mcaddr_dev(dev, skb, cb, &ctx->addr_idx,
+                                          flags);
+               if (err < 0)
+                       break;
+       }
+out:
+       rcu_read_unlock();
+       return err;
+}
+
 struct sk_buff *rtmsg_ifinfo_build_skb(int type, struct net_device *dev,
                                       unsigned int change,
                                       u32 event, gfp_t flags, int *new_nsid,
@@ -7251,6 +7378,8 @@ static const struct rtnl_msg_handler 
rtnetlink_rtnl_msg_handlers[] __initconst =
        {.msgtype = RTM_SETSTATS, .doit = rtnl_stats_set},
        {.msgtype = RTM_NEWLINKPROP, .doit = rtnl_newlinkprop},
        {.msgtype = RTM_DELLINKPROP, .doit = rtnl_dellinkprop},
+       {.protocol = PF_PACKET, .msgtype = RTM_GETMULTICAST,
+        .dumpit = rtnl_dump_mcaddr, .flags = RTNL_FLAG_DUMP_UNLOCKED},
        {.protocol = PF_BRIDGE, .msgtype = RTM_GETLINK,
         .dumpit = rtnl_bridge_getlink},
        {.protocol = PF_BRIDGE, .msgtype = RTM_DELLINK,
-- 
2.43.0


Reply via email to