diff options
author | stephen hemminger <shemminger@vyatta.com> | 2010-03-03 09:19:59 +0100 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-03-04 09:39:34 +0100 |
commit | 8f37ada5b5f6bfb4d251a7f510f249cb855b77b3 (patch) | |
tree | 8f39575febf0319570798117f8738f5127084634 /net | |
parent | IPv6: addrconf notify when address is unavailable (diff) | |
download | linux-8f37ada5b5f6bfb4d251a7f510f249cb855b77b3.tar.xz linux-8f37ada5b5f6bfb4d251a7f510f249cb855b77b3.zip |
IPv6: fix race between cleanup and add/delete address
This solves a potential race problem during the cleanup process.
The issue is that addrconf_ifdown() needs to traverse address list,
but then drop lock to call the notifier. The version in -next
could get confused if add/delete happened during this window.
Original code (2.6.32 and earlier) was okay because all addresses
were always deleted.
Signed-off-by: Stephen Hemminger <shemminger@vyatta.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/ipv6/addrconf.c | 18 |
1 files changed, 13 insertions, 5 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 7a4bf7671285..6cf3ee14ace3 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -2615,7 +2615,7 @@ static void addrconf_bonding_change(struct net_device *dev, unsigned long event) static int addrconf_ifdown(struct net_device *dev, int how) { struct inet6_dev *idev; - struct inet6_ifaddr *ifa, **bifa; + struct inet6_ifaddr *ifa, *keep_list, **bifa; struct net *net = dev_net(dev); int i; @@ -2689,8 +2689,12 @@ static int addrconf_ifdown(struct net_device *dev, int how) write_lock_bh(&idev->lock); } #endif - bifa = &idev->addr_list; - while ((ifa = *bifa) != NULL) { + keep_list = NULL; + bifa = &keep_list; + while ((ifa = idev->addr_list) != NULL) { + idev->addr_list = ifa->if_next; + ifa->if_next = NULL; + addrconf_del_timer(ifa); /* If just doing link down, and address is permanent @@ -2698,6 +2702,9 @@ static int addrconf_ifdown(struct net_device *dev, int how) if (how == 0 && (ifa->flags&IFA_F_PERMANENT) && !(ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)) { + + /* Move to holding list */ + *bifa = ifa; bifa = &ifa->if_next; /* If not doing DAD on this address, just keep it. */ @@ -2714,8 +2721,6 @@ static int addrconf_ifdown(struct net_device *dev, int how) ifa->flags |= IFA_F_TENTATIVE; in6_ifa_hold(ifa); } else { - *bifa = ifa->if_next; - ifa->if_next = NULL; ifa->dead = 1; } write_unlock_bh(&idev->lock); @@ -2726,6 +2731,9 @@ static int addrconf_ifdown(struct net_device *dev, int how) write_lock_bh(&idev->lock); } + + idev->addr_list = keep_list; + write_unlock_bh(&idev->lock); /* Step 5: Discard multicast list */ |