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

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

diff -u --recursive --new-file v2.2.3/linux/net/ipv6/addrconf.c linux/net/ipv6/addrconf.c
@@ -5,7 +5,7 @@
  *	Authors:
  *	Pedro Roque		<roque@di.fc.ul.pt>	
  *
- *	$Id: addrconf.c,v 1.46 1999/01/12 14:34:47 davem Exp $
+ *	$Id: addrconf.c,v 1.47 1999/03/21 05:22:50 davem Exp $
  *
  *	This program is free software; you can redistribute it and/or
  *      modify it under the terms of the GNU General Public License
@@ -88,6 +88,34 @@
 	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. inet6_get_lladdr() is used only from bh protected context.
+
+   3. 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 device *dev, int how);
 
 static void addrconf_dad_start(struct inet6_ifaddr *ifp);
@@ -188,7 +216,7 @@
 	if (dev->mtu < IPV6_MIN_MTU)
 		return NULL;
 
-	ndev = kmalloc(sizeof(struct inet6_dev), gfp_any());
+	ndev = kmalloc(sizeof(struct inet6_dev), GFP_KERNEL);
 
 	if (ndev) {
 		memset(ndev, 0, sizeof(struct inet6_dev));
@@ -227,9 +255,9 @@
 		idev = ipv6_add_dev(dev);
 		if (idev == NULL)
 			return NULL;
+		if (dev->flags&IFF_UP)
+			ipv6_mc_up(idev);
 	}
-	if (dev->flags&IFF_UP)
-		ipv6_mc_up(idev);
 	return idev;
 }
 
@@ -260,13 +288,13 @@
 	return NULL;
 }
 
-struct inet6_ifaddr * ipv6_add_addr(struct inet6_dev *idev, 
-				    struct in6_addr *addr, int scope)
+static struct inet6_ifaddr *
+ipv6_add_addr(struct inet6_dev *idev, struct in6_addr *addr, int scope)
 {
 	struct inet6_ifaddr *ifa;
 	int hash;
 
-	ifa = kmalloc(sizeof(struct inet6_ifaddr), gfp_any());
+	ifa = kmalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC);
 
 	if (ifa == NULL) {
 		ADBG(("ipv6_add_addr: malloc failed\n"));
@@ -312,7 +340,9 @@
 
 	for (; iter; iter = iter->lst_next) {
 		if (iter == ifp) {
+			net_serialize_enter();
 			*back = ifp->lst_next;
+			net_serialize_leave();
 			ifp->lst_next = NULL;
 			break;
 		}
@@ -324,7 +354,9 @@
 
 	for (; iter; iter = iter->if_next) {
 		if (iter == ifp) {
+			net_serialize_enter();
 			*back = ifp->if_next;
+			net_serialize_leave();
 			ifp->if_next = NULL;
 			break;
 		}
@@ -343,24 +375,23 @@
  *	ii)	see if there is a specific route for the destination and use
  *		an address of the attached interface 
  *	iii)	don't use deprecated addresses
- *
- *	at the moment I believe only iii) is missing.
  */
-struct inet6_ifaddr * ipv6_get_saddr(struct dst_entry *dst,
-				     struct in6_addr *daddr)
+int ipv6_get_saddr(struct dst_entry *dst,
+		   struct in6_addr *daddr, struct in6_addr *saddr)
 {
 	int scope;
 	struct inet6_ifaddr *ifp = NULL;
 	struct inet6_ifaddr *match = NULL;
 	struct device *dev = NULL;
 	struct rt6_info *rt;
+	int err;
 	int i;
 
 	rt = (struct rt6_info *) dst;
 	if (rt)
 		dev = rt->rt6i_dev;
 	
-	atomic_inc(&addr_list_lock);
+	addrconf_lock();
 
 	scope = ipv6_addr_scope(daddr);
 	if (rt && (rt->rt6i_flags & RTF_ALLONLINK)) {
@@ -388,10 +419,10 @@
 			if (idev->dev == dev) {
 				for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
 					if (ifp->scope == scope) {
-						if (!(ifp->flags & ADDR_STATUS))
+						if (!(ifp->flags & (ADDR_STATUS|DAD_STATUS)))
 							goto out;
 
-						if (!(ifp->flags & ADDR_INVALID))
+						if (!(ifp->flags & (ADDR_INVALID|DAD_STATUS)))
 							match = ifp;
 					}
 				}
@@ -410,10 +441,10 @@
 	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))
