diff options
author | Herbert Xu <herbert@gondor.apana.org.au> | 2010-05-18 18:54:18 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-05-18 18:54:18 -0400 |
commit | 4c5ff6a6fe794f102479db998c69054319279e3c (patch) | |
tree | 07e2e4de160e3eb2dbbb3f7749314cd556671826 /net/ipv6 | |
parent | e9d3e084975869754d16f639378675c353560be9 (diff) |
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/ipv6')
-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) | |||
715 | { | 715 | { |
716 | struct inet6_ifaddr *ifa, *ifn; | 716 | struct inet6_ifaddr *ifa, *ifn; |
717 | struct inet6_dev *idev = ifp->idev; | 717 | struct inet6_dev *idev = ifp->idev; |
718 | int state; | ||
718 | int hash; | 719 | int hash; |
719 | int deleted = 0, onlink = 0; | 720 | int deleted = 0, onlink = 0; |
720 | unsigned long expires = jiffies; | 721 | unsigned long expires = jiffies; |
721 | 722 | ||
722 | hash = ipv6_addr_hash(&ifp->addr); | 723 | hash = ipv6_addr_hash(&ifp->addr); |
723 | 724 | ||
725 | spin_lock_bh(&ifp->state_lock); | ||
726 | state = ifp->state; | ||
724 | ifp->state = INET6_IFADDR_STATE_DEAD; | 727 | ifp->state = INET6_IFADDR_STATE_DEAD; |
728 | spin_unlock_bh(&ifp->state_lock); | ||
729 | |||
730 | if (state == INET6_IFADDR_STATE_DEAD) | ||
731 | goto out; | ||
725 | 732 | ||
726 | spin_lock_bh(&addrconf_hash_lock); | 733 | spin_lock_bh(&addrconf_hash_lock); |
727 | hlist_del_init_rcu(&ifp->addr_lst); | 734 | hlist_del_init_rcu(&ifp->addr_lst); |
@@ -819,6 +826,7 @@ static void ipv6_del_addr(struct inet6_ifaddr *ifp) | |||
819 | dst_release(&rt->u.dst); | 826 | dst_release(&rt->u.dst); |
820 | } | 827 | } |
821 | 828 | ||
829 | out: | ||
822 | in6_ifa_put(ifp); | 830 | in6_ifa_put(ifp); |
823 | } | 831 | } |
824 | 832 | ||
@@ -2626,6 +2634,7 @@ static int addrconf_ifdown(struct net_device *dev, int how) | |||
2626 | struct inet6_dev *idev; | 2634 | struct inet6_dev *idev; |
2627 | struct inet6_ifaddr *ifa; | 2635 | struct inet6_ifaddr *ifa; |
2628 | LIST_HEAD(keep_list); | 2636 | LIST_HEAD(keep_list); |
2637 | int state; | ||
2629 | 2638 | ||
2630 | ASSERT_RTNL(); | 2639 | ASSERT_RTNL(); |
2631 | 2640 | ||
@@ -2666,7 +2675,6 @@ static int addrconf_ifdown(struct net_device *dev, int how) | |||
2666 | ifa = list_first_entry(&idev->tempaddr_list, | 2675 | ifa = list_first_entry(&idev->tempaddr_list, |
2667 | struct inet6_ifaddr, tmp_list); | 2676 | struct inet6_ifaddr, tmp_list); |
2668 | list_del(&ifa->tmp_list); | 2677 | list_del(&ifa->tmp_list); |
2669 | ifa->state = INET6_IFADDR_STATE_DEAD; | ||
2670 | write_unlock_bh(&idev->lock); | 2678 | write_unlock_bh(&idev->lock); |
2671 | spin_lock_bh(&ifa->lock); | 2679 | spin_lock_bh(&ifa->lock); |
2672 | 2680 | ||
@@ -2704,23 +2712,34 @@ static int addrconf_ifdown(struct net_device *dev, int how) | |||
2704 | 2712 | ||
2705 | /* Flag it for later restoration when link comes up */ | 2713 | /* Flag it for later restoration when link comes up */ |
2706 | ifa->flags |= IFA_F_TENTATIVE; | 2714 | ifa->flags |= IFA_F_TENTATIVE; |
2707 | in6_ifa_hold(ifa); | 2715 | |
2708 | write_unlock_bh(&idev->lock); | 2716 | write_unlock_bh(&idev->lock); |
2717 | |||
2718 | in6_ifa_hold(ifa); | ||
2709 | } else { | 2719 | } else { |
2710 | list_del(&ifa->if_list); | 2720 | list_del(&ifa->if_list); |
2711 | ifa->state = INET6_IFADDR_STATE_DEAD; | ||
2712 | write_unlock_bh(&idev->lock); | ||
2713 | 2721 | ||
2714 | /* clear hash table */ | 2722 | /* clear hash table */ |
2715 | spin_lock_bh(&addrconf_hash_lock); | 2723 | spin_lock_bh(&addrconf_hash_lock); |
2716 | hlist_del_init_rcu(&ifa->addr_lst); | 2724 | hlist_del_init_rcu(&ifa->addr_lst); |
2717 | spin_unlock_bh(&addrconf_hash_lock); | 2725 | spin_unlock_bh(&addrconf_hash_lock); |
2726 | |||
2727 | write_unlock_bh(&idev->lock); | ||
2728 | spin_lock_bh(&ifa->state_lock); | ||
2729 | state = ifa->state; | ||
2730 | ifa->state = INET6_IFADDR_STATE_DEAD; | ||
2731 | spin_unlock_bh(&ifa->state_lock); | ||
2732 | |||
2733 | if (state == INET6_IFADDR_STATE_DEAD) | ||
2734 | goto put_ifa; | ||
2718 | } | 2735 | } |
2719 | 2736 | ||
2720 | __ipv6_ifa_notify(RTM_DELADDR, ifa); | 2737 | __ipv6_ifa_notify(RTM_DELADDR, ifa); |
2721 | if (ifa->state == INET6_IFADDR_STATE_DEAD) | 2738 | if (ifa->state == INET6_IFADDR_STATE_DEAD) |
2722 | atomic_notifier_call_chain(&inet6addr_chain, | 2739 | atomic_notifier_call_chain(&inet6addr_chain, |
2723 | NETDEV_DOWN, ifa); | 2740 | NETDEV_DOWN, ifa); |
2741 | |||
2742 | put_ifa: | ||
2724 | in6_ifa_put(ifa); | 2743 | in6_ifa_put(ifa); |
2725 | 2744 | ||
2726 | write_lock_bh(&idev->lock); | 2745 | write_lock_bh(&idev->lock); |