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

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

diff -u --recursive --new-file v2.1.78/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.30 1997/12/09 17:12:47 freitag Exp $
+ *	$Id: addrconf.c,v 1.32 1997/12/27 20:41:18 kuznet Exp $
  *
  *	This program is free software; you can redistribute it and/or
  *      modify it under the terms of the GNU General Public License
@@ -35,6 +35,9 @@
 #include <linux/route.h>
 #include <linux/inetdevice.h>
 #include <linux/init.h>
+#ifdef CONFIG_SYSCTL
+#include <linux/sysctl.h>
+#endif
 
 #include <linux/proc_fs.h>
 #include <net/sock.h>
@@ -47,6 +50,7 @@
 #include <net/addrconf.h>
 #include <net/ip.h>
 #include <linux/if_tunnel.h>
+#include <linux/rtnetlink.h>
 
 #include <asm/uaccess.h>
 
@@ -62,17 +66,12 @@
 /*
  *	Configured unicast address list
  */
-struct inet6_ifaddr		*inet6_addr_lst[IN6_ADDR_HSIZE];
-
-/*
- *	Hash list of configured multicast addresses
- */
-struct ifmcaddr6		*inet6_mcast_lst[IN6_ADDR_HSIZE];
+static struct inet6_ifaddr		*inet6_addr_lst[IN6_ADDR_HSIZE];
 
 /*
  *	AF_INET6 device list
  */
-struct inet6_dev		*inet6_dev_lst[IN6_ADDR_HSIZE];
+static struct inet6_dev		*inet6_dev_lst[IN6_ADDR_HSIZE];
 
 static atomic_t			addr_list_lock = ATOMIC_INIT(0);
 
@@ -83,12 +82,13 @@
 	0, 0, addrconf_verify
 };
 
-static int addrconf_ifdown(struct device *dev);
+static int addrconf_ifdown(struct device *dev, int how);
 
 static void addrconf_dad_start(struct inet6_ifaddr *ifp);
 static void addrconf_dad_timer(unsigned long data);
 static void addrconf_dad_completed(struct inet6_ifaddr *ifp);
 static void addrconf_rs_timer(unsigned long data);
+static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);
 
 int ipv6_addr_type(struct in6_addr *addr)
 {
@@ -157,6 +157,14 @@
 		memset(ndev, 0, sizeof(struct inet6_dev));
 
 		ndev->dev = dev;
+		ndev->nd_parms = neigh_parms_alloc(&nd_tbl);
+		if (ndev->nd_parms == NULL)
+			ndev->nd_parms = &nd_tbl.parms;
+#ifdef CONFIG_SYSCTL
+		else
+			ndev->nd_parms->sysctl_table =
+				neigh_sysctl_register(dev, ndev->nd_parms, NET_IPV6, NET_IPV6_NEIGH, "ipv6");
+#endif
 		hash = ipv6_devindex_hash(dev->ifindex);
 		bptr = &inet6_dev_lst[hash];
 		iter = *bptr;
@@ -165,10 +173,25 @@
 			bptr = &iter->next;
 
 		*bptr = ndev;
+
 	}
 	return ndev;
 }
 
+static struct inet6_dev * ipv6_find_idev(struct device *dev)
+{
+	struct inet6_dev *idev;
+
+	if ((idev = ipv6_get_idev(dev)) == NULL) {
+		idev = ipv6_add_dev(dev);
+		if (idev == NULL)
+			return NULL;
+	}
+	if (dev->flags&IFF_UP)
+		ipv6_mc_up(idev);
+	return idev;
+}
+
 void addrconf_forwarding_on(void)
 {
 	struct inet6_dev *idev;
@@ -180,18 +203,7 @@
 			printk(KERN_DEBUG "dev %s\n", idev->dev->name);
 #endif
 
-			if (idev->dev->type == ARPHRD_ETHER) {
-				struct in6_addr maddr;
-
-#if ACONF_DEBUG >= 2
-				printk(KERN_DEBUG "joining all-routers\n");
-#endif
-				idev->router = 1;
-
-				/* Wrong. It is user level function. */
-				ipv6_addr_all_routers(&maddr);
-				ipv6_dev_mc_inc(idev->dev, &maddr);
-			}
+			idev->router = 1;
 		}
 	}
 }
@@ -244,11 +256,13 @@
 	return ifa;
 }
 
