Re: [PATCH net-next v2 1/3] rtnetlink: add AF_PACKET multicast dumps
From: Nicolas Dichtel <hidden>
Date: 2026-09-09 08:44:44
Also in:
linux-kselftest, lkml
Le 09/09/2026 à 03:33, Yuyang Huang a écrit :
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")The global flag is also set for addresses added via dev_mc_add_excl(), ie by some drivers.
quoted hunk ↗ jump to hunk
- 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 <redacted> --- 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;
I wonder if adding a new attribute IFA_F_GLOBAL would not make the API more understandable.
+ 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);For consistency with ipv4/ipv6, it would be nice to handle IFA_TARGET_NETNSID.
quoted hunk ↗ jump to hunk
+ 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,