/*
 *      Mobile-node functionality
 *
 *      Authors:
 *      Sami Kivisaari          <skivisaa@cc.hut.fi>
 *
 *      $Id: s.mn.c 1.160 02/12/20 09:30:48+02:00 antti@jon.mipl.mediapoli.com $
 *
 *      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/autoconf.h>
#include <linux/sched.h>
#include <linux/ipv6.h>
#include <linux/net.h>
#include <linux/init.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/if_arp.h>
#include <linux/ipsec.h>
#include <linux/notifier.h>
#include <linux/list.h>
#include <linux/route.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>
#include <linux/tqueue.h>
#include <linux/proc_fs.h>

#include <asm/uaccess.h>

#include <net/ipv6.h>
#include <net/addrconf.h>
#include <net/ndisc.h>
#include <net/ipv6_tunnel.h>
#include <net/ip6_route.h>
#include <net/mipglue.h>

#include "util.h"
#include "mdetect.h"
#include "bul.h"
#include "mobhdr.h"
#include "hashlist.h"
#include "debug.h"
#include "mn.h"
#include "mipv6_icmp.h"
#include "multiaccess_ctl.h"
#include "prefix.h"
#include "tunnel.h"
#include "stats.h"

#define MIPV6_BUL_SIZE 128
#define EXPIRE_INFINITE 0xffffffff

static LIST_HEAD(mn_info_list);
/* Lock for list of MN infos */
rwlock_t mn_info_lock = RW_LOCK_UNLOCKED;

static spinlock_t ifrh_lock = SPIN_LOCK_UNLOCKED;

static LIST_HEAD(ifrh_list);

struct ifr_holder {
	struct list_head list;
	struct in6_ifreq ifr;
	int old_ifi;
};

static struct tq_struct mv_home_addr_task;

/* Whether all parts are initialized, from mipv6.c */
extern int mipv6_is_initialized;

/* Determines whether manually configured home addresses are preferred as 
 * source addresses over dynamically configured ones
 */
int mipv6_use_preconfigured_hoaddr = 1; 

/* Determines whether home addresses, which are at home are preferred as 
 * source addresses over other home addresses
 */
int mipv6_use_topol_corr_hoaddr = 0;

/*  Defined in ndisc.c of IPv6 module */
extern void ndisc_send_na(
	struct net_device *dev, struct neighbour *neigh,
	struct in6_addr *daddr, struct in6_addr *solicited_addr,
	int router, int solicited, int override, int inc_opt);

static spinlock_t icmpv6_id_lock = SPIN_LOCK_UNLOCKED;
static __u16 icmpv6_id = 0;

static inline __u16 mipv6_get_dhaad_id(void)
{
	__u16 ret;
	spin_lock_bh(&icmpv6_id_lock);
	ret = ++icmpv6_id;
	spin_unlock_bh(&icmpv6_id_lock);
	return ret;
}

/** 
 * mipv6_mn_get_info - Returns mn_info for a home address
 * @haddr: home address of MN
 *
 * Returns mn_info on success %NULL otherwise.  Caller MUST hold
 * @mn_info_lock (read or write).
 **/
struct mn_info *mipv6_mn_get_info(struct in6_addr *haddr)
{
	struct list_head *lh;
	struct mn_info *minfo;

	DEBUG_FUNC();

	if (!haddr)
		return NULL;

	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		if (!ipv6_addr_cmp(&minfo->home_addr, haddr)) {
			return minfo;
		}
	}
	return NULL;
}

/** 
 * mipv6_mn_add_info - Adds a new home info for MN
 * @home_addr:  Home address of MN, must be set
 * @plen: prefix length of the home address, must be set
 * @isathome : home address at home
 * @lifetime: lifetime of the home address, 0 is infinite
 * @ha: home agent for the home address
 * @ha_plen: prefix length of home agent's address, can be zero 
 * @ha_lifetime: Lifetime of the home address, 0 is infinite
 *
 * The function adds a new home info entry for MN, allowing it to
 * register the home address with the home agent.  Starts home
 * registration process.  If @ha is %ADDRANY, DHAAD is performed to
 * find a home agent.  Returns 0 on success, a negative value
 * otherwise.  Caller MUST NOT hold @mn_info_lock or
 * @addrconf_hash_lock.
 **/
void mipv6_mn_add_info(struct in6_addr *home_addr, int plen, int isathome,
		       unsigned long lifetime, struct in6_addr *ha, 
		       int ha_plen, unsigned long ha_lifetime, int man_conf)
{
	struct mn_info *minfo = NULL, minfo_init;
	struct inet6_ifaddr *ifp;
	struct in6_addr coa;

	DEBUG_FUNC();

	write_lock_bh(&mn_info_lock);
	if ((minfo = mipv6_mn_get_info(home_addr)) == NULL) {
	        minfo = kmalloc(sizeof(struct mn_info), GFP_ATOMIC);
		if (!minfo) {
			write_unlock_bh(&mn_info_lock);
			return;
		}
		memset(minfo, 0, sizeof(struct mn_info));
		minfo->lock = SPIN_LOCK_UNLOCKED;
		list_add(&minfo->list, &mn_info_list);
	}
	
	ipv6_addr_copy(&minfo->home_addr, home_addr);

	if (ha)
		ipv6_addr_copy(&minfo->ha, ha);
	minfo->home_plen = plen;       
	ifp = ipv6_get_ifaddr(home_addr, NULL);

	if (ifp != NULL) {
		if(!lifetime)
			lifetime = ifp->valid_lft;
		minfo->ifindex = ifp->idev->dev->ifindex;
		in6_ifa_put(ifp);
	} else {
		lifetime = 0;
		minfo->ifindex = 0;
	}
	minfo->home_addr_expires = jiffies + lifetime * HZ;
	/* manual configuration flag cannot be unset by dynamic updates 
	 *  from prefix advertisements
	 */
	if (!minfo->man_conf) minfo->man_conf = man_conf; 
	minfo->is_at_home = isathome;

	memcpy(&minfo_init, minfo, sizeof(minfo_init));
	write_unlock_bh(&mn_info_lock);

	mipv6_get_care_of_address(&minfo_init.home_addr, &coa); 
	init_home_registration(&minfo_init.home_addr, &coa);
}

/** 
 * mipv6_mninfo_get_by_home - Returns mn_info for a home address
 * @haddr: home address of MN
 *
 * Returns mn_info on success %NULL otherwise.  Caller MUST hold
 * @mn_info_lock (read or write).
 **/
struct mn_info *mipv6_mninfo_get_by_home(struct in6_addr *haddr)
{
	struct list_head *lh;
	struct mn_info *minfo;

	DEBUG_FUNC();

	if (!haddr)
		return NULL;

	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		if (!ipv6_addr_cmp(&minfo->home_addr, haddr)) {
			spin_unlock(&minfo->lock);
			return minfo;
		}
		spin_unlock(&minfo->lock);
	}
	return NULL;
}

