patch-2.3.15 linux/net/ipv6/addrconf.c

Next file: linux/net/ipv6/af_inet6.c
Previous file: linux/net/ipv4/udp.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.3.14/linux/net/ipv6/addrconf.c linux/net/ipv6/addrconf.c
@@ -4,8 +4,9 @@
  *
  *	Authors:
  *	Pedro Roque		<roque@di.fc.ul.pt>	
+ *	Alexey Kuznetsov	<kuznet@ms2.inr.ac.ru>
  *
- *	$Id: addrconf.c,v 1.50 1999/06/09 10:11:09 davem Exp $
+ *	$Id: addrconf.c,v 1.52 1999/08/20 11:06:14 davem Exp $
  *
  *	This program is free software; you can redistribute it and/or
  *      modify it under the terms of the GNU General Public License
@@ -69,17 +70,17 @@
 static void addrconf_sysctl_unregister(struct ipv6_devconf *p);
 #endif
 
-/*
- *	Configured unicast address list
- */
-static struct inet6_ifaddr		*inet6_addr_lst[IN6_ADDR_HSIZE];
+int inet6_dev_count;
+int inet6_ifa_count;
 
 /*
- *	AF_INET6 device list
+ *	Configured unicast address hash table
  */
-static struct inet6_dev		*inet6_dev_lst[IN6_ADDR_HSIZE];
+static struct inet6_ifaddr		*inet6_addr_lst[IN6_ADDR_HSIZE];
+static rwlock_t	addrconf_hash_lock = RW_LOCK_UNLOCKED;
 
-static atomic_t			addr_list_lock = ATOMIC_INIT(0);
+/* Protects inet6 devices */
+rwlock_t addrconf_lock = RW_LOCK_UNLOCKED;
 
 void addrconf_verify(unsigned long);
 
@@ -88,32 +89,6 @@
 	0, 0, addrconf_verify
 };
 
-/* These locks protect only against address deletions,
-   but not against address adds or status updates.
-   It is OK. The only race is when address is selected,
-   which becomes invalid immediately after selection.
-   It is harmless, because this address could be already invalid
-   several usecs ago.
-
-   Its important, that:
-
-   1. The result of inet6_add_addr() is used only inside lock
-      or from bh_atomic context.
-
-   2. The result of ipv6_chk_addr() is not used outside of bh protected context.
- */
-
-static __inline__ void addrconf_lock(void)
-{
-	atomic_inc(&addr_list_lock);
-	synchronize_bh();
-}
-
-static __inline__ void addrconf_unlock(void)
-{
-	atomic_dec(&addr_list_lock);
-}
-
 static int addrconf_ifdown(struct net_device *dev, int how);
 
 static void addrconf_dad_start(struct inet6_ifaddr *ifp);
@@ -206,10 +181,64 @@
 	return IPV6_ADDR_RESERVED;
 }
 
+static void addrconf_del_timer(struct inet6_ifaddr *ifp)
+{
+	if (del_timer(&ifp->timer))
+		__in6_ifa_put(ifp);
+}
+
+enum addrconf_timer_t
+{
+	AC_NONE,
+	AC_DAD,
+	AC_RS,
+};
+
+static void addrconf_mod_timer(struct inet6_ifaddr *ifp,
+			       enum addrconf_timer_t what,
+			       unsigned long when)
+{
+	if (!del_timer(&ifp->timer))
+		in6_ifa_hold(ifp);
+
+	switch (what) {
+	case AC_DAD:
+		ifp->timer.function = addrconf_dad_timer;
+		break;
+	case AC_RS:
+		ifp->timer.function = addrconf_rs_timer;
+		break;
+	default:
+	}
+	ifp->timer.expires = jiffies + when;
+	add_timer(&ifp->timer);
+}
+
+
+/* Nobody refers to this device, we may destroy it. */
+
+void in6_dev_finish_destroy(struct inet6_dev *idev)
+{
+	struct net_device *dev = idev->dev;
+	BUG_TRAP(idev->addr_list==NULL);
+	BUG_TRAP(idev->mc_list==NULL);
+#ifdef NET_REFCNT_DEBUG
+	printk(KERN_DEBUG "in6_dev_finish_destroy: %s\n", dev ? dev->name : "NIL");
+#endif
+	dev_put(dev);
+	if (!idev->dead) {
+		printk("Freeing alive inet6 device %p\n", idev);
+		return;
+	}
+	inet6_dev_count--;
+	kfree(idev);
+}
+
 static struct inet6_dev * ipv6_add_dev(struct net_device *dev)
 {
-	struct inet6_dev *ndev, **bptr, *iter;
-	int hash;
+	struct inet6_dev *ndev;
+
+	ASSERT_RTNL();
 
 	if (dev->mtu < IPV6_MIN_MTU)
 		return NULL;
@@ -219,6 +248,7 @@
 	if (ndev) {
 		memset(ndev, 0, sizeof(struct inet6_dev));
 
+		ndev->lock = RW_LOCK_UNLOCKED;
 		ndev->dev = dev;
 		memcpy(&ndev->cnf, &ipv6_devconf_dflt, sizeof(ndev->cnf));
 		ndev->cnf.mtu6 = dev->mtu;
@@ -228,19 +258,20 @@
 			kfree(ndev);
 			return NULL;
 		}
+		inet6_dev_count++;
+		/* We refer to the device */
+		dev_hold(dev);
+
+		write_lock_bh(&addrconf_lock);
+		dev->ip6_ptr = ndev;
+		/* One reference from device */
+		in6_dev_hold(ndev);
+		write_unlock_bh(&addrconf_lock);
+
 #ifdef CONFIG_SYSCTL
 		neigh_sysctl_register(dev, ndev->nd_parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
 		addrconf_sysctl_register(ndev, &ndev->cnf);
 #endif
-		hash = ipv6_devindex_hash(dev->ifindex);
-		bptr = &inet6_dev_lst[hash];
-		iter = *bptr;
-
-		for (; iter; iter = iter->next)
-			bptr = &iter->next;
-
-		*bptr = ndev;
-
 	}
 	return ndev;
 }
