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

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

diff -u --recursive --new-file v2.1.7/linux/net/ipv6/addrconf.c linux/net/ipv6/addrconf.c
@@ -0,0 +1,1311 @@
+/*
+ *	IPv6 Address [auto]configuration
+ *	Linux INET6 implementation
+ *
+ *	Authors:
+ *	Pedro Roque		<roque@di.fc.ul.pt>	
+ *
+ *
+ *	This program is free software; you can redistribute it and/or
+ *      modify it under the terms of the GNU General Public License
+ *      as published by the Free Software Foundation; either version
+ *      2 of the License, or (at your option) any later version.
+ */
+
+
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/sockios.h>
+#include <linux/sched.h>
+#include <linux/net.h>
+#include <linux/in6.h>
+#include <linux/netdevice.h>
+#include <linux/if_arp.h>
+
+#include <linux/proc_fs.h>
+#include <net/sock.h>
+#include <net/snmp.h>
+
+#include <net/ipv6.h>
+#include <net/protocol.h>
+#include <net/ndisc.h>
+#include <net/ipv6_route.h>
+#include <net/addrconf.h>
+#include <net/sit.h>
+
+#include <asm/uaccess.h>
+
+#define HASH_SIZE		16
+/*
+ *	Configured unicast address list
+ */
+struct inet6_ifaddr		*inet6_addr_lst[HASH_SIZE];
+
+/*
+ *	Hash list of configured multicast addresses
+ */
+struct ipv6_mc_list		*inet6_mcast_lst[HASH_SIZE];
+
+/*
+ *	AF_INET6 device list
+ */
+struct inet6_dev		*inet6_dev_lst;
+int				in6_ifnum = 0;
+
+atomic_t			addr_list_lock = 0;
+
+void addrconf_verify(unsigned long);
+
+static struct timer_list addr_chk_timer = {
+	NULL, NULL,
+	0, 0, addrconf_verify
+};
+
+
+int DupAddrDetectTransmits = 1;
+
+/*
+ *	/proc/sys switch for autoconf (enabled by default)
+ */
+int addrconf_sys_autoconf  = 1;
+
+static void addrconf_dad_start(struct inet6_ifaddr *ifp);
+static void addrconf_rs_timer(unsigned long data);
+
+int ipv6_addr_type(struct in6_addr *addr)
+{
+	u32 st;
+
+	st = addr->s6_addr32[0];
+
+	/* 
+	 * UCast Provider Based Address
+	 * 0x4/3
+	 */
+
+	if ((st & __constant_htonl(0xE0000000)) == 
+	    __constant_htonl(0x40000000))
+	{
+		return IPV6_ADDR_UNICAST;
+	}
+
+	if ((st & __constant_htonl(0xFF000000)) == 
+	    __constant_htonl(0xFF000000))
+	{
+		int type = IPV6_ADDR_MULTICAST;
+
+		switch((st >> 16) & 0x0f)
+		{
+			case 0x01:
+				type |= IPV6_ADDR_LOOPBACK;
+				break;
+			case 0x02:
+				type |= IPV6_ADDR_LINKLOCAL;
+				break;
+			case 0x05:
+				type |= IPV6_ADDR_SITELOCAL;
+				break;
+		}
+		return type;
+	}
+	
+	if ((st & __constant_htonl(0xFFC00000)) == 
+	    __constant_htonl(0xFE800000))
+	{
+		return (IPV6_ADDR_LINKLOCAL | IPV6_ADDR_UNICAST);
+	}
+
+	if ((st & __constant_htonl(0xFFC00000)) == 
+	    __constant_htonl(0xFEC00000))
+	{
+		return (IPV6_ADDR_SITELOCAL | IPV6_ADDR_UNICAST);
+	}
+
+	if ((addr->s6_addr32[0] | addr->s6_addr32[1]) == 0)
+	{
+		if (addr->s6_addr32[2] == 0)
+		{
+			if (addr->in6_u.u6_addr32[3] == 0)
+			{
+				return IPV6_ADDR_ANY;
+			}
+
+			if (addr->s6_addr32[3] == __constant_htonl(0x00000001))
+			{
+				return (IPV6_ADDR_LOOPBACK | 
+					IPV6_ADDR_UNICAST);
+			}
+
+			return (IPV6_ADDR_COMPATv4 | IPV6_ADDR_UNICAST);
+		}
+
+		if (addr->s6_addr32[2] == __constant_htonl(0x0000ffff))
+			return IPV6_ADDR_MAPPED;
+	}
+
+	return IPV6_ADDR_RESERVED;
+}
+
+struct inet6_dev * ipv6_add_dev(struct device *dev)
+{
+	struct inet6_dev *dev6;
+
+	/*
+	 *	called by netdev notifier from a syscall
+	 */
+	dev6 = (struct inet6_dev *) kmalloc(sizeof(struct inet6_dev), 
+					    GFP_ATOMIC);
+
+	if (dev6 == NULL)
+		return NULL;
+
+	memset(dev6, 0, sizeof(struct inet6_dev));
+	dev6->dev = dev;
+	dev6->if_index = ++in6_ifnum;
+
+	/*
+	 *	insert at head.
+	 */
+
+	dev6->next = inet6_dev_lst;
+	inet6_dev_lst = dev6;
+
+	return dev6;
+}
+
+struct inet6_dev * ipv6_dev_by_index(int index)
+{
+	struct inet6_dev *in6_dev;
+
+	for (in6_dev = inet6_dev_lst; in6_dev; in6_dev = in6_dev->next)
+	{
+		if (in6_dev->if_index == index)
+			return in6_dev;
+	}
+
+	return NULL;
+}
+
+void addrconf_forwarding_on(void)
+{
+	struct inet6_dev *in6_dev;
+	struct in6_addr maddr;
+
+	for (in6_dev = inet6_dev_lst; in6_dev; in6_dev = in6_dev->next)
+	{
+		printk(KERN_DEBUG "dev %s\n", in6_dev->dev->name);
+
+		if (in6_dev->dev->type == ARPHRD_ETHER)
+		{
+			printk(KERN_DEBUG "joining all-routers\n");
+			in6_dev->router = 1;
+			ipv6_addr_all_routers(&maddr);
+			ipv6_dev_mc_inc(in6_dev->dev, &maddr);		
+		}
+	}
+
+	if (last_resort_rt && (last_resort_rt->rt_flags & RTI_ALLONLINK))
+	{
+		rt_release(last_resort_rt);
+		last_resort_rt = NULL;
+	}
+}
+
+struct inet6_dev * ipv6_get_idev(struct device *dev)
+{
+	struct inet6_dev *in6_dev;
+
+	for (in6_dev = inet6_dev_lst; in6_dev; in6_dev = in6_dev->next)
+	{
+		if (in6_dev->dev == dev)
+		{
+			return in6_dev;
+		}
+	}
+	return NULL;
+}
+
+struct inet6_ifaddr * ipv6_add_addr(struct inet6_dev *idev, 
+				    struct in6_addr *addr, int scope)
+{
+	struct inet6_ifaddr * ifaddr;
+	int hash;
+	unsigned long flags;
+
+	save_flags(flags);
+	cli();
+
+	ifaddr = (struct inet6_ifaddr *) kmalloc(sizeof(struct inet6_ifaddr), 
+						 GFP_ATOMIC);
+
+	if (ifaddr == NULL)
+	{
+		printk(KERN_DEBUG "ipv6_add_addr: malloc failed\n");
+		restore_flags(flags);
+		return NULL;
+	}
+
+	memset(ifaddr, 0, sizeof(struct inet6_ifaddr));
+	memcpy(&ifaddr->addr, addr, sizeof(struct in6_addr));
+
+	ifaddr->scope = scope;
+	ifaddr->idev = idev;
+	
+
+	/* add to list */
+
+	hash = ipv6_addr_hash(addr);
+
+	ifaddr->lst_next = inet6_addr_lst[hash];
+	inet6_addr_lst[hash] = ifaddr;
+
+
+	/* add to inet6_dev unicast addr list */
+	ifaddr->if_next = idev->addr_list;
+	idev->addr_list = ifaddr;
+
+	restore_flags(flags);
+	return ifaddr;
+	
+}
+
+void ipv6_del_addr(struct inet6_ifaddr *ifp)
+{
+	struct inet6_ifaddr *iter, **back;
+	int hash;
+
+	if (addr_list_lock)
+	{
+		ifp->flags |= ADDR_INVALID;
+		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;
+			ifp->lst_next = NULL;
+			break;
+		}
+		back = &(iter->lst_next);
+	}
+
+	iter = ifp->idev->addr_list;
+	back = &ifp->idev->addr_list;
+
+	for (; iter; iter = iter->if_next)
+	{
+		if (iter == ifp)
+		{
+			*back = ifp->if_next;
+			ifp->if_next = NULL;
+			break;
+		}
+		back = &(iter->if_next);
+	}
+	
+	kfree(ifp);
+}
+
+/*
+ *	Choose an apropriate source address
+ *	should do:
+ *	i)	get an address with an apropriate scope
+ *	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 rt6_info *rt, struct in6_addr *daddr)
+{
+	int scope;
+	struct inet6_ifaddr * ifp = NULL;
+	struct inet6_dev    * i6dev;
+	struct inet6_ifaddr * match = NULL;
+	struct device *dev = NULL;
+	int i;
+
+	if (rt)
+	{
+		dev = rt->rt_dev;
+	}
+	
+	atomic_inc(&addr_list_lock);
+
+	scope = ipv6_addr_type(daddr);
+
+	scope &= IPV6_ADDR_SCOPE_MASK;
+
+	if (rt && (rt->rt_flags & RTI_ALLONLINK))
+	{
+		/*
+		 *	route for the "all destinations on link" rule
+		 *	when no routers are present
+		 */
+		scope = IFA_LINK;
+	}
+
+	/*
+	 *	known dev
+	 *	search dev and walk through dev addresses
+	 */
+
+	if (dev)
+	{
+		if (dev->flags & IFF_LOOPBACK)
+		{
+			scope = IFA_HOST;
+		}
+
+		for (i6dev = inet6_dev_lst; i6dev; i6dev=i6dev->next)
+		{
+			if (i6dev->dev == dev)
+			{
+				for (ifp=i6dev->addr_list; ifp; 
+				     ifp=ifp->if_next)
+				{
+					if (ifp->scope == scope)
+					{
+						if (!(ifp->flags & ADDR_STATUS))
+						{
+							goto out;
+						}
+						if (!(ifp->flags & ADDR_INVALID))
+						{
+							match = ifp;
+						}
+					}
+				}
+				break;
+			}
+		}
+	}
+
+	if (scope == IFA_LINK)
+	{
+		goto out;
+	}
+
+	/*
+	 *	dev == NULL or search failed for specified dev
+	 */
+
+	for (i=0; i < HASH_SIZE; i++)
+	{
+		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next)
+		{
+			if (ifp->scope == scope)
+			{
+				if (!(ifp->flags & ADDR_STATUS))
+				{
+					goto out;
+				}
+				if (!(ifp->flags & ADDR_INVALID))
+				{
+					match = ifp;
+				}
+			}
+		}
+	}
+
+  out:
+	if (ifp == NULL && match)
+	{
+		ifp = match;
+	}
+	atomic_dec(&addr_list_lock);
+	return ifp;
+}
+
+struct inet6_ifaddr * ipv6_get_lladdr(struct device *dev)
+{
+	struct inet6_ifaddr *ifp;
+	struct inet6_dev *i6dev;
+
+	for (i6dev = inet6_dev_lst; i6dev; i6dev=i6dev->next)
+	{
+		if (i6dev->dev == dev)
+		{
+			for (ifp=i6dev->addr_list; ifp; ifp=ifp->if_next)
+			{
+				if (ifp->scope == IFA_LINK)
+					return ifp;
+			}
+			break;
+		}
+	}
+	return NULL;
+}
+
+/*
+ *	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 inet6_ifaddr * ifp;
+	u8 hash;
+
+	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;
+		}
+	}
+
+	atomic_dec(&addr_list_lock);
+	return ifp;	
+}
+
+static void sit_route_add(struct device *dev)
+{
+	struct in6_rtmsg rtmsg;	
+	int err;
+
+	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
+
+	memset(&rtmsg.rtmsg_dst, 0, sizeof(struct in6_addr));
+	memset(&rtmsg.rtmsg_gateway, 0, sizeof(struct in6_addr));
+
+	if (dev->pa_dstaddr == 0)
+	{
+		/* prefix length - 96 bytes "::d.d.d.d" */
+		rtmsg.rtmsg_prefixlen = 96;
+		rtmsg.rtmsg_metric = 1;
+		rtmsg.rtmsg_flags = RTF_NEXTHOP|RTF_UP;
+	}
+	else
+	{
+		rtmsg.rtmsg_prefixlen = 128;
+		rtmsg.rtmsg_dst.s6_addr32[3] = dev->pa_dstaddr;
+		rtmsg.rtmsg_metric = 1;
+		rtmsg.rtmsg_flags = RTF_HOST|RTF_UP;
+	}
+
+	strcpy(rtmsg.rtmsg_device, dev->name);
+
+	err = ipv6_route_add(&rtmsg);
+
+	if (err)
+	{
+		printk(KERN_DEBUG "sit_route_add: error in route_add\n");
+	}
+}
+
+static void init_loopback(struct device *dev)
+{
+	struct in6_addr addr;
+	struct inet6_dev  *idev;
+	struct inet6_ifaddr * ifp;
+	struct in6_rtmsg rtmsg;
+	char devname[] = "lo";
+	int err;
+
+	/* ::1 */
+
+	memset(&addr, 0, sizeof(struct in6_addr));
+	addr.s6_addr[15] = 1;
+
+	idev = ipv6_add_dev(dev);
+
+	if (idev == NULL)
+	{
+		printk(KERN_DEBUG "init loopback: add_dev failed\n");
+		return;
+	}
+
+	ifp = ipv6_add_addr(idev, &addr, IFA_HOST);
+
+	if (ifp == NULL)
+	{
+		printk(KERN_DEBUG "init_loopback: add_addr failed\n");
+		return;
+	}
+
+	ifp->flags |= ADDR_PERMANENT;
+
+	memcpy(&rtmsg.rtmsg_dst, &addr, sizeof(struct in6_addr));
+	memset(&rtmsg.rtmsg_gateway, 0, sizeof(struct in6_addr));
+
+	rtmsg.rtmsg_prefixlen = 128;
+	rtmsg.rtmsg_metric = 1;
+	strcpy(rtmsg.rtmsg_device, devname);
+
+	rtmsg.rtmsg_flags = RTF_NEXTHOP|RTF_HOST|RTF_UP;
+
+	err = ipv6_route_add(&rtmsg);
+
+	if (err)
+	{
+		printk(KERN_DEBUG "init_loopback: error in route_add\n");
+	}
+
+	/* add route for ::127.0.0.1 */
+}
+
+static void addrconf_eth_config(struct device *dev)
+{
+	struct in6_addr addr;
+	struct in6_addr maddr;
+	struct inet6_ifaddr * ifp;
+	struct inet6_dev    * idev;
+
+	memset(&addr, 0, sizeof(struct in6_addr));
+
+	/* generate link local address*/
+	addr.s6_addr[0] = 0xFE;
+	addr.s6_addr[1] = 0x80;
+
+	memcpy(addr.s6_addr + (sizeof(struct in6_addr) - dev->addr_len), 
+	       dev->dev_addr, dev->addr_len);
+
+	idev = ipv6_add_dev(dev);
+			
+	if (idev == NULL)
+		return;
+	
+	ifp = ipv6_add_addr(idev, &addr, IFA_LINK);
+			
+	if (ifp == NULL)
+		return;
+
+	ifp->flags |= (DAD_INCOMPLETE | ADDR_PERMANENT);
+	ifp->prefix_len = 10;
+
+	/* join to all nodes multicast group */
+	ipv6_addr_all_nodes(&maddr);
+	ipv6_dev_mc_inc(dev, &maddr);
+	
+	if (ipv6_forwarding)
+	{
+		idev->router = 1;
+		ipv6_addr_all_routers(&maddr);
+		ipv6_dev_mc_inc(dev, &maddr);		
+	}
+
+	/* join to solicited addr multicast group */
+	addrconf_addr_solict_mult(&addr, &maddr);
+	ipv6_dev_mc_inc(dev, &maddr);
+			
+	/* start dad */
+	addrconf_dad_start(ifp);
+}
+
+void addrconf_prefix_rcv(struct device *dev, u8 *opt, int len)
+{
+	struct prefix_info *pinfo;
+	struct rt6_info *rt;
+	__u32 valid_lft;
+	__u32 prefered_lft;
+	int addr_type;
+	unsigned long rt_expires;
+
+	pinfo = (struct prefix_info *) opt;
+	
+	if (len < sizeof(struct prefix_info))
+	{
+		printk(KERN_DEBUG "addrconf: prefix option too short\n");
+		return;
+	}
+	
+	/*
+	 *	Validation checks ([ADDRCONF], page 19)
+	 */
+
+	addr_type = ipv6_addr_type(&pinfo->prefix);
+
+	if (addr_type & IPV6_ADDR_LINKLOCAL)
+	{
+		return;
+	}
+
+	valid_lft = ntohl(pinfo->valid);
+	prefered_lft = ntohl(pinfo->prefered);
+
+	if (prefered_lft > valid_lft)
+	{
+		printk(KERN_WARNING
+		       "addrconf: prefix option has invalid lifetime\n");
+		return;
+	}
+
+	/*
+	 *	If we where using an "all destinations on link" route
+	 *	delete it
+	 */
+
+	if (last_resort_rt && (last_resort_rt->rt_flags & RTI_ALLONLINK))
+	{
+		rt_release(last_resort_rt);
+		last_resort_rt = NULL;
+	}
+
+	/*
+	 *	Two things going on here:
+	 *	1) Add routes for on-link prefixes
+	 *	2) Configure prefixes with the auto flag set
+	 */
+
+	rt_expires = jiffies + valid_lft * HZ;
+	if (rt_expires < jiffies)
+	{
+		rt_expires = ~0;
+	}
+
+	rt = fibv6_lookup(&pinfo->prefix, dev, RTI_DYNAMIC|RTI_GATEWAY);
+		
+	if (rt)
+	{
+		if (pinfo->onlink == 0 || valid_lft == 0)
+		{
+			/*
+			 *	delete route
+			 */
+			fib6_del_rt(rt);
+			rt = NULL;
+		}
+		else
+		{
+			rt->rt_expires = rt_expires;
+		}
+	}
+	else if (pinfo->onlink && valid_lft)
+	{
+		struct in6_rtmsg rtmsg;
+
+		printk(KERN_DEBUG "adding on link route\n");
+		ipv6_addr_copy(&rtmsg.rtmsg_dst, &pinfo->prefix);
+		memset(&rtmsg.rtmsg_gateway, 0, sizeof(struct in6_addr));
+
+		rtmsg.rtmsg_prefixlen = pinfo->prefix_len;
+		rtmsg.rtmsg_metric = 1;
+		memcpy(rtmsg.rtmsg_device, dev->name, strlen(dev->name) + 1);
+		rtmsg.rtmsg_flags = RTF_UP | RTF_ADDRCONF;
+		rtmsg.rtmsg_info = rt_expires;
+
+		ipv6_route_add(&rtmsg);
+	}
+
+	if (pinfo->autoconf && addrconf_sys_autoconf)
+	{
+		struct inet6_ifaddr * ifp;
+		struct in6_addr addr;
+		int plen;
+
+		plen = pinfo->prefix_len >> 3;
+
+		if (plen + dev->addr_len == sizeof(struct in6_addr))
+		{
+			memcpy(&addr, &pinfo->prefix, plen);
+			memcpy(addr.s6_addr + plen, dev->dev_addr,
+			       dev->addr_len);
+		}
+		else
+		{
+			printk(KERN_DEBUG
+			       "addrconf: prefix_len invalid\n");
+			return;
+		}
+
+		ifp = ipv6_chk_addr(&addr);
+
+		if (ifp == NULL && valid_lft)
+		{
+			/* create */
+
+			struct inet6_dev *in6_dev;
+
+			in6_dev = ipv6_get_idev(dev);
+
+			if (in6_dev == NULL)
+			{
+				printk(KERN_DEBUG
+				       "addrconf: device not configured\n");
+			}
+			
+			ifp = ipv6_add_addr(in6_dev, &addr,
+					    addr_type & IPV6_ADDR_SCOPE_MASK);
+
+			if (dev->flags & IFF_MULTICAST)
+			{
+				struct in6_addr maddr;
+
+				/* join to solicited addr multicast group */
+				addrconf_addr_solict_mult(&addr, &maddr);
+				ipv6_dev_mc_inc(dev, &maddr);
+			}
+
+			ifp->flags |= DAD_INCOMPLETE;
+			ifp->prefix_len = pinfo->prefix_len;
+
+			addrconf_dad_start(ifp);
+			
+		}
+
+		if (ifp && valid_lft == 0)
+		{
+			ipv6_del_addr(ifp);
+			ifp = NULL;
+		}
+
+		if (ifp)
+		{
+			ifp->valid_lft = valid_lft;
+			ifp->prefered_lft = prefered_lft;
+			ifp->tstamp = jiffies;
+		}
+	}
+
+}
+
+/*
+ *	Set destination address.
+ *	Special case for SIT interfaces where we create a new "virtual"
+ *	device.
+ */
+int addrconf_set_dstaddr(void *arg)
+{
+	struct in6_ifreq ireq;
+	struct device *dev;
+	int err;
+
+	err = copy_from_user(&ireq, arg, sizeof(struct in6_ifreq));
+	
+	if (err)
+		return -EFAULT;
+
+	dev = dev_get(ireq.devname);
+
+	if (dev->type == ARPHRD_SIT)
+	{
+		struct device *dev;
+		
+		if (!(ipv6_addr_type(&ireq.addr) & IPV6_ADDR_COMPATv4))
+		{
+			return -EADDRNOTAVAIL;
+		}
+		
+		dev = sit_add_tunnel(ireq.addr.s6_addr32[3]);
+		
+		if (dev == NULL)
+			return -ENOMEM;
+
+		return 0;
+	}
+	
+	return -EINVAL;
+}
+
+/*
+ *	Manual configuration of address on an interface
+ */
+int addrconf_add_ifaddr(void *arg)
+{
+	struct inet6_dev *in6_dev;
+	struct in6_ifreq ireq;
+	struct inet6_ifaddr *ifp;
+	struct device *dev;
+	int addr_type;
+	int err;
+	
+	if (!suser())
+		return -EPERM;
+	
+	err = copy_from_user(&ireq, arg, sizeof(struct in6_ifreq));
+	if (err)
+		return -EFAULT;
+
+	dev = dev_get(ireq.devname);
+
+	if (dev == NULL)
+		return -EINVAL;
+
+	in6_dev = ipv6_get_idev(dev);
+
+	if (in6_dev == NULL)
+		return -EINVAL;
+
+	addr_type  = ipv6_addr_type(&ireq.addr);
+	addr_type &= IPV6_ADDR_SCOPE_MASK;
+	
+	ifp = ipv6_add_addr(in6_dev, &ireq.addr, addr_type);
+
+	if (ifp == NULL)
+		return -ENOMEM;
+
+	if (dev->flags & IFF_MULTICAST)
+	{
+		struct in6_addr maddr;
+
+		/* join to solicited addr multicast group */
+		addrconf_addr_solict_mult(&ireq.addr, &maddr);
+		ipv6_dev_mc_inc(dev, &maddr);
+	}
+
+
+	ifp->prefix_len = ireq.prefix_len;
+	ifp->flags |= ADDR_PERMANENT;
+
+	if (!(dev->flags & (IFF_NOARP|IFF_LOOPBACK)))
+	{
+		ifp->flags |= DAD_INCOMPLETE;
+		addrconf_dad_start(ifp);
+	}
+	return 0;
+}
+
+static void sit_add_v4_addrs(struct inet6_dev *idev)
+{
+	struct inet6_ifaddr * ifp;
+	struct in6_addr addr;
+	struct device *dev;
+	int flag;
+
+	memset(&addr, 0, sizeof(struct in6_addr));
+
+	if (idev->dev->pa_dstaddr)
+	{
+		addr.s6_addr32[0] = __constant_htonl(0xfe800000);
+		flag = IFA_LINK;
+	}
+	else
+	{
+		flag = IFA_GLOBAL | IPV6_ADDR_COMPATv4;
+	}
+
+        for (dev = dev_base; dev != NULL; dev = dev->next) 
+        {
+		if (dev->family == AF_INET && (dev->flags & IFF_UP))
+		{
+			addr.s6_addr32[3] = dev->pa_addr;
+
+			if (dev->flags & IFF_LOOPBACK)
+			{
+				if (idev->dev->pa_dstaddr)
+					continue;
+				
+				flag = IFA_HOST | IPV6_ADDR_COMPATv4;
+			}
+
+			ifp = ipv6_add_addr(idev, &addr, flag);
+			
+			if (ifp == NULL)
+				continue;
+
+			ifp->flags |= ADDR_PERMANENT;
+		}
+        }
+}
+
+int addrconf_notify(struct notifier_block *this, unsigned long event, 
+		    void * data)
+{
+	struct device *dev;
+	struct inet6_dev    * idev;
+
+	dev = (struct device *) data;
+
+	switch(event) {
+	case NETDEV_UP:
+		switch(dev->type) {
+		case ARPHRD_SIT:
+
+			printk(KERN_DEBUG "sit device up: %s\n", dev->name);
+
+			/* 
+			 * Configure the tunnel with one of our IPv4 
+			 * addresses... we should configure all of 
+			 * our v4 addrs in the tunnel
+			 */
+
+			idev = ipv6_add_dev(dev);
+			
+			sit_add_v4_addrs(idev);
+
+			/*
+			 *  we do an hack for now to configure the tunnel
+			 *  route.
+			 */
+
+			sit_route_add(dev);
+			break;
+
+		case ARPHRD_LOOPBACK:
+			init_loopback(dev);
+			break;
+
+		case ARPHRD_ETHER:
+
+			printk(KERN_DEBUG "Configuring eth interface\n");
+			addrconf_eth_config(dev);
+			break;
+		}
+		rt6_sndmsg(RTMSG_NEWDEVICE, NULL, NULL, 0, 0, dev->name, 0);
+		break;
+
+	case NETDEV_DOWN:
+		/*
+		 *	Remove all addresses from this interface
+		 *	and take the interface out of the list.
+		 */
+		rt6_sndmsg(RTMSG_NEWDEVICE, NULL, NULL, 0, 0, dev->name, 0);
+
+		break;
+	}
+	
+	return NOTIFY_OK;
+}
+
+static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
+{
+	struct in6_rtmsg rtmsg;
+	struct device *dev;
+	int err;
+
+
+	if (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL)
+	{
+		struct in6_addr all_routers;
+
+		/*
+		 *	1) configure a link route for this interface
+		 *	2) send a (delayed) router solicitation
+		 */
+
+		memcpy(&rtmsg.rtmsg_dst, &ifp->addr, sizeof(struct in6_addr));
+		memset(&rtmsg.rtmsg_gateway, 0, sizeof(struct in6_addr));
+
+		dev = ifp->idev->dev;
+
+		rtmsg.rtmsg_prefixlen = ifp->prefix_len;
+		rtmsg.rtmsg_metric = 1;
+		memcpy(rtmsg.rtmsg_device, dev->name, strlen(dev->name) + 1);
+
+		rtmsg.rtmsg_flags = RTF_UP;
+
+		err = ipv6_route_add(&rtmsg);
+		
+		if (err)
+		{
+			printk(KERN_DEBUG "dad_complete: error in route_add\n");
+		}
+
+		if (ipv6_forwarding == 0)
+		{
+			ipv6_addr_set(&all_routers,
+				      __constant_htonl(0xff020000U), 0, 0,
+				      __constant_htonl(0x2U));
+
+			/*
+			 *	If a host as already performed a random delay
+			 *	[...] as part of DAD [...] there is no need
+			 *	to delay again before sending the first RS
+			 */
+			ndisc_send_rs(ifp->idev->dev, &ifp->addr,
+				      &all_routers);
+
+			ifp->probes = 1;
+			ifp->timer.function = addrconf_rs_timer;
+			ifp->timer.expires = (jiffies + 
+					      RTR_SOLICITATION_INTERVAL);
+			ifp->idev->if_flags |= IF_RS_SENT;
+			add_timer(&ifp->timer);
+		}
+	}
+
+}
+
+static void addrconf_dad_timer(unsigned long data)
+{
+	struct inet6_ifaddr *ifp;
+	struct in6_addr unspec;
+	struct in6_addr mcaddr;
+
+	ifp = (struct inet6_ifaddr *) data;
+
+	if (ifp->probes-- == 0)
+	{
+		/*
+		 * DAD was successful
+		 */
+
+		ifp->flags &= ~DAD_INCOMPLETE;
+		addrconf_dad_completed(ifp);
+		return;
+	}
+
+	/* send a neighbour solicitation for our addr */
+	memset(&unspec, 0, sizeof(unspec));
+	addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
+
+	ndisc_send_ns(ifp->idev->dev, NULL, &ifp->addr, &mcaddr, &unspec);
+
+	ifp->timer.expires = jiffies + RETRANS_TIMER;
+	add_timer(&ifp->timer);
+}
+
+static void addrconf_rs_timer(unsigned long data)
+{
+	struct inet6_ifaddr *ifp;
+
+	ifp = (struct inet6_ifaddr *) data;
+
+	if (ipv6_forwarding)
+		return;
+
+	if (ifp->idev->if_flags & IF_RA_RCVD)
+	{
+		/*
+		 *	Announcement received after solicitation
+		 *	was sent
+		 */
+		return;
+	}
+
+	if (ifp->probes++ <= MAX_RTR_SOLICITATIONS)
+	{
+		struct in6_addr all_routers;
+
+		ipv6_addr_set(&all_routers,
+			      __constant_htonl(0xff020000U), 0, 0,
+			      __constant_htonl(0x2U));
+
+		ndisc_send_rs(ifp->idev->dev, &ifp->addr,
+			      &all_routers);
+	
+		
+		ifp->timer.function = addrconf_rs_timer;
+		ifp->timer.expires = jiffies + RTR_SOLICITATION_INTERVAL;
+		add_timer(&ifp->timer);
+	}
+	else
+	{
+		printk(KERN_DEBUG "%s: no IPv6 routers present\n",
+		       ifp->idev->dev->name);
+
+		if (!default_rt_list && !last_resort_rt)
+		{
+			struct rt6_info *rt;
+
+			/*
+			 *	create a last resort route with all
+			 *	destinations on link
+			 */
+			rt = kmalloc(sizeof(struct rt6_info), GFP_ATOMIC);
+
+			if (rt)
+			{
+				memset(rt, 0, sizeof(struct rt6_info));
+				rt->rt_dev = ifp->idev->dev;
+				rt->rt_ref = 1;
+				rt->rt_flags = (RTI_ALLONLINK | RTF_UP);
+				last_resort_rt = rt;
+			}
+		}
+	}
+}
+
+static void addrconf_dad_start(struct inet6_ifaddr *ifp)
+{
+	static int rand_seed = 1;
+	int rand_num;
+
+	if (rand_seed)
+	{
+		rand_seed = 0;
+		nd_rand_seed = ifp->addr.s6_addr32[3];
+	}
+
+	init_timer(&ifp->timer);
+	ifp->probes = DupAddrDetectTransmits;
+
+	rand_num = ipv6_random() % MAX_RTR_SOLICITATION_DELAY;
+
+	ifp->timer.function = addrconf_dad_timer;
+	ifp->timer.data = (unsigned long) ifp;
+	ifp->timer.expires = jiffies + rand_num;
+
+	add_timer(&ifp->timer);
+}
+
+static int iface_proc_info(char *buffer, char **start, off_t offset,
+			   int length, int dummy)
+{
+	struct inet6_ifaddr *ifp;
+	int i;
+	int len = 0;
+
+	for (i=0; i < HASH_SIZE; i++)
+		for (ifp=inet6_addr_lst[i]; ifp; ifp=ifp->lst_next)
+		{
+			int j;
+
+			for (j=0; j<16; j++)
+			{
+				sprintf(buffer + len, "%02x",
+					ifp->addr.s6_addr[j]);
+				len += 2;
+			}
+
+			len += sprintf(buffer + len,
+				       " %02x %02x %02x %02x %8s\n",
+				       ifp->idev->if_index,
+				       ifp->prefix_len,
+				       ifp->scope,
+				       ifp->flags,
+				       ifp->idev->dev->name);
+		}
+
+	*start = buffer + offset;
+
+	len -= offset;
+
+	if (len > length)
+		len = length;
+	return len;
+}
+
+struct proc_dir_entry iface_proc_entry =
+{
+        0, 8, "if_inet6",
+        S_IFREG | S_IRUGO, 1, 0, 0,
+        0, NULL,
+        &iface_proc_info
+};
+
+
+/*
+ *	Periodic address status verification
+ */
+
+void addrconf_verify(unsigned long foo)
+{
+	struct inet6_ifaddr *ifp;
+	unsigned long now = jiffies;
+	int i;
+
+	for (i=0; i < HASH_SIZE; i++)
+	{
+		for (ifp=inet6_addr_lst[i]; ifp;)
+		{
+			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;
+				
+				if (age > bp->valid_lft)
+				{
+					ipv6_del_addr(bp);
+				}
+				continue;
+			}
+			ifp=ifp->lst_next;
+		}
+	}
+
+	addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY;
+	add_timer(&addr_chk_timer);	
+}
+
+void addrconf_init()
+{
+	struct device *dev;
+
+	/* init addr hash list */	  
+	memset(inet6_addr_lst, 0, 16 * sizeof(struct inet6_ifaddr *));
+
+	memset(inet6_mcast_lst,   0, 16 * sizeof(struct ipv6_mc_list *));
+
+	inet6_dev_lst = NULL;
+
+	/* 
+	 *	Init loopback device
+	 */
+
+	dev = dev_get("lo");
+
+	if (dev && (dev->flags & IFF_UP))
+		init_loopback(dev);
+
+	/*
+	 *	and maybe:
+	 *	search availiable AF_INET devs and try to configure them
+	 */
+
+	dev = dev_get("eth0");
+
+	if (dev && (dev->flags & IFF_UP))
+		addrconf_eth_config(dev);
+	
+	proc_register_dynamic(&proc_net, &iface_proc_entry);
+	
+	addr_chk_timer.expires = jiffies + ADDR_CHECK_FREQUENCY;
+	add_timer(&addr_chk_timer);
+}
+
+void addrconf_cleanup(void)
+{
+	struct inet6_dev *idev, *bidev;
+	struct inet6_ifaddr *ifa, *bifa;
+	int i;
+
+	del_timer(&addr_chk_timer);
+
+	/*
+	 *	clean dev list.
+	 */
+
+	for (idev = inet6_dev_lst; idev; )
+	{
+		bidev = idev;
+		idev = idev->next;
+		kfree(bidev);
+	}
+
+	/*
+	 *	clean addr_list
+	 */
+
+	for (i=0; i<16; i++)
+	{
+		for (ifa=inet6_addr_lst[i]; ifa; )
+		{
+			bifa = ifa;
+			ifa = ifa->lst_next;
+			kfree(bifa);
+		}
+	}
+
+	proc_unregister(&proc_net, iface_proc_entry.low_ino);
+}
+
+/*
+ * Local variables:
+ * c-file-style: "Linux"
+ * End:
+ */

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen, slshen@lbl.gov