summaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorHerbert Xu <herbert@gondor.apana.org.au>2010-05-18 15:54:18 -0700
committerDavid S. Miller <davem@davemloft.net>2010-05-18 15:54:18 -0700
commit4c5ff6a6fe794f102479db998c69054319279e3c (patch)
tree07e2e4de160e3eb2dbbb3f7749314cd556671826 /net
parente9d3e084975869754d16f639378675c353560be9 (diff)
downloadlinux-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>
Diffstat (limited to 'net')
-rw-r--r--net/ipv6/addrconf.c27
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);