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:
parent
5c237967e6
commit
c3718936ec
@ -144,7 +144,7 @@ struct ipv6_ac_socklist {
|
||||
struct ifacaddr6 {
|
||||
struct in6_addr aca_addr;
|
||||
struct fib6_info *aca_rt;
|
||||
struct ifacaddr6 *aca_next;
|
||||
struct ifacaddr6 __rcu *aca_next;
|
||||
struct hlist_node aca_addr_lst;
|
||||
int aca_users;
|
||||
refcount_t aca_refcnt;
|
||||
@ -196,7 +196,7 @@ struct inet6_dev {
|
||||
spinlock_t mc_report_lock; /* mld query report lock */
|
||||
struct mutex mc_lock; /* mld global lock */
|
||||
|
||||
struct ifacaddr6 *ac_list;
|
||||
struct ifacaddr6 __rcu *ac_list;
|
||||
rwlock_t lock;
|
||||
refcount_t refcnt;
|
||||
__u32 if_flags;
|
||||
|
@ -5314,8 +5314,8 @@ next:
|
||||
case ANYCAST_ADDR:
|
||||
fillargs->event = RTM_GETANYCAST;
|
||||
/* anycast address */
|
||||
for (ifaca = idev->ac_list; ifaca;
|
||||
ifaca = ifaca->aca_next, ip_idx++) {
|
||||
for (ifaca = rcu_dereference(idev->ac_list); ifaca;
|
||||
ifaca = rcu_dereference(ifaca->aca_next), ip_idx++) {
|
||||
if (ip_idx < s_ip_idx)
|
||||
continue;
|
||||
err = inet6_fill_ifacaddr(skb, ifaca, fillargs);
|
||||
|
@ -296,7 +296,8 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
|
||||
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)) {
|
||||
aca->aca_users++;
|
||||
err = 0;
|
||||
@ -317,13 +318,13 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
|
||||
goto out;
|
||||
}
|
||||
|
||||
aca->aca_next = idev->ac_list;
|
||||
idev->ac_list = aca;
|
||||
|
||||
/* Hold this for addrconf_join_solict() below before we unlock,
|
||||
* it is already exposed via idev->ac_list.
|
||||
*/
|
||||
aca_get(aca);
|
||||
aca->aca_next = idev->ac_list;
|
||||
rcu_assign_pointer(idev->ac_list, aca);
|
||||
|
||||
write_unlock_bh(&idev->lock);
|
||||
|
||||
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);
|
||||
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))
|
||||
break;
|
||||
prev_aca = aca;
|
||||
@ -364,9 +366,9 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr)
|
||||
return 0;
|
||||
}
|
||||
if (prev_aca)
|
||||
prev_aca->aca_next = aca->aca_next;
|
||||
rcu_assign_pointer(prev_aca->aca_next, aca->aca_next);
|
||||
else
|
||||
idev->ac_list = aca->aca_next;
|
||||
rcu_assign_pointer(idev->ac_list, aca->aca_next);
|
||||
write_unlock_bh(&idev->lock);
|
||||
ipv6_del_acaddr_hash(aca);
|
||||
addrconf_leave_solict(idev, &aca->aca_addr);
|
||||
@ -392,8 +394,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
|
||||
struct ifacaddr6 *aca;
|
||||
|
||||
write_lock_bh(&idev->lock);
|
||||
while ((aca = idev->ac_list) != NULL) {
|
||||
idev->ac_list = aca->aca_next;
|
||||
while ((aca = rtnl_dereference(idev->ac_list)) != NULL) {
|
||||
rcu_assign_pointer(idev->ac_list, aca->aca_next);
|
||||
write_unlock_bh(&idev->lock);
|
||||
|
||||
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);
|
||||
if (idev) {
|
||||
read_lock_bh(&idev->lock);
|
||||
for (aca = idev->ac_list; aca; aca = aca->aca_next)
|
||||
for (aca = rcu_dereference(idev->ac_list); aca;
|
||||
aca = rcu_dereference(aca->aca_next))
|
||||
if (ipv6_addr_equal(&aca->aca_addr, addr))
|
||||
break;
|
||||
read_unlock_bh(&idev->lock);
|
||||
return aca != NULL;
|
||||
}
|
||||
return false;
|
||||
@ -477,30 +478,25 @@ bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev,
|
||||
struct ac6_iter_state {
|
||||
struct seq_net_private p;
|
||||
struct net_device *dev;
|
||||
struct inet6_dev *idev;
|
||||
};
|
||||
|
||||
#define ac6_seq_private(seq) ((struct ac6_iter_state *)(seq)->private)
|
||||
|
||||
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 net *net = seq_file_net(seq);
|
||||
struct ifacaddr6 *im = NULL;
|
||||
|
||||
state->idev = NULL;
|
||||
for_each_netdev_rcu(net, state->dev) {
|
||||
struct inet6_dev *idev;
|
||||
|
||||
idev = __in6_dev_get(state->dev);
|
||||
if (!idev)
|
||||
continue;
|
||||
read_lock_bh(&idev->lock);
|
||||
im = idev->ac_list;
|
||||
if (im) {
|
||||
state->idev = idev;
|
||||
im = rcu_dereference(idev->ac_list);
|
||||
if (im)
|
||||
break;
|
||||
}
|
||||
read_unlock_bh(&idev->lock);
|
||||
}
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
if (likely(state->idev != NULL))
|
||||
read_unlock_bh(&state->idev->lock);
|
||||
|
||||
state->dev = next_net_device_rcu(state->dev);
|
||||
if (!state->dev) {
|
||||
state->idev = NULL;
|
||||
if (!state->dev)
|
||||
break;
|
||||
}
|
||||
state->idev = __in6_dev_get(state->dev);
|
||||
if (!state->idev)
|
||||
idev = __in6_dev_get(state->dev);
|
||||
if (!idev)
|
||||
continue;
|
||||
read_lock_bh(&state->idev->lock);
|
||||
im = state->idev->ac_list;
|
||||
im = rcu_dereference(idev->ac_list);
|
||||
}
|
||||
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)
|
||||
__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();
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user