mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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:
commit
7888fedc74
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user