/*
 * $Id: ipsec.c 1.52 06/05/15 18:34:56+03:00 vnuorval@tcs.hut.fi $
 *
 * This file is part of the MIPL Mobile IPv6 for Linux.
 *
 * Authors:
 *  Shinta Sugimoto	<shinta.sugimoto@ericsson.com>
 *
 * Copyright 2004-2005 USAGI/WIDE Project
 *
 * MIPL Mobile IPv6 for Linux 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; version 2 of
 * the License.
 *
 * MIPL Mobile IPv6 for Linux is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with MIPL Mobile IPv6 for Linux; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 * 02111-1307 USA.
 */

/*
 * Special thanks to Francis Dupont who initially had the idea of making
 * PF_KEY extension as an interface between Mobile IPv6 and IPsec/IKE
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#include <sys/param.h>
#include <sys/types.h>
#include <arpa/inet.h>

#include <netinet/ip6.h>
#include <netinet/in.h>
#include <netinet/ip6mh.h>

#include "ipsec.h"
#include "xfrm.h"
#include "conf.h"
#include "bul.h"
#include "util.h"
#include "mn.h"
#include "rtnl.h"
#include "debug.h"

static void _set_tmpl(struct xfrm_user_tmpl *tmpl,
		      uint16_t family,
		      uint8_t proto,
		      uint8_t mode,
		      const struct in6_addr *tdst,
		      const struct in6_addr *tsrc,
		      uint32_t reqid)
{
	memset(tmpl, 0, sizeof(*tmpl));
	tmpl->family = family;
        tmpl->ealgos = ~(uint32_t)0;
        tmpl->aalgos = ~(uint32_t)0;
	tmpl->id.proto = proto;
	tmpl->optional = 0;
	tmpl->mode = mode;
	tmpl->reqid = reqid;
	if (mode == XFRM_MODE_TUNNEL && tdst)
                memcpy(&tmpl->id.daddr, tdst, sizeof(tmpl->id.daddr));
	if (mode == XFRM_MODE_TUNNEL && tsrc)
                memcpy(&tmpl->saddr, tsrc, sizeof(tmpl->saddr));
}

static void _set_sp(struct xfrm_userpolicy_info *sp,
		    struct ipsec_policy_entry *e,
		    int dir,
		    const struct in6_addr *in6_dst,
		    int  dst_len,
		    const struct in6_addr *in6_src,
		    int src_len,
		    __attribute__ ((unused)) int ifindex,
		    int nodetype)
{
	assert(sp);
	assert(e);
	assert(in6_dst);
	assert(in6_src);

	memset(sp, 0, sizeof(*sp));

	sp->sel.family = AF_INET6;
	sp->dir = dir;
	sp->action = e->action;
	memcpy(&sp->sel.saddr.a6, in6_src, sizeof(sp->sel.saddr.a6));
	memcpy(&sp->sel.daddr.a6, in6_dst, sizeof(sp->sel.daddr.a6));
	sp->sel.prefixlen_s = src_len;
	if (!src_len && (!IN6_ARE_ADDR_EQUAL(in6_src, &in6addr_any)))
		sp->sel.prefixlen_s = 128;
	sp->sel.prefixlen_d = dst_len;
	if (!dst_len && (!IN6_ARE_ADDR_EQUAL(in6_dst, &in6addr_any)))
		sp->sel.prefixlen_d = 128;

	sp->sel.ifindex = 0;

	switch (e->type) {
		/* Tunnel */
	case IPSEC_POLICY_TYPE_TUNNELHOMETESTING:
		if (dir == XFRM_POLICY_IN || dir == XFRM_POLICY_FWD) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(IP6_MH_TYPE_HOT);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(IP6_MH_TYPE_HOTI);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else if (dir == XFRM_POLICY_OUT) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(IP6_MH_TYPE_HOTI);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(IP6_MH_TYPE_HOT);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else {
			sp->sel.sport = 0;
		}
		sp->sel.proto = IPPROTO_MH;
		sp->priority = MIP6_PRIO_RO_SIG_RR;
		break;
	case IPSEC_POLICY_TYPE_TUNNELMH:
		sp->sel.proto = IPPROTO_MH;
		sp->priority = MIP6_PRIO_RO_SIG_RR;
		break;
	case IPSEC_POLICY_TYPE_TUNNELPAYLOAD:
		sp->priority = MIP6_PRIO_NO_RO_DATA;
		break;

		/* Transport */
	case IPSEC_POLICY_TYPE_HOMEREGBINDING:
		if (dir == XFRM_POLICY_IN) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(IP6_MH_TYPE_BACK);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(IP6_MH_TYPE_BU);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else if (dir == XFRM_POLICY_OUT) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(IP6_MH_TYPE_BU);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(IP6_MH_TYPE_BACK);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else {
			sp->sel.sport = 0;
		}
	case IPSEC_POLICY_TYPE_MH:
		sp->sel.proto = IPPROTO_MH;
		sp->priority = MIP6_PRIO_HOME_SIG;
		break;
	case IPSEC_POLICY_TYPE_BERROR:
		sp->sel.proto = IPPROTO_MH;
		sp->priority = MIP6_PRIO_HOME_SIG;
		break;
	case IPSEC_POLICY_TYPE_MOBPFXDISC:
		if (dir == XFRM_POLICY_IN) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(MIP_PREFIX_ADVERT);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(MIP_PREFIX_SOLICIT);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else if (dir == XFRM_POLICY_OUT) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(MIP_PREFIX_SOLICIT);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(MIP_PREFIX_ADVERT);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else {
			sp->sel.sport = 0;
		}
		sp->sel.proto = IPPROTO_ICMPV6;
		sp->priority = MIP6_PRIO_HOME_DATA_IPSEC;
		break;
	case IPSEC_POLICY_TYPE_NDISC: /* XXX Makes sense ? --arno */
		if (dir == XFRM_POLICY_IN) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(ND_NEIGHBOR_SOLICIT);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(ND_NEIGHBOR_ADVERT);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else if (dir == XFRM_POLICY_OUT) {
			if (nodetype == MIP6_ENTITY_MN) {
				sp->sel.sport = htons(ND_NEIGHBOR_ADVERT);
				sp->sel.sport_mask = ~((__u16)0);
			} else if (nodetype == MIP6_ENTITY_HA) {
				sp->sel.sport = htons(MIP_PREFIX_SOLICIT);
				sp->sel.sport_mask = ~((__u16)0);
			} else
				sp->sel.sport = 0;
		} else {
			sp->sel.sport = 0;
		}
		sp->sel.proto = IPPROTO_ICMPV6;
		sp->priority = MIP6_PRIO_HOME_SIG;
		break;
	case IPSEC_POLICY_TYPE_ICMP:
		sp->sel.proto = IPPROTO_ICMPV6;
		sp->priority = MIP6_PRIO_HOME_DATA_IPSEC;
		break;
	case IPSEC_POLICY_TYPE_ANY:
		sp->priority = MIP6_PRIO_HOME_DATA_IPSEC;
		break;
	default:
		/* not tunnel IPsec type */
		break;
	}

	return;
}