/**
 * mipv6_mninfo_get_by_ha - Lookup mn_info with Home Agent address
 * @home_agent: Home Agent address
 *
 * Searches for a mn_info entry with @ha set to @home_agent.  You MUST
 * hold @mn_info_lock when calling this function.  Returns pointer to
 * mn_info entry or %NULL on failure.
 **/
struct mn_info *mipv6_mninfo_get_by_ha(struct in6_addr *home_agent)
{
	struct list_head *lh;
	struct mn_info *minfo;

	if (!home_agent)
		return NULL;

	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		if (!ipv6_addr_cmp(&minfo->ha, home_agent)) {
			spin_unlock(&minfo->lock);
			return minfo;
		}
		spin_unlock(&minfo->lock);
	}
	return NULL;
}

/**
 * mipv6_mninfo_get_by_id - Lookup mn_info with id
 * @id: DHAAD identifier
 *
 * Searches for a mn_info entry with @dhaad_id set to @id.  You MUST
 * hold @mn_info_lock when calling this function.  Returns pointer to
 * mn_info entry or %NULL on failure.
 **/
struct mn_info *mipv6_mninfo_get_by_id(unsigned short id)
{
	struct list_head *lh;
	struct mn_info *minfo = 0;

	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		if (minfo->dhaad_id == id) {
			spin_unlock(&minfo->lock);
			return minfo;
		}
		spin_unlock(&minfo->lock);
	}
	return NULL;
}

/** 
 * mipv6_mninfo_get_by_index - gets mn_info at index  
 * @info: pointer to memory where mn_info will be copied 
 * @index: index of entry 
 *
 * Returns 0 on success and -1 on failure.  Caller MUST NOT hold
 * @mn_info_lock.
 **/
int mipv6_mninfo_get_by_index(int index, struct mn_info *info)
{
	struct list_head *lh;
	struct mn_info *minfo; 
	int i = 0;

	read_lock_bh(&mn_info_lock);
	list_for_each(lh, &mn_info_list) {
		if (index == i++) {
			minfo = list_entry(lh, struct mn_info, list);
			memcpy(info, minfo, sizeof(struct mn_info));
			read_unlock_bh(&mn_info_lock);
			return 0;
		}
	}
	read_unlock_bh(&mn_info_lock);
	return -1;
}

/** 
 * mipv6_mninfo_add - Adds a new home info for MN
 * @ifindex: Interface for home address
 * @home_addr:  Home address of MN, must be set
 * @plen: prefix length of the home address, must be set
 * @isathome : home address at home
 * @lifetime: lifetime of the home address, 0 is infinite
 * @ha: home agent for the home address
 * @ha_plen: prefix length of home agent's address, can be zero 
 * @ha_lifetime: Lifetime of the home address, 0 is infinite
 *
 * The function adds a new home info entry for MN, allowing it to
 * register the home address with the home agent.  Starts home
 * registration process.  If @ha is %ADDRANY, DHAAD is performed to
 * find a home agent.  Returns 0 on success, a negative value
 * otherwise.  Caller MUST NOT hold @mn_info_lock or
 * @addrconf_hash_lock.
 **/
void mipv6_mninfo_add(int ifindex, struct in6_addr *home_addr, int plen, 
		      int isathome, unsigned long lifetime, struct in6_addr *ha, 
		      int ha_plen, unsigned long ha_lifetime, int man_conf)
{
	struct mn_info *minfo;
	struct in6_addr coa;

	DEBUG_FUNC();

	write_lock_bh(&mn_info_lock);
	if ((minfo = mipv6_mninfo_get_by_home(home_addr)) == NULL) {
	        minfo = kmalloc(sizeof(struct mn_info), GFP_ATOMIC);
		if (!minfo) {
			write_unlock_bh(&mn_info_lock);
			return;
		}
		memset(minfo, 0, sizeof(struct mn_info));
		spin_lock_init(&minfo->lock);
	}
	
	ipv6_addr_copy(&minfo->home_addr, home_addr);

	if (ha)
		ipv6_addr_copy(&minfo->ha, ha);
	minfo->home_plen = plen;       
	minfo->ifindex = ifindex;

	/* TODO: we should get home address lifetime from somewhere */
	/* minfo->home_addr_expires = jiffies + lifetime * HZ; */

	/* manual configuration flag cannot be unset by dynamic updates 
	 *  from prefix advertisements
	 */
	if (!minfo->man_conf) minfo->man_conf = man_conf; 
	minfo->is_at_home = isathome;

	list_add(&minfo->list, &mn_info_list);
	write_unlock_bh(&mn_info_lock);

	mipv6_get_care_of_address(home_addr, &coa); 
	init_home_registration(home_addr, &coa);
}

/**
 * mipv6_mninfo_del - Delete home info for MN 
 * @home_addr : Home address or prefix 
 * @del_dyn_only : Delete only dynamically created home entries 
 *
 *
 * Deletes every mn_info entry that matches the first plen bits of
 * @home_addr.  Returns number of deleted entries on success and a
 * negative value otherwise.  Caller MUST NOT hold @mn_info_lock.
 **/
int mipv6_mninfo_del(struct in6_addr *home_addr, int del_dyn_only)
{
	struct list_head *lh, *next;
	struct mn_info *minfo;
	int ret = -1;
	if (!home_addr)
		return -1;

	write_lock(&mn_info_lock);

	list_for_each_safe(lh, next, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		if (!mipv6_prefix_compare(&minfo->home_addr, home_addr, 
					  minfo->home_plen) 
		    && ((!minfo->man_conf && del_dyn_only) || !del_dyn_only)) {
			list_del(&minfo->list);
			kfree(minfo);
			ret++;
		}
	}
	write_unlock(&mn_info_lock);
	return ret;
}

/* Delete mninfo's by searching on ha's */
void mipv6_delete_minfo_by_ha(struct in6_addr *addr)
{
	struct mn_info *minfo;

        write_lock_bh(&mn_info_lock);
	while ((minfo = mipv6_mninfo_get_by_ha(addr))) {
		list_del(&minfo->list);
	}
        write_unlock_bh(&mn_info_lock);
}

void mipv6_mn_set_home(int ifindex, struct in6_addr *homeaddr, int plen,
		       struct in6_addr *homeagent, int ha_plen)
{
	mipv6_mninfo_add(ifindex, homeaddr, plen, 0, 0, 
			 homeagent, ha_plen, 0, 1);
}

/**
 * mipv6_mn_is_home_addr - Determines if addr is node's home address
 * @addr: IPv6 address
 *
 * Returns 1 if addr is node's home address.  Otherwise returns zero.
 **/
int mipv6_mn_is_home_addr(struct in6_addr *addr)
{
	int ret = 0;

	if (addr == NULL) {
		DEBUG(DBG_CRITICAL, "Null argument");
		return -1;
	}
	read_lock_bh(&mn_info_lock);
	if (mipv6_mninfo_get_by_home(addr))
		ret = 1;
	read_unlock_bh(&mn_info_lock);

	return (ret);
}


/** 
 * mipv6_mn_is_at_home - determine if node is home for a home address
 * @home_addr : home address of MN
 *
 * Returns 1 if home address in question is in the home network, 0
 * otherwise.  Caller MUST NOT not hold @mn_info_lock.
 **/ 
