/* $USAGI: addrconf.c,v 1.56 2001/11/19 05:52:10 yoshfuji Exp $ */

/*
 *	IPv6 Address [auto]configuration
 *	Linux INET6 implementation
 *
 *	Authors:
 *	Pedro Roque		<roque@di.fc.ul.pt>	
 *
 *	$Id: addrconf.c,v 1.46 2001/10/23 12:37:47 yoshfuji Exp $
 *
 *	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.
 */

/*
 *	Changes:
 *
 *	Janos Farkas			:	delete timer on ifdown
 *	<chexum@bankinf.banki.hu>
 *	Andi Kleen			:	kill doube kfree on module
 *						unload.
 */

#include <linux/config.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/random.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/sched.h>
#include <linux/net.h>
#include <linux/in6.h>
#ifdef CONFIG_IPV6_NODEINFO
#include <linux/icmpv6.h>
#endif
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/route.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif
#include <linux/delay.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/ip6_route.h>
#include <net/addrconf.h>
#include <net/ip.h>
#include <linux/if_tunnel.h>
#include <linux/rtnetlink.h>

#ifdef CONFIG_IPV6_PRIVACY
#include <linux/md5.h>
#endif

#include <asm/uaccess.h>

#ifdef CONFIG_IPV6_ACONF_DEBUG
#include <linux/inet.h>
#endif

/* Set to 3 to get tracing... */
#ifdef CONFIG_IPV6_ACONF_DEBUG
#define ACONF_DEBUG 3
#else
#define ACONF_DEBUG 1
#endif

#define ADBG(x)		printk x
#define NOADBG(x)	do { ; } while(0)
#if ACONF_DEBUG >= 3
#define ADBG3(x)	ADBG(x)
#else
#define ADBG3(x)	NOADBG(x)
#endif

#if ACONF_DEBUG >= 2
#define ADBG2(x)	ADBG(x)
#else
#define ADBG2(x)	NOADBG(x)
#endif

#if ACONF_DEBUG >=1
#define ADBG1(x)	ADBG(x)
#else
#define ADBG1(x)	NOADBG(x)
#endif

/* /proc/net/snmp6, /proc/net/dev_snmp6/ things */
#ifdef CONFIG_PROC_FS
extern struct proc_dir_entry *proc_net_devsnmp6;
extern int afinet6_read_devsnmp(char *buffer, char **start, off_t offset, int length, int *eof, void *data);
#endif

#if HZ == 100
#define timeticks(_h)	(_h)
#else
#define timeticks(_h)	((unsigned long)(((unsigned long long)(_h))*100/HZ))
#endif

#ifdef CONFIG_SYSCTL
static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p);
static void addrconf_sysctl_unregister(struct ipv6_devconf *p);
#endif

#ifdef CONFIG_IPV6_PRIVACY
static int __ipv6_update_tmpid(struct inet6_dev *idev, u8 *eui64);
static void ipv6_regenerate_tmpid(unsigned long data);
static void ipv6_init_tmpid(struct inet6_dev *idev);

static int desync_factor = MAX_DESYNC_FACTOR * HZ;
#endif

int ipv6_addrselect_scope(const struct in6_addr *addr);

/*
 *	Configured unicast address list
 */
static struct inet6_ifaddr		*inet6_addr_lst[IN6_ADDR_HSIZE];

/*
 *	AF_INET6 device list
 */
static struct inet6_dev		*inet6_dev_lst[IN6_ADDR_HSIZE];

static atomic_t			addr_list_lock = ATOMIC_INIT(0);

void addrconf_verify(unsigned long);

static struct timer_list addr_chk_timer = {
	NULL, NULL,
	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);
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);
#ifdef CONFIG_IPV6_PRIVACY
static void addrconf_tmp_timer(unsigned long data);
#endif
static void ipv6_ifa_notify(int event, struct inet6_ifaddr *ifa);

struct ipv6_devconf ipv6_devconf =
{
	0,				/* forwarding		*/
	IPV6_DEFAULT_HOPLIMIT,		/* hop limit		*/
	IPV6_MIN_MTU,			/* mtu			*/
	1,				/* accept RAs		*/
	1,				/* accept redirects	*/
	1,				/* autoconfiguration	*/
	1,				/* dad transmits	*/
	MAX_RTR_SOLICITATIONS,		/* router solicits	*/
	RTR_SOLICITATION_INTERVAL,	/* rtr solicit interval	*/
	MAX_RTR_SOLICITATION_DELAY,	/* rtr solicit delay	*/
#ifdef CONFIG_IPV6_NODEINFO
	1,				/* accept NIs           */
#endif
#ifdef CONFIG_IPV6_PRIVACY
	use_tempaddr:		1,
	temp_valid_lft:		TEMP_VALID_LIFETIME,
	temp_prefered_lft:	TEMP_PREFERRED_LIFETIME,
	regen_max_retry:	REGEN_MAX_RETRY,
	max_desync_factor:	MAX_DESYNC_FACTOR,
#endif
};

static struct ipv6_devconf ipv6_devconf_dflt =
{
	0,				/* forwarding		*/
	IPV6_DEFAULT_HOPLIMIT,		/* hop limit		*/
	IPV6_MIN_MTU,			/* mtu			*/
	1,				/* accept RAs		*/
	1,				/* accept redirects	*/
	1,				/* autoconfiguration	*/
	1,				/* dad transmits	*/
	MAX_RTR_SOLICITATIONS,		/* router solicits	*/
	RTR_SOLICITATION_INTERVAL,	/* rtr solicit interval	*/
	MAX_RTR_SOLICITATION_DELAY,	/* rtr solicit delay	*/
#ifdef CONFIG_IPV6_NODEINFO
	1,				/* accept NIs           */
#endif
#ifdef CONFIG_IPV6_PRIVACY
	use_tempaddr:		1,
	temp_valid_lft:		TEMP_VALID_LIFETIME,
	temp_prefered_lft:	TEMP_PREFERRED_LIFETIME,
	regen_max_retry:	REGEN_MAX_RETRY,
	max_desync_factor:	MAX_DESYNC_FACTOR,
#endif
};

struct addrselect_cache {
	struct inet6_ifaddr *ifp;
	int	match;
	int	deprecated;
	int	home;
	int	temporary;
	int	device;
	int	scope;
	int	label;
	int	matchlen;
};

/*
 * from ip6_fib.c
 */
static __inline__ int inet6_addr_diff(void *token1, void *token2)
{
	__u32 *a1 = token1;
	__u32 *a2 = token2;
	int addrlen = 16;
	int i;

	addrlen >>= 2;

	for (i = 0; i < addrlen; i++) {
		__u32 xb;

		xb = a1[i] ^ a2[i];

		if (xb) {
			int j = 31;

			xb = ntohl(xb);

			while (test_bit(j, &xb) == 0)
				j--;

			return (i * 32 + 31 - j);
		}
	}

	return addrlen<<5;
}

int ipv6_addr_type(struct in6_addr *addr)
{
	u32 st;

	st = addr->s6_addr32[0];

	/* Consider all addresses with the first three bits different of
	   000 and 111 as unicasts.
	 */
	if ((st & __constant_htonl(0xE0000000)) != __constant_htonl(0x00000000) &&
	    (st & __constant_htonl(0xE0000000)) != __constant_htonl(0xE0000000))
		return IPV6_ADDR_UNICAST;

	if ((st & __constant_htonl(0xFF000000)) == __constant_htonl(0xFF000000)) {
		int type = IPV6_ADDR_MULTICAST;

		switch((st & __constant_htonl(0x00FF0000))) {
			case __constant_htonl(0x00010000):
				type |= IPV6_ADDR_LOOPBACK;
				break;

			case __constant_htonl(0x00020000):
				type |= IPV6_ADDR_LINKLOCAL;
				break;

			case __constant_htonl(0x00050000):
				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;
}

static struct inet6_dev * ipv6_add_dev(struct device *dev)
{
	struct inet6_dev *ndev, **bptr, *iter;
	int hash;

	if (dev->mtu < IPV6_MIN_MTU)
		return NULL;

	ndev = kmalloc(sizeof(struct inet6_dev), GFP_KERNEL);

	if (ndev) {
		char name[64];
		memset(ndev, 0, sizeof(struct inet6_dev));

		ndev->dev = dev;
		memcpy(&ndev->cnf, &ipv6_devconf_dflt, sizeof(ndev->cnf));
		ndev->cnf.mtu6 = dev->mtu;
		ndev->cnf.sysctl = NULL;
		ndev->nd_parms = neigh_parms_alloc(dev, &nd_tbl);
		if (ndev->nd_parms == NULL) {
			kfree(ndev);
			return NULL;
		}
#ifdef CONFIG_IPV6_PRIVACY
		ipv6_init_tmpid(ndev);
#endif

		ipv6_statistics.Ip6LastChange = timeticks(jiffies);
		ndev->stats.ipv6.Ip6LastChange = timeticks(jiffies);

#ifdef CONFIG_PROC_FS
		sprintf(name, "%d", dev->ifindex);
		ndev->stats.proc_dir_entry = create_proc_entry(name, 0, proc_net_devsnmp6);
		if (!ndev->stats.proc_dir_entry)
			ADBG1((KERN_WARNING
				"addrconf_notify(): cannot create /proc/net/dev_snmp6/%s\n",name));
		ndev->stats.proc_dir_entry->read_proc = afinet6_read_devsnmp;
		ndev->stats.proc_dir_entry->data = ndev;
#endif

#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;
}

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;
	}
	return idev;
}

static void dev_forward_change(struct inet6_dev *idev)
{
	struct device *dev;
	struct in6_addr addr;

	if (!idev)
		return;

	dev = idev->dev;
	if (!dev)
		return;

	ipv6_addr_all_routers(&addr);

	if (idev->cnf.forwarding)
		ipv6_dev_mc_inc(dev, &addr);
	else
		ipv6_dev_mc_dec(dev, &addr);
}

static void addrconf_forward_change(void)
{
	int i;

	for (i = 0; i < IN6_ADDR_HSIZE; i++) {
		struct inet6_dev *idev;
		for (idev = inet6_dev_lst[i]; idev; idev = idev->next) {
			int changed = idev->cnf.forwarding != ipv6_devconf.forwarding;
			idev->cnf.forwarding = ipv6_devconf.forwarding;
			if (changed)
				dev_forward_change(idev);
		}
	}
}

struct inet6_dev * ipv6_get_idev(struct device *dev)
{
	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)
			return idev;
	}
	return NULL;
}

