diff options
Diffstat (limited to 'net/ipv6/addrconf.c')
| -rw-r--r-- | net/ipv6/addrconf.c | 107 | 
1 files changed, 66 insertions, 41 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 88fd8c5877e..413054f02aa 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c @@ -53,6 +53,7 @@  #include <linux/route.h>  #include <linux/inetdevice.h>  #include <linux/init.h> +#include <linux/slab.h>  #ifdef CONFIG_SYSCTL  #include <linux/sysctl.h>  #endif @@ -1380,6 +1381,8 @@ static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)  		if (dad_failed)  			ifp->flags |= IFA_F_DADFAILED;  		spin_unlock_bh(&ifp->lock); +		if (dad_failed) +			ipv6_ifa_notify(0, ifp);  		in6_ifa_put(ifp);  #ifdef CONFIG_IPV6_PRIVACY  	} else if (ifp->flags&IFA_F_TEMPORARY) { @@ -2615,7 +2618,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; @@ -2649,11 +2652,11 @@ static int addrconf_ifdown(struct net_device *dev, int how)  		write_lock_bh(&addrconf_hash_lock);  		while ((ifa = *bifa) != NULL) {  			if (ifa->idev == idev && -			    (how || !(ifa->flags&IFA_F_PERMANENT))) { +			    (how || !(ifa->flags&IFA_F_PERMANENT) || +			     ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)) {  				*bifa = ifa->lst_next;  				ifa->lst_next = NULL; -				addrconf_del_timer(ifa); -				in6_ifa_put(ifa); +				__in6_ifa_put(ifa);  				continue;  			}  			bifa = &ifa->lst_next; @@ -2689,31 +2692,51 @@ static int addrconf_ifdown(struct net_device *dev, int how)  		write_lock_bh(&idev->lock);  	}  #endif -	bifa = &idev->addr_list; -	while ((ifa = *bifa) != NULL) { -		if (how == 0 && (ifa->flags&IFA_F_PERMANENT)) { -			/* Retain permanent address on admin down */ +	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 +		   and not link-local, then retain it. */ +		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; -			/* Restart DAD if needed when link comes back up */ -			if ( !((dev->flags&(IFF_NOARP|IFF_LOOPBACK)) || -			       idev->cnf.accept_dad <= 0 || -			       (ifa->flags & IFA_F_NODAD))) -				ifa->flags |= IFA_F_TENTATIVE; -		} else { -			*bifa = ifa->if_next; -			ifa->if_next = NULL; +			/* If not doing DAD on this address, just keep it. */ +			if ((dev->flags&(IFF_NOARP|IFF_LOOPBACK)) || +			    idev->cnf.accept_dad <= 0 || +			    (ifa->flags & IFA_F_NODAD)) +				continue; +			/* If it was tentative already, no need to notify */ +			if (ifa->flags & IFA_F_TENTATIVE) +				continue; + +			/* Flag it for later restoration when link comes up */ +			ifa->flags |= IFA_F_TENTATIVE; +			in6_ifa_hold(ifa); +		} else {  			ifa->dead = 1; -			write_unlock_bh(&idev->lock); +		} +		write_unlock_bh(&idev->lock); -			__ipv6_ifa_notify(RTM_DELADDR, ifa); -			atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa); -			in6_ifa_put(ifa); +		__ipv6_ifa_notify(RTM_DELADDR, ifa); +		atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa); +		in6_ifa_put(ifa); -			write_lock_bh(&idev->lock); -		} +		write_lock_bh(&idev->lock);  	} + +	idev->addr_list = keep_list; +  	write_unlock_bh(&idev->lock);  	/* Step 5: Discard multicast list */ @@ -2739,28 +2762,29 @@ static int addrconf_ifdown(struct net_device *dev, int how)  static void addrconf_rs_timer(unsigned long data)  {  	struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data; +	struct inet6_dev *idev = ifp->idev; -	if (ifp->idev->cnf.forwarding) +	read_lock(&idev->lock); +	if (idev->dead || !(idev->if_flags & IF_READY))  		goto out; -	if (ifp->idev->if_flags & IF_RA_RCVD) { -		/* -		 *	Announcement received after solicitation -		 *	was sent -		 */ +	if (idev->cnf.forwarding) +		goto out; + +	/* Announcement received after solicitation was sent */ +	if (idev->if_flags & IF_RA_RCVD)  		goto out; -	}  	spin_lock(&ifp->lock); -	if (ifp->probes++ < ifp->idev->cnf.rtr_solicits) { +	if (ifp->probes++ < idev->cnf.rtr_solicits) {  		/* The wait after the last probe can be shorter */  		addrconf_mod_timer(ifp, AC_RS, -				   (ifp->probes == ifp->idev->cnf.rtr_solicits) ? -				   ifp->idev->cnf.rtr_solicit_delay : -				   ifp->idev->cnf.rtr_solicit_interval); +				   (ifp->probes == idev->cnf.rtr_solicits) ? +				   idev->cnf.rtr_solicit_delay : +				   idev->cnf.rtr_solicit_interval);  		spin_unlock(&ifp->lock); -		ndisc_send_rs(ifp->idev->dev, &ifp->addr, &in6addr_linklocal_allrouters); +		ndisc_send_rs(idev->dev, &ifp->addr, &in6addr_linklocal_allrouters);  	} else {  		spin_unlock(&ifp->lock);  		/* @@ -2768,10 +2792,11 @@ static void addrconf_rs_timer(unsigned long data)  		 * assumption any longer.  		 */  		printk(KERN_DEBUG "%s: no IPv6 routers present\n", -		       ifp->idev->dev->name); +		       idev->dev->name);  	}  out: +	read_unlock(&idev->lock);  	in6_ifa_put(ifp);  } @@ -2850,9 +2875,9 @@ static void addrconf_dad_timer(unsigned long data)  	struct inet6_dev *idev = ifp->idev;  	struct in6_addr mcaddr; -	read_lock_bh(&idev->lock); -	if (idev->dead) { -		read_unlock_bh(&idev->lock); +	read_lock(&idev->lock); +	if (idev->dead || !(idev->if_flags & IF_READY)) { +		read_unlock(&idev->lock);  		goto out;  	} @@ -2864,7 +2889,7 @@ static void addrconf_dad_timer(unsigned long data)  		ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);  		spin_unlock(&ifp->lock); -		read_unlock_bh(&idev->lock); +		read_unlock(&idev->lock);  		addrconf_dad_completed(ifp); @@ -2874,7 +2899,7 @@ static void addrconf_dad_timer(unsigned long data)  	ifp->probes--;  	addrconf_mod_timer(ifp, AC_DAD, ifp->idev->nd_parms->retrans_time);  	spin_unlock(&ifp->lock); -	read_unlock_bh(&idev->lock); +	read_unlock(&idev->lock);  	/* send a neighbour solicitation for our addr */  	addrconf_addr_solict_mult(&ifp->addr, &mcaddr); @@ -3586,7 +3611,7 @@ static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb,  		hlist_for_each_entry_rcu(dev, node, head, index_hlist) {  			if (idx < s_idx)  				goto cont; -			if (idx > s_idx) +			if (h > s_h || idx > s_idx)  				s_ip_idx = 0;  			ip_idx = 0;  			if ((idev = __in6_dev_get(dev)) == NULL)  |