int mipv6_mn_is_at_home(struct in6_addr *home_addr)
{
	struct mn_info *minfo;
	int ret = 0;
	read_lock_bh(&mn_info_lock);
	if ((minfo = mipv6_mninfo_get_by_home(home_addr)) != NULL) {
		spin_lock(&minfo->lock);
		ret = (minfo->is_at_home == MN_AT_HOME);
		spin_unlock(&minfo->lock);
	}
	read_unlock_bh(&mn_info_lock);
	return ret;
}	

/**
 * mipv6_mn_get_homeaddr - Get node's home address
 * @home_addr: buffer to store home address
 *
 * Stores Mobile Node's home address in the given space.  Returns
 * prefix length of home address.  Negative return value means failure
 * to retrieve home address.  Caller MUST NOT hold @mn_info_lock.
 **/
int mipv6_mn_get_homeaddr(struct in6_addr *home_addr)
{
	struct mn_info *minfo;
	struct list_head *p;
	int plen = 0;

	if (!home_addr) return -1;

	read_lock_bh(&mn_info_lock);

	if (list_empty(&mn_info_list)) {
		read_unlock_bh(&mn_info_lock);
		return -1;
	}

	p = mn_info_list.next;

	if (p != NULL) {
		minfo = list_entry(p, struct mn_info, list);
		ipv6_addr_copy(home_addr, &minfo->home_addr);
		spin_lock(&minfo->lock);
		plen = minfo->home_plen;
		spin_unlock(&minfo->lock);
	}
	read_unlock_bh(&mn_info_lock);

	return plen;
}

void mipv6_get_saddr_hook(struct inet6_ifaddr *ifp,
			  struct in6_addr *homeaddr)
{
	int found = 0, reiter = 0;
	struct list_head *lh;
	struct mn_info *minfo = NULL;
	struct in6_addr coa;

	read_lock_bh(&mn_info_lock);
restart:
	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		if ((ipv6_addr_scope(homeaddr) != ipv6_addr_scope(&minfo->home_addr)) 
		    || ipv6_chk_addr(&minfo->home_addr, NULL) == 0)
			continue; 

		spin_lock(&minfo->lock);
		if (minfo->is_at_home == MN_AT_HOME || minfo->has_home_reg) {
			if ((mipv6_use_topol_corr_hoaddr && 
			     minfo->is_at_home == MN_AT_HOME) || 
			    (mipv6_use_preconfigured_hoaddr && 
			     minfo->man_conf) ||
			    (!(mipv6_use_preconfigured_hoaddr || 
			       mipv6_use_topol_corr_hoaddr) || reiter)) {
				spin_unlock(&minfo->lock);
				ipv6_addr_copy(homeaddr, &minfo->home_addr);
				found = 1;
				break;
			}
		}
		spin_unlock(&minfo->lock);
	}
	if (!found && !reiter) {
		reiter = 1;
		goto restart;
	}

	if (!found && minfo && 
	    !mipv6_get_care_of_address(&minfo->home_addr, &coa)) {
		ipv6_addr_copy(homeaddr, &coa); 
	}
	read_unlock_bh(&mn_info_lock);

	DEBUG(DBG_INFO, "Source address selection:  %x:%x:%x:%x:%x:%x:%x:%x", 
	      NIPV6ADDR(homeaddr));
	return;
}

static void mv_home_addr(void *arg)
{
	mm_segment_t oldfs;
	int err = 0, new_if = 0, new_plen = 0;
	struct list_head *lh, *next;
	struct ifr_holder *ifrh;

	DEBUG(DBG_INFO, "mipv6 move home address task");

	oldfs = get_fs(); set_fs(KERNEL_DS);
	spin_lock_bh(&ifrh_lock);
	list_for_each_safe(lh, next, &ifrh_list) {
		ifrh = list_entry(lh, struct ifr_holder, list);
		/* need to inform that we are doing this inside kernel */
		if (ifrh->old_ifi) {
			new_if = ifrh->ifr.ifr6_ifindex;
			ifrh->ifr.ifr6_ifindex = ifrh->old_ifi;
			new_plen = ifrh->ifr.ifr6_prefixlen;
			ifrh->ifr.ifr6_prefixlen = 128;
			err = addrconf_del_ifaddr(&ifrh->ifr); 
			ifrh->ifr.ifr6_prefixlen = new_plen;
			ifrh->ifr.ifr6_ifindex = new_if;
		}
		if(!err) {
			err = addrconf_add_ifaddr(&ifrh->ifr);
		}
		list_del(&ifrh->list);
		kfree(ifrh);
	}
	spin_unlock_bh(&ifrh_lock);
	set_fs(oldfs);

	if (err < 0)
		DEBUG(DBG_WARNING, "adding of home address to a new interface failed %d", err);
	else
		DEBUG(DBG_WARNING, "adding of home address to a new interface OK");
}

struct dhaad_halist {
	struct list_head list;
	struct in6_addr addr;
	int retry;
};

/* clear all has from candidate list.  do this when a new dhaad reply
 * is received. */
int mipv6_mn_flush_ha_candidate(struct list_head *ha)
{
	struct list_head *p, *tmp;
	struct dhaad_halist *e;

	list_for_each_safe(p, tmp, ha) {
		e = list_entry(p, struct dhaad_halist, list);
		list_del(p);
		kfree(e);
		e = NULL;
	}
	return 0;
}

/* add new ha to candidates. only done when dhaad reply is received. */
int mipv6_mn_add_ha_candidate(struct list_head *ha, struct in6_addr *addr)
{
	struct dhaad_halist *e;

	e = kmalloc(sizeof(*e), GFP_ATOMIC);
	memset(e, 0, sizeof(*e));
	ipv6_addr_copy(&e->addr, addr);

	list_add_tail(&e->list, ha);
	return 0;
}

#define MAX_RETRIES_PER_HA 3

/* get next ha candidate.  this is done when dhaad reply has been
 * received and we want to register with the best available ha. */
int mipv6_mn_get_ha_candidate(struct list_head *ha, struct in6_addr *addr)
{
	struct list_head *p;

	list_for_each(p, ha) {
		struct dhaad_halist *e;
		e = list_entry(p, typeof(*e), list);
		if (e->retry >= 0 && e->retry < MAX_RETRIES_PER_HA) {
			ipv6_addr_copy(addr, &e->addr);
			return 0;
		}
	}
	return -1;
}

/* change candidate status.  if registration with ha fails, we
 * increase retry for ha candidate.  if retry is >= 3 we set it to -1
 * (failed), do get_ha_candidate() again */
int mipv6_mn_try_ha_candidate(struct list_head *ha, struct in6_addr *addr)
{
	struct list_head *p;

	list_for_each(p, ha) {
		struct dhaad_halist *e;
		e = list_entry(p, typeof(*e), list);
		if (ipv6_addr_cmp(addr, &e->addr) == 0) {
			if (e->retry >= MAX_RETRIES_PER_HA) e->retry = -1;
			else if (e->retry >= 0) e->retry++;
			return 0;
		}
	}
	return -1;
}

