/*
 *      Mobile IPv6-related Destination Options processing
 *
 *      Authors:
 *      Toni Nykanen <tpnykane@cc.hut.fi>
 *
 *      Marko Myllynen <myllynen@lut.fi> - small fixes
 *
 *      $Id: procrcv.c,v 1.47 2001/07/19 11:47:30 antti 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.
 *
 */


/*
  sub-options:
  BU:
    Unique Identifier Sub-Option
    Alternate Care-of Address Sub-Option

  BA:

  BR:
    Unique Identifier Sub-Option

  HA:
*/

#include <linux/autoconf.h>
#include <linux/types.h>
#include <linux/in6.h>
#include <linux/spinlock.h>
#include <linux/skbuff.h>
#include <linux/ipsec.h>
#include <linux/init.h>
#include <net/ipv6.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/mipglue.h>

#include "dstopts.h"
#include "mipv6.h"

#include "bcache.h"
#include "bul.h"
#include "sendopts.h"
#include "ha.h"
#include "access.h"
#include "stats.h"
#include "mn.h"
#include "ah.h"
#include "debug.h"


/* module ID for this source module */
static char module_id[] = "mipv6/procrcv";

#define DISCARD -1
#define FALSE 0
#define TRUE 1

static rwlock_t destopt_lock = RW_LOCK_UNLOCKED;
static unsigned long destopt_lock_flags;

/* mipv6_skb_parm is similar to inet6_skb_parm.  We use this instead
 * to accommodate a new option offset 'ha' and not to interfere with
 * inet6_sbk_parm and frag6_sbk_cb users. */
struct mipv6_skb_parm {
	int iif;
	__u16 reserved[7];
	__u16 ha;
};

/*
 * these print the contents of several destination options.
 */
void dump_bu(__u8 opt, __u8 ack, __u8 home, __u8 router, __u8 dad,
	     __u8 plength, __u16 sequence, __u32 lifetime,
	     struct mipv6_subopt_info sinfo)
{
	DEBUG((DBG_INFO, "Binding Update Destination Option:"));

	DEBUG((DBG_INFO, "Option type: %d", opt));
	if (ack)
		DEBUG((DBG_INFO, "Binding ack requested."));
	if (home)
		DEBUG((DBG_INFO, "Home registration bit is set."));
	if (router)
		DEBUG((DBG_INFO, "Router bit is set."));
	if (dad)
		DEBUG((DBG_INFO, "DAD bit is set."));
	DEBUG((DBG_INFO, "Prefix length: %d", plength));
	DEBUG((DBG_INFO, "Sequence number: %d", sequence));
	DEBUG((DBG_INFO, "Lifetime: %d", lifetime));

	if (sinfo.fso_flags != 0) {
		DEBUG((DBG_INFO, "BU contains the following sub-options:"));
		mipv6_print_subopt_info(&sinfo);
	} else {
		DEBUG((DBG_INFO, "BU has no sub-options"));
	}
}

void dump_ba(__u8 opt, __u8 status, __u16 sequence, __u32 lifetime,
	     __u32 refresh, struct mipv6_subopt_info sinfo)
{
	DEBUG((DBG_INFO, "Binding Ack Destination Option:"));

	DEBUG((DBG_INFO, "Option type: %d", opt));
	DEBUG((DBG_INFO, "Status: %d", status));
	DEBUG((DBG_INFO, "Sequence number: %d", sequence));
	DEBUG((DBG_INFO, "Lifetime: %d", lifetime));

	if (sinfo.fso_flags != 0) {
		DEBUG((DBG_INFO, "BA contains the following sub-options:"));
		mipv6_print_subopt_info(&sinfo);
	} else {
		DEBUG((DBG_INFO, "BA has no sub-options"));
	}
}

void dump_br(__u8 opt, struct mipv6_subopt_info sinfo)
{
	DEBUG((DBG_INFO, "Binding Req Destination Option:"));
	DEBUG((DBG_INFO, "Option type: %d", opt));

	if (sinfo.fso_flags != 0) {
		DEBUG((DBG_INFO, "BR contains the following sub-options:"));
		mipv6_print_subopt_info(&sinfo);
	} else {
		DEBUG((DBG_INFO, "BR has no sub-options"));
	}
}

