Merge branch 'rtnetlink-dump-link-layer-multicast-addresses'

Yuyang Huang says:

====================
rtnetlink: dump link-layer multicast addresses

"ip maddr show" prints link-layer, IPv4 and IPv6 entries. The IPv4 and
IPv6 ones come over netlink today, with IFA_MC_USERS for the user count.
The link-layer list, dev->mc, is only exported via /proc/net/dev_mcast,
so iproute2 and other users still parse procfs for it.

This series adds the AF_PACKET family to RTM_GETMULTICAST so that the
link-layer multicast addresses are reported the same way. Patch 4 dumps
dev->mc in the ifaddrmsg format of the IPv4 and IPv6 dumps: IFA_MULTICAST
carries the address, IFA_MC_USERS the reference count and a new
IFA_F_GLOBAL flag marks entries added explicitly, the "static" column of
/proc/net/dev_mcast. ifa_index limits the dump to one device and
IFA_TARGET_NETNSID selects another netns. Patches 2 and 3 add a
generation counter for dev->mc changes so a multi-part dump reports
NLM_F_DUMP_INTR like the IPv4 and IPv6 ones. Patches 1 and 5 update the
rt-addr spec and patch 6 adds a selftest.

AF_PACKET dumps returned -EOPNOTSUPP before, so iproute2 can keep the
procfs fallback for older kernels. The iproute2 side is ready and will
be posted once this is in.
====================

Link: https://patch.msgid.link/20261012011707.3378-1-sigefriedhyy@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni 2026-10-21 10:51:28 +02:00
commit 7888fedc74
11 changed files with 357 additions and 35 deletions

View File

@ -77,6 +77,8 @@ definitions:
name: mcautojoin
-
name: stable-privacy
-
name: global
attribute-sets:
-
@ -119,7 +121,7 @@ attribute-sets:
type: u32
-
name: target-netnsid
type: binary
type: s32
-
name: proto
type: u8
@ -168,7 +170,17 @@ operations:
attributes: *ifaddr-all
-
name: getmulticast
doc: Get / dump IPv4/IPv6 multicast addresses.
doc: |
Get / dump multicast addresses. ifa-family must select the address
family: AF_INET or AF_INET6 for the IP multicast groups joined on
a device, AF_PACKET for the link-layer multicast addresses in the
device filter. Link-layer entries added explicitly, e.g. with
SIOCADDMULTI or "bridge fdb add ... self", rather than by a
protocol join are reported with the global flag set. For AF_PACKET
a non-zero ifa-index restricts the dump to that device and
ifa-prefixlen, ifa-flags and ifa-scope must be zero, AF_INET and
AF_INET6 apply this and target-netnsid with NETLINK_GET_STRICT_CHK
only.
attribute-set: addr-attrs
fixed-header: ifaddrmsg
do:
@ -181,10 +193,13 @@ operations:
- multicast
- mc-users
- cacheinfo
- flags
- target-netnsid
dump:
request:
value: 58
attributes: []
attributes:
- target-netnsid
reply:
value: 58
attributes: *mcaddr-attrs

View File