+				if (!(ifp->flags & (ADDR_STATUS|DAD_STATUS)))
 					goto out;
 
-				if (!(ifp->flags & ADDR_INVALID))
+				if (!(ifp->flags & (ADDR_INVALID|DAD_STATUS)))
 					match = ifp;
 			}
 		}
@@ -422,28 +453,30 @@
 out:
 	if (ifp == NULL)
 		ifp = match;
-	atomic_dec(&addr_list_lock);
-	return ifp;
+
+	err = -ENETUNREACH;
+	if (ifp) {
+		memcpy(saddr, &ifp->addr, sizeof(struct in6_addr));
+		err = 0;
+	}
+	addrconf_unlock();
+	return err;
 }
 
 struct inet6_ifaddr * ipv6_get_lladdr(struct device *dev)
 {
-	struct inet6_ifaddr *ifp;
+	struct inet6_ifaddr *ifp = NULL;
 	struct inet6_dev *idev;
-	int hash;
 
-	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 == IFA_LINK)
-					return ifp;
-			}
-			break;
+	if ((idev = ipv6_get_idev(dev)) != NULL) {
+		addrconf_lock();
+		for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
+			if (ifp->scope == IFA_LINK)
+				break;
 		}
+		addrconf_unlock();
 	}
-	return NULL;
+	return ifp;
 }
 
 /*
@@ -461,7 +494,7 @@
 	if (!nd)
 		flags |= DAD_STATUS|ADDR_INVALID;
 
-	atomic_inc(&addr_list_lock);
+	addrconf_lock();
 
 	hash = ipv6_addr_hash(addr);
 	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
@@ -472,7 +505,7 @@
 		}
 	}
 
-	atomic_dec(&addr_list_lock);
+	addrconf_unlock();
 	return ifp;
 }
 
@@ -665,13 +698,6 @@
 	}
 
 	/*
-	 *	If we where using an "all destinations on link" route
-	 *	delete it
-	 */
-
-	rt6_purge_dflt_routers(RTF_ALLONLINK);
-
-	/*
 	 *	Two things going on here:
 	 *	1) Add routes for on-link prefixes
 	 *	2) Configure prefixes with the auto flag set
@@ -845,14 +871,17 @@
 
 	scope = ipv6_addr_scope(pfx);
 
-	if ((ifp = ipv6_add_addr(idev, pfx, scope)) == NULL)
-		return -ENOMEM;
-
-	ifp->prefix_len = plen;
-	ifp->flags |= ADDR_PERMANENT;
+	addrconf_lock();
+	if ((ifp = ipv6_add_addr(idev, pfx, scope)) != NULL) {
+		ifp->prefix_len = plen;
+		ifp->flags |= ADDR_PERMANENT;
+		addrconf_dad_start(ifp);
+		addrconf_unlock();
+		return 0;
+	}
+	addrconf_unlock();
 
-	addrconf_dad_start(ifp);
-	return 0;
+	return -ENOBUFS;
 }
 
 static int inet6_addr_del(int ifindex, struct in6_addr *pfx, int plen)
@@ -870,20 +899,22 @@
 
 	scope = ipv6_addr_scope(pfx);
 
+	start_bh_atomic();
 	for (ifp = idev->addr_list; ifp; ifp=ifp->if_next) {
 		if (ifp->scope == scope && ifp->prefix_len == plen &&
 		    (!memcmp(pfx, &ifp->addr, sizeof(struct in6_addr)))) {
 			ipv6_del_addr(ifp);
+			end_bh_atomic();
 
 			/* If the last address is deleted administratively,
 			   disable IPv6 on this interface.
 			 */