-void ipv6_del_addr(struct inet6_ifaddr *ifp)
+static void ipv6_del_addr(struct inet6_ifaddr *ifp)
 {
 	struct inet6_ifaddr *iter, **back;
 	int hash;
 
+	ipv6_ifa_notify(RTM_DELADDR, ifp);
+
 	if (atomic_read(&addr_list_lock)) {
 		ifp->flags |= ADDR_INVALID;
 		return;
@@ -399,33 +413,75 @@
  *	to the host.
  */
 
-struct inet6_ifaddr * ipv6_chk_addr(struct in6_addr *addr)
+struct inet6_ifaddr * ipv6_chk_addr(struct in6_addr *addr, struct device *dev, int nd)
 {
 	struct inet6_ifaddr * ifp;
 	u8 hash;
+	unsigned flags = 0;
+
+	if (!nd)
+		flags |= DAD_STATUS|ADDR_INVALID;
 
 	atomic_inc(&addr_list_lock);
 
 	hash = ipv6_addr_hash(addr);
 	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
-		if (ipv6_addr_cmp(&ifp->addr, addr) == 0)
-			break;
+		if (ipv6_addr_cmp(&ifp->addr, addr) == 0 && !(ifp->flags&flags)) {
+			if (dev == NULL || ifp->idev->dev == dev ||
+			    !(ifp->scope&(IFA_LINK|IFA_HOST)))
+				break;
+		}
 	}
 
 	atomic_dec(&addr_list_lock);
-	return ifp;	
+	return ifp;
 }
 
+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);
+}
+
+
 /* Join to solicited addr multicast group. */
 
 static void addrconf_join_solict(struct device *dev, struct in6_addr *addr)
 {
 	struct in6_addr maddr;
 
-	addrconf_addr_solict_mult(addr, &maddr);
+	if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
+		return;
+
+#ifndef CONFIG_IPV6_NO_PB
+	addrconf_addr_solict_mult_old(addr, &maddr);
+	ipv6_dev_mc_inc(dev, &maddr);
+#endif
+#ifdef CONFIG_IPV6_EUI64
+	addrconf_addr_solict_mult_new(addr, &maddr);
 	ipv6_dev_mc_inc(dev, &maddr);
+#endif
 }
 
+static void addrconf_leave_solict(struct device *dev, struct in6_addr *addr)
+{
+	struct in6_addr maddr;
+
+	if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
+		return;
+
+#ifndef CONFIG_IPV6_NO_PB
+	addrconf_addr_solict_mult_old(addr, &maddr);
+	ipv6_dev_mc_dec(dev, &maddr);
+#endif
+#ifdef CONFIG_IPV6_EUI64
+	addrconf_addr_solict_mult_new(addr, &maddr);
+	ipv6_dev_mc_dec(dev, &maddr);
+#endif
+}
+
+
 #ifdef CONFIG_IPV6_EUI64
 static int ipv6_generate_eui64(u8 *eui, struct device *dev)
 {
@@ -462,6 +518,7 @@
 	rtmsg.rtmsg_ifindex = dev->ifindex;
 	rtmsg.rtmsg_info = info;
 	rtmsg.rtmsg_flags = RTF_UP|RTF_ADDRCONF;
+	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
 
 	/* Prevent useless cloning on PtP SIT.
 	   This thing is done here expecting that the whole
@@ -469,12 +526,8 @@
 	 */
 	if (dev->type == ARPHRD_SIT && (dev->flags&IFF_POINTOPOINT))
 		rtmsg.rtmsg_flags |= RTF_NONEXTHOP;
-	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
 
 	ip6_route_add(&rtmsg, &err);
-
-	if (err)
-		printk(KERN_DEBUG "IPv6: error %d adding prefix route\n", err);
 }
 
 /* Create "default" multicast route to the interface */