/**
 * mipv6_mn_get_bulifetime - Get lifetime for a binding update
 * @home_addr: home address for BU 
 * @coa: care-of address for BU
 * @flags: flags used for BU 
 *
 * Returns maximum lifetime for BUs determined by the lifetime of
 * care-of address and the lifetime of home address.
 **/
__u32 mipv6_mn_get_bulifetime(struct in6_addr *home_addr, struct in6_addr *coa,
			      __u8 flags)
{
	
	__u32 lifetime; 
	
	struct inet6_ifaddr * ifp_coa, *ifp_hoa;

	ifp_hoa = ipv6_get_ifaddr(home_addr, NULL);
	if(!ifp_hoa) {
		DEBUG(DBG_ERROR, "home address missing");
		return 0;
	}
	ifp_coa = ipv6_get_ifaddr(coa, NULL);
	if (!ifp_coa) {
		in6_ifa_put(ifp_hoa);
		DEBUG(DBG_ERROR, "care-of address missing");
		return 0;
	}
	if (flags & MIPV6_BU_F_HOME)
		lifetime = HA_BU_DEF_LIFETIME;
	else
		lifetime = CN_BU_DEF_LIFETIME;

	if (!(ifp_hoa->flags & IFA_F_PERMANENT)){
		if (ifp_hoa->valid_lft)
			lifetime = min_t(__u32, lifetime, ifp_hoa->valid_lft);
		else
			DEBUG(DBG_ERROR, "Zero lifetime for home address");
	}
	if (!(ifp_coa->flags & IFA_F_PERMANENT)) {
		if(ifp_coa->valid_lft)
			lifetime = min_t(__u32, lifetime, ifp_coa->valid_lft);
		else
			DEBUG(DBG_ERROR, 
			      "Zero lifetime for care-of address");
	}
	in6_ifa_put(ifp_hoa);
	in6_ifa_put(ifp_coa);
	DEBUG(DBG_INFO, "Lifetime for binding is %ld", lifetime);
	return lifetime;
}

static int 
mipv6_mn_tnl_rcv_send_bu_hook(struct ipv6_tnl *t, struct sk_buff *skb)
{
	struct ipv6hdr *inner = (struct ipv6hdr *)skb->h.raw;
	struct ipv6hdr *outer = skb->nh.ipv6h; 
	struct mn_info *minfo = NULL;
	struct inet6_skb_parm *opt;
	__u32 lifetime;

	DEBUG_FUNC();

	if (!is_mipv6_tnl(t))
		return IPV6_TNL_ACCEPT;

	read_lock(&mn_info_lock);
	minfo = mipv6_mninfo_get_by_home(&inner->daddr);

	if (!minfo) {
		DEBUG(DBG_WARNING, "MN info missing");
		read_unlock(&mn_info_lock);
		return IPV6_TNL_ACCEPT;
	}
	DEBUG(DBG_DATADUMP, "MIPV6 MN: Received a tunneled IPv6 packet"
	      " to %x:%x:%x:%x:%x:%x:%x:%x,"
	      " from %x:%x:%x:%x:%x:%x:%x:%x with\n tunnel header"
	      "daddr: %x:%x:%x:%x:%x:%x:%x:%x,"
	      "saddr: %x:%x:%x:%x:%x:%x:%x:%x", 
	       NIPV6ADDR(&inner->daddr), NIPV6ADDR(&inner->saddr),
	       NIPV6ADDR(&outer->daddr), NIPV6ADDR(&outer->saddr));

	spin_lock(&minfo->lock);

	/* We don't send bus in response to all tunneled packets */

        if (!ipv6_addr_cmp(&minfo->ha, &inner->saddr)) {
		spin_unlock(&minfo->lock);
		read_unlock(&mn_info_lock);
                DEBUG(DBG_ERROR, "HA BUG: Received a tunneled packet "
		      "originally sent by home agent, not sending BU");
		return IPV6_TNL_ACCEPT;
        }
	if (ipv6_addr_cmp(&minfo->ha, &outer->saddr)) {
		spin_unlock(&minfo->lock);
		read_unlock(&mn_info_lock);
		DEBUG(DBG_WARNING, "MIPV6 MN: Received a tunneled IPv6 packet"
		      " that was not tunneled by HA %x:%x:%x:%x:%x:%x:%x:%x,"
		      " but by %x:%x:%x:%x:%x:%x:%x:%x", 
		      NIPV6ADDR(&minfo->ha), NIPV6ADDR(&outer->saddr));
		return IPV6_TNL_ACCEPT;
        }
	spin_unlock(&minfo->lock);
	read_unlock(&mn_info_lock);

	DEBUG(DBG_DATADUMP, "Sending BU to correspondent node");

	if (inner->nexthdr != IPPROTO_DSTOPTS && inner->nexthdr != IPPROTO_MOBILITY) {
		struct in6_addr coa;
		lifetime = mipv6_mn_get_bulifetime(&inner->daddr,
						   &outer->daddr, 0); 
		if(lifetime && 
		   !mipv6_get_care_of_address(&inner->daddr, &coa)) {
			write_lock(&bul_lock);
			mipv6_send_bu(&inner->daddr, &inner->saddr, &coa,
				      CN_BU_DELAY, INITIAL_BINDACK_TIMEOUT,
				      MAX_BINDACK_TIMEOUT , 1, 
#ifdef CN_REQ_ACK
				      MIPV6_BU_F_ACK, /* ack */
#else
				      0, /* no ack */
#endif
				      0, lifetime, NULL);
			write_unlock(&bul_lock);
		}
	}
	/* (Mis)use ipsec tunnel flag  */
	DEBUG(DBG_DATADUMP, "setting rcv_tunnel flag in skb");
	opt = (struct inet6_skb_parm *)skb->cb;
	opt->mipv6_flags |= MIPV6_RCV_TUNNEL;
	return IPV6_TNL_ACCEPT;
}

static struct ipv6_tnl_hook_ops mipv6_mn_tnl_rcv_send_bu_ops = {
	{NULL, NULL}, 
	IPV6_TNL_PRE_DECAP,
	IPV6_TNL_PRI_FIRST,
	mipv6_mn_tnl_rcv_send_bu_hook
};

static int
mipv6_mn_tnl_xmit_stats_hook(struct ipv6_tnl *t, struct sk_buff *skb)
{
	DEBUG_FUNC();
	if (is_mipv6_tnl(t))
		MIPV6_INC_STATS(n_encapsulations);
	return IPV6_TNL_ACCEPT;
}

static struct ipv6_tnl_hook_ops mipv6_mn_tnl_xmit_stats_ops = {
	{NULL, NULL},
	IPV6_TNL_PRE_ENCAP,
	IPV6_TNL_PRI_LAST,
	mipv6_mn_tnl_xmit_stats_hook
};