static struct inet6_ifaddr *
ipv6_add_addr(struct inet6_dev *idev, struct in6_addr *addr, int scope)
{
	struct inet6_ifaddr *ifa;
	int hash;

	/* Ingore adding duplicate addresses on an interface */
	if (ipv6_chk_same_addr(addr, idev->dev)) {
		ADBG1(("ipv6_add_addr: already assigned\n"));
		return NULL;
	}

	ifa = kmalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC);

	if (ifa == NULL) {
		ADBG(("ipv6_add_addr: malloc failed\n"));
		return NULL;
	}

	memset(ifa, 0, sizeof(struct inet6_ifaddr));
	memcpy(&ifa->addr, addr, sizeof(struct in6_addr));

	init_timer(&ifa->timer);
	ifa->timer.data = (unsigned long) ifa;
	ifa->scope = scope;
	ifa->idev = idev;

	/* Add to list. */
	hash = ipv6_addr_hash(addr);

	ifa->lst_next = inet6_addr_lst[hash];
	inet6_addr_lst[hash] = ifa;

	/* Add to inet6_dev unicast addr list. */
	ifa->if_next = idev->addr_list;
	idev->addr_list = ifa;
#ifdef CONFIG_IPV6_PRIVACY
	if (ifa->flags&IFA_F_TEMPORARY) {
		ifa->tmp_next = idev->tempaddr_list;
		idev->tempaddr_list = ifa;
	} else {
		ifa->tmp_next = NULL;
	}
#endif
	return ifa;
}

static void ipv6_del_addr(struct inet6_ifaddr *ifp)
{
	struct inet6_ifaddr *iter, **back;
	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->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;
			synchronize_bh();

			ifp->if_next = NULL;
			break;
		}
		back = &(iter->if_next);
	}
#ifdef CONFIG_IPV6_PRIVACY
	iter = ifp->idev->tempaddr_list;
	back = &ifp->idev->tempaddr_list;

	for (; iter; iter = iter->tmp_next) {
		if (iter == ifp) {
			*back = ifp->tmp_next;
			synchronize_bh();
			ifp->tmp_next = NULL;
			break;
		}
		back = &(iter->tmp_next);
	}
	if (ifp->ifpub) {
		synchronize_bh();
		ifp->ifpub = NULL;
	}
#endif
	ipv6_ifa_notify(RTM_DELADDR, ifp);
	
	kfree(ifp);
}

/*
 *	Choose an apropriate source address
 *	draft-ietf-ipngwg-default-addr-select-05.txt
 */
int ipv6_get_saddr(struct dst_entry *dst,
		   struct in6_addr *daddr, struct in6_addr *saddr)
{
	int daddr_scope;
	struct inet6_ifaddr *ifp = NULL;
	struct device *dev, *daddr_dev = NULL;
	struct inet6_dev *idev;
	struct rt6_info *rt;
	int err;
#ifdef CONFIG_IPV6_ACONF_DEBUG
	char daddrbuf[128];
	char saddrbuf[128];
#endif
	int update;
	struct addrselect_cache candidate = {NULL,0,0,0,0,0,0,0,0};

	rt = (struct rt6_info *) dst;
	if (rt)
		daddr_dev = rt->rt6i_dev;
	
	addrconf_lock();

	daddr_scope = ipv6_addrselect_scope(daddr);
#ifdef CONFIG_IPV6_ACONF_DEBUG
	in6_ntop(daddr, daddrbuf);
	ADBG3((KERN_DEBUG "ipv6_get_saddr(): daddr=%s\n", daddrbuf));
#endif

	for (dev = dev_base; dev; dev=dev->next) {
	        idev = ipv6_get_idev(dev);

#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
		ADBG3((KERN_DEBUG "processing dev = %p (%s), idev = %p\n", dev, dev->name, idev));
#endif

		if (!idev)
			continue;

		for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
			struct addrselect_cache temp = {NULL,0,0,0,0,0,0,0,0};
			update = 0;
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
			ADBG3((KERN_DEBUG "starting examin source address\n"));
			in6_ntop(&ifp->addr, saddrbuf);
			ADBG3((KERN_DEBUG
				"ipv6_get_saddr(): saddr=%s\n", saddrbuf));
#endif

			/* Rule 1: Prefer same address */
			temp.match = ipv6_addr_cmp(&ifp->addr, daddr) == 0;
			if (!update)
				update = temp.match - candidate.match;
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 1: %d < %d\n",
					temp.match,candidate.match));
#endif
				continue;
			}

			/* Rule 2: Prefer appropriate scope */
			temp.scope = ipv6_addrselect_scope(&ifp->addr);
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
			ADBG3((KERN_DEBUG "temp.scope = %d\n", temp.scope));
			ADBG3((KERN_DEBUG "candidate.scope = %d\n", candidate.scope));
#endif
			if (!update) {
				update = temp.scope - candidate.scope;
				if (update > 0) {
					 update = candidate.scope < daddr_scope ? 1 : -1;
				} else if (update < 0) {
					update = temp.scope < daddr_scope ? -1 : 1;
				}
			}
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 2: %d < %d\n",
					temp.scope,candidate.scope));
#endif
				continue;
			}

			/* Rule 3: Avoid deprecated address */
			temp.deprecated = ifp->flags & IFA_F_DEPRECATED;
			if (!update)
				update = candidate.deprecated - temp.deprecated;
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 3: %d < %d\n",
					temp.deprecated,candidate.deprecated));
#endif
				continue;
			}

			/* Rule 4: Prefer home address, but MIPv6 isnt supported */

			/* Rule 5: Prefer outgoing interface */
			temp.device = daddr_dev ? daddr_dev == (ifp->idev ? ifp->idev->dev : daddr_dev) : 0;
			if (!update)
				update = temp.device - candidate.device;
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 5: %d < %d\n",
					temp.home,candidate.home));
#endif
				continue;
			}

			/* Rule 6: Prefer matching label, this isnt currently supported yet*/
#if 0
			temp.label = ipv6_get_policy_label(&ifp->addr) == daddr_label;
#else
			temp.label = 0;
#endif
			if (!update)
				update = temp.label - candidate.label;
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 6: %d < %d\n",
					temp.label,candidate.label));
#endif
				continue;
			}

			/* Rule 7: Prefer public address */
#ifdef CONFIG_IPV6_PRIVACY
			temp.temporary = ifp->flags & IFA_F_TEMPORARY;
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR        
			ADBG3((KERN_DEBUG " ipv6_get_saddr(): temp.temporary = %d\n", temp.temporary));
			ADBG3((KERN_DEBUG " ipv6_get_saddr(): candidate.temporary = %d\n", candidate.temporary));
#endif
			if (!update) {
				if (ipv6_devconf.use_tempaddr > 1) {
					update = temp.temporary - candidate.temporary;
				} else {
					update = candidate.temporary - temp.temporary;
				}
			}
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 7: %d < %d\n",
					temp.temporary,candidate.temporary));
#endif
				continue;
			}