@@ -482,7 +535,6 @@
 static void addrconf_add_mroute(struct device *dev)
 {
 	struct in6_rtmsg rtmsg;
-	struct rt6_info *rt;
 	int err;
 
 	memset(&rtmsg, 0, sizeof(rtmsg));
@@ -493,25 +545,12 @@
 	rtmsg.rtmsg_ifindex = dev->ifindex;
 	rtmsg.rtmsg_flags = RTF_UP|RTF_ADDRCONF;
 	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
-
-	rt = ip6_route_add(&rtmsg, &err);
-
-	/*
-	 * Pedro makes interesting thing here, he attached
-	 * fake nexthop to multicast route.
-	 * It is trick to avoid cloning, ugly, but efficient. --ANK
-	 */
-
-	if (err)
-		printk(KERN_DEBUG "IPv6: error %d adding mroute\n", err);
-	else
-		rt->rt6i_nexthop = ndisc_get_neigh(dev, &rtmsg.rtmsg_dst);
+	ip6_route_add(&rtmsg, &err);
 }
 
 static void sit_route_add(struct device *dev)
 {
 	struct in6_rtmsg rtmsg;
-	struct rt6_info *rt;
 	int err;
 
 	memset(&rtmsg, 0, sizeof(rtmsg));
@@ -521,19 +560,10 @@
 
 	/* prefix length - 96 bytes "::d.d.d.d" */
 	rtmsg.rtmsg_dst_len	= 96;
-	rtmsg.rtmsg_flags	= RTF_UP;
+	rtmsg.rtmsg_flags	= RTF_UP|RTF_NONEXTHOP;
 	rtmsg.rtmsg_ifindex	= dev->ifindex;
 
-	rt = ip6_route_add(&rtmsg, &err);
-	
-	/* See comment in addrconf_add_mroute.
-	 * It is the same trick, but to avoid cloning for direct
-	 * sit routes i.e. IPv4 comaptible destinations.
-	 */
-	if (err)
-		printk(KERN_DEBUG "sit_route_add: error %d in route_add\n", err);
-	else
-		rt->rt6i_nexthop = ndisc_get_neigh(dev, &rtmsg.rtmsg_dst);
+	ip6_route_add(&rtmsg, &err);
 }
 
 static void addrconf_add_lroute(struct device *dev)
@@ -546,24 +576,16 @@
 
 static struct inet6_dev *addrconf_add_dev(struct device *dev)
 {
-	struct in6_addr maddr;
 	struct inet6_dev *idev;
 
-	if ((idev = ipv6_get_idev(dev)) == NULL) {
-		idev = ipv6_add_dev(dev);
-		if (idev == NULL)
-			return NULL;
-	}
+	if ((idev = ipv6_find_idev(dev)) == NULL)
+		return NULL;
 
 	/* Add default multicast route */
 	addrconf_add_mroute(dev);
 
 	/* Add link local route */
 	addrconf_add_lroute(dev);
-	
-	/* Join to all nodes multicast group. */
-	ipv6_addr_all_nodes(&maddr);
-	ipv6_dev_mc_inc(dev, &maddr);
 	return idev;
 }
 
@@ -613,9 +635,15 @@
 	 *	2) Configure prefixes with the auto flag set
 	 */
 
-	rt_expires = jiffies + valid_lft * HZ;
-	if (rt_expires < jiffies)
-		rt_expires = ~0;
+	/* Avoid arithemtic overflow. Really, we could
+	   save rt_expires in seconds, likely valid_lft,
+	   but it would require division in fib gc, that it
+	   not good.
+	 */
+	if (valid_lft >= 0x7FFFFFFF/HZ)
+		rt_expires = 0;
+	else
+		rt_expires = jiffies + valid_lft * HZ;
 
 	rt = rt6_lookup(&pinfo->prefix, NULL, dev, RTF_LINKRT);
 
@@ -660,9 +688,9 @@
 		return;
 
 ok:
-		ifp = ipv6_chk_addr(&addr);
+		ifp = ipv6_chk_addr(&addr, dev, 1);
 
-		if (ifp == NULL && valid_lft) {
+		if ((ifp == NULL || (ifp->flags&ADDR_INVALID)) && valid_lft) {
 			struct inet6_dev *in6_dev = ipv6_get_idev(dev);
 
 			if (in6_dev == NULL) {
@@ -670,8 +698,8 @@
 				return;
 			}
 
-			ifp = ipv6_add_addr(in6_dev, &addr,
-					    addr_type & IPV6_ADDR_SCOPE_MASK);
+			if (ifp == NULL)
+				ifp = ipv6_add_addr(in6_dev, &addr, addr_type & IPV6_ADDR_SCOPE_MASK);
 
 			if (ifp == NULL)
 				return;
@@ -687,9 +715,14 @@
 		}
 
 		if (ifp) {
+			int event = 0;
 			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);
 		}
 	}
 }
@@ -705,25 +738,26 @@
 	struct device *dev;
 	int err = -EINVAL;
 
-	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq))) {
-		err = -EFAULT;
+	rtnl_lock();
+
+	err = -EFAULT;
+	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
 		goto err_exit;
-	}
 
 	dev = dev_get_by_index(ireq.ifr6_ifindex);
 