#ifdef XFRM_MSG_MIGRATE
/*
 * xfrm_sendmigrate -- send MIGRATE message to the kernel
 *
 * @sp: security policy
 * @tmpl: template which includes key information {proto, mode, reqid,
 *        old_dst, old_src} for MIGRATE message
 * @ndst: new destination address
 * @nsrc: new source address
 *
 * return value:
 *	success:  0
 *	failure: -1
 */
static int xfrm_sendmigrate(struct xfrm_userpolicy_info *sp,
			    const struct xfrm_user_tmpl *tmpl,
			    const struct in6_addr *ndst,
			    const struct in6_addr *nsrc)
{
	struct {
		struct nlmsghdr			n;
		struct xfrm_userpolicy_id	xpid;
		char				buf[256];
	} req;
	struct xfrm_user_migrate um;
	struct xfrm_user_kmaddress kma;
	int err = 0;

	memset(&req, 0, sizeof(req));
	memset(&um, 0, sizeof(um));
	memset(&kma, 0, sizeof(kma));

	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(req.xpid));
	req.n.nlmsg_flags = NLM_F_REQUEST;
	req.n.nlmsg_type = XFRM_MSG_MIGRATE;

	/* policy information */
	memset(&req.xpid, 0, sizeof(req.xpid));
	memcpy(&req.xpid.sel, &sp->sel, sizeof(req.xpid.sel));
	req.xpid.index = 0;
	req.xpid.dir = sp->dir;

	/* migrate */
	um.old_family = AF_INET6;
	um.new_family = AF_INET6;
	um.proto = tmpl->id.proto;
	um.mode = tmpl->mode;
	um.reqid = tmpl->reqid;

	memcpy(&um.old_daddr, &tmpl->id.daddr, sizeof(um.old_daddr));
	memcpy(&um.old_saddr, &tmpl->saddr, sizeof(um.old_saddr));
	memcpy(&um.new_daddr, ndst, sizeof(um.new_daddr));
	memcpy(&um.new_saddr, nsrc, sizeof(um.new_saddr));

	addattr_l(&req.n, sizeof(req), XFRMA_MIGRATE,
		  (void *)&um, sizeof(struct xfrm_user_migrate));

	/* kmaddress */
	kma.family = AF_INET6;
	kma.reserved = 0;
	if (sp->dir == XFRM_POLICY_OUT) {
		memcpy(&kma.local,  nsrc, sizeof(kma.local));
		memcpy(&kma.remote, ndst, sizeof(kma.remote));
	} else {
		memcpy(&kma.local,  ndst, sizeof(kma.local));
		memcpy(&kma.remote, nsrc, sizeof(kma.remote));
	}


	addattr_l(&req.n, sizeof(req), XFRMA_KMADDRESS,
		  (void *)&kma, sizeof(struct xfrm_user_kmaddress));

