ipv6: anycast: complete RCU handling of struct ifacaddr6

struct ifacaddr6 are already freed after RCU grace period.

Add __rcu qualifier to aca_next pointer, and idev->ac_list

Add relevant rcu_assign_pointer() and dereference accessors.

ipv6_chk_acast_dev() no longer needs to acquire idev->lock.

/proc/net/anycast6 is now purely RCU protected, it no
longer acquires idev->lock.

Similarly in6_dump_addrs() can use RCU protection to iterate
through anycast addresses. It was relying on a mixture of RCU
and RTNL but next patches will get rid of RTNL there.

Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Jiri Pirko <jiri@nvidia.com>
Link: https://lore.kernel.org/r/20240223201054.220534-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Eric Dumazet 2024-02-23 20:10:54 +00:00 committed by Jakub Kicinski
parent 5c237967e6
commit c3718936ec
3 changed files with 27 additions and 42 deletions

View file

@ -144,7 +144,7 @@ struct ipv6_ac_socklist {
struct ifacaddr6 { struct ifacaddr6 {
struct in6_addr aca_addr; struct in6_addr aca_addr;
struct fib6_info *aca_rt; struct fib6_info *aca_rt;
struct ifacaddr6 *aca_next; struct ifacaddr6 __rcu *aca_next;
struct hlist_node aca_addr_lst; struct hlist_node aca_addr_lst;
int aca_users; int aca_users;
refcount_t aca_refcnt; refcount_t aca_refcnt;
@ -196,7 +196,7 @@ struct inet6_dev {
spinlock_t mc_report_lock; /* mld query report lock */ spinlock_t mc_report_lock; /* mld query report lock */
struct mutex mc_lock; /* mld global lock */ struct mutex mc_lock; /* mld global lock */
struct ifacaddr6 *ac_list; struct ifacaddr6 __rcu *ac_list;
rwlock_t lock; rwlock_t lock;
refcount_t refcnt; refcount_t refcnt;
__u32 if_flags; __u32 if_flags;

View file

@ -5314,8 +5314,8 @@ static int in6_dump_addrs(struct inet6_dev *idev, struct sk_buff *skb,
case ANYCAST_ADDR: case ANYCAST_ADDR:
fillargs->event = RTM_GETANYCAST; fillargs->event = RTM_GETANYCAST;
/* anycast address */ /* anycast address */
for (ifaca = idev->ac_list; ifaca; for (ifaca = rcu_dereference(idev->ac_list); ifaca;
ifaca = ifaca->aca_next, ip_idx++) { ifaca = rcu_dereference(ifaca->aca_next), ip_idx++) {
if (ip_idx < s_ip_idx) if (ip_idx < s_ip_idx)
continue; continue;
err = inet6_fill_ifacaddr(skb, ifaca, fillargs); err = inet6_fill_ifacaddr(skb, ifaca, fillargs);

View file

@ -296,7 +296,8 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
goto out; goto out;
} }
for (aca = idev->ac_list; aca; aca = aca->aca_next) { for (aca = rtnl_dereference(idev->ac_list); aca;
aca = rtnl_dereference(aca->aca_next)) {
if (ipv6_addr_equal(&aca->aca_addr, addr)) { if (ipv6_addr_equal(&aca->aca_addr, addr)) {
aca->aca_users++; aca->aca_users++;
err = 0; err = 0;
@ -317,13 +318,13 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
goto out; goto out;
} }
aca->aca_next = idev->ac_list;
idev->ac_list = aca;
/* Hold this for addrconf_join_solict() below before we unlock, /* Hold this for addrconf_join_solict() below before we unlock,
* it is already exposed via idev->ac_list. * it is already exposed via idev->ac_list.
*/ */
aca_get(aca); aca_get(aca);
aca->aca_next = idev->ac_list;
rcu_assign_pointer(idev->ac_list, aca);
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_add_acaddr_hash(net, aca); ipv6_add_acaddr_hash(net, aca);
@ -350,7 +351,8 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr)
write_lock_bh(&idev->lock); write_lock_bh(&idev->lock);
prev_aca = NULL; prev_aca = NULL;
for (aca = idev->ac_list; aca; aca = aca->aca_next) { for (aca = rtnl_dereference(idev->ac_list); aca;
aca = rtnl_dereference(aca->aca_next)) {
if (ipv6_addr_equal(&aca->aca_addr, addr)) if (ipv6_addr_equal(&aca->aca_addr, addr))
break; break;
prev_aca = aca; prev_aca = aca;
@ -364,9 +366,9 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr)
return 0; return 0;
} }
if (prev_aca) if (prev_aca)
prev_aca->aca_next = aca->aca_next; rcu_assign_pointer(prev_aca->aca_next, aca->aca_next);
else else
idev->ac_list = aca->aca_next; rcu_assign_pointer(idev->ac_list, aca->aca_next);
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_del_acaddr_hash(aca); ipv6_del_acaddr_hash(aca);
addrconf_leave_solict(idev, &aca->aca_addr); addrconf_leave_solict(idev, &aca->aca_addr);
@ -392,8 +394,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
struct ifacaddr6 *aca; struct ifacaddr6 *aca;
write_lock_bh(&idev->lock); write_lock_bh(&idev->lock);
while ((aca = idev->ac_list) != NULL) { while ((aca = rtnl_dereference(idev->ac_list)) != NULL) {
idev->ac_list = aca->aca_next; rcu_assign_pointer(idev->ac_list, aca->aca_next);
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_del_acaddr_hash(aca); ipv6_del_acaddr_hash(aca);
@ -420,11 +422,10 @@ static bool ipv6_chk_acast_dev(struct net_device *dev, const struct in6_addr *ad
idev = __in6_dev_get(dev); idev = __in6_dev_get(dev);
if (idev) { if (idev) {
read_lock_bh(&idev->lock); for (aca = rcu_dereference(idev->ac_list); aca;
for (aca = idev->ac_list; aca; aca = aca->aca_next) aca = rcu_dereference(aca->aca_next))
if (ipv6_addr_equal(&aca->aca_addr, addr)) if (ipv6_addr_equal(&aca->aca_addr, addr))
break; break;
read_unlock_bh(&idev->lock);
return aca != NULL; return aca != NULL;
} }
return false; return false;
@ -477,30 +478,25 @@ bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev,
struct ac6_iter_state { struct ac6_iter_state {
struct seq_net_private p; struct seq_net_private p;
struct net_device *dev; struct net_device *dev;
struct inet6_dev *idev;
}; };
#define ac6_seq_private(seq) ((struct ac6_iter_state *)(seq)->private) #define ac6_seq_private(seq) ((struct ac6_iter_state *)(seq)->private)
static inline struct ifacaddr6 *ac6_get_first(struct seq_file *seq) static inline struct ifacaddr6 *ac6_get_first(struct seq_file *seq)
{ {
struct ifacaddr6 *im = NULL;
struct ac6_iter_state *state = ac6_seq_private(seq); struct ac6_iter_state *state = ac6_seq_private(seq);
struct net *net = seq_file_net(seq); struct net *net = seq_file_net(seq);
struct ifacaddr6 *im = NULL;
state->idev = NULL;
for_each_netdev_rcu(net, state->dev) { for_each_netdev_rcu(net, state->dev) {
struct inet6_dev *idev; struct inet6_dev *idev;
idev = __in6_dev_get(state->dev); idev = __in6_dev_get(state->dev);
if (!idev) if (!idev)
continue; continue;
read_lock_bh(&idev->lock); im = rcu_dereference(idev->ac_list);
im = idev->ac_list; if (im)
if (im) {
state->idev = idev;
break; break;
}
read_unlock_bh(&idev->lock);
} }
return im; return im;
} }
@ -508,22 +504,17 @@ static inline struct ifacaddr6 *ac6_get_first(struct seq_file *seq)
static struct ifacaddr6 *ac6_get_next(struct seq_file *seq, struct ifacaddr6 *im) static struct ifacaddr6 *ac6_get_next(struct seq_file *seq, struct ifacaddr6 *im)
{ {
struct ac6_iter_state *state = ac6_seq_private(seq); struct ac6_iter_state *state = ac6_seq_private(seq);
struct inet6_dev *idev;
im = im->aca_next; im = rcu_dereference(im->aca_next);
while (!im) { while (!im) {
if (likely(state->idev != NULL))
read_unlock_bh(&state->idev->lock);
state->dev = next_net_device_rcu(state->dev); state->dev = next_net_device_rcu(state->dev);
if (!state->dev) { if (!state->dev)
state->idev = NULL;
break; break;
} idev = __in6_dev_get(state->dev);
state->idev = __in6_dev_get(state->dev); if (!idev)
if (!state->idev)
continue; continue;
read_lock_bh(&state->idev->lock); im = rcu_dereference(idev->ac_list);
im = state->idev->ac_list;
} }
return im; return im;
} }
@ -555,12 +546,6 @@ static void *ac6_seq_next(struct seq_file *seq, void *v, loff_t *pos)
static void ac6_seq_stop(struct seq_file *seq, void *v) static void ac6_seq_stop(struct seq_file *seq, void *v)
__releases(RCU) __releases(RCU)
{ {
struct ac6_iter_state *state = ac6_seq_private(seq);
if (likely(state->idev != NULL)) {
read_unlock_bh(&state->idev->lock);
state->idev = NULL;
}
rcu_read_unlock(); rcu_read_unlock();
} }