-	if (dev == NULL) {
-		err = -ENODEV;
+	err = -ENODEV;
+	if (dev == NULL)
 		goto err_exit;
-	}
 
 	if (dev->type == ARPHRD_SIT) {
 		struct ifreq ifr;
 		mm_segment_t	oldfs;
 		struct ip_tunnel_parm p;
 
+		err = -EADDRNOTAVAIL;
 		if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
-			return -EADDRNOTAVAIL;
+			goto err_exit;
 
 		memset(&p, 0, sizeof(p));
 		p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
@@ -747,27 +781,21 @@
 	}
 
 err_exit:
+	rtnl_unlock();
 	return err;
 }
 
 /*
  *	Manual configuration of address on an interface
  */
-int addrconf_add_ifaddr(void *arg)
+static int inet6_addr_add(int ifindex, struct in6_addr *pfx, int plen)
 {
-	struct inet6_dev *idev;
-	struct in6_ifreq ireq;
 	struct inet6_ifaddr *ifp;
+	struct inet6_dev *idev;
 	struct device *dev;
 	int scope;
 	
-	if (!suser())
-		return -EPERM;
-	
-	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
-		return -EFAULT;
-
-	if ((dev = dev_get_by_index(ireq.ifr6_ifindex)) == NULL)
+	if ((dev = dev_get_by_index(ifindex)) == NULL)
 		return -ENODEV;
 	
 	if (!(dev->flags&IFF_UP))
@@ -776,49 +804,83 @@
 	if ((idev = addrconf_add_dev(dev)) == NULL)
 		return -ENOBUFS;
 
-	scope = ipv6_addr_scope(&ireq.ifr6_addr);
+	scope = ipv6_addr_scope(pfx);
 
-	if((ifp = ipv6_add_addr(idev, &ireq.ifr6_addr, scope)) == NULL)
+	if ((ifp = ipv6_add_addr(idev, pfx, scope)) == NULL)
 		return -ENOMEM;
 
-	ifp->prefix_len = ireq.ifr6_prefixlen;
+	ifp->prefix_len = plen;
 	ifp->flags |= ADDR_PERMANENT;
 
 	addrconf_dad_start(ifp);
 	return 0;
 }
 
-int addrconf_del_ifaddr(void *arg)
+static int inet6_addr_del(int ifindex, struct in6_addr *pfx, int plen)
 {
-	struct in6_ifreq ireq;
 	struct inet6_ifaddr *ifp;
+	struct inet6_dev *idev;
 	struct device *dev;
 	int scope;
-	struct inet6_dev *idev;
 	
-	if (!suser())
-		return -EPERM;
-	
-	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
-		return -EFAULT;
-
-	if ((dev = dev_get_by_index(ireq.ifr6_ifindex)) == NULL)
+	if ((dev = dev_get_by_index(ifindex)) == NULL)
 		return -ENODEV;
 
 	if ((idev = ipv6_get_idev(dev)) == NULL)
 		return -ENXIO;
 
-	scope = ipv6_addr_scope(&ireq.ifr6_addr);
+	scope = ipv6_addr_scope(pfx);
 
-	for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
-		if (ifp->scope == scope && 
-		    (!memcmp(&ireq.ifr6_addr, &ifp->addr, sizeof(struct in6_addr)))) {
+	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);
-			break;
+
+			/* If the last address is deleted administratively,
+			   disable IPv6 on this interface.
+			 */
+			   
+			if (idev->addr_list == NULL)
+				addrconf_ifdown(idev->dev, 1);
+			return 0;
 		}
 	}
+	return -EADDRNOTAVAIL;
+}
 
-	return 0;
+
+int addrconf_add_ifaddr(void *arg)
+{
+	struct in6_ifreq ireq;
+	int err;
+	
+	if (!suser())
+		return -EPERM;
+	
+	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+		return -EFAULT;
+
+	rtnl_lock();
+	err = inet6_addr_add(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen);
+	rtnl_unlock();
+	return err;
+}
+
+int addrconf_del_ifaddr(void *arg)
+{
+	struct in6_ifreq ireq;
+	int err;
+	
+	if (!suser())
+		return -EPERM;
+	
+	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
+		return -EFAULT;
+
+	rtnl_lock();
+	err = inet6_addr_add(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen);
+	rtnl_unlock();
+	return err;
 }
 
 static void sit_add_v4_addrs(struct inet6_dev *idev)
@@ -843,7 +905,7 @@
 		if (ifp) {
 			ifp->flags |= ADDR_PERMANENT;
 			ifp->prefix_len = 128;
-			ip6_rt_addr_add(&ifp->addr, idev->dev);
+			ipv6_ifa_notify(RTM_NEWADDR, ifp);
 		}
 		return;
 	}