#if 0
	dbg("sel.family = %d\n", xpid.sel.family);
	dbg("sel.saddr = %x:%x:%x:%x:%x:%x:%x:%x\n",
	    NIP6ADDR((struct in6_addr *)&xpid.sel.saddr));
	dbg("sel.prefixlen_s = %d\n", xpid.sel.prefixlen_s);
	dbg("sel.sport = %d\n", xpid.sel.sport);
	dbg("sel.sport_mask = %d\n", xpid.sel.sport_mask);
	dbg("sel.daddr = %x:%x:%x:%x:%x:%x:%x:%x\n",
	    NIP6ADDR((struct in6_addr *)&xpid.sel.daddr));
	dbg("sel.prefixlen_d = %d\n", xpid.sel.prefixlen_d);
	dbg("sel.dport = %d\n", xpid.sel.dport);
	dbg("sel.dport_mask = %d\n", xpid.sel.dport_mask);
	dbg("sel.proto = %d\n", xpid.sel.proto);
	dbg("sel.ifindex = %d\n", xpid.sel.ifindex);
	dbg("sel.user = %d\n", xpid.sel.user);
#endif

	err = rtnl_xfrm_do(&req.n, NULL);
	if (err < 0)
		dbg("err = %d (%s)\n", err, strerror(-err));

	return ((err == 0 || err == -ENOENT) ? 0 : -1);
}
#else
static int xfrm_sendmigrate(struct xfrm_userpolicy_info *sp,
			    const struct xfrm_user_tmpl *tmpl,
			    const struct in6_addr *ndst,
			    const struct in6_addr *nsrc)
{
	dbg("Error because it is built without XFRM_MSG_MIGRATE\n");
	return -1;
}
#endif

int ipsec_policy_apply(const struct in6_addr *haaddr,
		       const struct in6_addr *hoa,
		       int (* func)(const struct in6_addr *haaddr,
				    const struct in6_addr *hoa,
				    struct ipsec_policy_entry *e, void *arg),
		       void *arg)
{
	struct list_head *lp;
	int ret = 0;

	list_for_each(lp, &conf.ipsec_policies) {
		struct ipsec_policy_entry *e;
		
		e = list_entry(lp, struct ipsec_policy_entry, list);

		if (haaddr && !IN6_ARE_ADDR_EQUAL(haaddr, &e->ha_addr))
			continue;

		if (hoa && !IN6_ARE_ADDR_EQUAL(hoa, &e->mn_addr))
			continue;

		ret = func(&e->ha_addr, &e->mn_addr, e, arg);
		if (ret)
			break;
	}
	return ret;
}