static int
mipv6_mn_tnl_rcv_stats_hook(struct ipv6_tnl *t, struct sk_buff *skb)
{
	DEBUG_FUNC();	
	if (is_mipv6_tnl(t))
		MIPV6_INC_STATS(n_decapsulations);
	return IPV6_TNL_ACCEPT;
}

static struct ipv6_tnl_hook_ops mipv6_mn_tnl_rcv_stats_ops = {
	{NULL, NULL},
	IPV6_TNL_PRE_DECAP,
	IPV6_TNL_PRI_LAST,
	mipv6_mn_tnl_rcv_stats_hook
};

void mipv6_check_tunneled_packet(struct sk_buff *skb)
{
	struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
	DEBUG_FUNC();
	/* If tunnel flag was set */
	if (opt->mipv6_flags & MIPV6_RCV_TUNNEL) {
		struct in6_addr coa; 
		__u32 lifetime;
		mipv6_get_care_of_address(&skb->nh.ipv6h->daddr, &coa);
		lifetime = mipv6_mn_get_bulifetime(&skb->nh.ipv6h->daddr,
 							 &coa, 0); 

		DEBUG(DBG_WARNING, "packet to address %x:%x:%x:%x:%x:%x:%x:%x"
		      "was tunneled. NOT Sending BU to CN" 
		      "%x:%x:%x:%x:%x:%x:%x:%x", 
		      NIPV6ADDR(&skb->nh.ipv6h->daddr),
		      NIPV6ADDR(&skb->nh.ipv6h->saddr)); 
		/* This should work also with home address option */
		write_lock(&bul_lock);
		mipv6_send_bu(&skb->nh.ipv6h->daddr, &skb->nh.ipv6h->saddr, 
			      &coa, CN_BU_DELAY, INITIAL_BINDACK_TIMEOUT,
			      MAX_BINDACK_TIMEOUT, 1, 
#ifdef CN_REQ_ACK
			      MIPV6_BU_F_ACK, /* ack */
#else
			      0, /* no ack */
#endif
			      64, lifetime, NULL);
		write_unlock(&bul_lock);
	}
}
int sched_mv_home_addr_task(struct mn_info *minfo, int plen_new, 
			    int newif, int oldif)
{
	struct ifr_holder *ifrh = NULL;

	ifrh = kmalloc(sizeof(*ifrh), GFP_ATOMIC);
	if (ifrh == NULL) {
		DEBUG(DBG_ERROR, "Out of memory");
		return -1;
	}
	/* must queue task to avoid deadlock with rtnl */
	ifrh->ifr.ifr6_ifindex = newif;
	ifrh->ifr.ifr6_prefixlen = plen_new;
	ipv6_addr_copy(&ifrh->ifr.ifr6_addr, &minfo->home_addr);
	ifrh->old_ifi = oldif;
	
	spin_lock_bh(&ifrh_lock);
	list_add_tail(&ifrh->list, &ifrh_list);
	spin_unlock_bh(&ifrh_lock);
	schedule_task(&mv_home_addr_task);

	minfo->ifindex = newif;

	return 0;
}

static void mn_ha_handoff(struct handoff *ho)
{
	struct list_head *lh;
	struct mn_info *minfo;
	
	read_lock(&mn_info_lock);
	list_for_each(lh, &mn_info_list) {
		__u8 has_home_reg;
		int ifindex;
		struct in6_addr ha;
		__u8 athome;
		__u32 lifetime;
		struct mipv6_bul_entry *entry = NULL;
		
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		has_home_reg = minfo->has_home_reg;
		ifindex = minfo->ifindex;
		ipv6_addr_copy(&ha, &minfo->ha);
		
		if (mipv6_prefix_compare(&ho->rtr_new.raddr, &minfo->home_addr,
					 ho->rtr_new.pfix_len)) {
			if (minfo->has_home_reg)
				athome = minfo->is_at_home = MN_RETURNING_HOME;
			else
				athome = minfo->is_at_home = MN_AT_HOME;
			spin_unlock(&minfo->lock);
			
			/* Cancel prefix solicitation */
			mipv6_pfx_cancel_send(&ho->rtr_new.raddr, ifindex);
			
			/* Check if home address has been moved around */
			if (ifindex != ho->rtr_new.ifindex) {
				DEBUG(DBG_INFO, 
				      "Moving home address back to "
				      "the home interface");
				sched_mv_home_addr_task(minfo, 
							ho->rtr_new.pfix_len,
							ho->rtr_new.ifindex, 
							ifindex);
			}
			if (!has_home_reg)
				continue;

			lifetime = 0;
		} else {
			athome = minfo->is_at_home = MN_NOT_AT_HOME;
			spin_unlock(&minfo->lock);
			
			if (!has_home_reg) {
				init_home_registration(&minfo->home_addr, 
						       &ho->rtr_new.CoA);
				continue;
			}
			lifetime = mipv6_mn_get_bulifetime(&minfo->home_addr, 
							   &ho->rtr_new.CoA,
							   MIPV6_BU_F_HOME);
			
		}
		write_lock(&bul_lock);
		if (!(entry = mipv6_bul_get(&ha, &minfo->home_addr)) ||
		    !(entry->flags & MIPV6_BU_F_HOME)) {
			DEBUG(DBG_ERROR, 
			      "Unable to find home registration for "
			      "home address: %x:%x:%x:%x:%x:%x:%x:%x!\n",
			      NIPV6ADDR(&minfo->home_addr));
			write_unlock(&bul_lock);
			continue;
		}
		DEBUG(DBG_INFO, "Sending home de ? %d registration for "
		      "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" 
		      "to home agent %x:%x:%x:%x:%x:%x:%x:%x, "
		      "with lifetime %ld, prefixlength %d", 
		      (athome != MN_NOT_AT_HOME),  
		      NIPV6ADDR(&entry->home_addr), 
		      NIPV6ADDR(&entry->cn_addr), lifetime, entry->prefix);
		mipv6_send_bu(&entry->home_addr, &entry->cn_addr, 
			      &ho->rtr_new.CoA, HA_BU_DELAY, 
			      INITIAL_BINDACK_TIMEOUT, MAX_BINDACK_TIMEOUT, 1, 
			      entry->flags, entry->prefix, lifetime, NULL);
		write_unlock(&bul_lock);

	}
	read_unlock(&mn_info_lock);
}
/**
 * mn_cn_handoff - called for every bul entry to send BU to CN
 * @rawentry: bul entry
 * @args: handoff event
 * @sortkey:
 *
 * Since MN can have many home addresses and home networks, every BUL
 * entry needs to be checked
 **/