@@ -876,7 +938,7 @@
 				else
 					ifp->prefix_len = 96;
 				ifp->flags |= ADDR_PERMANENT;
-				ip6_rt_addr_add(&ifp->addr, dev);
+				ipv6_ifa_notify(RTM_NEWADDR, ifp);
 			}
 		}
         }
@@ -887,16 +949,13 @@
 	struct in6_addr addr;
 	struct inet6_dev  *idev;
 	struct inet6_ifaddr * ifp;
-	int err;
 
 	/* ::1 */
 
 	memset(&addr, 0, sizeof(struct in6_addr));
 	addr.s6_addr[15] = 1;
 
-	idev = ipv6_add_dev(dev);
-
-	if (idev == NULL) {
+	if ((idev = ipv6_find_idev(dev)) == NULL) {
 		printk(KERN_DEBUG "init loopback: add_dev failed\n");
 		return;
 	}
@@ -909,10 +968,9 @@
 	}
 
 	ifp->flags |= ADDR_PERMANENT;
+	ifp->prefix_len = 128;
 
-	err = ip6_rt_addr_add(&addr, dev);
-	if (err)
-		printk(KERN_DEBUG "init_loopback: error in route_add\n");
+	ipv6_ifa_notify(RTM_NEWADDR, ifp);
 }
 
 static void addrconf_add_linklocal(struct inet6_dev *idev, struct in6_addr *addr)