int ipsec_policy_dump_config(const struct in6_addr *haaddr,
			     const struct in6_addr *hoa,
			     struct ipsec_policy_entry *e,
			     __attribute__ ((unused)) void *arg)
{
	dbg("IPsec: HA address = %x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(haaddr));
	dbg("IPsec: Home address = %x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(hoa));

	dbg("IPsec: IPsec type = %s\n",
	    (e->type == IPSEC_POLICY_TYPE_HOMEREGBINDING) ? "HomeRegBinding" :
	    (e->type == IPSEC_POLICY_TYPE_MH) ? "Mh" :
	    (e->type == IPSEC_POLICY_TYPE_MOBPFXDISC) ? "MobPfxDisc" :
	    (e->type == IPSEC_POLICY_TYPE_ICMP) ? "ICMP" :
	    (e->type == IPSEC_POLICY_TYPE_ANY) ? "any" :
	    (e->type == IPSEC_POLICY_TYPE_TUNNELHOMETESTING) ? "TunnelHomeTesting" :
	    (e->type == IPSEC_POLICY_TYPE_TUNNELMH) ? "TunnelMh" :
	    (e->type == IPSEC_POLICY_TYPE_TUNNELPAYLOAD) ? "TunnelPayload" : "?");
	dbg("IPsec: IPsec templates = %s%s%s\n",
	    ipsec_use_esp(e) ? "ESP " : "",
	    ipsec_use_ah(e) ? "AH " : "",
	    ipsec_use_ipcomp(e) ? "IPComp " : "");
	dbg("IPsec: IPsec reqid to-HA, to-MN = %u, %u\n", e->reqid_toha, e->reqid_tomn);
	dbg("IPsec: IPsec action = %s\n",
	    e->action == XFRM_POLICY_ALLOW ? "allow" : "block");
	dbg("IPsec: ---\n");

	return 0;
}

static void dump_migrate(int ifindex,
			 u_int8_t ipsec_proto,
			 const struct in6_addr *hoa,
			 const struct in6_addr *haaddr,
			 const struct in6_addr *oldcoa,
			 const struct in6_addr *newcoa)
{
	dbg("ifindex\t%d\n", ifindex);
	dbg("hoa\t%x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(hoa));
	dbg("ha\t%x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(haaddr));
	if (oldcoa)
		dbg("ocoa\t%x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(oldcoa));
	if (newcoa)
		dbg("ncoa\t%x:%x:%x:%x:%x:%x:%x:%x\n", NIP6ADDR(newcoa));
	dbg("ipsec\t%s\n",
	    (ipsec_proto == IPPROTO_ESP) ? "ESP" :
	    (ipsec_proto == IPPROTO_AH) ? "AH" :
	    (ipsec_proto == IPPROTO_COMP) ? "IPComp" : "?");

	return;
}

int ipsec_policy_walk(int (* func)(const struct in6_addr *haaddr,
				   const struct in6_addr *hoa,
				   struct ipsec_policy_entry *e, void *arg),
		      void *arg)
{
	return ipsec_policy_apply(NULL, NULL, func, arg);
}

int ipsec_policy_entry_check(const struct in6_addr *haaddr,
			     const struct in6_addr *hoa,
			     int type)
{
	struct list_head *lp;
	int ret = 0;

	list_for_each(lp, &conf_parsed->ipsec_policies) {
		struct ipsec_policy_entry *e;

		e = list_entry(lp, struct ipsec_policy_entry, list);

		if ((haaddr && !IN6_ARE_ADDR_EQUAL(haaddr, &e->ha_addr)) ||
		    (hoa && !IN6_ARE_ADDR_EQUAL(hoa, &e->mn_addr)))
			continue;

		if (e->type & type) {
			ret = e->ipsec_protos;
			break;
		}
	}
	return ret;
}

struct ha_ipsec_tnl_update {
	int tunnel;
	struct in6_addr coa;
	struct in6_addr old_coa;
	struct list_head *mnp;
};

/*
 *   Tunnel Update (for HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_tnl_update(const struct in6_addr *haaddr,
			  const struct in6_addr *hoa,
			  struct ipsec_policy_entry *e,
			  void *arg)
{
	int err = 0;
	struct ha_ipsec_tnl_update *info = (struct ha_ipsec_tnl_update *)arg;
	int ifindex;
	const struct in6_addr *oldcoa, *newcoa;
	const struct in6_addr *peer_addr = hoa;
	struct list_head *mnp;
	u_int8_t ipsec_proto;
	struct xfrm_user_tmpl tmpl;
	struct xfrm_userpolicy_info sp;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	switch (e->type) {
	case IPSEC_POLICY_TYPE_TUNNELHOMETESTING:
	case IPSEC_POLICY_TYPE_TUNNELMH:
	case IPSEC_POLICY_TYPE_TUNNELPAYLOAD:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}
	
	ifindex = info->tunnel;
	oldcoa = IN6_ARE_ADDR_EQUAL(&info->old_coa, &in6addr_any) ?
		peer_addr : &info->old_coa;
	newcoa = &info->coa;
	mnp = info->mnp;

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, oldcoa, newcoa);

	/* inbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, oldcoa, e->reqid_toha);
	_set_sp(&sp, e, XFRM_POLICY_IN, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_HA);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
		dbg("migrate for INBOUND policy failed\n");
        	goto end;
	}

	/* forward */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, oldcoa, e->reqid_toha);
	_set_sp(&sp, e, XFRM_POLICY_FWD, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_HA);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
		dbg("migrate for FORWARD policy failed\n");
		goto end;
	}

	/* outbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
		  oldcoa, haaddr, e->reqid_tomn);
	_set_sp(&sp, e, XFRM_POLICY_OUT, hoa, 0, &in6addr_any, 0,
		ifindex, MIP6_ENTITY_HA);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
		dbg("migrate for OUTBOUND policy failed\n");
        	goto end;
	}

	/* Mobile router case */
	if ( (e->type == IPSEC_POLICY_TYPE_TUNNELPAYLOAD) && mnp)
	{
		struct list_head *list;

		/* We have to modify rules to protect traffic to and from MNP's, the same way as HoA */
		list_for_each(list, mnp)
		{
			struct prefix_list_entry *p;
			p = list_entry(list, struct prefix_list_entry, list);

			/* inbound */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  haaddr, oldcoa, e->reqid_toha);
			_set_sp(&sp, e, XFRM_POLICY_IN, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
				ifindex, MIP6_ENTITY_HA);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
				dbg("migrate for INBOUND policy failed\n");
        			goto end;
			}

			/* forward */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  haaddr, oldcoa, e->reqid_toha);
			_set_sp(&sp, e, XFRM_POLICY_FWD, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
				ifindex, MIP6_ENTITY_HA);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
				dbg("migrate for FORWARD policy failed\n");
				goto end;
			}

			/* outbound */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  oldcoa, haaddr, e->reqid_tomn);
			_set_sp(&sp, e, XFRM_POLICY_OUT, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
				ifindex, MIP6_ENTITY_HA);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
				dbg("migrate for OUTBOUND policy failed\n");
        			goto end;
			}
		}
	}

 end:
	return err;
}

int ha_ipsec_tnl_update(const struct in6_addr *haaddr,
			const struct in6_addr *hoa,
			const struct in6_addr *coa,
			const struct in6_addr *old_coa,
			int tunnel,
			struct list_head *mnp)
{
	struct ha_ipsec_tnl_update b;
	b.coa = *coa;
	b.old_coa = *old_coa;
	b.tunnel = tunnel;
	b.mnp = mnp;
	return ipsec_policy_apply(haaddr, hoa, _ha_tnl_update, &b);
}

struct ha_ipsec_mnp_update {
	int tunnel;
	struct list_head *old_mnps;
	struct list_head *new_mnps;
};

/*
 *   Add/Delete MNP IPsec Security Policy
 */