/*
 * lock the processing of Destination Options of the packet, for the
 * following reason:
 *
 * (DRAFT11, 8.1)
 *  "Packets sent by a mobile node while away from home generally include
 *   a Home Address option.  When any node receives a packet containing
 *   a Home Address option, it MUST process the option in a manner
 *   consistent with copying the Home Address field from the Home Address
 *   option into the IPv6 header, replacing the original value of the
 *   Source Address field there.  However, any actual modifications to
 *   the Source Address field in the packet's IPv6 header MUST not be
 *   performed until after all processing of other options contained in
 *   this same Destination Options extension header is completed."
 *
 * And, since the IPv6 code processes Destination Options separately,
 * the actual processing must be delayed, and executed in sequence without
 * any intervention by another thread/process/whatever.
 *
 * The other way to achieve this would require some additional buffer for
 * the options inside the sk_buff, in such a place that in case the buffer
 * get copied (cow'ed for example), the pointers get copied also. Then,
 * no locking would be needed..
 */

static void lock_procrcv(void)
{
	DEBUG_FUNC();
	read_lock_irqsave(&destopt_lock, destopt_lock_flags);
}


static void unlock_procrcv(void)
{
	DEBUG_FUNC();
	read_unlock_irqrestore(&destopt_lock, destopt_lock_flags);
}


/* only called for home agent. */
static int mipv6_is_authorized(struct in6_addr* haddr)
{
	DEBUG((DBG_WARNING, "%s: mipv6_is_authorized: %d", module_id,
	       mipv6_is_allowed_home_addr(mipv6_mobile_node_acl, haddr)));

	return mipv6_is_allowed_home_addr(mipv6_mobile_node_acl, haddr);
}

/* This is 64 since we have defined CONFIG_IPV6_EUI64 and CONFIG_IPV6_NO_PB */
#define PREFIX_LENGTH 64

static struct inet6_ifaddr *is_on_link_ipv6_address(struct in6_addr* mn_haddr)
{
	struct rt6_info *rt;
	struct inet6_dev *in6_dev = NULL;
	struct inet6_ifaddr *ifp = NULL;

	rt = rt6_lookup(mn_haddr, NULL, 0, 0);

	if (rt) {
		in6_dev = in6_dev_get(rt->rt6i_dev);
		if (in6_dev)
			ifp = in6_dev->addr_list;
	}

	while (ifp != NULL) {
		spin_lock_bh(&ifp->lock);
		if (mipv6_prefix_compare(&ifp->addr, mn_haddr,
					PREFIX_LENGTH) == TRUE &&
			!(ifp->flags&IFA_F_TENTATIVE)) {
				spin_unlock_bh(&ifp->lock);
				DEBUG((DBG_INFO, "%s: Home Addr Opt: on-link",
					module_id));
				in6_dev_put(in6_dev);
				return ifp;
		}
		spin_unlock_bh(&ifp->lock);
		ifp = ifp->if_next;
	}

	DEBUG((DBG_WARNING, "%s: Home Addr Opt NOT on-link", module_id));
	if (in6_dev)
		in6_dev_put(in6_dev);
	return ifp;
}

/*
 * Lifetime checks. ifp->valid_lft >= ifp->prefered_lft always (see addrconf.c)
 * Returned value is in seconds.
 */

static __u32 get_min_lifetime(struct inet6_ifaddr *ifp, __u32 lifetime)
{
	__u32 rem_lifetime;
	unsigned long now = jiffies;

	if (ifp->valid_lft == 0) {
		rem_lifetime = lifetime;
	} else {
		__u32 valid_lft_left =
			ifp->valid_lft - time_after(now, ifp->tstamp) / HZ;
		rem_lifetime = (valid_lft_left > lifetime)
		? lifetime : valid_lft_left;
	}

	if (ifp->prefered_lft != 0) {
		__u32 prefered_lft_left =
			ifp->prefered_lft - time_after(now, ifp->tstamp) / HZ;
		rem_lifetime = (prefered_lft_left > rem_lifetime)
			? rem_lifetime : prefered_lft_left;
	}

	return rem_lifetime;
}

/*
 * only called by mipv6_dstopt_process_bu. could be inlined.
 *
 * performs the actual processing of a binding update the purpose
 * of which is to add a binding.
 */
