ipv6: Use state_lock to protect ifa state
This patch makes use of the new state_lock to synchronise between updates to the ifa state. This fixes the issue where a remotely triggered address deletion (through DAD failure) coincides with a local administrative address deletion, causing certain actions to be performed twice incorrectly. Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
e9d3e08497
commit
4c5ff6a6fe
1 changed files with 23 additions and 4 deletions
|
@ -715,13 +715,20 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
|
||||||
{
|
{
|
||||||
struct inet6_ifaddr *ifa, *ifn;
|
struct inet6_ifaddr *ifa, *ifn;
|
||||||
struct inet6_dev *idev = ifp->idev;
|
struct inet6_dev *idev = ifp->idev;
|
||||||
|
int state;
|
||||||
int hash;
|
int hash;
|
||||||
int deleted = 0, onlink = 0;
|
int deleted = 0, onlink = 0;
|
||||||
unsigned long expires = jiffies;
|
unsigned long expires = jiffies;
|
||||||
|
|
||||||
hash = ipv6_addr_hash(&ifp->addr);
|
hash = ipv6_addr_hash(&ifp->addr);
|
||||||
|
|
||||||
|
spin_lock_bh(&ifp->state_lock);
|
||||||
|
state = ifp->state;
|
||||||
ifp->state = INET6_IFADDR_STATE_DEAD;
|
ifp->state = INET6_IFADDR_STATE_DEAD;
|
||||||
|
spin_unlock_bh(&ifp->state_lock);
|
||||||
|
|
||||||
|
if (state == INET6_IFADDR_STATE_DEAD)
|
||||||
|
goto out;
|
||||||
|
|
||||||
spin_lock_bh(&addrconf_hash_lock);
|
spin_lock_bh(&addrconf_hash_lock);
|
||||||
hlist_del_init_rcu(&ifp->addr_lst);
|
hlist_del_init_rcu(&ifp->addr_lst);
|
||||||
|
@ -819,6 +826,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp)
|
||||||
dst_release(&rt->u.dst);
|
dst_release(&rt->u.dst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
in6_ifa_put(ifp);
|
in6_ifa_put(ifp);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2626,6 +2634,7 @@ static int addrconf_ifdown(struct net_device *dev, int how)
|
||||||
struct inet6_dev *idev;
|
struct inet6_dev *idev;
|
||||||
struct inet6_ifaddr *ifa;
|
struct inet6_ifaddr *ifa;
|
||||||
LIST_HEAD(keep_list);
|
LIST_HEAD(keep_list);
|
||||||
|
int state;
|
||||||
|
|
||||||
ASSERT_RTNL();
|
ASSERT_RTNL();
|
||||||
|
|
||||||
|
@ -2666,7 +2675,6 @@ static int addrconf_ifdown(struct net_device *dev, int how)
|
||||||
ifa = list_first_entry(&idev->tempaddr_list,
|
ifa = list_first_entry(&idev->tempaddr_list,
|
||||||
struct inet6_ifaddr, tmp_list);
|
struct inet6_ifaddr, tmp_list);
|
||||||
list_del(&ifa->tmp_list);
|
list_del(&ifa->tmp_list);
|
||||||
ifa->state = INET6_IFADDR_STATE_DEAD;
|
|
||||||
write_unlock_bh(&idev->lock);
|
write_unlock_bh(&idev->lock);
|
||||||
spin_lock_bh(&ifa->lock);
|
spin_lock_bh(&ifa->lock);
|
||||||
|
|
||||||
|
@ -2704,23 +2712,34 @@ static int addrconf_ifdown(struct net_device *dev, int how)
|
||||||
|
|
||||||
/* Flag it for later restoration when link comes up */
|
/* Flag it for later restoration when link comes up */
|
||||||
ifa->flags |= IFA_F_TENTATIVE;
|
ifa->flags |= IFA_F_TENTATIVE;
|
||||||
in6_ifa_hold(ifa);
|
|
||||||
write_unlock_bh(&idev->lock);
|
write_unlock_bh(&idev->lock);
|
||||||
|
|
||||||
|
in6_ifa_hold(ifa);
|
||||||
} else {
|
} else {
|
||||||
list_del(&ifa->if_list);
|
list_del(&ifa->if_list);
|
||||||
ifa->state = INET6_IFADDR_STATE_DEAD;
|
|
||||||
write_unlock_bh(&idev->lock);
|
|
||||||
|
|
||||||
/* clear hash table */
|
/* clear hash table */
|
||||||
spin_lock_bh(&addrconf_hash_lock);
|
spin_lock_bh(&addrconf_hash_lock);
|
||||||
hlist_del_init_rcu(&ifa->addr_lst);
|
hlist_del_init_rcu(&ifa->addr_lst);
|
||||||
spin_unlock_bh(&addrconf_hash_lock);
|
spin_unlock_bh(&addrconf_hash_lock);
|
||||||
|
|
||||||
|
write_unlock_bh(&idev->lock);
|
||||||
|
spin_lock_bh(&ifa->state_lock);
|
||||||
|
state = ifa->state;
|
||||||
|
ifa->state = INET6_IFADDR_STATE_DEAD;
|
||||||
|
spin_unlock_bh(&ifa->state_lock);
|
||||||
|
|
||||||
|
if (state == INET6_IFADDR_STATE_DEAD)
|
||||||
|
goto put_ifa;
|
||||||
}
|
}
|
||||||
|
|
||||||
__ipv6_ifa_notify(RTM_DELADDR, ifa);
|
__ipv6_ifa_notify(RTM_DELADDR, ifa);
|
||||||
if (ifa->state == INET6_IFADDR_STATE_DEAD)
|
if (ifa->state == INET6_IFADDR_STATE_DEAD)
|
||||||
atomic_notifier_call_chain(&inet6addr_chain,
|
atomic_notifier_call_chain(&inet6addr_chain,
|
||||||
NETDEV_DOWN, ifa);
|
NETDEV_DOWN, ifa);
|
||||||
|
|
||||||
|
put_ifa:
|
||||||
in6_ifa_put(ifa);
|
in6_ifa_put(ifa);
|
||||||
|
|
||||||
write_lock_bh(&idev->lock);
|
write_lock_bh(&idev->lock);
|
||||||
|
|
Loading…
Add table
Reference in a new issue