diff options
author | Herbert Xu <herbert@gondor.apana.org.au> | 2010-05-18 15:54:18 -0700 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-05-18 15:54:18 -0700 |
commit | 4c5ff6a6fe794f102479db998c69054319279e3c (patch) | |
tree | 07e2e4de160e3eb2dbbb3f7749314cd556671826 | |
parent | e9d3e084975869754d16f639378675c353560be9 (diff) | |
download | linux-4c5ff6a6fe794f102479db998c69054319279e3c.tar.bz2 |
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>
-rw-r--r-- | net/ipv6/addrconf.c | 27 |
1 files changed, 23 insertions, 4 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 4e5ad9de1679..2e42162c9042 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -715,13 +715,20 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp) { struct inet6_ifaddr *ifa, *ifn; struct inet6_dev *idev = ifp->idev; + int state; int hash; int deleted = 0, onlink = 0; unsigned long expires = jiffies; hash = ipv6_addr_hash(&ifp->addr); + spin_lock_bh(&ifp->state_lock); + state = ifp->state; 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); 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); } +out: in6_ifa_put(ifp); } @@ -2626,6 +2634,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) struct inet6_dev *idev; struct inet6_ifaddr *ifa; LIST_HEAD(keep_list); + int state; ASSERT_RTNL(); @@ -2666,7 +2675,6 @@ static int addrconf_ifdown(struct net_device *dev, int how) ifa = list_first_entry(&idev->tempaddr_list, struct inet6_ifaddr, tmp_list); list_del(&ifa->tmp_list); - ifa->state = INET6_IFADDR_STATE_DEAD; write_unlock_bh(&idev->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 */ ifa->flags |= IFA_F_TENTATIVE; - in6_ifa_hold(ifa); + write_unlock_bh(&idev->lock); + + in6_ifa_hold(ifa); } else { list_del(&ifa->if_list); - ifa->state = INET6_IFADDR_STATE_DEAD; - write_unlock_bh(&idev->lock); /* clear hash table */ spin_lock_bh(&addrconf_hash_lock); hlist_del_init_rcu(&ifa->addr_lst); 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); if (ifa->state == INET6_IFADDR_STATE_DEAD) atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa); + +put_ifa: in6_ifa_put(ifa); write_lock_bh(&idev->lock); |