#endif

			/* Rule 8: Use longest matching prefix */
			temp.matchlen = inet6_addr_diff(&ifp->addr, daddr);
			if (!update)
				update = temp.matchlen - candidate.matchlen;
			if (update < 0) {
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
				ADBG3((KERN_DEBUG
					"ipv6_get_saddr(): lose at Rule 8: %d < %d\n",
					temp.matchlen,candidate.matchlen));
#endif
				continue;
			}

			/* Final Rule */
			if (update <= 0)
				continue;
#ifdef CONFIG_IPV6_ACONF_DEBUG_SADDR
			ADBG3((KERN_DEBUG
				"ipv6_get_saddr(): %s is win\n", saddrbuf));
#endif

			/* update dandidate date */
			temp.ifp = ifp;
			candidate = temp;
		}
	}
out:
	if (candidate.ifp) {
		ipv6_addr_copy(saddr, &candidate.ifp->addr);
		err = 0;
	} else {
		err = -ENETUNREACH;
	}
#ifdef CONFIG_IPV6_ACONF_DEBUG
	in6_ntop(saddr, saddrbuf);
	ADBG3((KERN_DEBUG "ipv6_get_saddr(): %s is selected\n", saddrbuf));
#endif
	addrconf_unlock();
	return err;
}

struct inet6_ifaddr * ipv6_get_lladdr(struct device *dev)
{
	struct inet6_ifaddr *ifp = NULL;
	struct inet6_dev *idev;

	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 ifp;
}

/*
 *	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 device *dev, int nd)
{
	struct inet6_ifaddr * ifp;
	u8 hash;
	unsigned flags = 0;

	if (!nd)
		flags |= DAD_STATUS|ADDR_INVALID;

	addrconf_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 && !(ifp->flags&flags)) {
			if (dev == NULL || ifp->idev->dev == dev ||
			    !(ifp->scope&(IFA_LINK|IFA_HOST)))
				break;
		}
	}

	addrconf_unlock();
	return ifp;
}

int ipv6_chk_same_addr(struct in6_addr *addr, struct device *dev)
{
	struct inet6_ifaddr * ifp;
	u8 hash = ipv6_addr_hash(addr);

	addrconf_lock();

	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
		if (ipv6_addr_cmp(&ifp->addr, addr) == 0) {
			if (dev != NULL && ifp->idev->dev == dev)
				break;
		}
	}
	addrconf_unlock();
	return ifp != NULL;
}

static struct inet6_ifaddr *__ipv6_get_ifaddr(struct in6_addr *addr, struct device *dev)
{
	struct inet6_ifaddr * ifp;
	u8 hash;

	hash = ipv6_addr_hash(addr);
	for(ifp = inet6_addr_lst[hash]; ifp; ifp=ifp->lst_next) {
		if (ipv6_addr_cmp(&ifp->addr, addr) == 0) {
			if (dev == NULL || ifp->idev->dev == dev) {
				if (!(ifp->scope&IFA_HOST))
					break;
			}
		}
	}
	return ifp;
}

struct inet6_ifaddr *ipv6_get_ifaddr(struct in6_addr *addr, struct device *dev)
{
	struct inet6_ifaddr *ifp;
	addrconf_lock();
	ifp = __ipv6_get_ifaddr(addr, dev);
	addrconf_unlock();
	return ifp;
}

/* Default address selection */

int ipv6_addrselect_scope(const struct in6_addr *addr)
{
	u32 st;

	st = addr->s6_addr32[0];

	if ((st & __constant_htonl(0xE0000000)) != __constant_htonl(0x00000000) &&
	    (st & __constant_htonl(0xE0000000)) != __constant_htonl(0xE0000000))
		return IPV6_ADDR_SCOPE_GLOBAL;

	if ((st & __constant_htonl(0xFF000000)) == __constant_htonl(0xFF000000))
		return IPV6_ADDR_MC_SCOPE(addr);
        
	if ((st & __constant_htonl(0xFFC00000)) == __constant_htonl(0xFE800000))
		return IPV6_ADDR_SCOPE_LINKLOCAL;

	if ((st & __constant_htonl(0xFFC00000)) == __constant_htonl(0xFEC00000))
		return IPV6_ADDR_SCOPE_SITELOCAL;

	if ((st | addr->s6_addr32[1]) == 0) {
		if (addr->s6_addr32[2] == 0) {
			if (addr->s6_addr32[3] == 0)
				return IPV6_ADDR_SCOPE_ANY;

			if (addr->s6_addr32[3] == __constant_htonl(0x00000001))
				return IPV6_ADDR_SCOPE_LINKLOCAL;	/* section 2.4 */

			return IPV6_ADDR_SCOPE_GLOBAL;			/* section 2.3 */
		}

		if (addr->s6_addr32[2] == __constant_htonl(0x0000FFFF)) {
			if (addr->s6_addr32[3] == __constant_htonl(0xA9FF0000))
				return IPV6_ADDR_SCOPE_LINKLOCAL;	/* section 2.2 */
			if (addr->s6_addr32[3] == __constant_htonl(0xAC000000)) {
				if (addr->s6_addr32[3] == __constant_htonl(0xAC100000))
					return IPV6_ADDR_SCOPE_SITELOCAL;	/* section 2.2 */

				return IPV6_ADDR_SCOPE_LINKLOCAL;	/* section 2.2 */
			}
			if (addr->s6_addr32[3] == __constant_htonl(0x0A000000))
				return IPV6_ADDR_SCOPE_SITELOCAL;	/* section 2.2 */
			if (addr->s6_addr32[3] == __constant_htonl(0xC0A80000))
				return IPV6_ADDR_SCOPE_SITELOCAL;	/* section 2.2 */

                        return IPV6_ADDR_SCOPE_GLOBAL;                  /* section 2.2 */
		}
	}

	return IPV6_ADDR_SCOPE_RESERVED;
}