static int _ha_mnp_pol_mod(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg,
			   int add)
{
	int err = 0;
	struct ha_ipsec_mnp_update *parms = (struct ha_ipsec_mnp_update *)arg;
	struct xfrm_userpolicy_info sp;
	struct xfrm_user_tmpl tmpl;
	u_int16_t ipsec_proto;
	struct list_head *list, *old_mnps, *new_mnps, *main_mnps, *ref_mnps;
	int ifindex;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	ifindex = parms->tunnel;
	old_mnps = parms->old_mnps;
	new_mnps = parms->new_mnps;

	if (e->type != IPSEC_POLICY_TYPE_TUNNELPAYLOAD)
		goto end;

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}

	/* Reverse the search logic on lists based on expected
	 * action (add/del) */
	main_mnps = add ? new_mnps : old_mnps;
	ref_mnps = add ? old_mnps : new_mnps;

	if (main_mnps == NULL)
		goto end;

	/* We have to add/delete rules to protect traffic to
	   and from MNP's, the same way as HoA */
	list_for_each(list, main_mnps) {
		struct prefix_list_entry *p;
		p = list_entry(list, struct prefix_list_entry, list);

		if (ref_mnps &&
		    prefix_list_find(ref_mnps, &p->ple_prefix, p->ple_plen))
			continue;

		/* inbound */
		_set_sp(&sp, e, XFRM_POLICY_IN, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
			ifindex, MIP6_ENTITY_HA);
		_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
			  haaddr, hoa, e->reqid_toha);
		if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
			dbg("modifying INBOUND policy failed\n");
			err = -1;
			goto end;
		}

		/* forward */
		_set_sp(&sp, e, XFRM_POLICY_FWD, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
			ifindex, MIP6_ENTITY_HA);
		_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
			  haaddr, hoa, e->reqid_toha);
		if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
			dbg("modifying FORWARD policy failed\n");
			err = -1;
			goto end;
		}

		/* outbound */
		_set_sp(&sp, e, XFRM_POLICY_OUT, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
			ifindex, MIP6_ENTITY_HA);
		_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
			  hoa, haaddr, e->reqid_tomn);
		if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
			dbg("modifying OUTBOUND policy failed\n");
			err = -1;
			goto end;
		}
	}

 end:
	return err;
}


/*
 *   Add SP entry (for MNP on HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_mnp_pol_add(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _ha_mnp_pol_mod(haaddr, hoa, e, arg, 1);
}

int ha_ipsec_mnp_pol_add(const struct in6_addr *our_addr,
			 const struct in6_addr *peer_addr,
			 struct list_head *old_mnps,
			 struct list_head *new_mnps,
			 int tunnel)
{
	struct ha_ipsec_mnp_update b;
	b.tunnel = tunnel;
	b.old_mnps = old_mnps;
	b.new_mnps = new_mnps;

	return ipsec_policy_apply(our_addr, peer_addr, _ha_mnp_pol_add, &b);
}

/*
 *   Delete SP entry (for MNP on HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_mnp_pol_del(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _ha_mnp_pol_mod(haaddr, hoa, e, arg, 0);
}

int ha_ipsec_mnp_pol_del(const struct in6_addr *our_addr,
			 const struct in6_addr *peer_addr,
			 struct list_head *old_mnps,
			 struct list_head *new_mnps,
			 int tunnel)
{
	struct ha_ipsec_mnp_update b;
	b.tunnel = tunnel;
	b.old_mnps = old_mnps;
	b.new_mnps = new_mnps;

	return ipsec_policy_apply(our_addr, peer_addr,
				  _ha_mnp_pol_del, &b);
}


struct ha_ipsec_trns_update {
	int tunnel;
	struct in6_addr coa;
	struct in6_addr old_coa;
};

/*
 *   Transport Update (for HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_trns_update(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	int err = 0;
	struct ha_ipsec_trns_update *info = (struct ha_ipsec_trns_update *)arg;
	int ifindex;
	const struct in6_addr *oldcoa, *newcoa;
	const struct in6_addr *peer_addr = hoa;
	u_int8_t ipsec_proto;
	struct xfrm_user_tmpl tmpl;
	struct xfrm_userpolicy_info sp;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	switch (e->type) {
	case IPSEC_POLICY_TYPE_HOMEREGBINDING:
	case IPSEC_POLICY_TYPE_BERROR:
	case IPSEC_POLICY_TYPE_MH:
	case IPSEC_POLICY_TYPE_MOBPFXDISC:
	case IPSEC_POLICY_TYPE_NDISC:
	case IPSEC_POLICY_TYPE_ICMP:
	case IPSEC_POLICY_TYPE_ANY:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}

	ifindex = info->tunnel;
	oldcoa = IN6_ARE_ADDR_EQUAL(&info->old_coa, &in6addr_any) ?
		peer_addr : &info->old_coa;
	newcoa = &info->coa;

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, oldcoa, newcoa);

	/* inbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TRANSPORT,
		  haaddr, oldcoa, e->reqid_toha);
	_set_sp(&sp, e, XFRM_POLICY_IN, haaddr, 128,
		hoa, 128, ifindex, MIP6_ENTITY_HA);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
		dbg("migrate for INBOUND policy failed\n");
        	goto end;
	}

	/* outbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TRANSPORT,
		  oldcoa, haaddr, e->reqid_tomn);
	_set_sp(&sp, e, XFRM_POLICY_OUT, hoa, 128,
		haaddr, 128, ifindex, MIP6_ENTITY_HA);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
		dbg("migrate for OUTBOUND policy failed\n");
        	goto end;
	}

 end:
	return err;
}

int ha_ipsec_trns_update(const struct in6_addr *haaddr,
			 const struct in6_addr *hoa,
			 const struct in6_addr *coa,
			 const struct in6_addr *old_coa,
			 int tunnel)
{
	struct ha_ipsec_trns_update b;
	b.tunnel = tunnel;
	b.coa = *coa;
	b.old_coa = *old_coa;
	return ipsec_policy_apply(haaddr, hoa, _ha_trns_update, &b);
}




/*
 *   Add/Delete IPsec Security Policy 
 */