@@ -249,9 +280,10 @@
 {
 	struct inet6_dev *idev;
 
-	if ((idev = ipv6_get_idev(dev)) == NULL) {
-		idev = ipv6_add_dev(dev);
-		if (idev == NULL)
+	ASSERT_RTNL();
+
+	if ((idev = __in6_dev_get(dev)) == NULL) {
+		if ((idev = ipv6_add_dev(dev)) == NULL)
 			return NULL;
 		if (dev->flags&IFF_UP)
 			ipv6_mc_up(idev);
@@ -261,33 +293,48 @@
 
 static void addrconf_forward_change(struct inet6_dev *idev)
 {
-	int i;
+	struct net_device *dev;
 
 	if (idev)
 		return;
 
-	for (i = 0; i < IN6_ADDR_HSIZE; i++) {
-		for (idev = inet6_dev_lst[i]; idev; idev = idev->next)
+	read_lock(&dev_base_lock);
+	for (dev=dev_base; dev; dev=dev->next) {
+		read_lock(&addrconf_lock);
+		idev = __in6_dev_get(dev);
+		if (idev)
 			idev->cnf.forwarding = ipv6_devconf.forwarding;
+		read_unlock(&addrconf_lock);
 	}
+	read_unlock(&dev_base_lock);
 }
 
-struct inet6_dev * ipv6_get_idev(struct net_device *dev)
+/* Nobody refers to this ifaddr, destroy it */
+
+void inet6_ifa_finish_destroy(struct inet6_ifaddr *ifp)
 {
-	struct inet6_dev *idev;
-	int hash;
+	BUG_TRAP(ifp->if_next==NULL);
+	BUG_TRAP(ifp->lst_next==NULL);
+	printk(KERN_DEBUG "inet6_ifa_finish_destroy\n");
+
+	in6_dev_put(ifp->idev);
 
-	hash = ipv6_devindex_hash(dev->ifindex);
+	if (del_timer(&ifp->timer))
+		printk("Timer is still running, when freeing ifa=%p\n", ifp);
 
-	for (idev = inet6_dev_lst[hash]; idev; idev = idev->next) {
-		if (idev->dev == dev)
-			return idev;
+	if (!ifp->dead) {
+		printk("Freeing alive inet6 address %p\n", ifp);
+		return;
 	}
-	return NULL;
+	inet6_ifa_count--;
+	kfree(ifp);
 }
 
+/* On success it returns ifp with increased reference count */
+
 static struct inet6_ifaddr *
-ipv6_add_addr(struct inet6_dev *idev, struct in6_addr *addr, int scope)
+ipv6_add_addr(struct inet6_dev *idev, struct in6_addr *addr, int pfxlen,
+	      int scope, unsigned flags)
 {
 	struct inet6_ifaddr *ifa;
 	int hash;
@@ -300,70 +347,90 @@
 	}
 
 	memset(ifa, 0, sizeof(struct inet6_ifaddr));
-	memcpy(&ifa->addr, addr, sizeof(struct in6_addr));
+	ipv6_addr_copy(&ifa->addr, addr);
 
+	spin_lock_init(&ifa->lock);
 	init_timer(&ifa->timer);
 	ifa->timer.data = (unsigned long) ifa;
 	ifa->scope = scope;
+	ifa->prefix_len = pfxlen;
+	ifa->flags = flags | IFA_F_TENTATIVE;
+
+	read_lock(&addrconf_lock);
+	if (idev->dead) {
+		read_unlock(&addrconf_lock);
+		kfree(ifa);
+		return NULL;
+	}
+
+	inet6_ifa_count++;
 	ifa->idev = idev;
+	in6_dev_hold(idev);
+	/* For caller */
+	in6_ifa_hold(ifa);
 
-	/* Add to list. */
+	/* Add to big hash table */
 	hash = ipv6_addr_hash(addr);
 
+	write_lock_bh(&addrconf_hash_lock);
 	ifa->lst_next = inet6_addr_lst[hash];
 	inet6_addr_lst[hash] = ifa;
+	in6_ifa_hold(ifa);
+	write_unlock_bh(&addrconf_hash_lock);
 
+	write_lock_bh(&idev->lock);
 	/* Add to inet6_dev unicast addr list. */
 	ifa->if_next = idev->addr_list;
 	idev->addr_list = ifa;
+	in6_ifa_hold(ifa);
+	write_unlock_bh(&idev->lock);
+	read_unlock(&addrconf_lock);
 
 	return ifa;
 }
 
+/* This function wants to get referenced ifp and releases it before return */
+
 static void ipv6_del_addr(struct inet6_ifaddr *ifp)
 {
-	struct inet6_ifaddr *iter, **back;
+	struct inet6_ifaddr *ifa, **ifap;
+	struct inet6_dev *idev = ifp->idev;
 	int hash;
 
-	if (atomic_read(&addr_list_lock)) {
-		ifp->flags |= ADDR_INVALID;
-		ipv6_ifa_notify(RTM_DELADDR, ifp);
-		return;
-	}
-
 	hash = ipv6_addr_hash(&ifp->addr);
 
-	iter = inet6_addr_lst[hash];
-	back = &inet6_addr_lst[hash];
-
-	for (; iter; iter = iter->lst_next) {
-		if (iter == ifp) {
-			*back = ifp->lst_next;
-			synchronize_bh();
+	ifp->dead = 1;
 
-			ifp->lst_next = NULL;
+	write_lock_bh(&addrconf_hash_lock);
+	for (ifap = &inet6_addr_lst[hash]; (ifa=*ifap) != NULL;
+	     ifap = &ifa->lst_next) {
+		if (ifa == ifp) {
+			*ifap = ifa->lst_next;
+			__in6_ifa_put(ifp);
+			ifa->lst_next = NULL;
 			break;
 		}
-		back = &(iter->lst_next);
 	}
+	write_unlock_bh(&addrconf_hash_lock);
 
-	iter = ifp->idev->addr_list;
-	back = &ifp->idev->addr_list;
-
-	for (; iter; iter = iter->if_next) {
-		if (iter == ifp) {
-			*back = ifp->if_next;
-			synchronize_bh();
-
-			ifp->if_next = NULL;
+	write_lock_bh(&idev->lock);
+	for (ifap = &idev->addr_list; (ifa=*ifap) != NULL;
+	     ifap = &ifa->if_next) {
+		if (ifa == ifp) {
+			*ifap = ifa->if_next;
+			__in6_ifa_put(ifp);
+			ifa->if_next = NULL;
 			break;
 		}
-		back = &(iter->if_next);
 	}
+	write_unlock_bh(&idev->lock);
 
 	ipv6_ifa_notify(RTM_DELADDR, ifp);
-	
-	kfree(ifp);
+
+
+	addrconf_del_timer(ifp);
+
+	in6_ifa_put(ifp);
 }
 
 /*
@@ -381,15 +448,13 @@
 	struct inet6_ifaddr *ifp = NULL;
 	struct inet6_ifaddr *match = NULL;
 	struct net_device *dev = NULL;
+	struct inet6_dev *idev;
 	struct rt6_info *rt;
 	int err;
-	int i;
 
 	rt = (struct rt6_info *) dst;
 	if (rt)
 		dev = rt->rt6i_dev;
-	
-	addrconf_lock();
 
 	scope = ipv6_addr_scope(daddr);
 	if (rt && (rt->rt6i_flags & RTF_ALLONLINK)) {
@@ -406,27 +471,31 @@
 	 */
 
 	if (dev) {
-		struct inet6_dev *idev;
-		int hash;
-
 		if (dev->flags & IFF_LOOPBACK)
 			scope = IFA_HOST;
 
-		hash = ipv6_devindex_hash(dev->ifindex);
-		for (idev = inet6_dev_lst[hash]; idev; idev=idev->next) {
-			if (idev->dev == dev) {
-				for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
-					if (ifp->scope == scope) {
-						if (!(ifp->flags & (ADDR_STATUS|DAD_STATUS)))
-							goto out;
+		read_lock(&addrconf_lock);
+		idev = __in6_dev_get(dev);
+		if (idev) {
+			read_lock_bh(&idev->lock);
+			for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+				if (ifp->scope == scope) {
+					if (!(ifp->flags & (IFA_F_DEPRECATED|IFA_F_TENTATIVE))) {
+						in6_ifa_hold(ifp);
+						read_unlock_bh(&idev->lock);
+						read_unlock(&addrconf_lock);
+						goto out;
+					}
 
-						if (!(ifp->flags & (ADDR_INVALID|DAD_STATUS)))
-							match = ifp;
+					if (!match && !(ifp->flags & IFA_F_TENTATIVE)) {
+						match = ifp;
+						in6_ifa_hold(ifp);
 					}
 				}
-				break;
 			}
+			read_unlock_bh(&idev->lock);
 		}
+		read_unlock(&addrconf_lock);
 	}
 
 	if (scope == IFA_LINK)
@@ -436,85 +505,126 @@
 	 *	dev == NULL or search failed for specified dev
 	 */
 
-	for (i=0; i < IN6_ADDR_HSIZE; i++) {
-		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
-			if (ifp->scope == scope) {
-				if (!(ifp->flags & (ADDR_STATUS|DAD_STATUS)))
-					goto out;
+	read_lock(&dev_base_lock);
+	read_lock(&addrconf_lock);
+	for (dev = dev_base; dev; dev=dev->next) {
+		idev = __in6_dev_get(dev);
+		if (idev) {
+			read_lock_bh(&idev->lock);
+			for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+				if (ifp->scope == scope) {
+					if (!(ifp->flags&(IFA_F_DEPRECATED|IFA_F_TENTATIVE))) {
+						in6_ifa_hold(ifp);
+						read_unlock_bh(&idev->lock);
+						goto out_unlock_base;
+					}
 
-				if (!(ifp->flags & (ADDR_INVALID|DAD_STATUS)))
-					match = ifp;
+					if (!match && !(ifp->flags&IFA_F_TENTATIVE)) {
+						match = ifp;
+						in6_ifa_hold(ifp);
+					}
+				}
 			}
+			read_unlock_bh(&idev->lock);
 		}
 	}
 
+out_unlock_base:
+	read_unlock(&addrconf_lock);
+	read_unlock(&dev_base_lock);
+
 out:
-	if (ifp == NULL)
+	if (ifp == NULL) {
 		ifp = match;
+		match = NULL;
+	}
 
-	err = -ENETUNREACH;
+	err = -EADDRNOTAVAIL;
 	if (ifp) {
-		memcpy(saddr, &ifp->addr, sizeof(struct in6_addr));
+		ipv6_addr_copy(saddr, &ifp->addr);
 		err = 0;
+		in6_ifa_put(ifp);
 	}
-	addrconf_unlock();
+	if (match)
+		in6_ifa_put(match);
+
 	return err;
 }
 
 int ipv6_get_lladdr(struct net_device *dev, struct in6_addr *addr)
 {
-	struct inet6_ifaddr *ifp = NULL;
 	struct inet6_dev *idev;
+	int err = -EADDRNOTAVAIL;
+
+	read_lock(&addrconf_lock);
+	if ((idev = __in6_dev_get(dev)) != NULL) {
+		struct inet6_ifaddr *ifp;
 
-	if ((idev = ipv6_get_idev(dev)) != NULL) {
-		addrconf_lock();
+		read_lock_bh(&idev->lock);
 		for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
-			if (ifp->scope == IFA_LINK) {
+			if (ifp->scope == IFA_LINK && !(ifp->flags&IFA_F_TENTATIVE)) {
 				ipv6_addr_copy(addr, &ifp->addr);
-				addrconf_unlock();
-				return 0;
+				err = 0;
+				break;
 			}
 		}
-		addrconf_unlock();
+		read_unlock_bh(&idev->lock);
 	}
-	return -EADDRNOTAVAIL;
+	read_unlock(&addrconf_lock);
+	return err;
 }
 
-/*
- *	Retrieve the ifaddr struct from an v6 address
- *	Called from ipv6_rcv to check if the address belongs 
- *	to the host.
- */
-
-struct inet6_ifaddr * ipv6_chk_addr(struct in6_addr *addr, struct net_device *dev, int nd)
+int ipv6_chk_addr(struct in6_addr *addr, struct net_device *dev)
 {
 	struct inet6_ifaddr * ifp;
-	u8 hash;
-	unsigned flags = 0;
+	u8 hash = ipv6_addr_hash(addr);
 
-	if (!nd)
-		flags |= DAD_STATUS|ADDR_INVALID;
+	read_lock_bh(&addrconf_hash_lock);
+	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
+		if (ipv6_addr_cmp(&ifp->addr, addr) == 0 &&
+		    !(ifp->flags&IFA_F_TENTATIVE)) {
+			if (dev == NULL || ifp->idev->dev == dev ||
+			    !(ifp->scope&(IFA_LINK|IFA_HOST)))
+				break;
+		}
+	}
+	read_unlock_bh(&addrconf_hash_lock);
+	return ifp != NULL;
+}
 
-	addrconf_lock();
+struct inet6_ifaddr * ipv6_get_ifaddr(struct in6_addr *addr, struct net_device *dev)
+{
+	struct inet6_ifaddr * ifp;
+	u8 hash = ipv6_addr_hash(addr);
 
-	hash = ipv6_addr_hash(addr);
+	read_lock_bh(&addrconf_hash_lock);
 	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
-		if (ipv6_addr_cmp(&ifp->addr, addr) == 0 && !(ifp->flags&flags)) {
+		if (ipv6_addr_cmp(&ifp->addr, addr) == 0) {
 			if (dev == NULL || ifp->idev->dev == dev ||
-			    !(ifp->scope&(IFA_LINK|IFA_HOST)))
+			    !(ifp->scope&(IFA_LINK|IFA_HOST))) {
+				in6_ifa_hold(ifp);
 				break;
+			}
 		}
 	}
+	read_unlock_bh(&addrconf_hash_lock);
 
-	addrconf_unlock();
 	return ifp;
 }
 
+/* Gets referenced address, destroys ifaddr */
+
 void addrconf_dad_failure(struct inet6_ifaddr *ifp)
 {
 	printk(KERN_INFO "%s: duplicate address detected!\n", ifp->idev->dev->name);
-	del_timer(&ifp->timer);
-	ipv6_del_addr(ifp);
+	if (ifp->flags&IFA_F_PERMANENT) {
+		spin_lock_bh(&ifp->lock);
+		addrconf_del_timer(ifp);
+		ifp->flags |= IFA_F_TENTATIVE;
+		spin_unlock_bh(&ifp->lock);
+		in6_ifa_put(ifp);
+	} else
+		ipv6_del_addr(ifp);
 }
 
 
@@ -648,6 +758,8 @@
 {
 	struct inet6_dev *idev;
 
+	ASSERT_RTNL();
+
 	if ((idev = ipv6_find_idev(dev)) == NULL)
 		return NULL;
 
@@ -667,12 +779,7 @@
 	__u32 prefered_lft;
 	int addr_type;
 	unsigned long rt_expires;
-	struct inet6_dev *in6_dev = ipv6_get_idev(dev);
-
-	if (in6_dev == NULL) {
-		printk(KERN_DEBUG "addrconf: device %s not configured\n", dev->name);
-		return;
-	}
+	struct inet6_dev *in6_dev;
 
 	pinfo = (struct prefix_info *) opt;
 	
@@ -698,6 +805,13 @@
 		return;
 	}
 
+	in6_dev = in6_dev_get(dev);
+
+	if (in6_dev == NULL) {
+		printk(KERN_DEBUG "addrconf: device %s not configured\n", dev->name);
+		return;
+	}
+
 	/*
 	 *	Two things going on here:
 	 *	1) Add routes for on-link prefixes
@@ -720,6 +834,7 @@
 		if (rt->rt6i_flags&RTF_EXPIRES) {
 			if (pinfo->onlink == 0 || valid_lft == 0) {
 				ip6_del_rt(rt);
+				rt = NULL;
 			} else {
 				rt->rt6i_expires = rt_expires;
 			}
@@ -743,8 +858,10 @@
 #ifdef CONFIG_IPV6_EUI64
 		if (pinfo->prefix_len == 64) {
 			memcpy(&addr, &pinfo->prefix, 8);
-			if (ipv6_generate_eui64(addr.s6_addr + 8, dev))
+			if (ipv6_generate_eui64(addr.s6_addr + 8, dev)) {
+				in6_dev_put(in6_dev);
 				return;
+			}
 			goto ok;
 		}
 #endif
@@ -757,20 +874,21 @@
 		}
 #endif
 		printk(KERN_DEBUG "IPv6 addrconf: prefix with wrong length %d\n", pinfo->prefix_len);
+		in6_dev_put(in6_dev);
 		return;
 
 ok:
-		ifp = ipv6_chk_addr(&addr, dev, 1);
 
-		if ((ifp == NULL || (ifp->flags&ADDR_INVALID)) && valid_lft) {
+		ifp = ipv6_get_ifaddr(&addr, dev);
 
-			if (ifp == NULL)
-				ifp = ipv6_add_addr(in6_dev, &addr, addr_type & IPV6_ADDR_SCOPE_MASK);
+		if (ifp == NULL && valid_lft) {
+			ifp = ipv6_add_addr(in6_dev, &addr, pinfo->prefix_len,
+					    addr_type&IPV6_ADDR_SCOPE_MASK, 0);
 
-			if (ifp == NULL)
+			if (ifp == NULL) {
+				in6_dev_put(in6_dev);
 				return;
-
-			ifp->prefix_len = pinfo->prefix_len;
+			}
 
 			addrconf_dad_start(ifp);
 		}
@@ -781,16 +899,23 @@
 		}
 
 		if (ifp) {
-			int event = 0;
+			int flags;
+
+			spin_lock(&ifp->lock);
 			ifp->valid_lft = valid_lft;
 			ifp->prefered_lft = prefered_lft;
 			ifp->tstamp = jiffies;
-			if (ifp->flags & ADDR_INVALID)
-				event = RTM_NEWADDR;
-			ifp->flags &= ~(ADDR_DEPRECATED|ADDR_INVALID);
-			ipv6_ifa_notify(event, ifp);
+			flags = ifp->flags;
+			ifp->flags &= ~IFA_F_DEPRECATED;
+			spin_unlock(&ifp->lock);
+
+			if (!(flags&IFA_F_TENTATIVE))
+				ipv6_ifa_notify((flags&IFA_F_DEPRECATED) ?
+						0 : RTM_NEWADDR, ifp);
+			in6_ifa_put(ifp);
 		}
 	}
+	in6_dev_put(in6_dev);
 }
 
 /*
@@ -810,7 +935,7 @@
 	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
 		goto err_exit;
 
-	dev = dev_get_by_index(ireq.ifr6_ifindex);
+	dev = __dev_get_by_index(ireq.ifr6_ifindex);
 
 	err = -ENODEV;
 	if (dev == NULL)
@@ -840,7 +965,7 @@
 
 		if (err == 0) {
 			err = -ENOBUFS;
-			if ((dev = dev_get(p.name)) == NULL)
+			if ((dev = __dev_get_by_name(p.name)) == NULL)
 				goto err_exit;
 			err = dev_open(dev);
 		}
@@ -860,8 +985,10 @@
 	struct inet6_dev *idev;
 	struct net_device *dev;
 	int scope;
+
+	ASSERT_RTNL();
 	
-	if ((dev = dev_get_by_index(ifindex)) == NULL)
+	if ((dev = __dev_get_by_index(ifindex)) == NULL)
 		return -ENODEV;
 	
 	if (!(dev->flags&IFF_UP))
@@ -872,15 +999,11 @@
 
 	scope = ipv6_addr_scope(pfx);
 
-	addrconf_lock();
-	if ((ifp = ipv6_add_addr(idev, pfx, scope)) != NULL) {
-		ifp->prefix_len = plen;
-		ifp->flags |= ADDR_PERMANENT;
+	if ((ifp = ipv6_add_addr(idev, pfx, plen, scope, IFA_F_PERMANENT)) != NULL) {
 		addrconf_dad_start(ifp);
-		addrconf_unlock();
+		in6_ifa_put(ifp);
 		return 0;
 	}
-	addrconf_unlock();
 
 	return -ENOBUFS;
 }
@@ -890,22 +1013,21 @@
 	struct inet6_ifaddr *ifp;
 	struct inet6_dev *idev;
 	struct net_device *dev;
-	int scope;
 	
-	if ((dev = dev_get_by_index(ifindex)) == NULL)
+	if ((dev = __dev_get_by_index(ifindex)) == NULL)
 		return -ENODEV;
 
-	if ((idev = ipv6_get_idev(dev)) == NULL)
+	if ((idev = __in6_dev_get(dev)) == NULL)
 		return -ENXIO;
 
-	scope = ipv6_addr_scope(pfx);
-
-	start_bh_atomic();
+	read_lock_bh(&idev->lock);
 	for (ifp = idev->addr_list; ifp; ifp=ifp->if_next) {
-		if (ifp->scope == scope && ifp->prefix_len == plen &&
+		if (ifp->prefix_len == plen &&
 		    (!memcmp(pfx, &ifp->addr, sizeof(struct in6_addr)))) {
+			in6_ifa_hold(ifp);
+			read_unlock_bh(&idev->lock);
+			
 			ipv6_del_addr(ifp);
-			end_bh_atomic();
 
 			/* If the last address is deleted administratively,
 			   disable IPv6 on this interface.
@@ -915,7 +1037,7 @@
 			return 0;
 		}
 	}
-	end_bh_atomic();
+	read_unlock_bh(&idev->lock);
 	return -EADDRNOTAVAIL;
 }
 
@@ -961,6 +1083,8 @@
 	struct net_device *dev;
 	int scope;
 
+	ASSERT_RTNL();
+
 	memset(&addr, 0, sizeof(struct in6_addr));
 	memcpy(&addr.s6_addr32[3], idev->dev->dev_addr, 4);
 
@@ -972,28 +1096,29 @@
 	}
 
 	if (addr.s6_addr32[3]) {
-		addrconf_lock();
-		ifp = ipv6_add_addr(idev, &addr, scope);
+		ifp = ipv6_add_addr(idev, &addr, 128, scope, IFA_F_PERMANENT);
 		if (ifp) {
-			ifp->flags |= ADDR_PERMANENT;
-			ifp->prefix_len = 128;
+			spin_lock_bh(&ifp->lock);
+			ifp->flags &= ~IFA_F_TENTATIVE;
+			spin_unlock_bh(&ifp->lock);
 			ipv6_ifa_notify(RTM_NEWADDR, ifp);
+			in6_ifa_put(ifp);
 		}
-		addrconf_unlock();
 		return;
 	}
 
-	read_lock(&dev_base_lock);
         for (dev = dev_base; dev != NULL; dev = dev->next) {
-		if (dev->ip_ptr && (dev->flags & IFF_UP)) {
-			struct in_device * in_dev = dev->ip_ptr;
+		struct in_device * in_dev = __in_dev_get(dev);
+		if (in_dev && (dev->flags & IFF_UP)) {
 			struct in_ifaddr * ifa;
 
 			int flag = scope;
 
 			for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
+				int plen;
+
 				addr.s6_addr32[3] = ifa->ifa_local;
-				
+
 				if (ifa->ifa_scope == RT_SCOPE_LINK)
 					continue;
 				if (ifa->ifa_scope >= RT_SCOPE_HOST) {
@@ -1001,22 +1126,23 @@
 						continue;
 					flag |= IFA_HOST;
 				}
+				if (idev->dev->flags&IFF_POINTOPOINT)
+					plen = 10;
+				else
+					plen = 96;
 
-				addrconf_lock();
-				ifp = ipv6_add_addr(idev, &addr, flag);
+				ifp = ipv6_add_addr(idev, &addr, plen, flag,
+						    IFA_F_PERMANENT);
 				if (ifp) {
-					if (idev->dev->flags&IFF_POINTOPOINT)
-						ifp->prefix_len = 10;
-					else
-						ifp->prefix_len = 96;
-					ifp->flags |= ADDR_PERMANENT;
+					spin_lock_bh(&ifp->lock);
+					ifp->flags &= ~IFA_F_TENTATIVE;
+					spin_unlock_bh(&ifp->lock);
 					ipv6_ifa_notify(RTM_NEWADDR, ifp);
+					in6_ifa_put(ifp);
 				}
-				addrconf_unlock();
 			}
 		}
         }
-	read_unlock(&dev_base_lock);
 }
 
 static void init_loopback(struct net_device *dev)
@@ -1027,6 +1153,8 @@
 
 	/* ::1 */
 
+	ASSERT_RTNL();
+
 	memset(&addr, 0, sizeof(struct in6_addr));
 	addr.s6_addr[15] = 1;
 
@@ -1035,29 +1163,25 @@
 		return;
 	}
 
-	addrconf_lock();
-	ifp = ipv6_add_addr(idev, &addr, IFA_HOST);
-
+	ifp = ipv6_add_addr(idev, &addr, 128, IFA_HOST, IFA_F_PERMANENT);
 	if (ifp) {
-		ifp->flags |= ADDR_PERMANENT;
-		ifp->prefix_len = 128;
+		spin_lock_bh(&ifp->lock);
+		ifp->flags &= ~IFA_F_TENTATIVE;
+		spin_unlock_bh(&ifp->lock);
 		ipv6_ifa_notify(RTM_NEWADDR, ifp);
+		in6_ifa_put(ifp);
 	}
-	addrconf_unlock();
 }
 
 static void addrconf_add_linklocal(struct inet6_dev *idev, struct in6_addr *addr)
 {
 	struct inet6_ifaddr * ifp;
 
-	addrconf_lock();
-	ifp = ipv6_add_addr(idev, addr, IFA_LINK);
+	ifp = ipv6_add_addr(idev, addr, 10, IFA_LINK, IFA_F_PERMANENT);
 	if (ifp) {
-		ifp->flags = ADDR_PERMANENT;
-		ifp->prefix_len = 10;
 		addrconf_dad_start(ifp);
+		in6_ifa_put(ifp);
 	}
-	addrconf_unlock();
 }
 
 static void addrconf_dev_config(struct net_device *dev)
@@ -1065,6 +1189,8 @@
 	struct in6_addr addr;
 	struct inet6_dev    * idev;
 
+	ASSERT_RTNL();
+
 	if (dev->type != ARPHRD_ETHER) {
 		/* Alas, we support only Ethernet autoconfiguration. */
 		return;
@@ -1100,6 +1226,8 @@
 {
 	struct inet6_dev *idev;
 
+	ASSERT_RTNL();
+
 	/* 
 	 * Configure the tunnel with one of our IPv4 
 	 * addresses... we should configure all of 
@@ -1143,17 +1271,13 @@
 			addrconf_dev_config(dev);
 			break;
 		};
-
-#ifdef CONFIG_IPV6_NETLINK
-		rt6_sndmsg(RTMSG_NEWDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0);
-#endif
 		break;
 
 	case NETDEV_CHANGEMTU:
 		if (dev->mtu >= IPV6_MIN_MTU) {
 			struct inet6_dev *idev;
 
-			if ((idev = ipv6_get_idev(dev)) == NULL)
+			if ((idev = __in6_dev_get(dev)) == NULL)
 				break;
 			idev->cnf.mtu6 = dev->mtu;
 			rt6_mtu_change(dev, dev->mtu);
@@ -1167,12 +1291,7 @@
 		/*
 		 *	Remove all addresses from this interface.
 		 */
-		if (addrconf_ifdown(dev, event != NETDEV_DOWN) == 0) {
-#ifdef CONFIG_IPV6_NETLINK
-			rt6_sndmsg(RTMSG_DELDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0);
-#endif
-		}
-
+		addrconf_ifdown(dev, event != NETDEV_DOWN);
 		break;
 	case NETDEV_CHANGE:
 		break;
@@ -1183,102 +1302,114 @@
 
 static int addrconf_ifdown(struct net_device *dev, int how)
 {
-	struct inet6_dev *idev, **bidev;
+	struct inet6_dev *idev;
 	struct inet6_ifaddr *ifa, **bifa;
-	int i, hash;
+	int i;
+
+	ASSERT_RTNL();
 
 	rt6_ifdown(dev);
 	neigh_ifdown(&nd_tbl, dev);
 
-	idev = ipv6_get_idev(dev);
+	idev = __in6_dev_get(dev);
 	if (idev == NULL)
 		return -ENODEV;
 
-	start_bh_atomic();
-
-	/* Discard address list */
-
-	idev->addr_list = NULL;
-
-	/*
-	 * Clean addresses hash table
+	/* Step 1: remove reference to ipv6 device from parent device.
+	           Do not dev_put!
 	 */
+	if (how == 1) {
+		write_lock_bh(&addrconf_lock);
+		dev->ip6_ptr = NULL;
+		idev->dead = 1;
+		write_unlock_bh(&addrconf_lock);
+	}
 
-	for (i=0; i<16; i++) {
+	/* Step 2: clear hash table */
+	for (i=0; i<IN6_ADDR_HSIZE; i++) {
 		bifa = &inet6_addr_lst[i];
 
+		write_lock_bh(&addrconf_hash_lock);
 		while ((ifa = *bifa) != NULL) {
 			if (ifa->idev == idev) {
 				*bifa = ifa->lst_next;
-				del_timer(&ifa->timer);
-				ipv6_ifa_notify(RTM_DELADDR, ifa);
-				kfree(ifa);
+				ifa->lst_next = NULL;
+				addrconf_del_timer(ifa);
+				in6_ifa_put(ifa);
 				continue;
 			}
 			bifa = &ifa->lst_next;
 		}
+		write_unlock_bh(&addrconf_hash_lock);
 	}
 
-	/* Discard multicast list */
+	/* Step 3: clear address list */
+
+	write_lock_bh(&idev->lock);
+	while ((ifa = idev->addr_list) != NULL) {
+		idev->addr_list = ifa->if_next;
+		ifa->if_next = NULL;
+		ifa->dead = 1;
+		addrconf_del_timer(ifa);
+		write_unlock_bh(&idev->lock);
+
+		ipv6_ifa_notify(RTM_DELADDR, ifa);
+		in6_ifa_put(ifa);
+
+		write_lock_bh(&idev->lock);
+	}
+	write_unlock_bh(&idev->lock);
+
+	/* Step 4: Discard multicast list */
 
 	if (how == 1)
 		ipv6_mc_destroy_dev(idev);
 	else
 		ipv6_mc_down(idev);
 
-	/* Delete device from device hash table (if unregistered) */
+	/* Shot the device (if unregistered) */
 
 	if (how == 1) {
-		hash = ipv6_devindex_hash(dev->ifindex);
-
-		for (bidev = &inet6_dev_lst[hash]; (idev=*bidev) != NULL; bidev = &idev->next) {
-			if (idev->dev == dev) {
-				*bidev = idev->next;
-				neigh_parms_release(&nd_tbl, idev->nd_parms);
+		neigh_parms_release(&nd_tbl, idev->nd_parms);
 #ifdef CONFIG_SYSCTL
-				addrconf_sysctl_unregister(&idev->cnf);
+		addrconf_sysctl_unregister(&idev->cnf);
 #endif
-				kfree(idev);
-				break;
-			}
-		}
+		in6_dev_put(idev);
 	}
-	end_bh_atomic();
 	return 0;
 }
 
-
 static void addrconf_rs_timer(unsigned long data)
 {
-	struct inet6_ifaddr *ifp;
-
-	ifp = (struct inet6_ifaddr *) data;
+	struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
 
 	if (ifp->idev->cnf.forwarding)
-		return;
+		goto out;
 
 	if (ifp->idev->if_flags & IF_RA_RCVD) {
 		/*
 		 *	Announcement received after solicitation
 		 *	was sent
 		 */
-		return;
+		goto out;
 	}
 
+	spin_lock(&ifp->lock);
 	if (ifp->probes++ <= ifp->idev->cnf.rtr_solicits) {
 		struct in6_addr all_routers;
 
+		addrconf_mod_timer(ifp, AC_RS,
+				   ifp->idev->cnf.rtr_solicit_interval);
+		spin_unlock(&ifp->lock);
+
 		ipv6_addr_all_routers(&all_routers);
 
 		ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers);
-		
-		ifp->timer.function = addrconf_rs_timer;
-		ifp->timer.expires = (jiffies + 
-				      ifp->idev->cnf.rtr_solicit_interval);
-		add_timer(&ifp->timer);
 	} else {
 		struct in6_rtmsg rtmsg;
 
+		spin_unlock(&ifp->lock);
+
 		printk(KERN_DEBUG "%s: no IPv6 routers present\n",
 		       ifp->idev->dev->name);
 
@@ -1292,6 +1423,9 @@
 
 		ip6_route_add(&rtmsg);
 	}
+
+out:
+	in6_ifa_put(ifp);
 }
 
 /*
@@ -1306,49 +1440,53 @@
 
 	addrconf_join_solict(dev, &ifp->addr);
 
-	if (ifp->prefix_len != 128 && (ifp->flags&ADDR_PERMANENT))
+	if (ifp->prefix_len != 128 && (ifp->flags&IFA_F_PERMANENT))
 		addrconf_prefix_route(&ifp->addr, ifp->prefix_len, dev, 0, RTF_ADDRCONF);
 
-	if (dev->flags&(IFF_NOARP|IFF_LOOPBACK)) {
-		start_bh_atomic();
-		ifp->flags &= ~DAD_INCOMPLETE;
+	net_srandom(ifp->addr.s6_addr32[3]);
+	rand_num = net_random() % (ifp->idev->cnf.rtr_solicit_delay ? : 1);
+
+	spin_lock_bh(&ifp->lock);
+
+	if (dev->flags&(IFF_NOARP|IFF_LOOPBACK) ||
+	    !(ifp->flags&IFA_F_TENTATIVE)) {
+		ifp->flags &= ~IFA_F_TENTATIVE;
+		spin_unlock_bh(&ifp->lock);
+
 		addrconf_dad_completed(ifp);
-		end_bh_atomic();
 		return;
 	}
 
-	net_srandom(ifp->addr.s6_addr32[3]);
-
 	ifp->probes = ifp->idev->cnf.dad_transmits;
-	ifp->flags |= DAD_INCOMPLETE;
+	addrconf_mod_timer(ifp, AC_DAD, rand_num);
 
-	rand_num = net_random() % ifp->idev->cnf.rtr_solicit_delay;
-
-	ifp->timer.function = addrconf_dad_timer;
-	ifp->timer.expires = jiffies + rand_num;
-
-	add_timer(&ifp->timer);
+	spin_unlock_bh(&ifp->lock);
 }
 
 static void addrconf_dad_timer(unsigned long data)
 {
-	struct inet6_ifaddr *ifp;
+	struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
 	struct in6_addr unspec;
 	struct in6_addr mcaddr;
 
-	ifp = (struct inet6_ifaddr *) data;
-
+	spin_lock_bh(&ifp->lock);
 	if (ifp->probes == 0) {
 		/*
 		 * DAD was successful
 		 */
 
-		ifp->flags &= ~DAD_INCOMPLETE;
+		ifp->flags &= ~IFA_F_TENTATIVE;
+		spin_unlock_bh(&ifp->lock);
+
 		addrconf_dad_completed(ifp);
+
+		in6_ifa_put(ifp);
 		return;
 	}
 
 	ifp->probes--;
+	addrconf_mod_timer(ifp, AC_DAD, ifp->idev->cnf.rtr_solicit_interval);
+	spin_unlock_bh(&ifp->lock);
 
 	/* send a neighbour solicitation for our addr */
 	memset(&unspec, 0, sizeof(unspec));
@@ -1361,8 +1499,7 @@
 	ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec);
 #endif
 
-	ifp->timer.expires = jiffies + ifp->idev->cnf.rtr_solicit_interval;
-	add_timer(&ifp->timer);
+	in6_ifa_put(ifp);
 }
 
 static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
@@ -1393,12 +1530,11 @@
 		 */
 		ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers);
 
+		spin_lock_bh(&ifp->lock);
 		ifp->probes = 1;
-		ifp->timer.function = addrconf_rs_timer;
-		ifp->timer.expires = (jiffies +
-				      ifp->idev->cnf.rtr_solicit_interval);
 		ifp->idev->if_flags |= IF_RS_SENT;
-		add_timer(&ifp->timer);
+		addrconf_mod_timer(ifp, AC_RS, ifp->idev->cnf.rtr_solicit_interval);
+		spin_unlock_bh(&ifp->lock);
 	}
 }
 
@@ -1412,9 +1548,8 @@
 	off_t pos=0;
 	off_t begin=0;
 
-	addrconf_lock();
-
 	for (i=0; i < IN6_ADDR_HSIZE; i++) {
+		read_lock_bh(&addrconf_hash_lock);
 		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
 			int j;
 
@@ -1436,13 +1571,15 @@
 				len=0;
 				begin=pos;
 			}
-			if(pos>offset+length)
+			if(pos>offset+length) {
+				read_unlock_bh(&addrconf_hash_lock);
 				goto done;
+			}
 		}
+		read_unlock_bh(&addrconf_hash_lock);
 	}
 
 done:
-	addrconf_unlock();
 
 	*start=buffer+(offset-begin);
 	len-=(offset-begin);
@@ -1472,44 +1609,47 @@
 	unsigned long now = jiffies;
 	int i;
 
-	if (atomic_read(&addr_list_lock)) {
-		addr_chk_timer.expires = jiffies + 1*HZ;
-		add_timer(&addr_chk_timer);
-		return;
-	}
-
 	for (i=0; i < IN6_ADDR_HSIZE; i++) {
-		for (ifp=inet6_addr_lst[i]; ifp;) {
-			if (ifp->flags & ADDR_INVALID) {
-				struct inet6_ifaddr *bp = ifp;
-				ifp= ifp->lst_next;
-				ipv6_del_addr(bp);
+
+restart:
+		write_lock(&addrconf_hash_lock);
+		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
+			unsigned long age;
+
+			if (ifp->flags & IFA_F_PERMANENT)
 				continue;
-			}
-			if (!(ifp->flags & ADDR_PERMANENT)) {
-				struct inet6_ifaddr *bp;
-				unsigned long age;
-
-				age = (now - ifp->tstamp) / HZ;
-
-				bp = ifp;
-				ifp= ifp->lst_next;
-				
-				if (age > bp->valid_lft)
-					ipv6_del_addr(bp);
-				else if (age > bp->prefered_lft) {
-					bp->flags |= ADDR_DEPRECATED;
-					ipv6_ifa_notify(0, bp);
+
+			age = (now - ifp->tstamp) / HZ;
+
+			if (age > ifp->valid_lft) {
+				in6_ifa_hold(ifp);
+				write_unlock(&addrconf_hash_lock);
+				ipv6_del_addr(ifp);
+				goto restart;
+			} else if (age > ifp->prefered_lft) {
+				int deprecate = 0;
+
+				spin_lock(&ifp->lock);
+				if (!(ifp->flags&IFA_F_DEPRECATED)) {
+					deprecate = 1;
+					ifp->flags |= IFA_F_DEPRECATED;
 				}
+				spin_unlock(&ifp->lock);
 
-				continue;
+				if (deprecate) {
+					in6_ifa_hold(ifp);
+					write_unlock(&addrconf_hash_lock);
+
+					ipv6_ifa_notify(0, ifp);
+					in6_ifa_put(ifp);
+					goto restart;
+				}
 			}
-			ifp = ifp->lst_next;
 		}
+		write_unlock(&addrconf_hash_lock);
 	}
 
-	addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY;
-	add_timer(&addr_chk_timer);
+	mod_timer(&addr_chk_timer, jiffies + ADDR_CHECK_FREQUENCY);
 }
 
 #ifdef CONFIG_RTNETLINK
@@ -1532,6 +1672,8 @@
 			return -EINVAL;
 		pfx = RTA_DATA(rta[IFA_LOCAL-1]);
 	}
+	if (pfx == NULL)
+		return -EINVAL;
 
 	return inet6_addr_del(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
 }
@@ -1554,6 +1696,8 @@
 			return -EINVAL;
 		pfx = RTA_DATA(rta[IFA_LOCAL-1]);
 	}