void addrconf_dad_failure(struct inet6_ifaddr *ifp)
{
	ADBG3((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;

	if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
		return;

	addrconf_addr_solict_mult(addr, &maddr);
	ipv6_dev_mc_inc(dev, &maddr);
}

static void addrconf_leave_solict(struct device *dev, struct in6_addr *addr)
{
	struct in6_addr maddr;

	if (dev->flags&(IFF_LOOPBACK|IFF_NOARP))
		return;

	addrconf_addr_solict_mult(addr, &maddr);
	ipv6_dev_mc_dec(dev, &maddr);
}


static int ipv6_generate_eui64(u8 *eui, struct device *dev)
{
	switch (dev->type) {
	case ARPHRD_ETHER:
		if (dev->addr_len != ETH_ALEN)
			return -1;
		memcpy(eui, dev->dev_addr, 3);
		memcpy(eui + 5, dev->dev_addr+3, 3);
		eui[3] = 0xFF;
		eui[4] = 0xFE;
		eui[0] ^= 2;
		return 0;
	}
	return -1;
}

static int ipv6_inherit_eui64(u8 *eui, struct inet6_dev *idev)
{
	int err = -1;
	struct inet6_ifaddr *ifp;

	for (ifp=idev->addr_list; ifp; ifp=ifp->if_next) {
		if (ifp->scope == IFA_LINK && !(ifp->flags&(ADDR_STATUS|DAD_STATUS))) {
			memcpy(eui, ifp->addr.s6_addr+8, 8);
			err = 0;
			break;
		}
	}
	return err;
}

#ifdef CONFIG_IPV6_PRIVACY
/*
 *	Temporary address (RFC 3041)
 */

static void ipv6_init_tmpid(struct inet6_dev *idev)
{
	/* call this in idev->lock semantics */
	get_random_bytes(idev->tmpid, sizeof(idev->tmpid));
	get_random_bytes(idev->entropy, sizeof(idev->entropy));
	memset(&idev->regen_timer, 0, sizeof(idev->regen_timer));
	init_timer(&idev->regen_timer);
	idev->regen_timer.function = ipv6_regenerate_tmpid;
	idev->regen_timer.data = (unsigned long) idev;
	__ipv6_update_tmpid(idev, NULL);
}

/* (re)generation of randomized interface identifier (RFC 3041 3.2, 3.5) */
static int __ipv6_update_tmpid(struct inet6_dev *idev, u8 *eui64)
{
	struct device *dev;
	u8 eui64_buf[8];
	u8 digest[16];
	MD5_CTX ctx;

	del_timer(&idev->regen_timer);

	if (time_before(jiffies, idev->regen_tstamp + idev->nd_parms->retrans_time))
		goto readd_timer;

	if (!eui64) {
		dev = idev->dev;
#if 0
		if (!dev) {
			panic("__ipv6_update_tmpid(idev=%p): idev->dev=NULL\n");
		}
#endif
		if (ipv6_generate_eui64(eui64_buf, dev)) {
			ADBG1(("__ipv6_update_tmpid(idev=%p): cannot get EUI64 identifier; use random bytes.\n", idev));
			get_random_bytes(eui64_buf, sizeof(eui64_buf));
		}
		eui64 = eui64_buf;
	}

regen:
		MD5Init(&ctx);
		MD5Update(&ctx, idev->entropy, 8);
		MD5Update(&ctx, eui64, 8);
		MD5Final(digest, &ctx);
		memcpy(idev->tmpid, &digest[0], 8);
		idev->tmpid[0] &= ~0x02;
		memcpy(idev->entropy, &digest[8], 8);
	/*
	 * <draft-ietf-ipngwg-temp-addresses-v2-00.txt>:
	 * check if generated address is not inappropriate
	 * 
	 *  - Reserved subnet anycast (RFC 2526)
	 * 	11111101 11....11 1xxxxxxx
	 *  - ISATAP (draft-ietf-ngtrans-isatap-01.txt) 4.3
	 * 	00-00-5E-FE-xx-xx-xx-xx
	 *  - value 0
	 *  - XXX: already assigned to an address on the device
	 */
	if (idev->tmpid[0] == 0xfd && 
	    (idev->tmpid[1]&idev->tmpid[2]&idev->tmpid[3]&idev->tmpid[4]&idev->tmpid[5]&idev->tmpid[6]) == 0xff &&
	    (idev->tmpid[7]&0x80))
		goto regen;
	if ((idev->tmpid[0]|idev->tmpid[1]) == 0) {
		if (idev->tmpid[2] == 0x5e && idev->tmpid[3] == 0xfe)
			goto regen;
		if ((idev->tmpid[2]|idev->tmpid[3]|idev->tmpid[4]|idev->tmpid[5]|idev->tmpid[6]|idev->tmpid[7]) == 0x00)
			goto regen;
	}

	idev->regen_timer.expires = jiffies + 
					idev->cnf.temp_prefered_lft * HZ - 
					idev->cnf.regen_max_retry * idev->cnf.dad_transmits * idev->nd_parms->retrans_time - desync_factor;
	if (time_before(idev->regen_timer.expires, jiffies)) {
		/* XXX: warning */
		idev->regen_timer.expires = jiffies + HZ;
	}

	idev->regen_tstamp = jiffies;
readd_timer:
	add_timer(&idev->regen_timer);

	return 0;
}

static void ipv6_regenerate_tmpid(unsigned long data)
{
	struct inet6_dev *idev = (struct inet6_dev *) data;
	addrconf_lock();
	__ipv6_update_tmpid(idev, NULL);
	addrconf_unlock();
}

static void addrconf_tmp_timer(unsigned long data)
{
	struct inet6_ifaddr *ifa = (struct inet6_ifaddr *) data, *ift, *ifp;
	struct inet6_dev *idev = ifa->idev;
	struct in6_addr addr;
	ipv6_regenerate_tmpid((unsigned long)idev);
	memcpy(&addr.s6_addr32[0], &ifa->addr.s6_addr32[0], 8);
	ifp = ifa->ifpub;
	memcpy(&addr.s6_addr32[2], idev->tmpid, 8);
	ift = ipv6_get_ifaddr(&addr, idev->dev);
	if (ift) {
		/* XXX 2.2.x dont have addrconf_mod_timer */
		/* addrconf_mod_timer(ift, AC_REGEN, HZ); */
		ift->timer.function = addrconf_tmp_timer;
		ift->lasttimer = jiffies;
		ift->timer.expires = jiffies + HZ;
		add_timer(&ift->timer);
	} else {
		unsigned long tmp_valid_lft, tmp_prefered_lft, tstamp;
		int prefix_len, scope;
		tmp_valid_lft = ifp->valid_lft;
		tmp_prefered_lft = ifp->prefered_lft;
		tstamp = ifa->tstamp;
		prefix_len = ifa->prefix_len;
		scope = ifa->scope;
		ifa->ifpub = NULL;
		tmp_valid_lft = tmp_valid_lft < idev->cnf.temp_valid_lft ? tmp_valid_lft : idev->cnf.temp_valid_lft;
		tmp_prefered_lft = tmp_prefered_lft < idev->cnf.temp_prefered_lft - desync_factor / HZ ? tmp_prefered_lft : idev->cnf.temp_prefered_lft - desync_factor / HZ;
		ift = ipv6_add_addr(idev, &addr, scope);
		if (ift) {
			ift->flags |= IFA_F_TEMPORARY;
			ift->prefix_len = prefix_len;
			ift->valid_lft = tmp_valid_lft;
			ift->prefered_lft = tmp_prefered_lft;
			ift->tstamp = tstamp;
			addrconf_dad_start(ift);
		}
	}
}
#endif


/*
 *	Add prefix route.
 */

static void
addrconf_prefix_route(struct in6_addr *pfx, int plen, struct device *dev,
		      unsigned long expires, unsigned flags)
{
	struct in6_rtmsg rtmsg;

	memset(&rtmsg, 0, sizeof(rtmsg));
	memcpy(&rtmsg.rtmsg_dst, pfx, sizeof(struct in6_addr));
	rtmsg.rtmsg_dst_len = plen;
	rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
	rtmsg.rtmsg_ifindex = dev->ifindex;
	rtmsg.rtmsg_info = expires;
	rtmsg.rtmsg_flags = RTF_UP|flags;
	rtmsg.rtmsg_type = RTMSG_NEWROUTE;

	/* Prevent useless cloning on PtP SIT.
	   This thing is done here expecting that the whole
	   class of non-broadcast devices need not cloning.
	 */
	if (
#ifdef CONFIG_NET_IPIP_IPV6
	    dev->type == ARPHRD_SIT 
#else
	    dev->type == ARPHRD_TUNNEL
#endif
	    && (dev->flags&IFF_POINTOPOINT))
		rtmsg.rtmsg_flags |= RTF_NONEXTHOP;

	ip6_route_add(&rtmsg);
}

/* Create "default" multicast route to the interface */

static void addrconf_add_mroute(struct device *dev)
{
	struct in6_rtmsg rtmsg;

	memset(&rtmsg, 0, sizeof(rtmsg));
	ipv6_addr_set(&rtmsg.rtmsg_dst,
		      __constant_htonl(0xFF000000), 0, 0, 0);
	rtmsg.rtmsg_dst_len = 8;
	rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
	rtmsg.rtmsg_ifindex = dev->ifindex;
	rtmsg.rtmsg_flags = RTF_UP|RTF_ADDRCONF;
	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
	ip6_route_add(&rtmsg);
}

static void sit_route_add(struct device *dev)
{
	struct in6_rtmsg rtmsg;

	memset(&rtmsg, 0, sizeof(rtmsg));

	rtmsg.rtmsg_type	= RTMSG_NEWROUTE;
	rtmsg.rtmsg_metric	= IP6_RT_PRIO_ADDRCONF;

	/* prefix length - 96 bits "::d.d.d.d" */
	rtmsg.rtmsg_dst_len	= 96;
	rtmsg.rtmsg_flags	= RTF_UP|RTF_NONEXTHOP;
	rtmsg.rtmsg_ifindex	= dev->ifindex;

	ip6_route_add(&rtmsg);
}

static void addrconf_add_lroute(struct device *dev)
{
	struct in6_addr addr;

	ipv6_addr_set(&addr,  __constant_htonl(0xFE800000), 0, 0, 0);
	addrconf_prefix_route(&addr, 64, dev, 0, RTF_ADDRCONF);
}

static struct inet6_dev *addrconf_add_dev(struct device *dev)
{
	struct inet6_dev *idev;

	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);
	return idev;
}

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;
	struct inet6_dev *in6_dev = ipv6_get_idev(dev);
	int event = 0;

	if (in6_dev == NULL) {
		ADBG1((KERN_DEBUG "addrconf: device %s not configured\n", dev->name));
		return;
	}

	pinfo = (struct prefix_info *) opt;
	
	if (len < sizeof(struct prefix_info)) {
		ADBG2(("addrconf: prefix option too short\n"));
		return;
	}
	
	/*
	 *	Validation checks ([ADDRCONF], page 19)
	 */

	addr_type = ipv6_addr_type(&pinfo->prefix);

	if (addr_type & (IPV6_ADDR_MULTICAST|IPV6_ADDR_LINKLOCAL))
		return;

	valid_lft = ntohl(pinfo->valid);
	prefered_lft = ntohl(pinfo->prefered);

	if (prefered_lft > valid_lft) {
		ADBG2((KERN_WARNING "addrconf: prefix option has invalid lifetime\n"));
		return;
	}

	/*
	 *	Two things going on here:
	 *	1) Add routes for on-link prefixes
	 *	2) Configure prefixes with the auto flag set
	 */

	/* 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->ifindex, RT6_LOOKUP_FLAG_STRICT);

	if (rt && ((rt->rt6i_flags & (RTF_GATEWAY | RTF_DEFAULT)) == 0)) {
		if (rt->rt6i_flags&RTF_EXPIRES) {
			if (pinfo->onlink == 0 || valid_lft == 0) {
				ip6_del_rt(rt);
			} else {
				rt->rt6i_expires = rt_expires;
			}
		}
	} else if (pinfo->onlink && valid_lft) {
		addrconf_prefix_route(&pinfo->prefix, pinfo->prefix_len,
				      dev, rt_expires, RTF_ADDRCONF|RTF_EXPIRES);
	}
	if (rt)
		dst_release(&rt->u.dst);

	/* Try to figure out our local address for this prefix */

	if (pinfo->autoconf && in6_dev->cnf.autoconf) {
		struct inet6_ifaddr *ifp = NULL, *ift = NULL;
		int flags;
		struct in6_addr addr;
		int update_lft = 0, create = 0;
		unsigned long now, stored_lft;

		if (pinfo->prefix_len != 64) {
			ADBG2((KERN_DEBUG "IPv6 addrconf: prefix with wrong length %d\n", pinfo->prefix_len));
			goto autoconf_fail;
		}
		memcpy(&addr, &pinfo->prefix, 8);
		if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
		    ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev)) {
			goto autoconf_fail;
		}

		ifp = ipv6_chk_addr(&addr, dev, 1);

		if (ifp == NULL || (ifp->flags&ADDR_INVALID)) {
			if (!valid_lft)
				goto autoconf_fail;

			if (ifp == NULL)
				ifp = ipv6_add_addr(in6_dev, &addr, addr_type & IPV6_ADDR_SCOPE_MASK);
			if (ifp == NULL)
				goto autoconf_fail;

			ifp->prefix_len = pinfo->prefix_len;

			addrconf_dad_start(ifp);
			update_lft = 1;
			create = 1;
		}