@ -573,14 +573,14 @@ static void octeon_mgmt_set_rx_filtering(struct net_device *netdev)
memset(&cam_state, 0, sizeof(cam_state));
if ((netdev->flags & IFF_PROMISC) || netdev->uc.count > 7) {
if ((netdev->flags & IFF_PROMISC) || netdev_uc_count(netdev) > 7) {
cam_mode = 0;
available_cam_entries = 8;
} else {
/* One CAM entry for the primary address, leaves seven
* for the secondary addresses.
*/
available_cam_entries = 7 - netdev->uc.count;
available_cam_entries = 7 - netdev_uc_count(netdev);
}
if (netdev->flags & IFF_MULTICAST) {

View File

@ -704,17 +704,17 @@ static void wilc_set_multicast_list(struct net_device *dev)
return;
if (dev->flags & IFF_ALLMULTI ||
dev->mc.count > WILC_MULTICAST_TABLE_SIZE) {
netdev_mc_count(dev) > WILC_MULTICAST_TABLE_SIZE) {
wilc_setup_multicast_filter(vif, 0, 0, NULL);
return;
}
if (dev->mc.count == 0) {
if (netdev_mc_empty(dev)) {
wilc_setup_multicast_filter(vif, 1, 0, NULL);
return;
}
mc_list = kmalloc_array(dev->mc.count, ETH_ALEN, GFP_ATOMIC);
mc_list = kmalloc_array(netdev_mc_count(dev), ETH_ALEN, GFP_ATOMIC);
if (!mc_list)
return;
@ -727,7 +727,7 @@ static void wilc_set_multicast_list(struct net_device *dev)
cur_mc += ETH_ALEN;
}
if (wilc_setup_multicast_filter(vif, 1, dev->mc.count, mc_list))
if (wilc_setup_multicast_filter(vif, 1, netdev_mc_count(dev), mc_list))
kfree(mc_list);
}

View File

@ -252,13 +252,19 @@ struct netdev_hw_addr {
struct netdev_hw_addr_list {
struct list_head list;
int count;
/* Only changed through the __hw_addr_count_* helpers after
* __hw_addr_init()
*/
int _count;
/* Auxiliary tree for faster lookup on addition and deletion */
struct rb_root tree;
/* Set for dev->mc only, the device the list belongs to */
struct net_device *mc_dev;
};
#define netdev_hw_addr_list_count(l) ((l)->count)
#define netdev_hw_addr_list_count(l) ((l)->_count)
#define netdev_hw_addr_list_empty(l) (netdev_hw_addr_list_count(l) == 0)
#define netdev_hw_addr_list_for_each(ha, l) \
list_for_each_entry(ha, &(l)->list, list)
@ -5175,6 +5181,7 @@ int dev_mc_sync_multiple(struct net_device *to, struct net_device *from);
void dev_mc_unsync(struct net_device *to, struct net_device *from);
void dev_mc_flush(struct net_device *dev);
void dev_mc_init(struct net_device *dev);
int dev_mc_dump(struct sk_buff *skb, struct netlink_callback *cb);
/**
* __dev_mc_sync - Synchronize device's multicast list

View File

@ -71,6 +71,7 @@ struct net {
spinlock_t rules_mod_lock;
unsigned int dev_base_seq; /* protected by rtnl_mutex */
atomic_t dev_mc_genid; /* dev->mc membership changes */
u32 ifindex;
spinlock_t nsid_lock;

View File

@ -57,6 +57,7 @@ enum {
#define IFA_F_NOPREFIXROUTE 0x200
#define IFA_F_MCAUTOJOIN 0x400
#define IFA_F_STABLE_PRIVACY 0x800
#define IFA_F_GLOBAL 0x1000
struct ifa_cacheinfo {
__u32 ifa_prefered;

View File

@ -10,12 +10,49 @@
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/export.h>
#include <linux/if_addr.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <net/netlink.h>
#include <net/rtnetlink.h>
#include <net/sock.h>
#include <kunit/visibility.h>
#include "dev.h"
/* RTM_GETMULTICAST dumps use the netns generation counter to detect
* changes of dev->mc between dump rounds.
*/
static void __hw_addr_changed(struct netdev_hw_addr_list *list)
{
if (list->mc_dev)
atomic_inc(&dev_net(list->mc_dev)->dev_mc_genid);
}
static void __hw_addr_count_add(struct netdev_hw_addr_list *list, int value)
{
list->_count += value;
__hw_addr_changed(list);
}
static void __hw_addr_count_inc(struct netdev_hw_addr_list *list)
{
__hw_addr_count_add(list, 1);
}
static void __hw_addr_count_dec(struct netdev_hw_addr_list *list)
{
__hw_addr_count_add(list, -1);
}
static void __hw_addr_count_reset(struct netdev_hw_addr_list *list)
{
if (!list->_count)
return;
list->_count = 0;
__hw_addr_changed(list);
}
/*
* General list handling functions
*/
@ -125,7 +162,7 @@ static int __hw_addr_add_ex(struct netdev_hw_addr_list *list,
rb_insert_color(&ha->node, &list->tree);
list_add_tail_rcu(&ha->list, &list->list);
list->count++;
__hw_addr_count_inc(list);
return 0;
}
@ -161,7 +198,7 @@ static int __hw_addr_del_entry(struct netdev_hw_addr_list *list,
list_del_rcu(&ha->list);
kfree_rcu(ha, rcu_head);
list->count--;
__hw_addr_count_dec(list);
return 0;
}
@ -492,15 +529,16 @@ void __hw_addr_flush(struct netdev_hw_addr_list *list)
list_del_rcu(&ha->list);
kfree_rcu(ha, rcu_head);
}
list->count = 0;
__hw_addr_count_reset(list);
}
EXPORT_SYMBOL_IF_KUNIT(__hw_addr_flush);
void __hw_addr_init(struct netdev_hw_addr_list *list)
{
INIT_LIST_HEAD(&list->list);
list->count = 0;
list->_count = 0;
list->tree = RB_ROOT;
list->mc_dev = NULL;
}
EXPORT_SYMBOL(__hw_addr_init);
@ -509,8 +547,8 @@ static void __hw_addr_splice(struct netdev_hw_addr_list *dst,
{
src->tree = RB_ROOT;
list_splice_init(&src->list, &dst->list);
dst->count += src->count;
src->count = 0;
__hw_addr_count_add(dst, netdev_hw_addr_list_count(src));
__hw_addr_count_reset(src);
}
/**
@ -532,11 +570,11 @@ int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
struct netdev_hw_addr *ha, *entry;
list_for_each_entry(ha, &list->list, list) {
if (cache->count) {
if (netdev_hw_addr_list_count(cache)) {
entry = list_first_entry(&cache->list,
struct netdev_hw_addr, list);
list_del(&entry->list);
cache->count--;
__hw_addr_count_dec(cache);
memcpy(entry->addr, ha->addr, addr_len);
entry->type = ha->type;
entry->global_use = false;
@ -554,7 +592,7 @@ int __hw_addr_list_snapshot(struct netdev_hw_addr_list *snap,
list_add_tail(&entry->list, &snap->list);
__hw_addr_insert(snap, entry, addr_len);
snap->count++;
__hw_addr_count_inc(snap);
}
return 0;
@ -604,14 +642,14 @@ void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
if (delta > 0) {
rb_erase(&ref_ha->node, &ref->tree);
list_del(&ref_ha->list);
ref->count--;
__hw_addr_count_dec(ref);
ref_ha->sync_cnt = delta;
ref_ha->refcount = delta;
list_add_tail_rcu(&ref_ha->list,
&real_list->list);
__hw_addr_insert(real_list, ref_ha,
addr_len);
real_list->count++;
__hw_addr_count_inc(real_list);
}
continue;
}
@ -622,7 +660,7 @@ void __hw_addr_list_reconcile(struct netdev_hw_addr_list *real_list,
rb_erase(&real_ha->node, &real_list->tree);
list_del_rcu(&real_ha->list);
kfree_rcu(real_ha, rcu_head);
real_list->count--;
__hw_addr_count_dec(real_list);
}
}
@ -1177,9 +1215,198 @@ EXPORT_SYMBOL(dev_mc_flush);
void dev_mc_init(struct net_device *dev)
{
__hw_addr_init(&dev->mc);
dev->mc.mc_dev = dev;
}
EXPORT_SYMBOL(dev_mc_init);
static int dev_mc_fill_addr(struct sk_buff *skb, const struct net_device *dev,
const struct netdev_hw_addr *ha, u32 portid,
u32 seq, unsigned int flags, int netnsid)
{
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 = 0;
ifm->ifa_scope = RT_SCOPE_LINK;
ifm->ifa_index = dev->ifindex;
if ((netnsid >= 0 &&
nla_put_s32(skb, IFA_TARGET_NETNSID, netnsid)) ||
nla_put(skb, IFA_MULTICAST, dev->addr_len, ha->addr) ||
nla_put_u32(skb, IFA_MC_USERS, ha->refcount) ||
nla_put_u32(skb, IFA_FLAGS, ha->global_use ? IFA_F_GLOBAL : 0)) {
nlmsg_cancel(skb, nlh);
return -EMSGSIZE;
}
nlmsg_end(skb, nlh);
return 0;
}
/* Combine dev_mc_genid and dev_base_seq to detect changes, like
* inet_base_seq().
*/
static u32 dev_mc_base_seq(const struct net *net)
{
u32 res = atomic_read(&net->dev_mc_genid) +
READ_ONCE(net->dev_base_seq);
/* Must not return 0 (see nl_dump_check_consistent()). */
if (!res)
res = 0x80000000;
return res;
}
static int dev_mc_dump_dev(struct net_device *dev, struct sk_buff *skb,
struct netlink_callback *cb, int *s_addr_idx,
unsigned int flags, int netnsid)
{
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 = dev_mc_fill_addr(skb, dev, ha, NETLINK_CB(cb->skb).portid,
cb->nlh->nlmsg_seq, flags, netnsid);
if (err < 0)
break;
addr_idx++;
}
netif_addr_unlock_bh(dev);
*s_addr_idx = err < 0 ? addr_idx : 0;
return err;
}
struct dev_mc_dump_filter {
struct net *tgt_net;
netns_tracker ns_tracker;
int netnsid;
int ifindex;
};
static const struct nla_policy dev_mc_dump_policy[IFA_MAX + 1] = {
[IFA_TARGET_NETNSID] = { .type = NLA_S32 },
};
static int dev_mc_valid_dump_req(const struct nlmsghdr *nlh, struct sock *sk,
struct dev_mc_dump_filter *filter,
struct netlink_ext_ack *extack)
{
struct nlattr *tb[IFA_MAX + 1];
struct ifaddrmsg *ifm;
int err;
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;
}
err = nlmsg_parse(nlh, sizeof(*ifm), tb, IFA_MAX,
dev_mc_dump_policy, extack);
if (err < 0)
return err;
if (tb[IFA_TARGET_NETNSID]) {
struct net *net;
filter->netnsid = nla_get_s32(tb[IFA_TARGET_NETNSID]);
net = rtnl_get_net_ns_capable(sk, filter->netnsid);
if (IS_ERR(net)) {
NL_SET_ERR_MSG(extack,
"Invalid target network namespace id");
return PTR_ERR(net);
}
netns_tracker_alloc(net, &filter->ns_tracker, GFP_KERNEL);
filter->tgt_net = net;
}
filter->ifindex = ifm->ifa_index;
return 0;
}
int dev_mc_dump(struct sk_buff *skb, struct netlink_callback *cb)
{
struct dev_mc_dump_filter filter = {
.tgt_net = sock_net(skb->sk),
.netnsid = -1,
};
unsigned int flags = NLM_F_MULTI;
struct {
unsigned long ifindex;
int addr_idx;
} *ctx = (void *)cb->ctx;
unsigned long s_ifindex;
struct net_device *dev;
int err;
err = dev_mc_valid_dump_req(cb->nlh, skb->sk, &filter, cb->extack);
if (err < 0)
return err;
rcu_read_lock();
cb->seq = dev_mc_base_seq(filter.tgt_net);
if (filter.ifindex) {
cb->answer_flags |= NLM_F_DUMP_FILTERED;
flags |= NLM_F_DUMP_FILTERED;
dev = dev_get_by_index_rcu(filter.tgt_net, filter.ifindex);
if (dev)
err = dev_mc_dump_dev(dev, skb, cb, &ctx->addr_idx,
flags, filter.netnsid);
else
err = -ENODEV;
} else {
s_ifindex = ctx->ifindex;
for_each_netdev_dump(filter.tgt_net, dev, ctx->ifindex) {
/* The device the dump stopped at is gone, do not
* skip entries of the next one.
*/
if (dev->ifindex != s_ifindex)
ctx->addr_idx = 0;
err = dev_mc_dump_dev(dev, skb, cb, &ctx->addr_idx,
flags, filter.netnsid);
if (err < 0)
break;
}
}
/* A round without a message has nothing to flag, the NLMSG_DONE
* check then compares against the previous round.
*/
if (skb->len)
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
/* Let the NLMSG_DONE check see changes made during the last round */
cb->seq = dev_mc_base_seq(filter.tgt_net);
rcu_read_unlock();
if (filter.netnsid >= 0)
put_net_track(filter.tgt_net, &filter.ns_tracker);
return err;
}
static int netif_addr_lists_snapshot(struct net_device *dev,
struct netdev_hw_addr_list *uc_snap,
struct netdev_hw_addr_list *mc_snap,

View File

@ -291,7 +291,7 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
netif_addr_unlock_bh(netdev);
/* Real entry should now reflect the sync: sync_cnt=1, refcount=2 */
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 1, netdev_uc_count(netdev));
ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
@ -303,7 +303,7 @@ static void dev_addr_test_snapshot_sync(struct kunit *test)
dev_addr_test_unsync);
KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
KUNIT_EXPECT_EQ(test, 0, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 1, netdev_uc_count(netdev));
__hw_addr_flush(&cache);
rtnl_unlock();
@ -351,7 +351,7 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
/* Concurrent removal: user deletes ADDR_A while driver was working */
memset(addr, ADDR_A, sizeof(addr));
KUNIT_EXPECT_EQ(test, 0, dev_uc_del(netdev, addr));
KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 0, netdev_uc_count(netdev));
/* Reconcile: ADDR_A gone from real list but driver synced it,
* so it gets re-inserted as stale (sync_cnt=1, refcount=1).
@ -361,7 +361,7 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
&cache);
netif_addr_unlock_bh(netdev);
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 1, netdev_uc_count(netdev));
ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
KUNIT_EXPECT_EQ(test, 1, ha->sync_cnt);
@ -373,7 +373,7 @@ static void dev_addr_test_snapshot_remove_during_sync(struct kunit *test)
dev_addr_test_unsync);
KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
KUNIT_EXPECT_EQ(test, 1 << ADDR_A, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 0, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 0, netdev_uc_count(netdev));
__hw_addr_flush(&cache);
rtnl_unlock();
@ -433,7 +433,7 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
* stale entry and bumps refcount from 1 -> 2. sync_cnt stays 1.
*/
KUNIT_EXPECT_EQ(test, 0, dev_uc_add(netdev, addr));
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 1, netdev_uc_count(netdev));
/* Reconcile: ref sync_cnt=1 matches real sync_cnt=1, delta=-1
* applied. Result: sync_cnt=0, refcount=1 (fresh).
@ -444,7 +444,7 @@ static void dev_addr_test_snapshot_readd_during_unsync(struct kunit *test)
netif_addr_unlock_bh(netdev);
/* Entry survives as fresh: needs re-sync to HW */
KUNIT_EXPECT_EQ(test, 1, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 1, netdev_uc_count(netdev));
ha = list_first_entry(&netdev->uc.list, struct netdev_hw_addr, list);
KUNIT_EXPECT_MEMEQ(test, ha->addr, addr, ETH_ALEN);
KUNIT_EXPECT_EQ(test, 0, ha->sync_cnt);
@ -528,7 +528,7 @@ static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
* ADDR_B: refcount went from 2->1 via dev_uc_del (still present, stale)
* ADDR_C: sync propagated (sync_cnt=1, refcount=2)
*/
KUNIT_EXPECT_EQ(test, 3, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 3, netdev_uc_count(netdev));
netdev_hw_addr_list_for_each(ha, &netdev->uc) {
u8 id = ha->addr[0];
@ -553,7 +553,7 @@ static void dev_addr_test_snapshot_add_and_remove(struct kunit *test)
dev_addr_test_unsync);
KUNIT_EXPECT_EQ(test, 0, datp->addr_synced);
KUNIT_EXPECT_EQ(test, 1 << ADDR_B, datp->addr_unsynced);
KUNIT_EXPECT_EQ(test, 2, netdev->uc.count);
KUNIT_EXPECT_EQ(test, 2, netdev_uc_count(netdev));
__hw_addr_flush(&cache);
rtnl_unlock();