static int mn_cn_handoff(void *rawentry, void *args, unsigned long *sortkey)
{
	struct mipv6_bul_entry *entry = (struct mipv6_bul_entry *)rawentry;
	struct handoff *ho = (struct handoff *)args;
	DEBUG_FUNC();

	/* Home registrations already handled by mn_ha_handoff */
	if (!(entry->flags & MIPV6_BU_F_HOME)) {
		__u32 lifetime;
		if (mipv6_prefix_compare(&ho->rtr_new.raddr, 
					 &entry->home_addr,
					 ho->rtr_new.pfix_len)) {
			lifetime = 0;
		} else {
			lifetime = mipv6_mn_get_bulifetime(&entry->home_addr, 
							   &ho->rtr_new.CoA,
							   entry->flags);
		}
		DEBUG(DBG_INFO, "Sending BU for home address: "
		      "%x:%x:%x:%x:%x:%x:%x:%x \n" 
		      "to CN: %x:%x:%x:%x:%x:%x:%x:%x, "
		      "with lifetime %ld",   NIPV6ADDR(&entry->home_addr), 
		      NIPV6ADDR(&entry->cn_addr), lifetime);
		/* BUL is locked by mipv6_mobile_node_moved which calls us 
		   through bul_iterate */
		mipv6_send_bu(&entry->home_addr, &entry->cn_addr, 
			      &ho->rtr_new.CoA, CN_BU_DELAY, 
			      INITIAL_BINDACK_TIMEOUT, MAX_BINDACK_TIMEOUT, 1,
			      entry->flags, entry->prefix, lifetime, 
			      NULL);
	}
	return ITERATOR_CONT;
}

/**
 * init_home_registration - start Home Registration process
 * @home_addr: home address
 * @coa: care-of address
 *
 * Checks whether we have a Home Agent address for this home address.
 * If not starts Dynamic Home Agent Address Discovery.  Otherwise
 * tries to register with home agent if not already registered.
 **/
int init_home_registration(struct in6_addr *home_addr, struct in6_addr *coa)
{
	struct mn_info *hinfo;
	struct in6_addr ha;
	__u8 man_conf;
	int ifindex;
	__u32 lifetime;
	int mipv6_dad = 0;

	DEBUG_FUNC();

	read_lock_bh(&mn_info_lock);
        if ((hinfo = mipv6_mninfo_get_by_home(home_addr)) == NULL) {
                DEBUG(DBG_ERROR, "No mn_info found for address: "
		      "%x:%x:%x:%x:%x:%x:%x:%x",
		      NIPV6ADDR(home_addr));
		read_unlock_bh(&mn_info_lock);
                return -ENOENT;
        }
	spin_lock(&hinfo->lock);
	if (mipv6_prefix_compare(&hinfo->home_addr, coa, hinfo->home_plen)) { 
		spin_unlock(&hinfo->lock);
		read_unlock_bh(&mn_info_lock);
		DEBUG(DBG_INFO, "Adding home address, MN at home");
		return 0;
	}
        if (ipv6_addr_any(&hinfo->ha)) {
                int dhaad_id = mipv6_get_dhaad_id();
                int home_plen = hinfo->home_plen;
                hinfo->dhaad_id = dhaad_id;
		spin_unlock(&hinfo->lock);
                mipv6_icmpv6_send_dhaad_req(home_addr, home_plen, dhaad_id);
		read_unlock_bh(&mn_info_lock);
                DEBUG(DBG_INFO,
		      "Home Agent address not set, initiating DHAAD");
                return 0;
        }
        ipv6_addr_copy(&ha, &hinfo->ha);
        man_conf = hinfo->man_conf;
        ifindex = hinfo->ifindex;
	spin_unlock(&hinfo->lock);
	read_unlock_bh(&mn_info_lock);
	
	if (man_conf)
		mipv6_pfx_add_ha(&ha, coa, ifindex);
	
	if (mipv6_bul_exists(&ha, home_addr)) {
		DEBUG(DBG_INFO, "BU already sent to HA");
		return 0;
	}
	lifetime = mipv6_mn_get_bulifetime(home_addr, coa, 
					   MIPV6_BU_F_HOME | MIPV6_BU_F_ACK);
	DEBUG(DBG_INFO, "Sending initial home registration for "
	      "home address: %x:%x:%x:%x:%x:%x:%x:%x\n" 
	      "to home agent %x:%x:%x:%x:%x:%x:%x:%x, "
	      "with lifetime %ld, prefixlength %d",   
	      NIPV6ADDR(home_addr), NIPV6ADDR(&ha), lifetime, 0);
#ifndef MIPV6_NO_PROXY_DAD
	mipv6_dad = MIPV6_BU_F_DAD;
#endif
	write_lock_bh(&bul_lock);
	mipv6_send_bu(home_addr, &ha, coa, HA_BU_DELAY, 
		      INITIAL_BINDACK_DAD_TIMEOUT, MAX_BINDACK_TIMEOUT, 1,
		      MIPV6_BU_F_HOME | MIPV6_BU_F_ACK | mipv6_dad, 
		      0, lifetime, NULL);
	write_unlock_bh(&bul_lock);

	return 0;
}

/**
 * mipv6_mobile_node_moved - Send BUs to all HAs and CNs
 * @ho: handoff structure contains the new and previous routers
 *
 * Event for handoff.  Sends BUs everyone on Binding Update List.
 **/
int mipv6_mobile_node_moved(struct handoff *ho)
{
#if 0
	int bu_to_prev_router = 1;
#endif
	int dummy;

	DEBUG_FUNC();

	if (!mipv6_is_initialized)
		return 0;

	ma_ctl_upd_iface(ho->rtr_new.ifindex, 
			 MA_IFACE_CURRENT | MA_IFACE_HAS_ROUTER, &dummy);

	/* First send BU to HA, then to all other nodes that are on BU list */
	mn_ha_handoff(ho);
	write_lock(&bul_lock);
	bul_iterate(mn_cn_handoff, ho);
	write_unlock(&bul_lock);

	mipv6_mn_prefix_route(&ho->rtr_new.raddr, ho->rtr_new.pfix_len, 0, 
			      EXPIRE_INFINITE);
#if 0	 
	/* Add current care-of address to mn_info list, if current router acts 
	   as a HA.*/ 

	if (ho->rtr_new.flags & ND_RA_FLAG_HA && 
	    ho->rtr_new.glob_addr && bu_to_prev_router) 
		mipv6_mninfo_add(&ho->rtr_new.CoA, ho->rtr_new.pfix_len, 
				 MN_AT_HOME, 0, &ho->rtr_new.raddr, 
				 ho->rtr_new.pfix_len, ROUTER_BU_DEF_LIFETIME,
				 0);
				  
#endif
	return 0;		
}

/**
 * mipv6_mn_send_home_na - send NA when returning home
 * @haddr: home address to advertise
 *
 * After returning home, MN must advertise all its valid addresses in
 * home link to all nodes.
 **/
void mipv6_mn_send_home_na(struct in6_addr *haddr)
{
	struct net_device *dev = NULL;
	struct in6_addr mc_allnodes;
	struct mn_info *hinfo = NULL;
	struct in6_addr addr;
 
	read_lock(&mn_info_lock);
	hinfo = mipv6_mninfo_get_by_home(haddr);
	if (!hinfo) {
		read_unlock(&mn_info_lock);
		return;
	}
	spin_lock(&hinfo->lock);
	hinfo->is_at_home = MN_AT_HOME;
	hinfo->has_home_reg = 0;
	dev = dev_get_by_index(hinfo->ifindex);
	spin_unlock(&hinfo->lock);
	read_unlock(&mn_info_lock);
	if (dev == NULL) {
		DEBUG(DBG_ERROR, "Send home_na: device not found.");
		return;
	}
	
	ipv6_addr_all_nodes(&mc_allnodes);
	if (ipv6_get_lladdr(dev, &addr) == 0)
		ndisc_send_na(dev, NULL, &mc_allnodes, &addr, 0, 0, 1, 1);
	ndisc_send_na(dev, NULL, &mc_allnodes, haddr, 0, 0, 1, 1);
	dev_put(dev);
}