-			   
 			if (idev->addr_list == NULL)
 				addrconf_ifdown(idev->dev, 1);
 			return 0;
 		}
 	}
+	end_bh_atomic();
 	return -EADDRNOTAVAIL;
 }
 
@@ -940,12 +971,14 @@
 	}
 
 	if (addr.s6_addr32[3]) {
+		addrconf_lock();
 		ifp = ipv6_add_addr(idev, &addr, scope);
 		if (ifp) {
 			ifp->flags |= ADDR_PERMANENT;
 			ifp->prefix_len = 128;
 			ipv6_ifa_notify(RTM_NEWADDR, ifp);
 		}
+		addrconf_unlock();
 		return;
 	}
 
@@ -967,17 +1000,17 @@
 					flag |= IFA_HOST;
 				}
 
+				addrconf_lock();
 				ifp = ipv6_add_addr(idev, &addr, flag);
-			
-				if (ifp == NULL)
-					continue;
-
-				if (idev->dev->flags&IFF_POINTOPOINT)
-					ifp->prefix_len = 10;
-				else
-					ifp->prefix_len = 96;
-				ifp->flags |= ADDR_PERMANENT;
-				ipv6_ifa_notify(RTM_NEWADDR, ifp);
+				if (ifp) {
+					if (idev->dev->flags&IFF_POINTOPOINT)
+						ifp->prefix_len = 10;
+					else
+						ifp->prefix_len = 96;
+					ifp->flags |= ADDR_PERMANENT;
+					ipv6_ifa_notify(RTM_NEWADDR, ifp);
+				}
+				addrconf_unlock();
 			}
 		}
         }
@@ -999,31 +1032,29 @@
 		return;
 	}
 
+	addrconf_lock();
 	ifp = ipv6_add_addr(idev, &addr, IFA_HOST);
 
-	if (ifp == NULL) {
-		printk(KERN_DEBUG "init_loopback: add_addr failed\n");
-		return;
+	if (ifp) {
+		ifp->flags |= ADDR_PERMANENT;
+		ifp->prefix_len = 128;
+		ipv6_ifa_notify(RTM_NEWADDR, ifp);
 	}
-
-	ifp->flags |= ADDR_PERMANENT;
-	ifp->prefix_len = 128;
-
-	ipv6_ifa_notify(RTM_NEWADDR, 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);
-	if (ifp == NULL)
-		return;
-
-	ifp->flags = ADDR_PERMANENT;
-	ifp->prefix_len = 10;
-
-	addrconf_dad_start(ifp);
+	if (ifp) {
+		ifp->flags = ADDR_PERMANENT;
+		ifp->prefix_len = 10;
+		addrconf_dad_start(ifp);
+	}
+	addrconf_unlock();
 }
 
 static void addrconf_dev_config(struct device *dev)
@@ -1375,8 +1406,12 @@
 	struct inet6_ifaddr *ifp;
 	int i;
 	int len = 0;
+	off_t pos=0;
+	off_t begin=0;
+
+	addrconf_lock();
 
-	for (i=0; i < IN6_ADDR_HSIZE; i++)
+	for (i=0; i < IN6_ADDR_HSIZE; i++) {
 		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next) {
 			int j;
 
@@ -1393,14 +1428,25 @@
 				       ifp->scope,
 				       ifp->flags,
 				       ifp->idev->dev->name);
+			pos=begin+len;
+			if(pos<offset) {
+				len=0;
+				begin=pos;
+			}
+			if(pos>offset+length)
+				goto done;
 		}
+	}
 
-	*start = buffer + offset;
-
-	len -= offset;
+done:
+	addrconf_unlock();
 
-	if (len > length)
-		len = length;
+	*start=buffer+(offset-begin);
+	len-=(offset-begin);
+	if(len>length)
+		len=length;
+	if(len<0)
+		len=0;
 	return len;
 }
 
@@ -1422,6 +1468,12 @@
 	struct inet6_ifaddr *ifp;
 	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;) {

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