View File

@ -7288,6 +7288,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 = dev_mc_dump, .flags = RTNL_FLAG_DUMP_UNLOCKED},
{.protocol = PF_BRIDGE, .msgtype = RTM_GETLINK,
.dumpit = rtnl_bridge_getlink},
{.protocol = PF_BRIDGE, .msgtype = RTM_DELLINK,

View File

@ -187,7 +187,7 @@ static inline u64 drv_prepare_multicast(struct ieee80211_local *local,
{
u64 ret = 0;
trace_drv_prepare_multicast(local, mc_list->count);
trace_drv_prepare_multicast(local, netdev_hw_addr_list_count(mc_list));
if (local->ops->prepare_multicast)
ret = local->ops->prepare_multicast(&local->hw, mc_list);

View File

@ -5,13 +5,18 @@ import socket
import struct
import time
from lib.py import bkg, ip, ksft_exit, ksft_run, ksft_eq, ksft_ge, ksft_true, KsftSkipEx
from lib.py import ksft_not_in, ksft_not_none
from lib.py import ksft_in, ksft_not_in, ksft_not_none
from lib.py import CmdExitFailure, NetNS, NetNSEnter, RtnlAddrFamily, RtnlRouteFamily
from lib.py import defer
IPV4_ALL_HOSTS_MULTICAST = b'\xe0\x00\x00\x01'
IPV4_TEST_MULTICAST = b'\xef\x01\x01\x01'
IPV6_TEST_MULTICAST = bytes.fromhex('ff020000000000000000000000000123')
ETH_ALL_HOSTS_MULTICAST = bytes.fromhex('01005e000001')
ETH_TEST_MULTICAST_STR = '01:00:5e:01:01:01'
ETH_TEST_MULTICAST = bytes.fromhex(ETH_TEST_MULTICAST_STR.replace(':', ''))
ETH_PEER_MULTICAST_STR = '01:00:5e:02:02:02'
ETH_PEER_MULTICAST = bytes.fromhex(ETH_PEER_MULTICAST_STR.replace(':', ''))
def _users_for(rtnl: RtnlAddrFamily, family: int, grp: bytes, ifindex: int):
@ -105,6 +110,69 @@ def dump_mcaddr6_check() -> None:
s2.close()
def dump_mcaddr_l2_check() -> None:
"""
Verify link-layer multicast addresses in an AF_PACKET RTM_GETMULTICAST
dump: the ifa-index filter, mc-users, the global flag and
target-netnsid.
"""
with NetNS() as ns, NetNSEnter(str(ns)):
for ifname in ("dummy1", "dummy2"):
ip(f"link add name {ifname} type dummy")
ip(f"link set {ifname} up")
dev_idx = socket.if_nametoindex("dummy1")
ip(f"maddr add {ETH_TEST_MULTICAST_STR} dev dummy1")
rtnl = RtnlAddrFamily()
defer(rtnl.close)
addresses = rtnl.getmulticast(
{"ifa-family": socket.AF_PACKET, "ifa-index": dev_idx},
dump=True)
# dummy2 has entries as well, only dummy1 may be listed
ksft_eq({addr['ifa-index'] for addr in addresses}, {dev_idx},
"AF_PACKET multicast dump ignored ifa-index filter")
entries = {addr['multicast']: addr for addr in addresses}
# Bringing an Ethernet device up joins 224.0.0.1, which maps
# to 01:00:5e:00:00:01 in the device multicast list.
all_hosts = entries.get(ETH_ALL_HOSTS_MULTICAST)
ksft_not_none(all_hosts,
"dummy1 does not have the all-hosts link-layer address")
if all_hosts is not None:
ksft_not_in('global', all_hosts['flags'],
"protocol entry is global")
static = entries.get(ETH_TEST_MULTICAST)
ksft_not_none(static, "dummy1 does not have the SIOCADDMULTI address")
if static is not None:
ksft_eq(static['mc-users'], 1,
"unexpected mc-users for the SIOCADDMULTI address")
ksft_in('global', static['flags'],
"SIOCADDMULTI entry is not global")
# target-netnsid dumps another netns, ifa-index is relative to it
with NetNS() as peer:
ip(f"netns set {peer} 5")
ip("link add name dummy3 type dummy", ns=peer)
ip("link set dummy3 up", ns=peer)
ip(f"maddr add {ETH_PEER_MULTICAST_STR} dev dummy3", ns=peer)
peer_idx = ip("link show dummy3", json=True, ns=peer)[0]['ifindex']
addresses = rtnl.getmulticast(
{"ifa-family": socket.AF_PACKET, "target-netnsid": 5,
"ifa-index": peer_idx}, dump=True)
ksft_eq({(addr['ifa-index'], addr['target-netnsid'])
for addr in addresses}, {(peer_idx, 5)},
"target-netnsid did not dump the peer netns")
# dummy1 in this netns can have the same ifindex as dummy3
ksft_in(ETH_PEER_MULTICAST,
{addr['multicast'] for addr in addresses},
"target-netnsid did not dump the peer device")
def ipv4_devconf_notify() -> None:
"""
Configure an interface and set ipv4-devconf values through netlink
@ -424,7 +492,8 @@ def ipv6_verify_inter_scope_addr_order() -> None:
def main() -> None:
ksft_run([dump_mcaddr_check, dump_mcaddr6_check, ipv4_devconf_notify,
ksft_run([dump_mcaddr_check, dump_mcaddr6_check, dump_mcaddr_l2_check,
ipv4_devconf_notify,
ipv6_route_del_reason_expired,
ipv6_route_del_reason_ra_withdrawn,
ipv6_route_del_reason_absent,