static int _ha_tnl_pol_mod(const struct in6_addr *haaddr, 
			   const struct in6_addr *hoa, 
			   struct ipsec_policy_entry *e, 
			   void *arg, 
			   int add)
{
	int err = 0;
	struct ha_ipsec_tnl_update *parms = (struct ha_ipsec_tnl_update *)arg;
	int ifindex;
	struct list_head *mnp;
	struct xfrm_userpolicy_info sp;
	struct xfrm_user_tmpl tmpl;
	u_int16_t ipsec_proto;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	ifindex = parms->tunnel;
	mnp = parms->mnp;

	switch (e->type) {
	case IPSEC_POLICY_TYPE_TUNNELHOMETESTING:
	case IPSEC_POLICY_TYPE_TUNNELMH:
	case IPSEC_POLICY_TYPE_TUNNELPAYLOAD:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, NULL, NULL);

	/* inbound */
	_set_sp(&sp, e, XFRM_POLICY_IN, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_HA);
	_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, hoa, e->reqid_toha);
	if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
		dbg("modifying INBOUND policy failed\n");
		err = -1;
		goto end;
	}

	/* forward */
	_set_sp(&sp, e, XFRM_POLICY_FWD, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_HA);
	_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, hoa, e->reqid_toha);
	if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
		dbg("modifying FORWARD policy failed\n");
		err = -1;
		goto end;
	}

	/* outbound */
	_set_sp(&sp, e, XFRM_POLICY_OUT, hoa, 0, &in6addr_any, 0,
		ifindex, MIP6_ENTITY_HA);
	_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
		  hoa, haaddr, e->reqid_tomn);
	if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
		dbg("modifying OUTBOUND policy failed\n");
		err = -1;
		goto end;
	}

	/* Mobile Router case */
	if ((e->type == IPSEC_POLICY_TYPE_TUNNELPAYLOAD) && mnp) {
		struct ha_ipsec_mnp_update b;

		b.tunnel = ifindex;
		b.old_mnps = add ? NULL : mnp;
		b.new_mnps = add ? mnp : NULL;

		err = _ha_mnp_pol_mod(haaddr, hoa, e, (void *)&b, add);
	}
 end:
	return err;
}

/*
 *   Add SP entry (for HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_tnl_pol_add(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _ha_tnl_pol_mod(haaddr, hoa, e, arg, 1);
}

int ha_ipsec_tnl_pol_add(const struct in6_addr *our_addr, 
			 const struct in6_addr *peer_addr,
			 int tunnel,
			 struct list_head *mnp)
{
	struct ha_ipsec_tnl_update b;
	b.tunnel = tunnel;
	b.mnp = mnp;

	return ipsec_policy_apply(our_addr, peer_addr, _ha_tnl_pol_add, &b);
}

/*
 *   Delete SP entry (for HA)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _ha_tnl_pol_del(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _ha_tnl_pol_mod(haaddr, hoa, e, arg, 0);
}

int ha_ipsec_tnl_pol_del(const struct in6_addr *our_addr, 
			 const struct in6_addr *peer_addr,
			 int tunnel,
			 struct list_head *mnp)
{
	struct ha_ipsec_tnl_update b;
	b.tunnel = tunnel;
	b.mnp = mnp;

	return ipsec_policy_apply(our_addr, peer_addr,
				  _ha_tnl_pol_del, &b);
}

/*
 *   Tunnel Update (for MN)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _mn_tnl_update(const struct in6_addr *haaddr,
			  const struct in6_addr *hoa,
			  struct ipsec_policy_entry *e,
			  void *arg)
{
	int err = 0;
	struct bulentry *bule;
	int ifindex;
	struct in6_addr *oldcoa, *newcoa;
	u_int8_t ipsec_proto;
	struct xfrm_user_tmpl tmpl;
	struct xfrm_userpolicy_info sp;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	switch (e->type) {
	case IPSEC_POLICY_TYPE_TUNNELHOMETESTING:
	case IPSEC_POLICY_TYPE_TUNNELMH:
	case IPSEC_POLICY_TYPE_TUNNELPAYLOAD:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}

	bule = (struct bulentry *)arg;
	ifindex = bule->home->if_tunnel;
	oldcoa = &bule->last_coa;
	newcoa = &bule->coa;

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, oldcoa, newcoa);

	/* outbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, oldcoa, e->reqid_toha);
	_set_sp(&sp, e, XFRM_POLICY_OUT, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_MN);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
		dbg("migrate for OUTBOUND policy failed\n");
		goto end;
	}

	/* inbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
		  oldcoa, haaddr, e->reqid_tomn);
	_set_sp(&sp, e, XFRM_POLICY_IN, hoa, 0, &in6addr_any, 0,
		ifindex, MIP6_ENTITY_MN);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
		dbg("migrate for INBOUND policy (1) failed\n");
		goto end;
	}

	/*
	 * Additionally, we need to update endpoint address stored in the
	 * policy entry for processing BU from peer MN. Note that incoming 
	 * BU is normally IPsec-tunneled by the HA.
	 */
	if (e->type == IPSEC_POLICY_TYPE_TUNNELMH) {
		/* template */
		_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
			  oldcoa, haaddr, e->reqid_tomn);
		_set_sp(&sp, e, XFRM_POLICY_IN, hoa, 0, &in6addr_any, 0,
			ifindex, MIP6_ENTITY_MN);
		/* additional settings */
		sp.priority = MIP6_PRIO_RO_SIG_IPSEC;
		sp.sel.sport = htons(IP6_MH_TYPE_BU);
		sp.sel.sport_mask = ~((__u16)0);
		if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
			dbg("migrate for INBOUND policy (2) failed\n");
			goto end;
		}
	}

	/*
	 * If we are a Mobile Router, we also need to migrate IN/FWD/OUT rules
	 * for forwarded traffic in case we have TUNNELPAYLOAD protection.
	 */
	if ((e->type == IPSEC_POLICY_TYPE_TUNNELPAYLOAD) && (bule->home->mob_rtr))
	{
		struct list_head *mnp;

		list_for_each(mnp, &bule->home->mob_net_prefixes)
		{
			struct prefix_list_entry *p;
			p = list_entry(mnp, struct prefix_list_entry, list);

			/* outbound */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  haaddr, oldcoa, e->reqid_toha);
			_set_sp(&sp, e, XFRM_POLICY_OUT, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
				ifindex, MIP6_ENTITY_MN);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
				dbg("migrate for OUTBOUND policy failed\n");
				goto end;
			}

			/* forwarded */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  oldcoa, haaddr, e->reqid_tomn);
			_set_sp(&sp, e, XFRM_POLICY_IN, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
				ifindex, MIP6_ENTITY_MN);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
				dbg("migrate for INBOUND policy (1) failed\n");
				goto end;
			}

			/* inbound */
			_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TUNNEL,
				  oldcoa, haaddr, e->reqid_tomn);
			_set_sp(&sp, e, XFRM_POLICY_IN, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
				ifindex, MIP6_ENTITY_MN);
			if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
				dbg("migrate for INBOUND policy (1) failed\n");
				goto end;
			}

		}
	}

 end:
	return err;
}