int mipv6_mn_use_hao(struct in6_addr *daddr, struct in6_addr *saddr)
{
	struct mipv6_bul_entry *entry;
	int add_ha = 0;

        if (mipv6_mn_is_home_addr(saddr)) {
		read_lock_bh(&bul_lock);
		if ((entry = mipv6_bul_get(daddr, saddr)) == NULL) {
			read_unlock_bh(&bul_lock);
			return add_ha;
		}
		add_ha = (!entry->rr || entry->rr->rr_state == RR_DONE || 
			  entry->flags & MIPV6_BU_F_HOME);
		read_unlock_bh(&bul_lock);
	}
	return add_ha;
}

static inline int 
add_prefix_route(struct in6_addr *saddr, struct in6_addr *pfx, int plen,
		 int ifindex, unsigned long expires)
{
	struct in6_rtmsg rtmsg;
	memset(&rtmsg, 0, sizeof(rtmsg));
	ipv6_addr_copy(&rtmsg.rtmsg_dst, pfx);
	rtmsg.rtmsg_dst_len = plen;
	ipv6_addr_copy(&rtmsg.rtmsg_src, saddr);
	rtmsg.rtmsg_src_len = 128;
	rtmsg.rtmsg_metric = IP6_RT_PRIO_MIPV6;
	rtmsg.rtmsg_ifindex = ifindex;
	rtmsg.rtmsg_info = expires;
	rtmsg.rtmsg_flags = RTF_UP | RTF_EXPIRES;
	rtmsg.rtmsg_type = RTMSG_NEWROUTE;
	return ip6_route_add(&rtmsg, NULL);
}


void mipv6_mn_prefix_route(struct in6_addr *pfx, int plen, int delete, 
			   unsigned long expires)
{
	struct list_head *lh;
	
	DEBUG_FUNC();

	read_lock(&mn_info_lock);
	
	list_for_each(lh, &mn_info_list) {
		struct mn_info *minfo; 
		struct in6_addr ha, coa;
		struct ipv6_tnl *t;
		struct rt6_info *rt;

		minfo = list_entry(lh, struct mn_info, list);	
		if (mipv6_prefix_compare(&minfo->home_addr, pfx, plen)) {
			continue;
		}
		if (mipv6_get_care_of_address(&minfo->home_addr, &coa) < 0) {
			DEBUG(DBG_WARNING, 
			      ": unable to get care-of address for "
			      "%x:%x:%x:%x:%x:%x:%x:%x", 
			      NIPV6ADDR(&minfo->home_addr));
			continue;
		}
		spin_lock(&minfo->lock);
		ipv6_addr_copy(&ha, &minfo->ha);
		spin_unlock(&minfo->lock);

		t = ipv6_ipv6_tnl_lookup(&ha, &coa);
		
		if (!is_mipv6_tnl(t)) {
			DEBUG(DBG_WARNING, "MIPv6 tunnel not found!");
			continue;
		}
		rt = rt6_lookup(pfx, &minfo->home_addr, t->dev->ifindex, 1);
	
		if (rt && !(rt->rt6i_flags & RTF_DEFAULT)) {  
			if (rt->rt6i_flags & RTF_EXPIRES) {
				if (delete) {
					ip6_del_rt(rt, NULL);
					rt = NULL;
				} else {
					rt->rt6i_expires = expires;
				}
			}
		} else if (!delete) {
			add_prefix_route(&minfo->home_addr, pfx, plen, 
					 t->dev->ifindex, expires);
		}
		if (rt) {
			dst_release(&rt->u.dst);
		}
	}
	read_unlock(&mn_info_lock);
}

static int 
mn_dev_event(struct notifier_block *nb, unsigned long event, void *ptr)
{
	struct net_device *dev = ptr;
	struct list_head *lh;
	struct mn_info *minfo;
	int newif = 0, err =0;

	/* here are probably the events we need to worry about */
	switch (event) {
	case NETDEV_UP:
		DEBUG(DBG_DATADUMP, "New netdevice %s registered.", dev->name);
		if ((dev->type != ARPHRD_LOOPBACK) &&
		    (dev->type != ARPHRD_IPV6_IPV6_TUNNEL))
			ma_ctl_add_iface(dev->ifindex);
		break;
	case NETDEV_GOING_DOWN:
		DEBUG(DBG_DATADUMP, "Netdevice %s disappeared.", dev->name);
		ma_ctl_upd_iface(dev->ifindex, MA_IFACE_NOT_PRESENT, &newif);
		newif =  ma_ctl_get_preferred_if();
		if (newif <= 0  || (newif == loopback_dev.ifindex)) {
			DEBUG(DBG_WARNING, "Couldn't find a valid interface to add home address into");
			err = -1;
		}
		else
			DEBUG(DBG_INFO, "Netdevice %s was in use.  Switch to %d.",
			       dev->name, newif);
		/* 
		 * Go through mn_info list and move all home addresses on the
		 * netdev going down to a new device. This will make it 
                 * practically impossible for the home address to return home,
		 * but allow MN to retain its connections using the address.
		 */

		read_lock_bh(&mn_info_lock);
		list_for_each(lh, &mn_info_list) {
			minfo = list_entry(lh, struct mn_info, list);
			spin_lock(&minfo->lock);
			if (minfo->ifindex == dev->ifindex) {
				if (err)
					minfo->ifindex = 0;
				else if (minfo->man_conf && 
					 sched_mv_home_addr_task(minfo, 128, 
								 newif, 
								 0) < 0) {
					spin_unlock(&minfo->lock);
					read_unlock_bh(&mn_info_lock);
					return NOTIFY_DONE;
				}
			}
			spin_unlock(&minfo->lock);				
		}
		
		read_unlock_bh(&mn_info_lock);
	}
	return NOTIFY_DONE;
}

struct notifier_block mipv6_mn_dev_notifier = {
	mn_dev_event,
	NULL,
	0 /* check if using zero is ok */
};

static void deprecate_addr(struct mn_info *minfo)
{
	/*
	 * Lookup address from IPv6 address list and set deprecated flag
	 */
	
}

int mipv6_mn_may_solicit_ha(struct in6_addr *target)
{
	struct list_head *lh;
	struct mn_info *minfo; 
	int ret = 0;

	DEBUG_FUNC();

	read_lock_bh(&mn_info_lock);
	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		if (!ipv6_addr_cmp(&minfo->ha, target) && 
		    (ret = (minfo->is_at_home == MN_RETURNING_HOME))) {
			spin_unlock(&minfo->lock);
			break;
		}
		spin_unlock(&minfo->lock);
	}
	read_unlock_bh(&mn_info_lock);
	return ret;
}

