Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4c5ff6a6 authored by Herbert Xu's avatar Herbert Xu Committed by David S. Miller
Browse files

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: default avatarHerbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent e9d3e084
Loading
Loading
Loading
Loading
+23 −4
Original line number Diff line number Diff line
@@ -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);