/*
 *   Transport mode Update (for MN)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 *   - It might be beneficial to merge it with tunnel version --arno
 */
static int _mn_trns_update(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	int err = 0;
	struct bulentry *bule;
	int ifindex;
	struct in6_addr *oldcoa, *newcoa;
	u_int8_t ipsec_proto;
	struct xfrm_user_tmpl tmpl;
	struct xfrm_userpolicy_info sp;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	switch (e->type) {
	case IPSEC_POLICY_TYPE_HOMEREGBINDING:
	case IPSEC_POLICY_TYPE_BERROR:
	case IPSEC_POLICY_TYPE_MH:
	case IPSEC_POLICY_TYPE_MOBPFXDISC:
	case IPSEC_POLICY_TYPE_NDISC:
	case IPSEC_POLICY_TYPE_ICMP:
	case IPSEC_POLICY_TYPE_ANY:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}

	bule = (struct bulentry *)arg;
	ifindex = bule->home->if_tunnel;
	oldcoa = &bule->last_coa;
	newcoa = &bule->coa;

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, oldcoa, newcoa);

	/* outbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TRANSPORT,
		  haaddr, oldcoa, e->reqid_toha);
	_set_sp(&sp, e, XFRM_POLICY_OUT, haaddr, 128,
		hoa, 128, ifindex, MIP6_ENTITY_MN);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, haaddr, newcoa)) < 0) {
		dbg("migrate for OUTBOUND policy failed\n");
		goto end;
	}

	/* inbound */
	_set_tmpl(&tmpl, 0, ipsec_proto, XFRM_MODE_TRANSPORT,
		  oldcoa, haaddr, e->reqid_tomn);
	_set_sp(&sp, e, XFRM_POLICY_IN, hoa, 128,
		haaddr, 128, ifindex, MIP6_ENTITY_MN);
	if ((err = xfrm_sendmigrate(&sp, &tmpl, newcoa, haaddr)) < 0) {
		dbg("migrate for INBOUND policy (1) failed\n");
		goto end;
	}

	/* XXX We should probably add something for MN-MN and MN-CN transport
	 * XXX mode entries. --arno */
 end:
	return err;
}

int mn_ipsec_trns_update(const struct in6_addr *haaddr,
			 const struct in6_addr *hoa,
			 void *arg)
{
	return ipsec_policy_apply(haaddr, hoa, _mn_trns_update, arg);
}

int mn_ipsec_tnl_update(const struct in6_addr *haaddr,
			const struct in6_addr *hoa,
			void *arg)
{
	return ipsec_policy_apply(haaddr, hoa, _mn_tnl_update, arg);
}