@@ -932,7 +990,6 @@
 static void addrconf_dev_config(struct device *dev)
 {
 	struct in6_addr addr;
-	struct in6_addr maddr;
 	struct inet6_dev    * idev;
 
 	if (dev->type != ARPHRD_ETHER) {
@@ -965,16 +1022,8 @@
 	addrconf_add_linklocal(idev, &addr);
 #endif
 
-	if (ipv6_config.forwarding) {
+	if (ipv6_config.forwarding)
 		idev->router = 1;
-
-		/* It is wrong.
-		   It is routing daemon or radvd that must make it,
-		   rather than kernel.
-		 */
-		ipv6_addr_all_routers(&maddr);
-		ipv6_dev_mc_inc(dev, &maddr);
-	}
 }
 
 static void addrconf_sit_config(struct device *dev)
@@ -987,8 +1036,7 @@
 	 * our v4 addrs in the tunnel
 	 */
 
-	idev = ipv6_add_dev(dev);
-	if (idev == NULL) {
+	if ((idev = ipv6_find_idev(dev)) == NULL) {
 		printk(KERN_DEBUG "init sit: add_dev failed\n");
 		return;
 	}
@@ -1026,19 +1074,20 @@
 			break;
 		};
 
+#ifdef CONFIG_IPV6_NETLINK
 		rt6_sndmsg(RTMSG_NEWDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0);
+#endif
 		break;
 
 	case NETDEV_DOWN:
+	case NETDEV_UNREGISTER:
 		/*
-		 *	Remove all addresses from this interface
-		 *	and take the interface out of the list.
+		 *	Remove all addresses from this interface.
 		 */
-		if (addrconf_ifdown(dev) == 0) {
-#if 0
-			rt6_ifdown(dev);
-#endif
+		if (addrconf_ifdown(dev, event == NETDEV_UNREGISTER) == 0) {
+#ifdef CONFIG_IPV6_NETLINK
 			rt6_sndmsg(RTMSG_DELDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0);
+#endif
 		}
 
 		break;
@@ -1047,57 +1096,65 @@
 	return NOTIFY_OK;
 }
 
-static int addrconf_ifdown(struct device *dev)
+static int addrconf_ifdown(struct device *dev, int how)
 {
 	struct inet6_dev *idev, **bidev;
 	struct inet6_ifaddr *ifa, **bifa;
 	int i, hash;
 
-	start_bh_atomic();
+	rt6_ifdown(dev);
+	neigh_ifdown(&nd_tbl, dev);
 
-	hash = ipv6_devindex_hash(dev->ifindex);
-	bidev = &inet6_dev_lst[hash];
+	idev = ipv6_get_idev(dev);
+	if (idev == NULL)
+		return -ENODEV;
 
-	for (idev = inet6_dev_lst[hash]; idev; idev = idev->next) {
-		if (idev->dev == dev) {
-			*bidev = idev->next;
-			break;
-		}
-		bidev = &idev->next;
-	}
+	start_bh_atomic();
 
-	if (idev == NULL) {
-		end_bh_atomic();
+	/* Discard multicast list */
 
-		printk(KERN_DEBUG "addrconf_ifdown: invalid device %p\n",dev);
-		return -ENODEV;
-	}
+	if (how == 1)
+		ipv6_mc_destroy_dev(idev);
+	else
+		ipv6_mc_down(idev);
 
-	/*
-	 *	FIXME: clear multicast group membership
-	 */
+	/* Discard address list */
+
+	idev->addr_list = NULL;
 
 	/*
-	 *	clean addr_list
+	 * Clean addresses hash table
 	 */
 
 	for (i=0; i<16; i++) {
 		bifa = &inet6_addr_lst[i];
 
-		for (ifa=inet6_addr_lst[i]; ifa; ) {
+		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 = *bifa;
 				continue;
 			}
 			bifa = &ifa->lst_next;
-			ifa = *bifa;
 		}
 	}
 
-	kfree(idev);
+	/* Delete device from device hash table (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);
+				kfree(idev);
+				break;
+			}
+		}
+	}
 	end_bh_atomic();
 	return 0;
 }
@@ -1123,12 +1180,9 @@
 	if (ifp->probes++ <= ipv6_config.rtr_solicits) {
 		struct in6_addr all_routers;
 
-		ipv6_addr_set(&all_routers,
-			      __constant_htonl(0xff020000U), 0, 0,
-			      __constant_htonl(0x2U));
+		ipv6_addr_all_routers(&all_routers);
 
-		ndisc_send_rs(ifp->idev->dev, &ifp->addr,
-			      &all_routers);
+		ndisc_send_rs(ifp->idev->dev, &ifp->addr, &all_routers);
 		
 		ifp->timer.function = addrconf_rs_timer;
 		ifp->timer.expires = (jiffies + 
@@ -1158,7 +1212,6 @@
  */
 static void addrconf_dad_start(struct inet6_ifaddr *ifp)
 {
-	static int rand_seed = 1;
 	struct device *dev;
 	unsigned long rand_num;
 
@@ -1177,15 +1230,12 @@
 		return;
 	}
 
-	if (rand_seed) {
-		rand_seed = 0;
-		nd_rand_seed = ifp->addr.s6_addr32[3];
-	}
+	net_srandom(ifp->addr.s6_addr32[3]);
 
 	ifp->probes = ipv6_config.dad_transmits;
 	ifp->flags |= DAD_INCOMPLETE;
 
-	rand_num = ipv6_random() % ipv6_config.rtr_solicit_delay;
+	rand_num = net_random() % ipv6_config.rtr_solicit_delay;
 
 	ifp->timer.function = addrconf_dad_timer;
 	ifp->timer.expires = jiffies + rand_num;
@@ -1215,9 +1265,14 @@
 
 	/* send a neighbour solicitation for our addr */
 	memset(&unspec, 0, sizeof(unspec));
-	addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
-
+#ifdef CONFIG_IPV6_EUI64
+	addrconf_addr_solict_mult_new(&ifp->addr, &mcaddr);
+	ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec);
+#endif
+#ifndef CONFIG_IPV6_NO_PB
+	addrconf_addr_solict_mult_old(&ifp->addr, &mcaddr);
 	ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec);
+#endif
 
 	ifp->timer.expires = jiffies + ipv6_config.rtr_solicit_interval;
 	add_timer(&ifp->timer);
@@ -1231,7 +1286,7 @@
 	 *	Configure the address for reception. Now it is valid.
 	 */
 