+	if (pfx == NULL)
+		return -EINVAL;
 
 	return inet6_addr_add(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
 }
@@ -1570,7 +1714,7 @@
 	ifm = NLMSG_DATA(nlh);
 	ifm->ifa_family = AF_INET6;
 	ifm->ifa_prefixlen = ifa->prefix_len;
-	ifm->ifa_flags = ifa->flags & ~ADDR_INVALID;
+	ifm->ifa_flags = ifa->flags;
 	ifm->ifa_scope = RT_SCOPE_UNIVERSE;
 	if (ifa->scope&IFA_HOST)
 		ifm->ifa_scope = RT_SCOPE_HOST;
@@ -1614,18 +1758,18 @@
 			continue;
 		if (idx > s_idx)
 			s_ip_idx = 0;
-		start_bh_atomic();
+		read_lock_bh(&addrconf_hash_lock);
 		for (ifa=inet6_addr_lst[idx], ip_idx = 0; ifa;
 		     ifa = ifa->lst_next, ip_idx++) {
 			if (ip_idx < s_ip_idx)
 				continue;
 			if (inet6_fill_ifaddr(skb, ifa, NETLINK_CB(cb->skb).pid,
 					      cb->nlh->nlmsg_seq, RTM_NEWADDR) <= 0) {
-				end_bh_atomic();
+				read_unlock_bh(&addrconf_hash_lock);
 				goto done;
 			}
 		}