static int _mn_tnl_pol_mod(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg,
			   int add)
{
	int err = 0;
	struct bulentry *bule = (struct bulentry *)arg;
	int ifindex;
	struct xfrm_userpolicy_info sp;
	struct xfrm_user_tmpl tmpl;
	u_int16_t ipsec_proto;

	assert(haaddr);
	assert(hoa);
	assert(e);
	assert(arg);

	switch (e->type) {
	case IPSEC_POLICY_TYPE_TUNNELHOMETESTING:
	case IPSEC_POLICY_TYPE_TUNNELMH:
	case IPSEC_POLICY_TYPE_TUNNELPAYLOAD:
		break;
	default:
		goto end;
	}

	/* XXX Limitation: Single IPsec proto can only be applied */
	if (ipsec_use_esp(e))
		ipsec_proto = IPPROTO_ESP;
	else if (ipsec_use_ah(e))
		ipsec_proto = IPPROTO_AH;
	else if (ipsec_use_ipcomp(e))
		ipsec_proto = IPPROTO_COMP;
	else {
		dbg("invalid ipsec proto\n");
		goto end;
	}
	
	ifindex = bule->home->if_tunnel;

	dump_migrate(ifindex, ipsec_proto, hoa, haaddr, NULL, NULL);

	/* inbound */
	_set_sp(&sp, e, XFRM_POLICY_IN, hoa, 0, &in6addr_any, 0,
		ifindex, MIP6_ENTITY_MN);
	_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
		  hoa, haaddr, e->reqid_tomn);
	if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
		dbg("modifying INBOUND policy failed.\n");
		err = -1;
		goto end;
	}
		
	/* outbound */
	_set_sp(&sp, e, XFRM_POLICY_OUT, &in6addr_any, 0, hoa, 0,
		ifindex, MIP6_ENTITY_MN);
	_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
		  haaddr, hoa, e->reqid_toha);
	if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
		dbg("modifying OUTBOUND policy failed.\n");
		err = -1;
		goto end;
	}
	
	/*
	 * Additionally, we need to create SPD entry to process incoming BU
	 * from peer MN.  Note that BU is normally IPsec-tunneled by the HA.
	 */
        if (e->type == IPSEC_POLICY_TYPE_TUNNELMH) {
		if (add) {
			/* flush wildrecv SPD entry for processing BU */
			cn_wildrecv_bu_pol_del();
			err = mn_ipsec_recv_bu_tnl_pol_add(bule, ifindex, e);
		} else {
			mn_ipsec_recv_bu_tnl_pol_del(bule, ifindex);
			/* restore wildrecv SPD entry for processing BU */
			err = cn_wildrecv_bu_pol_add();
		}
	}

	/*
	 * If we are a Mobile Router, we also need to create IN/FWD/OUT rules
	 * for forwarded traffic in case we have TUNNELPAYLOAD protection.
	 */
	if ((e->type == IPSEC_POLICY_TYPE_TUNNELPAYLOAD) && (bule->home->mob_rtr))
	{
		struct list_head *mnp;

		list_for_each(mnp, &bule->home->mob_net_prefixes)
		{
			struct prefix_list_entry *p;
			p = list_entry(mnp, struct prefix_list_entry, list);

			/* inbound */
			_set_sp(&sp, e, XFRM_POLICY_IN, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
				ifindex, MIP6_ENTITY_MN);
			_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
				  hoa, haaddr, e->reqid_tomn);
			if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
				dbg("modifying INBOUND policy failed.\n");
				err = -1;
				goto end;
			}

			/* forward */
			_set_sp(&sp, e, XFRM_POLICY_FWD, &p->ple_prefix, p->ple_plen, &in6addr_any, 0,
				ifindex, MIP6_ENTITY_MN);
			_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
				  hoa, haaddr, e->reqid_tomn);
			if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
				dbg("modifying INBOUND policy failed.\n");
				err = -1;
				goto end;
			}

			/* outbound */
			_set_sp(&sp, e, XFRM_POLICY_OUT, &in6addr_any, 0, &p->ple_prefix, p->ple_plen,
				ifindex, MIP6_ENTITY_MN);
			_set_tmpl(&tmpl, AF_INET6, ipsec_proto, XFRM_MODE_TUNNEL,
				  haaddr, hoa, e->reqid_toha);
			if (xfrm_ipsec_policy_mod(&sp, &tmpl, 1, add) < 0) {
				dbg("modifying OUTBOUND policy failed.\n");
				err = -1;
				goto end;
			}
		}
	}

 end:
	return err;
}

/*
 *   Add SP entry (for MN)
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _mn_tnl_pol_add(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _mn_tnl_pol_mod(haaddr, hoa, e, arg, 1);
}

int mn_ipsec_tnl_pol_add(const struct in6_addr *haaddr,
			 const struct in6_addr *hoa, void *arg)
{
	return ipsec_policy_apply(haaddr, hoa, _mn_tnl_pol_add, arg);
}

/*
 *   Delete SP entry
 *
 *   NOTE:
 *   - This is a hook routine to ipsec_policy_apply()
 */
static int _mn_tnl_pol_del(const struct in6_addr *haaddr,
			   const struct in6_addr *hoa,
			   struct ipsec_policy_entry *e,
			   void *arg)
{
	return _mn_tnl_pol_mod(haaddr, hoa, e, arg, 0);
}

int mn_ipsec_tnl_pol_del(const struct in6_addr *haaddr,
			 const struct in6_addr *hoa, void *arg)
{
	return ipsec_policy_apply(haaddr, hoa, _mn_tnl_pol_del, arg);
}