static void mipv6_bu_add(int ifindex, struct in6_addr *saddr, 
			 struct in6_addr *daddr, struct in6_addr *haddr,
			 struct in6_addr *coa,
			 int ack, int home, int router, int dad, int plength,
			 __u16 sequence, __u32 lifetime)
{
	__u8 ba_status = SUCCESS;
	__u32 ba_lifetime = 0;
	__u32 ba_refresh = 0;
	__u32 rem_lifetime = 0;
	__u64 maxdelay = 0;
	struct in6_addr *reply_addr = haddr;
	DEBUG_FUNC();

	if (home == 0) {
		/* Not "home registration", use 0 as plength */
		plength = 0;
		if (mipv6_bcache_add(ifindex, daddr, haddr, coa, lifetime, 
				     plength, sequence, router, 
				     CACHE_ENTRY) != 0) {
			DEBUG((DBG_ERROR, "%s: mipv6_bu_add: binding failed.",
			       module_id));
			ba_status = INSUFFICIENT_RESOURCES;
			reply_addr = saddr;
		} else
			DEBUG((DBG_INFO, "%s: mipv6_bu_add: binding succeeded",
			       module_id));

		DEBUG((DBG_DATADUMP, "%s: mipv6_bu_add: home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
		       module_id, NIPV6ADDR(haddr)));
		DEBUG((DBG_DATADUMP, "%s: mipv6_add: coa: %x:%x:%x:%x:%x:%x:%x:%x",
		       module_id, NIPV6ADDR(coa)));
		DEBUG((DBG_DATADUMP, "%s:mipv6_bu_add:lifet: %d, plen: %d, seq: %d",
		       module_id, lifetime, plength, sequence));

	} else if ((!mipv6_is_ha) && (home)) {
		ack = TRUE;
		ba_status = HOME_REGISTRATION_NOT_SUPPORTED;
		reply_addr = saddr;
	} else if (mipv6_is_ha) {
		struct inet6_ifaddr *ifp = NULL;
		/*
		  ADMINISTRATIVELY_PROHIBITED could be set..
		*/

		ifp = is_on_link_ipv6_address(haddr);

		if (mipv6_is_authorized(haddr) != TRUE || ifp == NULL) {
			ack = TRUE;
			ba_status = NOT_HA_FOR_MN;
			reply_addr = saddr;
		} else if (plength != 0 && plength != PREFIX_LENGTH) {
			ack = TRUE;
			ba_status = INCORRECT_INTERFACE_ID_LEN;
			reply_addr = saddr;
		} else {
			int send_nd = FALSE;
				  
			if (dad != 0 && ack != 0) {
				/* DAD is performed automatically when
                                 * an address is assigned to an
                                 * interface. */
			}

			rem_lifetime = get_min_lifetime(ifp, lifetime);

			if (plength != 0) {
				DEBUG((DBG_INFO, "%s: mipv6_bu_add:plength != 0",
					module_id));
				ifp = ifp->idev->addr_list;
				while (ifp != NULL) {
					rem_lifetime = get_min_lifetime(ifp,
								rem_lifetime);
					ifp = ifp->if_next;
				}
			}

			rem_lifetime = mipv6_lifetime_check(rem_lifetime);

			if(mipv6_bcache_exists(haddr) != HOME_REGISTRATION)
				send_nd = TRUE;

			if (send_nd) {
				if (mipv6_proxy_nd(haddr, plength,
						   router) == 0)
					DEBUG((DBG_INFO,"%s:bu_add:proxy_nd succ",
					       module_id));
				else
					DEBUG((DBG_WARNING,"%s:bu_add:proxy_nd fail",
					       module_id));
			}
			if (mipv6_bcache_add(ifindex, daddr, haddr, coa, 
					     rem_lifetime, plength, sequence, router,
					     HOME_REGISTRATION) != 0) {
				DEBUG((DBG_WARNING, "%s:bu_add: home reg failed.",
				       module_id));

				/* ack SHOULD be set (says draft) */
				ack = TRUE;
				ba_status = INSUFFICIENT_RESOURCES;
				reply_addr = saddr;
			} else {
				DEBUG((DBG_INFO, "%s:bu_add: home reg succeeded.",
				       module_id));
				ba_lifetime = rem_lifetime;

				/*
				  TODO (?): Lifetime could be less than this:
				  (in fact, if the cache is not crash proof,
				  the value really really ought to be less!!)
				*/
				ba_refresh = rem_lifetime;
			}

			DEBUG((DBG_DATADUMP, "%s:bu_add: home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(haddr)));
			DEBUG((DBG_DATADUMP, "%s: mipv6_add: coa: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(coa)));
			DEBUG((DBG_DATADUMP, "%s:bu_add: lifet:%d, plen:%d, seq:%d",
			       module_id, rem_lifetime, plength, sequence));
		}
	}

	if (ack) {
		DEBUG((DBG_INFO, "%s:mipv6_bu_add: sending ack (code=%d)",
		       module_id, ba_status));
                mipv6_send_ack_option(daddr, reply_addr, maxdelay, ba_status,
				      sequence, ba_lifetime, ba_refresh, NULL);
	}
}


/*
 * only called by mipv6_dstopt_process_bu. could well be inlined.
 *
 * performs the actual processing of a binding update the purpose
 * of which is to delete a binding.
 *
 */
static void mipv6_bu_delete(struct in6_addr *saddr, struct in6_addr *daddr,
			    struct in6_addr *haddr, struct in6_addr *coa,
			    int ack, int home, int router, int plength,
			    __u16 sequence, __u32 lifetime)
{
	__u8 ba_status = SUCCESS;
	__u32 ba_lifetime = 0;
	__u32 ba_refresh = 0;
	__u64 maxdelay = 0;

	DEBUG_FUNC();

	if (home == 0) {
		/* Primary Care-of Address Registration */

                if (mipv6_bcache_exists(haddr) == CACHE_ENTRY) {

			if (mipv6_bcache_delete(haddr, CACHE_ENTRY) != 0)
				DEBUG((DBG_ERROR, "%s:bu_delete: delete failed.",
				       module_id));
			else DEBUG((DBG_INFO, "%s:bu_delete: delete succeeded.",
				    module_id));

			DEBUG((DBG_DATADUMP, "%s: mipv6_bu_delete: home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(haddr)));
			DEBUG((DBG_DATADUMP, "%s: mipv6_delete: coa: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(coa)));
			DEBUG((DBG_DATADUMP, "%s:bu_del: lifet:%d, plen:%d, seq:%d",
			       module_id, lifetime, plength, sequence));

		} else {
			DEBUG((DBG_WARNING, "%s:bu_delete: entry is not in cache",
			       module_id));
			ba_status = REASON_UNSPECIFIED;
		}

        } else if (mipv6_is_ha && (home)) {
		/* Primary Care-of Address Deregistration */

		if (mipv6_bcache_exists(haddr) == HOME_REGISTRATION) {

			/* remove proxy_nd */
			if (mipv6_proxy_nd_rem(haddr, plength, router) == 0)
				DEBUG((DBG_INFO, "%s:bu_delete:proxy_nd succ.",
				       module_id));
			else
				DEBUG((DBG_WARNING, "%s:bu_delete: proxy_nd failed.",
      				       module_id));

		      	if (mipv6_bcache_delete(haddr,
						HOME_REGISTRATION) != 0)
				DEBUG((DBG_ERROR, "%s: bu_delete: delete failed.",
				       module_id));
			else
				DEBUG((DBG_INFO, "%s: bu_delete: delete succ.",
				       module_id));
		       
			ba_lifetime = 0;
			ba_refresh = 0;

			DEBUG((DBG_DATADUMP, "%s: bu_delete: home_addr: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(haddr)));
			DEBUG((DBG_DATADUMP, "%s: mipv6_delete: coa: %x:%x:%x:%x:%x:%x:%x:%x",
			       module_id, NIPV6ADDR(coa)));
			DEBUG((DBG_DATADUMP, "%s:bu_delete:lifet:%d,plen:%d,seq:%d",
			       module_id, lifetime, plength, sequence));

		} else {
			ack = TRUE;
			ba_status = NOT_HA_FOR_MN;
		}
	} else {
		/* Node is not a Home Agent, but the sender believes
		 * so.  The draft doesn't tell if, when _deleting_,
		 * sender should be informed with a code 132 (Home Reg
		 * not supported).  At this time we return
		 * REASON_UNSPECIFIED. */

		ba_status = REASON_UNSPECIFIED;
	}

	if (ack) {
                mipv6_send_ack_option(daddr, saddr, maxdelay, ba_status,
				      sequence, ba_lifetime, ba_refresh, NULL);
	}
}

/*
 * Performs the processing of the destination options saved before.
 *
 * Home Address Option,
 * Binding Update Option,
 * Binding Acknowledgements Option, and
 * Binding Request Option will be processed, and
 * corresponding actions taken, based on the configuration of
 * the node, and the contents of the Destination Option
 *
 */
#define ADDRANY {{{0, 0, 0, 0}}}

static int mipv6_parse_subopts(
	__u8 *opt, int subopt_start,
	struct mipv6_subopt_info *sinfo)
{
	int offset = subopt_start;
	int optlen = opt[1] + 2;
	struct mipv6_subopt_unique_id *ui;
	struct mipv6_subopt_alternate_coa *ac;

	DEBUG_FUNC();

	sinfo->f.flags = 0;

	while (offset < optlen) {
		switch (opt[offset]) {

		case MIPV6_SUBOPT_PAD1:
			offset++;
			break;

		case MIPV6_SUBOPT_PADN:
			offset += opt[offset + 1] + 2;
			break;

		case MIPV6_SUBOPT_UNIQUEID:		
			ui = (struct mipv6_subopt_unique_id *)(opt + offset);

			if (ui->length !=
			   sizeof(struct mipv6_subopt_unique_id) - 2)
				goto fail;
			
			if (sinfo->fso_uid)
				DEBUG((DBG_WARNING, "UID-suboption already exists"));
			
			sinfo->fso_uid = 1;
			sinfo->uid = ntohs(ui->unique_id);
			offset += ui->length + 2;
			break;

		case MIPV6_SUBOPT_ALTERNATE_COA:	
			ac = (struct mipv6_subopt_alternate_coa *)&opt[offset];

			if (ac->length !=
			   sizeof(struct mipv6_subopt_alternate_coa)-2)
				goto fail;
			
			if (sinfo->fso_alt_coa)
				DEBUG((DBG_WARNING, "ACOA-suboption already exists"));
			
			sinfo->fso_alt_coa = 1;
			ipv6_addr_copy(&sinfo->alt_coa, &ac->addr);
			offset += ac->length + 2;
			break;

		default:
			/* unrecognized suboption */
			DEBUG((DBG_WARNING, "unrecognized suboption identifier"));
			goto fail;
		}
	}
	
	/* check if succeeded */
	if (offset == optlen) return 0;
	
fail:
	DEBUG((DBG_WARNING, "malformed suboption field"));

	/* failed! */
	return 1;
}

int mipv6_handle_homeaddr(struct sk_buff *skb, int optoff)
{
	struct in6_addr *saddr = &(skb->nh.ipv6h->saddr);
	struct in6_addr coaddr;
	struct mipv6_subopt_info sinfo;
	struct mipv6_skb_parm *opt = (struct mipv6_skb_parm *)skb->cb;
	struct mipv6_dstopt_homeaddr *haopt = 
		(struct mipv6_dstopt_homeaddr *)&skb->nh.raw[optoff];

	DEBUG_FUNC();

	MIPV6_INC_STATS(n_ha_rcvd);

	if (haopt->length < 16) {
		DEBUG((DBG_WARNING, 
		       "Invalid Option Length for Home Address Option"));
		return 0; /* invalid option length */
	}

	/* store Home Address Option offset in cb */
	opt->ha = optoff + 2;

	ipv6_addr_copy(&coaddr, saddr);
	ipv6_addr_copy(saddr, &haopt->addr);
	ipv6_addr_copy(&haopt->addr, &coaddr);

	memset(&sinfo, 0, sizeof(sinfo));
	if (haopt->length > 16) {
		int ret;
		ret = mipv6_parse_subopts((__u8 *)haopt, sizeof(*haopt), &sinfo);
		if (ret < 0) {
			DEBUG((DBG_WARNING,
			       "Invalid Sub-option in Home Address Option"));
			return 0; /* invalid suboption */
		}
	}
       
	if (mipv6_is_mn) 
		mipv6_check_tunneled_packet(skb);
	return 1;
}

int mipv6_handle_bindack(struct sk_buff *skb, int optoff)
{
	struct in6_addr *saddr = &(skb->nh.ipv6h->saddr);
	struct mipv6_subopt_info sinfo;
	struct mipv6_dstopt_bindack *baopt =
		(struct mipv6_dstopt_bindack *)&skb->nh.raw[optoff];
	int ifindex = ((struct inet6_skb_parm *)skb->cb)->iif;
	__u8 status;
	__u16 sequence;
	__u32 lifetime, refresh;

	DEBUG_FUNC();

	MIPV6_INC_STATS(n_ba_rcvd);

#ifdef CONFIG_IPV6_MOBILITY_AH
	if (!(skb->security & RCV_AUTH)) {
		DEBUG((DBG_WARNING, 
		       "Authentication required for Binding Ack"));
		return 0; /* must be authenticated */
	}
#endif
	if (baopt->length < 11) {
		DEBUG((DBG_WARNING, 
		       "Invalid Option Length for Binding Ack Option"));
		return 0; /* invalid option length */
	}

	memset(&sinfo, 0, sizeof(sinfo));
	if (baopt->length > 11) {
		int ret;
		ret = mipv6_parse_subopts((__u8 *)baopt, sizeof(*baopt), &sinfo);
		if (ret < 0) {
			DEBUG((DBG_WARNING,
			       "Invalid Sub-option in Binding Ack Option"));
			return 0; /* invalid suboption */
		}
	}

	status = baopt->status;
	sequence = ntohs(baopt->seq);
	lifetime = ntohl(baopt->lifetime);
	refresh = ntohl(baopt->refresh);

	if (baopt->status >= REASON_UNSPECIFIED) {
		DEBUG((DBG_WARNING, "%s:ack status indicates error: %d",
		       module_id, baopt->status));
		mipv6_ack_rcvd(ifindex, saddr, sequence, lifetime, 
			       refresh, STATUS_REMOVE);
	} else if (mipv6_ack_rcvd(ifindex, saddr, sequence, lifetime, 
				  refresh, STATUS_UPDATE)) {
		DEBUG((DBG_WARNING, 
		       "mipv6_dstopt_process_ba: mipv6_ack_rcvd failed"));
	}

	/* dump the contents of the Binding Acknowledgment. */
	dump_ba(MIPV6_TLV_BINDACK, status, sequence, lifetime, refresh, sinfo);

	return 1;
}

int mipv6_handle_bindupdate(struct sk_buff *skb, int optoff)
{
	struct mipv6_skb_parm *opt = (struct mipv6_skb_parm *)skb->cb;
	struct in6_addr *haddr = &(skb->nh.ipv6h->saddr);
	struct in6_addr *daddr = &(skb->nh.ipv6h->daddr);
	struct in6_addr saddr, coaddr;
	struct mipv6_subopt_info sinfo;
	struct mipv6_dstopt_bindupdate *buopt =
		(struct mipv6_dstopt_bindupdate *)&skb->nh.raw[optoff];
	__u8 ack, home, router, dad, plength;
	__u16 sequence;
	__u32 lifetime;

	DEBUG_FUNC();

	MIPV6_INC_STATS(n_bu_rcvd);

	if (buopt->length < 8) {
		DEBUG((DBG_WARNING, 
		       "Invalid Option Length for Binding Update Option"));
		return 0; /* invalid option length */
	}

#ifdef CONFIG_IPV6_MOBILITY_AH
	if (!(skb->security & RCV_AUTH)) {
		DEBUG((DBG_WARNING, 
		       "Authentication required for Binding Update"));
		return 0; /* must be authenticated */
	}
#endif
	if (opt->ha == 0) {
		DEBUG((DBG_WARNING, 
		       "Home Address Option must be present for Bindind Update"));
		return 0; /* home address option must be present */
	}

	ipv6_addr_copy(&saddr, (struct in6_addr *)(skb->nh.raw + opt->ha));

	ack = !!(buopt->flags & MIPV6_BU_F_ACK);
	home = !!(buopt->flags & MIPV6_BU_F_HOME);
	router = !!(buopt->flags & MIPV6_BU_F_ROUTER);
	dad = !!(buopt->flags & MIPV6_BU_F_DAD);

	plength = buopt->prefixlen;
	sequence = ntohs(buopt->seq);
	lifetime = ntohl(buopt->lifetime);
	ipv6_addr_copy(&coaddr, &saddr);

	memset(&sinfo, 0, sizeof(sinfo));
	if (buopt->length > 8) {
		int ret;
		ret = mipv6_parse_subopts((__u8 *)buopt, sizeof(*buopt), &sinfo);
		if (ret < 0) {
			DEBUG((DBG_WARNING,
			       "Invalid Sub-option in Binding Ack Option"));
			return 0; /* invalid suboption */
		}
		if (sinfo.fso_alt_coa) 
			ipv6_addr_copy(&coaddr, &sinfo.alt_coa);
	}

	if ((lifetime != 0) && (ipv6_addr_cmp(&coaddr, haddr) != 0)) {
		DEBUG((DBG_INFO, "%s:dstopt_process_bu: calling bu_add.",
		       module_id));
		mipv6_bu_add(opt->iif, &saddr, daddr, haddr, &coaddr, ack, home, 
			     router, dad, plength, sequence, lifetime);

	} else if ((lifetime == 0) || (ipv6_addr_cmp(&coaddr, haddr) == 0)) {
		DEBUG((DBG_INFO, "%s:dstopt_process_bu: calling bu_delete.",
		       module_id));
		mipv6_bu_delete(&saddr, daddr, haddr, &coaddr, ack, home, 
				router,	plength, sequence, lifetime);
	}

	/* dump the contents of the binding update. */
	dump_bu(MIPV6_TLV_BINDUPDATE, ack, home, router, dad, plength, 
		sequence, lifetime, sinfo);

	return 1;
}

int mipv6_handle_bindrq(struct sk_buff *skb, int optoff)
{
	struct mipv6_dstopt_bindrq *bropt = 
		(struct mipv6_dstopt_bindrq *)&skb->nh.raw[optoff];
	struct in6_addr *daddr = &(skb->nh.ipv6h->daddr);
	struct mipv6_subopt_info sinfo;
	struct mipv6_bul_entry *entry;
	struct in6_addr home_agent;
	int lifetime = 0;

	DEBUG_FUNC();

	MIPV6_INC_STATS(n_br_rcvd);

	/* option length checking not done since default length is 0 */

	if (mipv6_mn_get_homeagent(&home_agent) < 0) {
		DEBUG((DBG_ERROR, "%s: no home agent found.",
		       module_id));
		return 0; /* no home agent, can't get lifetime */
	}
	entry = (struct mipv6_bul_entry *)mipv6_bul_get(&home_agent);

	lifetime = (entry->expire - jiffies) / HZ < CN_BU_DEF_LIFETIME ? 
		(entry->expire - jiffies) / HZ : CN_BU_DEF_LIFETIME;

	memset(&sinfo, 0, sizeof(sinfo));
	if (bropt->length > 0) {
		int ret;
		ret = mipv6_parse_subopts((__u8 *)bropt, sizeof(*bropt), &sinfo);
		if (ret < 0) {
			DEBUG((DBG_WARNING,
			       "Invalid Sub-option in Binding Request Option"));
			return 0; /* invalid suboption */
		}
	}
	
	if (mipv6_send_upd_option(&entry->home_addr, daddr, CN_BU_DELAY,
				  INITIAL_BINDACK_TIMEOUT, MAX_BINDACK_TIMEOUT,
				  0, MIPV6_BU_F_ACK, 0, lifetime, &sinfo))
		DEBUG((DBG_ERROR, "%s: BU send failed.", module_id));

	/* dump the contents of the Binding Request. */
	dump_br(MIPV6_TLV_BINDRQ, sinfo);

	return 1;
}

void __init mipv6_initialize_procrcv(void)
{
        DEBUG_FUNC();

	/* Set kernel hooks */
	MIPV6_SETCALL(mipv6_handle_bindupdate, mipv6_handle_bindupdate);
	MIPV6_SETCALL(mipv6_handle_bindack, mipv6_handle_bindack);
	MIPV6_SETCALL(mipv6_handle_bindrq, mipv6_handle_bindrq);
	MIPV6_SETCALL(mipv6_handle_homeaddr, mipv6_handle_homeaddr);
}
