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 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;
|
||||||
|
@ -5314,8 +5314,8 @@ next:
|
|||||||
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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user