int mipv6_mn_may_advertise(struct in6_addr *target, struct in6_addr *saddr)
{
	struct list_head *lh;
	struct mn_info *minfo = NULL; 
	int ret = 1;

	DEBUG_FUNC();

	read_lock(&mn_info_lock);

	/* Only answer to a NS to the  home address from the HA when it is 
	   returning home */

	list_for_each(lh, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		spin_lock(&minfo->lock);
		if (minfo->is_at_home == MN_RETURNING_HOME &&
		    !ipv6_addr_cmp(&minfo->home_addr, target)) {
			ret = !ipv6_addr_cmp(saddr, &minfo->ha);
			spin_unlock(&minfo->lock);
			break;
		}
		spin_unlock(&minfo->lock);
	}
	read_unlock(&mn_info_lock);

	return ret;
}

/*
 * Required because we can only modify addresses after the packet is
 * constructed.  We otherwise mess with higher level protocol
 * pseudoheaders. With strict protocol layering life would be SO much
 * easier!  
 */
static unsigned int modify_xmit_addrs(unsigned int hooknum,
				      struct sk_buff **pskb,
				      const struct net_device *in,
				      const struct net_device *out,
				      int (*okfn) (struct sk_buff *))
{
	struct sk_buff *skb = *pskb;

	DEBUG_FUNC();
	
	if (skb) {
		struct ipv6hdr *hdr = skb->nh.ipv6h;
		struct inet6_skb_parm *opt = (struct inet6_skb_parm *)skb->cb;
		struct in6_addr coa;

		if ((opt->mipv6_flags & MIPV6_SND_HAO) &&
		    !mipv6_get_care_of_address(&hdr->saddr, &coa)) {
			DEBUG(DBG_INFO, 
			      "Replace source address with CoA and reroute");
			ipv6_addr_copy(&hdr->saddr, &coa);
			skb->nfcache |= NFC_ALTERED;
		}
	}
	return NF_ACCEPT;
}

/* We set a netfilter hook so that we can modify outgoing packet's
 * source addresses 
 */
struct nf_hook_ops addr_modify_hook_ops = {
	{NULL, NULL},		/* List head, no predecessor, no successor */
	modify_xmit_addrs,
	PF_INET6,
	NF_IP6_LOCAL_OUT,
	NF_IP6_PRI_FIRST       	/* Should be of EXTREMELY high priority since we
				 * do not want to mess with IPSec (possibly
				 * implemented as packet filter)
				 */
};

#define MN_INFO_LEN 77

static int mn_proc_info(char *buffer, char **start, off_t offset,
			int length)
{
	struct list_head *p;
	struct mn_info *minfo;
	int len = 0, skip = 0;

	DEBUG_FUNC();

	read_lock_bh(&mn_info_lock);
	list_for_each(p, &mn_info_list) {
		if (len < offset / MN_INFO_LEN) {
			skip++;
			continue;
		}
		if (len >= length)
			break;
		minfo = list_entry(p, struct mn_info, list);
		spin_lock(&minfo->lock);
		len += sprintf(buffer + len, "%02d %04x%04x%04x%04x%04x%04x%04x%04x %02x "
			       "%04x%04x%04x%04x%04x%04x%04x%04x %d %d\n",
			       minfo->ifindex, NIPV6ADDR(&minfo->home_addr),
			       minfo->home_plen, NIPV6ADDR(&minfo->ha),
			       minfo->is_at_home, minfo->has_home_reg);
		spin_unlock(&minfo->lock);
	}
	read_unlock_bh(&mn_info_lock);

	*start = buffer;
	if (offset)
		*start += offset % MN_INFO_LEN;

	len -= offset % MN_INFO_LEN;

	if (len > length)
		len = length;
	if (len < 0)
		len = 0;
	
	return len;
}

int __init mipv6_mn_init(void)
{
	struct net_device *dev;

	DEBUG_FUNC();

	mipv6_bul_init(MIPV6_BUL_SIZE);
	mipv6_initialize_mdetect();

	INIT_TQUEUE(&mv_home_addr_task, mv_home_addr, NULL);

	ma_ctl_init();
	for (dev = dev_base; dev; dev = dev->next) {
		if ((dev->flags & IFF_UP) && 
		    (dev->type != ARPHRD_LOOPBACK) &&
		    (dev->type != ARPHRD_IPV6_IPV6_TUNNEL)) {
			ma_ctl_add_iface(dev->ifindex);
		}
	} 
	DEBUG(DBG_INFO, "Multiaccess support initialized");

	register_netdevice_notifier(&mipv6_mn_dev_notifier);

	ipv6_ipv6_tnl_register_hook(&mipv6_mn_tnl_rcv_send_bu_ops);
	ipv6_ipv6_tnl_register_hook(&mipv6_mn_tnl_xmit_stats_ops);
	ipv6_ipv6_tnl_register_hook(&mipv6_mn_tnl_rcv_stats_ops);

	MIPV6_SETCALL(mipv6_set_home, mipv6_mn_set_home);
	MIPV6_SETCALL(mipv6_prefix_route, mipv6_mn_prefix_route);

	/* COA to home transformation hook */
	MIPV6_SETCALL(mipv6_get_home_address, mipv6_get_saddr_hook);
	/* Actual HO, also deletes old routes after the addition of new ones 
	   in ndisc */
	MIPV6_SETCALL(mipv6_change_router, mipv6_change_router);

	MIPV6_SETCALL(mipv6_mn_may_solicit_ha, mipv6_mn_may_solicit_ha);
	MIPV6_SETCALL(mipv6_mn_may_advertise, mipv6_mn_may_advertise);

	proc_net_create("mip6_mninfo", 0, mn_proc_info);
	/* Set packet modification hook (source addresses) */
	nf_register_hook(&addr_modify_hook_ops);

	return 0;
}

void __exit mipv6_mn_exit(void)
{
	struct list_head *lh, *tmp;
	struct mn_info *minfo;
	DEBUG_FUNC();

	nf_unregister_hook(&addr_modify_hook_ops);
	proc_net_remove("mip6_mninfo");
	mipv6_shutdown_mdetect();
	ipv6_ipv6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_stats_ops);
	ipv6_ipv6_tnl_unregister_hook(&mipv6_mn_tnl_xmit_stats_ops);
	ipv6_ipv6_tnl_unregister_hook(&mipv6_mn_tnl_rcv_send_bu_ops);
	ma_ctl_clean();

	unregister_netdevice_notifier(&mipv6_mn_dev_notifier);
	write_lock_bh(&mn_info_lock);

	list_for_each_safe(lh, tmp, &mn_info_list) {
		minfo = list_entry(lh, struct mn_info, list);
		if (minfo->is_at_home == MN_NOT_AT_HOME) 
			deprecate_addr(minfo);
		list_del(&minfo->list);
		kfree(minfo);
	}
	write_unlock_bh(&mn_info_lock);
	mipv6_bul_exit();
	flush_scheduled_tasks();
}