#define TWO_HOURS	7200
		now = jiffies;
		if (ifp->valid_lft > (now - ifp->tstamp) / HZ)
			stored_lft = ifp->valid_lft - (now - ifp->tstamp) / HZ;
		else
			stored_lft = 0;
		if (!update_lft) {
			if (valid_lft > TWO_HOURS ||
			    valid_lft > stored_lft) {
				update_lft = 1;
			} else if (ifp->valid_lft <= TWO_HOURS
#if 0	/* this rule is logically redundant */
				   && valid_lft <= stored_lft
#endif
				   ) {
				update_lft = 0;
			} else {
				valid_lft = TWO_HOURS;
				if (valid_lft < prefered_lft)
					prefered_lft = valid_lft;
				update_lft = 1;
			}
		}
		if (update_lft) {
			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);
		}
#ifdef CONFIG_IPV6_PRIVACY
		addrconf_lock();

		if (!in6_dev->cnf.use_tempaddr)
			goto skip_privacy;

		/* update all temporary addresses in the list */
		for (ift=in6_dev->tempaddr_list; ift; ift=ift->tmp_next) {
			/*
			 * When adjusting the lifetimes of an existing
			 * temporary address, only lower the lifetimes.
			 * Implementations must not increase the
			 * lifetimes of an existing temporary address
			 * when processing a Prefix Information Option.
			 */
			flags = ift->flags;
			if (ift->valid_lft > valid_lft &&
			    (jiffies - ift->tstamp) / HZ > ift->valid_lft - valid_lft)
				ift->valid_lft = valid_lft + (jiffies - ift->tstamp) / HZ;
			if (ift->prefered_lft &&
			    (jiffies - ift->tstamp) / HZ >= ift->prefered_lft - prefered_lft)
				ift->prefered_lft = prefered_lft + (jiffies - ift->tstamp) / HZ;
			if (!(flags&IFA_F_TENTATIVE))
				ipv6_ifa_notify(0, ift);
		}
		if (create) {
			/*
			 * When a new public address is created as described in [ADDRCONF],
			 * also create a new temporary address.
			 */
			/* XXXX ipv6_get_ifaddr dont existed */
			memcpy(&addr.s6_addr32[2], in6_dev->tmpid, 8);
			ift = __ipv6_get_ifaddr(&addr, dev);
			if (ift) {
				/* we already have this address. */
				/* XXX 2.2.x dont have addrconf_mod_timer */
				/* addrconf_mod_timer(ift, AC_REGEN, HZ); */
				ift->timer.function = addrconf_tmp_timer;
				ift->lasttimer = jiffies;
				ift->timer.expires = jiffies + HZ;
				add_timer(&ift->timer);
			} else {
				unsigned long tmp_valid_lft, tmp_prefered_lft;
				tmp_valid_lft = valid_lft < in6_dev->cnf.temp_valid_lft ? valid_lft : in6_dev->cnf.temp_valid_lft;
				tmp_prefered_lft = prefered_lft < in6_dev->cnf.temp_prefered_lft - desync_factor / HZ ? prefered_lft : in6_dev->cnf.temp_prefered_lft - desync_factor / HZ;

				ift = ipv6_add_addr(in6_dev, &addr, addr_type&IPV6_ADDR_SCOPE_MASK);
				if (ift) {
					ift->flags |= IFA_F_TEMPORARY;
					ift->prefix_len = pinfo->prefix_len;
					ift->ifpub = ifp;
					ift->valid_lft = tmp_valid_lft;
					ift->prefered_lft = tmp_prefered_lft;
					ift->tstamp = ifp->tstamp;
					addrconf_dad_start(ift);
				}
			}
		}
skip_privacy:
		addrconf_unlock();
#endif
		addrconf_verify(0);
autoconf_fail:
	}
}

/*
 *	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 = -EINVAL;

	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);

	err = -ENODEV;
	if (dev == NULL)
		goto err_exit;

#ifdef CONFIG_NET_IPIP_IPV6
	if (dev->type == ARPHRD_TUNNEL)
#else
	if (dev->type == ARPHRD_SIT)
#endif
	{
		struct ifreq ifr;
		mm_segment_t	oldfs;
		struct ip_tunnel_parm p;

		err = -EADDRNOTAVAIL;
		if (!(ipv6_addr_type(&ireq.ifr6_addr) & IPV6_ADDR_COMPATv4))
			goto err_exit;

		memset(&p, 0, sizeof(p));
		p.iph.daddr = ireq.ifr6_addr.s6_addr32[3];
		p.iph.saddr = 0;
		p.iph.version = 4;
		p.iph.ihl = 5;
		p.iph.protocol = IPPROTO_IPV6;
		p.iph.ttl = 64;
		ifr.ifr_ifru.ifru_data = (void*)&p;

		oldfs = get_fs(); set_fs(KERNEL_DS);
		err = dev->do_ioctl(dev, &ifr, SIOCADDTUNNEL);
		set_fs(oldfs);

		if (err == 0) {
			err = -ENOBUFS;
			if ((dev = dev_get(p.name)) == NULL)
				goto err_exit;
			err = dev_open(dev);
		}
	}

err_exit:
	rtnl_unlock();
	return err;
}

/*
 *	Manual configuration of address on an interface
 */
static int inet6_addr_add(int ifindex, struct in6_addr *pfx, int plen)
{
	struct inet6_ifaddr *ifp;
	struct inet6_dev *idev;
	struct device *dev;
	int type, scope;
	
	if ((dev = dev_get_by_index(ifindex)) == NULL)
		return -ENODEV;
	
	if (!(dev->flags&IFF_UP))
		return -ENETDOWN;

	if ((idev = addrconf_add_dev(dev)) == NULL)
		return -ENOBUFS;

	type = ipv6_addr_type(pfx);
	if (type & IPV6_ADDR_MULTICAST)
		return -EINVAL;

	scope = ipv6_addr_scope(pfx);

	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();

	return -ENOBUFS;
}