-	ip6_rt_addr_add(&ifp->addr, dev);
+	ipv6_ifa_notify(RTM_NEWADDR, ifp);
 
 	/* If added prefix is link local and forwarding is off,
 	   start sending router solicitations.
@@ -1242,9 +1297,7 @@
 	    (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL)) {
 		struct in6_addr all_routers;
 
-		ipv6_addr_set(&all_routers,
-			      __constant_htonl(0xff020000U), 0, 0,
-			      __constant_htonl(0x2U));
+		ipv6_addr_all_routers(&all_routers);
 
 		/*
 		 *	If a host as already performed a random delay
@@ -1319,31 +1372,221 @@
 
 	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);
+				continue;
+			}
 			if (!(ifp->flags & ADDR_PERMANENT)) {
 				struct inet6_ifaddr *bp;
 				unsigned long age;
 
 				age = (now - ifp->tstamp) / HZ;
 
-				if (age > ifp->prefered_lft)
-					ifp->flags |= ADDR_DEPRECATED;
-
 				bp = ifp;
-				ifp=ifp->lst_next;
+				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);
+				}
 
 				continue;
 			}
-			ifp=ifp->lst_next;
+			ifp = ifp->lst_next;
 		}
 	}
 
 	addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY;
-	add_timer(&addr_chk_timer);	
+	add_timer(&addr_chk_timer);
+}
+
+#ifdef CONFIG_RTNETLINK
+static int
+inet6_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct kern_ifa  *k_ifa = arg;
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+	struct in6_addr *pfx;
+
+	pfx = NULL;
+	if (k_ifa->ifa_address)
+		pfx = k_ifa->ifa_address;
+	if (k_ifa->ifa_local) {
+		if (pfx && memcmp(pfx, k_ifa->ifa_local, sizeof(*pfx)))
+			return -EINVAL;
+		pfx = k_ifa->ifa_local;
+	}
+
+	return inet6_addr_del(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
+}
+
+static int
+inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
+{
+	struct kern_ifa  *k_ifa = arg;
+	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
+	struct in6_addr *pfx;
+
+	pfx = NULL;
+	if (k_ifa->ifa_address)
+		pfx = k_ifa->ifa_address;
+	if (k_ifa->ifa_local) {
+		if (pfx && memcmp(pfx, k_ifa->ifa_local, sizeof(*pfx)))
+			return -EINVAL;
+		pfx = k_ifa->ifa_local;
+	}
+
+	return inet6_addr_add(ifm->ifa_index, pfx, ifm->ifa_prefixlen);
+}
+
+static int inet6_fill_ifaddr(struct sk_buff *skb, struct inet6_ifaddr *ifa,
+			    pid_t pid, u32 seq, int event)
+{
+	struct ifaddrmsg *ifm;
+	struct nlmsghdr  *nlh;
+	struct ifa_cacheinfo ci;
+	unsigned char	 *b = skb->tail;
+
+	nlh = NLMSG_PUT(skb, pid, seq, event, sizeof(*ifm));
+	ifm = NLMSG_DATA(nlh);
+	ifm->ifa_family = AF_INET6;
+	ifm->ifa_prefixlen = ifa->prefix_len;
+	ifm->ifa_flags = ifa->flags & ~ADDR_INVALID;
+	ifm->ifa_scope = RT_SCOPE_UNIVERSE;
+	if (ifa->scope&IFA_HOST)
+		ifm->ifa_scope = RT_SCOPE_HOST;
+	else if (ifa->scope&IFA_LINK)
+		ifm->ifa_scope = RT_SCOPE_LINK;
+	else if (ifa->scope&IFA_SITE)
+		ifm->ifa_scope = RT_SCOPE_SITE;
+	ifm->ifa_index = ifa->idev->dev->ifindex;
+	RTA_PUT(skb, IFA_ADDRESS, 16, &ifa->addr);
+	if (!(ifa->flags&IFA_F_PERMANENT)) {
+		ci.ifa_prefered = ifa->prefered_lft;
+		ci.ifa_valid = ifa->valid_lft;
+		if (ci.ifa_prefered != 0xFFFFFFFF) {
+			long tval = (jiffies - ifa->tstamp)/HZ;
+			ci.ifa_prefered -= tval;
+			if (ci.ifa_valid != 0xFFFFFFFF)
+				ci.ifa_valid -= tval;
+		}
+		RTA_PUT(skb, IFA_CACHEINFO, sizeof(ci), &ci);
+	}
+	nlh->nlmsg_len = skb->tail - b;
+	return skb->len;
+
+nlmsg_failure:
+rtattr_failure:
+	skb_put(skb, b - skb->tail);
+	return -1;
+}
+
+static int inet6_dump_ifaddr(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	int idx, ip_idx;
+	int s_idx, s_ip_idx;
+ 	struct inet6_ifaddr *ifa;
+
+	s_idx = cb->args[0];
+	s_ip_idx = ip_idx = cb->args[1];
+
+	for (idx=0; idx < IN6_ADDR_HSIZE; idx++) {
+		if (idx < s_idx)
+			continue;
+		if (idx > s_idx)
+			s_ip_idx = 0;
+		start_bh_atomic();
+		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();
+				goto done;
+			}
+		}
+		end_bh_atomic();
+	}
+done:
+	cb->args[0] = idx;
+	cb->args[1] = ip_idx;
+
+	return skb->len;
 }
 
+static void inet6_ifa_notify(int event, struct inet6_ifaddr *ifa)
+{
+	struct sk_buff *skb;
+	int size = NLMSG_SPACE(sizeof(struct ifaddrmsg)+128);
+
+	skb = alloc_skb(size, GFP_KERNEL);
+	if (!skb) {
+		netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, ENOBUFS);
+		return;
+	}
+	if (inet6_fill_ifaddr(skb, ifa, 0, 0, event) < 0) {
+		kfree_skb(skb, 0);
+		netlink_set_err(rtnl, 0, RTMGRP_IPV6_IFADDR, EINVAL);
+		return;
+	}
+	NETLINK_CB(skb).dst_groups = RTMGRP_IPV6_IFADDR;
+	netlink_broadcast(rtnl, skb, 0, RTMGRP_IPV6_IFADDR, GFP_ATOMIC);
+}
+
+static struct rtnetlink_link inet6_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
+{
+	{ NULL,			NULL,			},
+	{ NULL,			NULL,			},
+	{ NULL,			rtnetlink_dump_ifinfo,	},
+	{ NULL,			NULL,			},
+
+	{ inet6_rtm_newaddr,	NULL,			},
+	{ inet6_rtm_deladdr,	NULL,			},
+	{ NULL,			inet6_dump_ifaddr,	},
+	{ NULL,			NULL,			},
+
+	{ inet6_rtm_newroute,	NULL,			},
+	{ inet6_rtm_delroute,	NULL,			},
+	{ NULL,			inet6_dump_fib,		},
+	{ NULL,			NULL,			},
+
+	{ neigh_add,		NULL,			},
+	{ neigh_delete,		NULL,			},
+	{ NULL,			neigh_dump_info,	},
+	{ NULL,			NULL,			},
+
+	{ NULL,			NULL,			},
+	{ NULL,			NULL,			},
+	{ NULL,			NULL,			},
+	{ NULL,			NULL,			},
+};
+#endif
+
+static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifp)
+{
+#ifdef CONFIG_RTNETLINK
+	inet6_ifa_notify(event ? : RTM_NEWADDR, ifp);
+#endif
+	switch (event) {
+	case RTM_NEWADDR:
+		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)
+			ip6_rt_addr_del(&ifp->addr, ifp->idev->dev);
+		end_bh_atomic();
+		break;
+	}
+}
+
+
 /*
  *	Init / cleanup code
  */