-		end_bh_atomic();
+		read_unlock_bh(&addrconf_hash_lock);
 	}
 done:
 	cb->args[0] = idx;
@@ -1682,11 +1826,9 @@
 		ip6_rt_addr_add(&ifp->addr, ifp->idev->dev);
 		break;
 	case RTM_DELADDR:
-		start_bh_atomic();
 		addrconf_leave_solict(ifp->idev->dev, &ifp->addr);
-		if (ipv6_chk_addr(&ifp->addr, ifp->idev->dev, 0) == NULL)
+		if (!ipv6_chk_addr(&ifp->addr, ifp->idev->dev))
 			ip6_rt_addr_del(&ifp->addr, ifp->idev->dev);
-		end_bh_atomic();
 		break;
 	}
 }
@@ -1708,8 +1850,10 @@
 
 		if (valp != &ipv6_devconf.forwarding) {
 			struct net_device *dev = dev_get_by_index(ctl->ctl_name);
-			if (dev)
-				idev = ipv6_get_idev(dev);
+			if (dev) {
+				idev = in6_dev_get(dev);
+				dev_put(dev);
+			}
 			if (idev == NULL)
 				return ret;
 		} else
@@ -1717,11 +1861,10 @@
 
 		addrconf_forward_change(idev);
 
-		if (*valp) {
-			start_bh_atomic();
+		if (*valp)
 			rt6_purge_dflt_routers(0);
-			end_bh_atomic();
-		}
+		if (idev)
+			in6_dev_put(idev);
 	}
 
         return ret;