static int inet6_addr_del(int ifindex, struct in6_addr *pfx, int plen)
{
	struct inet6_ifaddr *ifp;
	struct inet6_dev *idev;
	struct device *dev;
	
	if ((dev = dev_get_by_index(ifindex)) == NULL)
		return -ENODEV;

	if ((idev = ipv6_get_idev(dev)) == NULL)
		return -ENXIO;

	start_bh_atomic();
	for (ifp = idev->addr_list; ifp; ifp=ifp->if_next) {
		if (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;
}


int addrconf_add_ifaddr(void *arg)
{
	struct in6_ifreq ireq;
	int err;
	
	if (!capable(CAP_NET_ADMIN))
		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 (!capable(CAP_NET_ADMIN))
		return -EPERM;

	if (copy_from_user(&ireq, arg, sizeof(struct in6_ifreq)))
		return -EFAULT;

	rtnl_lock();
	err = inet6_addr_del(ireq.ifr6_ifindex, &ireq.ifr6_addr, ireq.ifr6_prefixlen);
	rtnl_unlock();
	return err;
}

static void __sit_add_v4_addr(struct inet6_dev *idev, struct in6_addr *addr, int plen, int scope)
{
	struct inet6_ifaddr *ifp;
	addrconf_lock();
	ifp = ipv6_add_addr(idev, addr, scope);
	if (ifp) {
		ifp->flags |= ADDR_PERMANENT;
		ifp->prefix_len = plen;
		ipv6_ifa_notify(RTM_NEWADDR, ifp);
	}
	addrconf_unlock();
}

static void sit_add_v4_addrs(struct inet6_dev *idev)
{
	struct in6_addr addr;
	int plen;
	struct device *dev;
	int scope;

	memset(&addr, 0, sizeof(struct in6_addr));
	memcpy(&addr.s6_addr32[3], idev->dev->dev_addr, 4);

	if (idev->dev->flags&IFF_POINTOPOINT) {
		addr.s6_addr32[0] = __constant_htonl(0xfe800000);
		plen = addr.s6_addr32[3] ? 128 : 64;
		scope = IFA_LINK;
	} else {
		plen = 96;
		scope = IPV6_ADDR_COMPATv4;
	}

	if (addr.s6_addr32[3]) {
		__sit_add_v4_addr(idev, &addr, plen, scope);
		return;
	}

        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_ifaddr * ifa;

			int flag = scope;

			for (ifa = in_dev->ifa_list; ifa; ifa = ifa->ifa_next) {
				addr.s6_addr32[3] = ifa->ifa_local;
				
				if (ifa->ifa_scope == RT_SCOPE_LINK)
					continue;
				if (ifa->ifa_scope >= RT_SCOPE_HOST) {
					if (idev->dev->flags&IFF_POINTOPOINT)
						continue;
					flag |= IFA_HOST;
				}

				__sit_add_v4_addr(idev, &addr, plen, flag);
			}
		}
        }
}

static void init_loopback(struct device *dev)
{
	struct in6_addr addr;
	struct inet6_dev  *idev;
	struct inet6_ifaddr * ifp;

	/* ::1 */

	memset(&addr, 0, sizeof(struct in6_addr));
	addr.s6_addr[15] = 1;

	if ((idev = ipv6_find_idev(dev)) == NULL) {
		ADBG1((KERN_DEBUG "init loopback: add_dev failed\n"));
		return;
	}
	ipv6_mc_up(idev);

	addrconf_lock();
	ifp = ipv6_add_addr(idev, &addr, IFA_HOST);

	if (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) {
		ifp->flags = ADDR_PERMANENT;
		ifp->prefix_len = 64;
		addrconf_dad_start(ifp);
	}
	addrconf_unlock();
}

static void addrconf_dev_config(struct device *dev)
{
	struct in6_addr addr;
	struct inet6_dev    * idev;

	if (dev->type != ARPHRD_ETHER) {
		/* Alas, we support only Ethernet autoconfiguration. */
		return;
	}

	idev = addrconf_add_dev(dev);
	if (idev == NULL)
		return;

	ipv6_mc_up(idev);

	memset(&addr, 0, sizeof(struct in6_addr));

	addr.s6_addr[0] = 0xFE;
	addr.s6_addr[1] = 0x80;

	if (ipv6_generate_eui64(addr.s6_addr + 8, dev) == 0)
		addrconf_add_linklocal(idev, &addr);
}

static void addrconf_sit_config(struct device *dev)
{
	struct inet6_dev *idev;

	/* 
	 * Configure the tunnel with one of our IPv4 
	 * addresses... we should configure all of 
	 * our v4 addrs in the tunnel
	 */

	if ((idev = ipv6_find_idev(dev)) == NULL) {
		ADBG1((KERN_DEBUG "init sit: add_dev failed\n"));
		return;
	}
	ipv6_mc_up(idev);

	sit_add_v4_addrs(idev);

	if (dev->flags&IFF_POINTOPOINT) {
		addrconf_add_mroute(dev);
		addrconf_add_lroute(dev);
	} else
		sit_route_add(dev);
}


int addrconf_notify(struct notifier_block *this, unsigned long event, 
		    void * data)
{
	struct device *dev;

	dev = (struct device *) data;

	switch(event) {
	case NETDEV_UP:
	{
		struct inet6_dev *idev = ipv6_get_idev(dev);
		switch(dev->type) {
#ifdef CONFIG_NET_IPIP_IPV6
		case ARPHRD_TUNNEL:
#else
		case ARPHRD_SIT:
#endif
			/* XXX idev = no katachini */
			addrconf_sit_config(dev);
			break;

		case ARPHRD_LOOPBACK:
			/* XXX idev = no katachini */
			init_loopback(dev);
			break;

		default:
			/* XXX idev = no katachini */
			addrconf_dev_config(dev);
			break;
		};
		if (idev)
			idev->stats.ipv6.Ip6LastChange = timeticks(jiffies);

#ifdef CONFIG_IPV6_NODEINFO
		icmpv6_ni_join_nigroup_dev(dev);
#endif

#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)
				break;
			idev->cnf.mtu6 = dev->mtu;
			rt6_mtu_change(dev, dev->mtu);
			break;
		}

		/* MTU falled under IPV6_MIN_MTU. Stop IPv6 on this interface. */

	case NETDEV_DOWN:
	case NETDEV_UNREGISTER:
		/*
		 *	Remove all addresses from this interface.
		 */
		if (addrconf_ifdown(dev, event != NETDEV_DOWN) == 0) {
			struct inet6_dev *idev = ipv6_get_idev(dev);
			if (idev)
				idev->stats.ipv6.Ip6LastChange = timeticks(jiffies);
#ifdef CONFIG_IPV6_NETLINK
			rt6_sndmsg(RTMSG_DELDEVICE, NULL, NULL, NULL, dev, 0, 0, 0, 0);
#endif
		}

		break;
	case NETDEV_CHANGE:
		break;
	};

	return NOTIFY_OK;
}

static int addrconf_ifdown(struct device *dev, int how)
{
	struct inet6_dev *idev, **bidev;
	struct inet6_ifaddr *ifa, **bifa;
	int i, hash;

	rt6_ifdown(dev);
	neigh_ifdown(&nd_tbl, dev);

	idev = ipv6_get_idev(dev);
	if (idev == NULL)
		return -ENODEV;

	start_bh_atomic();

	/* Discard address list */

	idev->addr_list = NULL;
#ifdef CONFIG_IPV6_PRIVACY
	idev->tempaddr_list = NULL;
#endif

	/*
	 * Clean addresses hash table
	 */

	for (i=0; i<16; i++) {
		bifa = &inet6_addr_lst[i];

		while ((ifa = *bifa) != NULL) {
			if (ifa->idev == idev) {
				*bifa = ifa->lst_next;
				del_timer(&ifa->timer);
				ipv6_ifa_notify(RTM_DELADDR, ifa);
				kfree(ifa);
				continue;
			}
			bifa = &ifa->lst_next;
		}
	}

	/* Discard multicast list */

	if (how == 1)
		ipv6_mc_destroy_dev(idev);
	else
		ipv6_mc_down(idev);

	/* Delete device from device hash table (if unregistered) */

	if (how == 1) {
		char name[64];
		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;
#ifdef CONFIG_IPV6_PRIVACY
				del_timer(&idev->regen_timer);
#endif
				neigh_parms_release(&nd_tbl, idev->nd_parms);
#ifdef CONFIG_PROC_FS
				sprintf(name, "%d", dev->ifindex);
				remove_proc_entry(name, proc_net_devsnmp6);
#endif

#ifdef CONFIG_SYSCTL
				addrconf_sysctl_unregister(&idev->cnf);
#endif
				ipv6_statistics.Ip6LastChange = timeticks(jiffies);
				kfree(idev);
				break;
			}
		}
	}
	end_bh_atomic();
	return 0;
}


static void addrconf_rs_timer(unsigned long data)
{
	struct inet6_ifaddr *ifp;

	ifp = (struct inet6_ifaddr *) data;

	if (ifp->idev->cnf.forwarding)
		return;

	if (ifp->idev->if_flags & IF_RA_RCVD) {
		/*
		 *	Announcement received after solicitation
		 *	was sent
		 */
		return;
	}

	if (ifp->probes++ < ifp->idev->cnf.rtr_solicits) {
		struct in6_addr all_routers;

		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;

		if (ifp->idev->if_flags & IF_RA_RCVD) {
		ADBG1((KERN_DEBUG "%s: no IPv6 routers present\n",
		       ifp->idev->dev->name));
		}

		memset(&rtmsg, 0, sizeof(struct in6_rtmsg));
		rtmsg.rtmsg_type = RTMSG_NEWROUTE;
		rtmsg.rtmsg_metric = IP6_RT_PRIO_ADDRCONF;
		rtmsg.rtmsg_flags = (RTF_ALLONLINK | RTF_ADDRCONF | 
				     RTF_DEFAULT | RTF_UP);

		rtmsg.rtmsg_ifindex = ifp->idev->dev->ifindex;

		ip6_route_add(&rtmsg);
	}
}