@@ -1352,19 +1595,7 @@
 {
 #ifdef MODULE
 	struct device *dev;
-#endif
-
-	/*
-	 *	init address and device hash lists
-	 */
-
-	memset(inet6_addr_lst, 0, IN6_ADDR_HSIZE * sizeof(struct inet6_ifaddr *));
-
-	memset(inet6_mcast_lst, 0, IN6_ADDR_HSIZE * sizeof(struct ifmcaddr6 *));
 
-	memset(inet6_dev_lst, 0, IN6_ADDR_HSIZE * sizeof(struct inet6_dev *));
-
-#ifdef MODULE
 	/* This takes sense only during module load. */
 
 	for (dev = dev_base; dev; dev = dev->next) {
@@ -1390,6 +1621,9 @@
 	
 	addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY;
 	add_timer(&addr_chk_timer);
+#ifdef CONFIG_RTNETLINK
+	rtnetlink_links[AF_INET6] = inet6_rtnetlink_table;
+#endif
 }
 
 #ifdef MODULE
@@ -1399,6 +1633,11 @@
  	struct inet6_ifaddr *ifa;
 	int i;
 
+#ifdef CONFIG_RTNETLINK
+	rtnetlink_links[AF_INET6] = NULL;
+#endif
+	start_bh_atomic();
+
 	del_timer(&addr_chk_timer);
 
 	/*
@@ -1409,7 +1648,7 @@
 		struct inet6_dev *next;
 		for (idev = inet6_dev_lst[i]; idev; idev = next) {
 			next = idev->next;
-			addrconf_ifdown(idev->dev);	
+			addrconf_ifdown(idev->dev, 1);
 		}
 	}
 
@@ -1423,9 +1662,13 @@
 
 			bifa = ifa;
 			ifa = ifa->lst_next;
-			kfree(bifa);
+			printk(KERN_DEBUG "bug: IPv6 address leakage detected: ifa=%p\n", bifa);
+			/* Do not free it; something is wrong.
+			   Now we can investigate it with debugger.
+			 */
 		}
 	}
+	end_bh_atomic();
 
 #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, slshen@lbl.gov