@@ -1845,7 +1988,7 @@
 	struct net_device *dev;
 
 	/* This takes sense only during module load. */
-	read_lock(&dev_base_lock);
+	rtnl_lock();
 	for (dev = dev_base; dev; dev = dev->next) {
 		if (!(dev->flags&IFF_UP))
 			continue;
@@ -1861,9 +2004,9 @@
 			/* Ignore all other */
 		}
 	}
-	read_unlock(&dev_base_lock);
+	rtnl_unlock();
 #endif
-	
+
 #ifdef CONFIG_PROC_FS
 	proc_net_register(&iface_proc_entry);
 #endif
@@ -1883,6 +2026,7 @@
 #ifdef MODULE
 void addrconf_cleanup(void)
 {
+ 	struct net_device *dev;
  	struct inet6_dev *idev;
  	struct inet6_ifaddr *ifa;
 	int i;
@@ -1895,25 +2039,23 @@
 	addrconf_sysctl_unregister(&ipv6_devconf);
 #endif
 
-	del_timer(&addr_chk_timer);
+	rtnl_lock();
 
 	/*
 	 *	clean dev list.
 	 */
 
-	for (i=0; i < IN6_ADDR_HSIZE; i++) {
-		struct inet6_dev *next;
-		for (idev = inet6_dev_lst[i]; idev; idev = next) {
-			next = idev->next;
-			addrconf_ifdown(idev->dev, 1);
-		}
+	for (dev=dev_base; dev; dev=dev->next) {
+		if ((idev = __in6_dev_get(dev)) == NULL)
+			continue;
+		addrconf_ifdown(dev, 1);
 	}
 
-	start_bh_atomic();
 	/*
-	 *	clean addr_list
+	 *	Check hash table.
 	 */
 
+	write_lock_bh(&addrconf_hash_lock);
 	for (i=0; i < IN6_ADDR_HSIZE; i++) {
 		for (ifa=inet6_addr_lst[i]; ifa; ) {
 			struct inet6_ifaddr *bifa;
@@ -1926,7 +2068,11 @@
 			 */
 		}
 	}
-	end_bh_atomic();
+	write_unlock_bh(&addrconf_hash_lock);
+
+	del_timer(&addr_chk_timer);
+
+	rtnl_unlock();
 
 #ifdef CONFIG_PROC_FS
 	proc_net_unregister(iface_proc_entry.low_ino);

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)