/*
 *	Duplicate Address Detection
 */
static void addrconf_dad_start(struct inet6_ifaddr *ifp)
{
	struct device *dev;
	unsigned long rand_num;

	dev = ifp->idev->dev;

	addrconf_join_solict(dev, &ifp->addr);

	if (ifp->prefix_len != 128 && (ifp->flags&ADDR_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;
		addrconf_dad_completed(ifp);
		end_bh_atomic();
		return;
	}

	get_random_bytes(&rand_num, sizeof(rand_num));

	ifp->probes = ifp->idev->cnf.dad_transmits;
	ifp->flags |= DAD_INCOMPLETE;

	rand_num %= (ifp->idev->cnf.rtr_solicit_delay ? : 1);

	ifp->timer.function = addrconf_dad_timer;
	ifp->timer.expires = jiffies + rand_num;

	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;
	}

	ifp->probes--;

	/* 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, 1);

	ifp->timer.expires = jiffies + ifp->idev->nd_parms->retrans_time;
	add_timer(&ifp->timer);
}

static void addrconf_dad_completed(struct inet6_ifaddr *ifp)
{
	struct device *	dev = ifp->idev->dev;

	/*
	 *	Configure the address for reception. Now it is valid.
	 */

	ipv6_ifa_notify(RTM_NEWADDR, ifp);

	/* If added prefix is link local and forwarding is off,
	   start sending router solicitations.
	 */

	if (ifp->idev->cnf.forwarding == 0 &&
	    (dev->flags&IFF_LOOPBACK) == 0 &&
	    (ipv6_addr_type(&ifp->addr) & IPV6_ADDR_LINKLOCAL)) {
		struct in6_addr all_routers;

		ipv6_addr_all_routers(&all_routers);

		/*
		 *	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 +
				      ifp->idev->cnf.rtr_solicit_interval);
		ifp->idev->if_flags |= IF_RS_SENT;
		add_timer(&ifp->timer);
	}
}

#ifdef CONFIG_PROC_FS
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;
	off_t pos=0;
	off_t begin=0;

	addrconf_lock();

	for (i=0; i < IN6_ADDR_HSIZE; 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->dev->ifindex,
				       ifp->prefix_len,
				       ifp->scope,
				       ifp->flags,
				       ifp->idev->dev->name);
			pos=begin+len;
			if(pos<offset) {
				len=0;
				begin=pos;
			}
			if(pos>offset+length)
				goto done;
		}
	}

done:
	addrconf_unlock();

	*start=buffer+(offset-begin);
	len-=(offset-begin);
	if(len>length)
		len=length;
	if(len<0)
		len=0;
	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
};
#endif	/* CONFIG_PROC_FS */

/*
 *	Periodic address status verification
 */

void addrconf_verify(unsigned long foo)
{
	struct inet6_ifaddr *ifp;
	unsigned long now;
	unsigned long next = ADDR_CHECK_FREQUENCY/HZ;
	int i;
#ifdef CONFIG_IPV6_ACONF_DEBUG
	char abuf[128];
#endif

	del_timer(&addr_chk_timer);

	if (atomic_read(&addr_list_lock)) {
		addr_chk_timer.expires = jiffies + 1*HZ;
		add_timer(&addr_chk_timer);
		return;
	}

	now = jiffies;

	for (i=0; i < IN6_ADDR_HSIZE; i++) {
		for (ifp=inet6_addr_lst[i]; ifp;) {
#ifdef CONFIG_IPV6_ACONF_DEBUG
			in6_ntop(&ifp->addr, abuf);
#endif
			if (ifp->flags & ADDR_INVALID) {
				struct inet6_ifaddr *bp = ifp;
#ifdef CONFIG_IPV6_ACONF_DEBUG
				ADBG3((KERN_DEBUG
					"addrconf_verify(): ifp->addr=%s, ADDR_INVALID\n",
					abuf));
#endif
				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;

				bp = ifp;
				ifp= ifp->lst_next;
				
				if (age >= bp->valid_lft) {
					/* jiffies - bp->tstamp > age > bp->valid_lft */
#ifdef CONFIG_IPV6_ACONF_DEBUG
					ADBG3((KERN_DEBUG
						"addrconf_verify(): ifp->addr=%s, prefered_lft(%u) <= age(%lu) < valid_lft(%u), next=%lu\n",
						abuf, bp->prefered_lft, age, bp->valid_lft, next));
#endif
					ipv6_del_addr(bp);
				} else if (age >= bp->prefered_lft) {
					/* bp->prefered_lft <= bp->valid_lft */
					bp->flags |= ADDR_DEPRECATED;
					ipv6_ifa_notify(0, bp);
					if (next > bp->valid_lft - age)
						next = bp->valid_lft - age;
#ifdef CONFIG_IPV6_ACONF_DEBUG
					ADBG3((KERN_DEBUG
						"addrconf_verify(): ifp->addr=%s, age(%lu) <= prefered_lft(%u) (<= valid_lft(%u)), next=%lu\n",
						abuf, age, bp->prefered_lft, bp->valid_lft, next));
#endif
				} else {
					if (next > bp->prefered_lft - age)
						next = bp->prefered_lft - age;
				}

				continue;
			}
#ifdef CONFIG_IPV6_ACONF_DEBUG
			ADBG3((KERN_DEBUG
				"addrconf_verify(): ifp->addr=%s, ADDR_PERMANENT\n",
				abuf));
#endif
			ifp = ifp->lst_next;
		}
	}
	next *= HZ;
	if (jiffies - now > HZ/2) {
		ADBG1((KERN_WARNING "addrconf_verify(): too slow; jiffies - now = %lu\n",
			jiffies - now));
	}
#ifdef CONFIG_IPV6_ACONF_DEBUG
	/* fire timer once a sec at most; now + next > jiffies + HZ  */
	ADBG3((KERN_DEBUG
		"addrconf_verify(): new timer: %lu (jiffies = %lu, after %lu sec)\n",
		next < jiffies - now + HZ ? jiffies + HZ : now + next,
		jiffies,
		(next < jiffies - now + HZ ? HZ : now + next - jiffies)/HZ
	));
#endif

	addr_chk_timer.expires = next < jiffies - now + HZ ? jiffies + HZ : now + next;
	add_timer(&addr_chk_timer);
}

#ifdef CONFIG_RTNETLINK

static int
inet6_rtm_deladdr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
	struct rtattr **rta = arg;
	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
	struct in6_addr *pfx;

	pfx = NULL;
	if (rta[IFA_ADDRESS-1]) {
		if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx))
			return -EINVAL;
		pfx = RTA_DATA(rta[IFA_ADDRESS-1]);
	}
	if (rta[IFA_LOCAL-1]) {
		if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx)))
			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);
}

static int
inet6_rtm_newaddr(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{
	struct rtattr  **rta = arg;
	struct ifaddrmsg *ifm = NLMSG_DATA(nlh);
	struct in6_addr *pfx;

	pfx = NULL;
	if (rta[IFA_ADDRESS-1]) {
		if (RTA_PAYLOAD(rta[IFA_ADDRESS-1]) < sizeof(*pfx))
			return -EINVAL;
		pfx = RTA_DATA(rta[IFA_ADDRESS-1]);
	}
	if (rta[IFA_LOCAL-1]) {
		if (pfx && memcmp(pfx, RTA_DATA(rta[IFA_LOCAL-1]), sizeof(*pfx)))
			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);
}

static int inet6_fill_ifaddr(struct sk_buff *skb, struct inet6_ifaddr *ifa,
			     u32 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_trim(skb, b - skb->data);
	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_ATOMIC);
	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);
		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,			NULL,			},
	{ NULL,			NULL,			},

	{ inet6_rtm_newaddr,	NULL,			},
	{ inet6_rtm_deladdr,	NULL,			},
	{ NULL,			inet6_dump_ifaddr,	},
	{ NULL,			NULL,			},

	{ inet6_rtm_newroute,	NULL,			},
	{ inet6_rtm_delroute,	NULL,			},
	{ inet6_rtm_getroute,	inet6_dump_fib,		},
	{ 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;
	}
}

#ifdef CONFIG_SYSCTL

static
int addrconf_sysctl_forward(ctl_table *ctl, int write, struct file * filp,
			   void *buffer, size_t *lenp)
{
	int *valp = ctl->data;
	int val = *valp;
	int ret;

	ret = proc_dointvec(ctl, write, filp, buffer, lenp);

	if (write && *valp != val) {
		if (valp == &ipv6_devconf.forwarding)
			addrconf_forward_change();
		else if (valp != &ipv6_devconf_dflt.forwarding)
			dev_forward_change((struct inet6_dev *)ctl->extra1);
#ifndef CONFIG_IPV6_EN_DFLT 
		if (valp != &ipv6_devconf_dflt.forwarding && *valp) {
			ADBG3((KERN_DEBUG "Purge default routes(0)\n"));
			start_bh_atomic();
			rt6_purge_dflt_routers(0);
			end_bh_atomic();
		}
#endif
	}

        return ret;
}

static struct addrconf_sysctl_table
{
	struct ctl_table_header *sysctl_header;
	ctl_table addrconf_vars[17];
	ctl_table addrconf_dev[2];
	ctl_table addrconf_conf_dir[2];
	ctl_table addrconf_proto_dir[2];
	ctl_table addrconf_root_dir[2];
} addrconf_sysctl = {
	NULL,
        {{NET_IPV6_FORWARDING, "forwarding",
         &ipv6_devconf.forwarding, sizeof(int), 0644, NULL,
         &addrconf_sysctl_forward},

	{NET_IPV6_HOP_LIMIT, "hop_limit",
         &ipv6_devconf.hop_limit, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_MTU, "mtu",
         &ipv6_devconf.mtu6, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_ACCEPT_RA, "accept_ra",
         &ipv6_devconf.accept_ra, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_ACCEPT_REDIRECTS, "accept_redirects",
         &ipv6_devconf.accept_redirects, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_AUTOCONF, "autoconf",
         &ipv6_devconf.autoconf, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_DAD_TRANSMITS, "dad_transmits",
         &ipv6_devconf.dad_transmits, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_RTR_SOLICITS, "router_solicitations",
         &ipv6_devconf.rtr_solicits, sizeof(int), 0644, NULL,
         &proc_dointvec},

	{NET_IPV6_RTR_SOLICIT_INTERVAL, "router_solicitation_interval",
         &ipv6_devconf.rtr_solicit_interval, sizeof(int), 0644, NULL,
         &proc_dointvec_jiffies},

	{NET_IPV6_RTR_SOLICIT_DELAY, "router_solicitation_delay",
         &ipv6_devconf.rtr_solicit_delay, sizeof(int), 0644, NULL,
         &proc_dointvec_jiffies},

#ifdef CONFIG_IPV6_NODEINFO
	{NET_IPV6_ACCEPT_NI, "accept_ni",
	 &ipv6_devconf.accept_ni, sizeof(int), 0644, NULL,
	 &proc_dointvec},
#endif

#ifdef CONFIG_IPV6_PRIVACY
	{NET_IPV6_USE_TEMPADDR, "use_tempaddr",
	 &ipv6_devconf.use_tempaddr, sizeof(int), 0644, NULL,
	 &proc_dointvec},

	{NET_IPV6_TEMP_VALID_LFT, "temp_valid_lft",
	 &ipv6_devconf.temp_valid_lft, sizeof(int), 0644, NULL,
	 &proc_dointvec},

	{NET_IPV6_TEMP_PREFERED_LFT, "temp_prefered_lft",
	 &ipv6_devconf.temp_prefered_lft, sizeof(int), 0644, NULL,
	 &proc_dointvec},

	{NET_IPV6_REGEN_MAX_RETRY, "regen_max_retry",
	 &ipv6_devconf.regen_max_retry, sizeof(int), 0644, NULL,
	 &proc_dointvec},

	{NET_IPV6_MAX_DESYNC_FACTOR, "max_desync_factor",
	 &ipv6_devconf.max_desync_factor, sizeof(int), 0644, NULL,
	 &proc_dointvec},
#endif
	{0}},

	{{NET_PROTO_CONF_ALL, "all", NULL, 0, 0555, addrconf_sysctl.addrconf_vars},{0}},
	{{NET_IPV6_CONF, "conf", NULL, 0, 0555, addrconf_sysctl.addrconf_dev},{0}},
	{{NET_IPV6, "ipv6", NULL, 0, 0555, addrconf_sysctl.addrconf_conf_dir},{0}},
	{{CTL_NET, "net", NULL, 0, 0555, addrconf_sysctl.addrconf_proto_dir},{0}}
};

static void addrconf_sysctl_register(struct inet6_dev *idev, struct ipv6_devconf *p)
{
	int i;
	struct device *dev = idev ? idev->dev : NULL;
	struct addrconf_sysctl_table *t;

	t = kmalloc(sizeof(*t), GFP_KERNEL);
	if (t == NULL)
		return;
	memcpy(t, &addrconf_sysctl, sizeof(*t));
	for (i=0; t->addrconf_vars[i].ctl_name; i++) {
		t->addrconf_vars[i].data += (char*)p - (char*)&ipv6_devconf;
		t->addrconf_vars[i].de = NULL;
		t->addrconf_vars[i].extra1 = idev; /* embedded; no ref */
	}
	if (dev) {
		t->addrconf_dev[0].procname = dev->name;
		t->addrconf_dev[0].ctl_name = dev->ifindex;
	} else {
		t->addrconf_dev[0].procname = "default";
		t->addrconf_dev[0].ctl_name = NET_PROTO_CONF_DEFAULT;
	}
	t->addrconf_dev[0].child = t->addrconf_vars;
	t->addrconf_dev[0].de = NULL;
	t->addrconf_conf_dir[0].child = t->addrconf_dev;
	t->addrconf_conf_dir[0].de = NULL;
	t->addrconf_proto_dir[0].child = t->addrconf_conf_dir;
	t->addrconf_proto_dir[0].de = NULL;
	t->addrconf_root_dir[0].child = t->addrconf_proto_dir;
	t->addrconf_root_dir[0].de = NULL;

	t->sysctl_header = register_sysctl_table(t->addrconf_root_dir, 0);
	if (t->sysctl_header == NULL)
		kfree(t);
	else
		p->sysctl = t;
}

static void addrconf_sysctl_unregister(struct ipv6_devconf *p)
{
	if (p->sysctl) {
		struct addrconf_sysctl_table *t = p->sysctl;
		p->sysctl = NULL;
		unregister_sysctl_table(t->sysctl_header);
		kfree(t);
	}
}


#endif

/*
 *	Init / cleanup code
 */

__initfunc(void addrconf_init(void))
{
#ifdef CONFIG_IPV6_PRIVACY
	unsigned long regen_advance;
#endif
#ifdef MODULE
	struct device *dev;
#endif
#ifdef CONFIG_IPV6_PRIVACY
	regen_advance = ipv6_devconf.regen_max_retry * ipv6_devconf.dad_transmits * HZ; /* XXX */
	get_random_bytes(&desync_factor, sizeof(desync_factor));
	desync_factor %= MAX_DESYNC_FACTOR * HZ < TEMP_VALID_LIFETIME * HZ - regen_advance ?
				MAX_DESYNC_FACTOR * HZ : TEMP_VALID_LIFETIME * HZ - regen_advance;
#endif
#ifdef MODULE
	/* This takes sense only during module load. */

	for (dev = dev_base; dev; dev = dev->next) {
		if (!(dev->flags&IFF_UP))
			continue;

		switch (dev->type) {
		case ARPHRD_LOOPBACK:	
			init_loopback(dev);
			break;
		case ARPHRD_ETHER:	
			addrconf_dev_config(dev);
			break;
		default:
			/* Ignore all other */
		}
	}
#endif
	
#ifdef CONFIG_PROC_FS
	proc_net_register(&iface_proc_entry);
#endif
	
	addrconf_verify(0);
#ifdef CONFIG_RTNETLINK
	rtnetlink_links[PF_INET6] = inet6_rtnetlink_table;
#endif
#ifdef CONFIG_SYSCTL
	addrconf_sysctl.sysctl_header =
		register_sysctl_table(addrconf_sysctl.addrconf_root_dir, 0);
	addrconf_sysctl_register(NULL, &ipv6_devconf_dflt);
#endif
}

#ifdef MODULE
void addrconf_cleanup(void)
{
 	struct inet6_dev *idev;
 	struct inet6_ifaddr *ifa;
	int i;

#ifdef CONFIG_RTNETLINK
	rtnetlink_links[PF_INET6] = NULL;
#endif
#ifdef CONFIG_SYSCTL
	addrconf_sysctl_unregister(&ipv6_devconf_dflt);
	addrconf_sysctl_unregister(&ipv6_devconf);
#endif

	del_timer(&addr_chk_timer);

	/*
	 *	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);
		}
	}

	start_bh_atomic();
	/*
	 *	clean addr_list
	 */

	for (i=0; i < IN6_ADDR_HSIZE; i++) {
		for (ifa=inet6_addr_lst[i]; ifa; ) {
			struct inet6_ifaddr *bifa;

			bifa = ifa;
			ifa = ifa->lst_next;
			ADBG1((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);
#endif
}
#endif	/* MODULE */
