/* $USAGI: ipsec6_output.c,v 1.31 2002/12/02 11:12:56 miyazawa Exp $ */
/*
 * Copyright (C)2001 USAGI/WIDE Project
 * 
 * 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.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * 
 *
 * Authors:
 *   Kazunori Miyazawa <miyazawa@linux-ipv6.org> / USAGI
 *   Mitsuru KANDA <mk@linux-ipv6.org> / USAGI
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/byteorder.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/sysctl.h>
#include <linux/inet.h>
#include <linux/ipv6.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/smp.h>
#include <linux/list.h>
#include <linux/random.h>
#include <asm/uaccess.h>
#include <net/ipv6.h>
#include <net/sadb.h>
#include <net/spd.h>
#include <net/ip6_route.h>
#include <net/addrconf.h>
#include <net/snmp.h>  
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/icmpv6.h>
#include <linux/ipsec.h>
#include <linux/ipsec6.h>
#include <linux/pfkeyv2.h> /* sa proto type */
#include <linux/pfkey.h>
#ifdef CONFIG_IPCOMP
#include <net/ipcomp.h>
#endif

int ipsec6_out_ah_calc(const void *data, unsigned length, inet_getfrag_t getfrag, 
		struct sk_buff *skb, struct ipv6_auth_hdr *authhdr, struct ipsec_sp *policy)
{
	struct ipsec_sa *sa = NULL;
	char* pseudo_packet;
	int packetlen;
	struct inet6_skb_parm* parm;
	struct ipv6_auth_hdr *pseudo_authhdr = NULL;
	__u8* authdata = NULL;
	
	IPSEC6_DEBUG("called.\n");
	if(!policy){
		return -EINVAL;
	}

	if (!data) length=0;
	if (!skb) {
		IPSEC6_DEBUG("skb is NULL!\n");
		return -EINVAL;
	}

	parm = (struct inet6_skb_parm*)skb->cb;

	if (!authhdr) {
		if (parm->auth) {
			authhdr = (struct ipv6_auth_hdr*)(skb->nh.raw + parm->auth);
		} else {
			return -EINVAL;
		}
	}

	read_lock_bh(&policy->lock);

	if (!policy->auth_sa_idx || !policy->auth_sa_idx->sa) {
		if (net_ratelimit())
			printk(KERN_WARNING "%s: ipsec(ah) SA missing.\n", __FUNCTION__);
		read_unlock_bh(&policy->lock);
		return -EINVAL;
	}

	ipsec_sa_hold(policy->auth_sa_idx->sa);
	sa = policy->auth_sa_idx->sa;

	read_unlock_bh(&policy->lock);

	packetlen = ntohs(skb->nh.ipv6h->payload_len) + sizeof(struct ipv6hdr);

	pseudo_packet = kmalloc(packetlen,GFP_ATOMIC);
	if (!pseudo_packet) {
		ipsec_sa_put(sa);
		return -ENOMEM;
	}

	if (length>0) {
		struct in6_addr *addr;
		addr=&skb->nh.ipv6h->saddr;
		getfrag(data,addr,&pseudo_packet[skb->len],0,length);
	}

	pseudo_authhdr = (struct ipv6_auth_hdr*)(pseudo_packet + parm->auth);

	authdata=kmalloc((sa->auth_algo.dx)->di->blocksize, GFP_ATOMIC);
	if (!authdata) {
		kfree(pseudo_packet);
		ipsec_sa_put(sa);
		return -ENOMEM;
	}

	write_lock_bh(&sa->lock);

	/* authhdr->spi = htonl(sa->spi); */
	IPSEC6_DEBUG("spi is 0x%x\n", ntohl(sa->spi));
	authhdr->spi = sa->spi; /* -mk */
	authhdr->seq_no = htonl(++sa->replay_window.seq_num);
	authhdr->reserved = 0;

	memcpy(pseudo_packet,skb->nh.ipv6h,skb->len);

	pseudo_authhdr->spi = authhdr->spi;
	pseudo_authhdr->seq_no = authhdr->seq_no;
	pseudo_authhdr->reserved = 0;

	zero_out_for_ah(parm, pseudo_packet);

	sa->auth_algo.dx->di->hmac_atomic(sa->auth_algo.dx,
			sa->auth_algo.key,
			sa->auth_algo.key_len,
			pseudo_packet, packetlen, authdata);

	memcpy(authhdr->auth_data, authdata, sa->auth_algo.digest_len);
	
	if (!sa->fuse_time) {
		sa->fuse_time = jiffies;
		sa->lifetime_c.usetime = (sa->fuse_time)/HZ;
		ipsec_sa_mod_timer(sa);
		IPSEC6_DEBUG("set fuse_time = %lu\n", sa->fuse_time);
	}

	sa->lifetime_c.bytes += packetlen;
	IPSEC6_DEBUG("sa->lifetime_c.bytes=%-9u %-9u\n",	/* XXX: %-18Lu */
			(__u32)((sa->lifetime_c.bytes) >> 32), 
			(__u32)(sa->lifetime_c.bytes));

	if (sa->lifetime_c.bytes >= sa->lifetime_s.bytes && 
	    sa->lifetime_s.bytes) {
		IPSEC6_DEBUG("change sa state DYING\n");
		sa->state = SADB_SASTATE_DYING;
	} 
	if (sa->lifetime_c.bytes >= sa->lifetime_h.bytes && 
	    sa->lifetime_h.bytes) {
		sa->state = SADB_SASTATE_DEAD;
		IPSEC6_DEBUG("change sa state DEAD\n");
	}

	write_unlock_bh(&sa->lock);
	ipsec_sa_put(sa);

	kfree(authdata);
	kfree(pseudo_packet); 
	return 0;

}

int ipsec6_out_get_ahsize(struct ipsec_sp *policy)
{
	int result = 0;
	struct ipsec_sa *sa_ah = NULL;

	IPSEC6_DEBUG("called.\n");

	if (!policy) return 0;

	write_lock_bh(&policy->lock);
	if (policy->auth_sa_idx && policy->auth_sa_idx->sa) {
		ipsec_sa_hold(policy->auth_sa_idx->sa);
		sa_ah = policy->auth_sa_idx->sa;
	}

	write_unlock_bh(&policy->lock);

	if (sa_ah) {
		read_lock_bh(&sa_ah->lock);
		if ( sa_ah->auth_algo.algo != SADB_AALG_NONE) {
			result += (offsetof(struct ipv6_auth_hdr, auth_data) + 
					sa_ah->auth_algo.digest_len + 7) & ~7;	/* 64 bit alignment */
		}
		read_unlock_bh(&sa_ah->lock);
		ipsec_sa_put(sa_ah);
	}

	IPSEC6_DEBUG("Calculated size is %d.\n", result);
	return result;
}

int ipsec6_out_get_espsize(struct ipsec_sp *policy)
{
	int result = 0;
	struct ipsec_sa *sa_esp = NULL;

	IPSEC6_DEBUG("called.\n");

	if (!policy) return 0;

	write_lock_bh(&policy->lock);

	if (policy->esp_sa_idx && policy->esp_sa_idx->sa) {
		ipsec_sa_hold(policy->esp_sa_idx->sa);
		sa_esp = policy->esp_sa_idx->sa;
	}
	write_unlock_bh(&policy->lock);

	if (sa_esp) {
		read_lock_bh(&sa_esp->lock);
		if ( sa_esp->esp_algo.algo != SADB_EALG_NONE){
			result += sizeof(struct ipv6_esp_hdr) - 8;
			result += sa_esp->esp_algo.cx->ci->ivsize;
			result += (sa_esp->esp_algo.cx->ci->blocksize + 3) & ~3;
			result += 4;	/* included pad_len and next_hdr  32 bit align */
		}else{
			read_unlock_bh(&sa_esp->lock);
			ipsec_sa_put(sa_esp);
			return 0;
		}
		if ( sa_esp->auth_algo.algo != SADB_AALG_NONE) {
			result += (sa_esp->auth_algo.digest_len + 3) & ~3;	/* 32 bit alignment */
		}
		read_unlock_bh(&sa_esp->lock);
		ipsec_sa_put(sa_esp);
	}
	IPSEC6_DEBUG("Calculated size is %d.\n", result);
	return result;
}

struct ipv6_txoptions *ipsec6_out_get_newopt(struct ipv6_txoptions *opt, struct ipsec_sp *policy)
{
	struct ipsec_sa *sa = NULL;
	int ah_len = 0;

	IPSEC6_DEBUG("called.\n");

	
	if (!policy) {
		if (net_ratelimit())
			printk(KERN_INFO "ipsec6_out_get_newopt: ipsec6_ptr/policy is NULL.\n");
		goto out;
	}

	read_lock_bh(&policy->lock);

	if (policy->auth_sa_idx && policy->auth_sa_idx->sa) {

		ipsec_sa_hold(policy->auth_sa_idx->sa);
		sa = policy->auth_sa_idx->sa;
		read_unlock_bh(&policy->lock);
	
		read_lock_bh(&sa->lock);
	
		if ( sa->auth_algo.algo == SADB_AALG_NONE ) {
			if (net_ratelimit())
				printk(KERN_INFO "ipsec6_out_get_newopt: Hash algorithm %d not present.\n",
					sa->auth_algo.algo);
			read_unlock_bh(&sa->lock);
			ipsec_sa_put(sa);
			goto out;
		}
	
		ah_len = (offsetof(struct ipv6_auth_hdr, auth_data) +
				sa->auth_algo.digest_len + 7) & ~7;
	
		IPSEC6_DEBUG("ah_len=%d hash_size=%d\n", ah_len, sa->auth_algo.digest_len);
		read_unlock_bh(&sa->lock);
		ipsec_sa_put(sa);
	
	} else {
		read_unlock_bh(&policy->lock);
	}

	if (ah_len) {
		void *ptr;

		if (!(ptr = ipv6_txoptions_grow(opt, ah_len)))
			return ERR_PTR(-ENOMEM);

		opt->auth = (struct ipv6_opt_hdr *) ptr;
		opt->tot_len += ah_len;
		opt->opt_flen += ah_len;
		opt->auth->hdrlen = (ah_len >> 2) - 2;
	}

out:	return opt;
}

/*** ESP ***/

void ipsec6_out_enc(const void *data, unsigned length, u8 proto, struct ipv6_txoptions *opt,
		void **newdata, unsigned *newlength, struct ipsec_sp *policy)
{
	struct ipsec_sa *sa = NULL;
	struct ipv6_esp_hdr *esphdr = NULL;
	u8* srcdata = NULL;
	u8* authdata = NULL;
	int encblocksize = 0;
	int encsize = 0, hashsize = 0, totalsize = 0;
	int dstoptlen = 0;
	int i;

	IPSEC6_DEBUG("called.\nData ptr is %p, data length is %d.\n",data,length);

	if (!policy) {
		if (net_ratelimit())
			printk(KERN_WARNING "%s; ipsec policy is NULL\n", __FUNCTION__);
		return;
	}

	read_lock_bh(&policy->lock);
	if (!policy->esp_sa_idx || !policy->esp_sa_idx->sa) {
		if (net_ratelimit())
			printk(KERN_WARNING "%s: ipsec(esp) SA missing.\n", __FUNCTION__);
		read_unlock_bh(&policy->lock);
		return;
	}
	ipsec_sa_hold(policy->esp_sa_idx->sa);
	sa = policy->esp_sa_idx->sa;
	read_unlock_bh(&policy->lock);

	write_lock_bh(&sa->lock);
	/* Get algorithms */
	if (sa->esp_algo.algo == SADB_EALG_NONE) {
		if (net_ratelimit())
			printk(KERN_WARNING "%s: ipsec(esp) encryption algorithm not present.\n", __FUNCTION__);
		goto unlock_finish;
		return;
	}


	if (!(sa->esp_algo.cx->ci)){
		if (net_ratelimit())
			printk(KERN_WARNING "%s: ipsec(esp) cipher_implementation not present.\n", __FUNCTION__);
		goto unlock_finish;
		return;
	}

	/* Calculate size */

	if (opt && opt->dst1opt) 
		dstoptlen = ipv6_optlen(opt->dst1opt);

	encblocksize = (sa->esp_algo.cx->ci->blocksize + 3) & ~3;
	encsize = dstoptlen + length + 2 + (encblocksize - 1);
	encsize -= encsize % encblocksize;

	/* The tail of payload does not have to be aligned with a multiple number of 64 bit.	*/
	/* 64 bit alignment is adapted to the position of top of header. 			*/

	if (sa->auth_algo.algo != SADB_AALG_NONE)
		hashsize = sa->auth_algo.digest_len;


	totalsize = sizeof(struct ipv6_esp_hdr) - 8 + sa->esp_algo.cx->ci->ivsize + encsize + hashsize;
	IPSEC6_DEBUG("IV size=%d, enc size=%d hash size=%d, total size=%d\n",
			 sa->esp_algo.cx->ci->ivsize, encsize, hashsize, totalsize);
	
	/* Get memory */
	esphdr = kmalloc(totalsize, GFP_ATOMIC);
	srcdata = kmalloc(encsize, GFP_ATOMIC);
	if (!esphdr || !srcdata) {
		if (net_ratelimit())
			printk(KERN_WARNING "ipsec6_out_enc: Out of memory.\n");
		if (esphdr) kfree(esphdr);
		if (srcdata) kfree(srcdata);
		goto unlock_finish;
		return;
	}

	memset(esphdr, 0, totalsize);
	memset(srcdata, 0, encsize);
	/* Handle sequence number and fill in header fields */
	esphdr->spi = sa->spi;
	esphdr->seq_no = htonl(++sa->replay_window.seq_num);

	/* Get source data, fill in padding and trailing fields */
	if (opt && opt->dst1opt) 
		memcpy(srcdata, opt->dst1opt, dstoptlen);

	memcpy(srcdata + dstoptlen, data, length);
	for (i = length + dstoptlen; i < encsize-2; i++) 
		srcdata[i] = (u8)(i-length+dstoptlen+1);
	srcdata[encsize-2] = (encsize-2)-length-dstoptlen;
	IPSEC6_DEBUG("length=%d, encsize=%d\n", length+dstoptlen, encsize);
	IPSEC6_DEBUG("encsize-2=%d\n", srcdata[encsize-2]);

	if (opt && opt->dst1opt) {
		/* ((struct ipv6_opt_hdr*)srcdata)->nexthdr = proto; */
		srcdata[0] = proto;
		opt->dst1opt = NULL;
		opt->opt_flen -= dstoptlen;
		srcdata[encsize-1] = NEXTHDR_DEST;
	} else {
		srcdata[encsize-1] = proto;
	}

	/* Do encryption */

#ifdef CONFIG_IPSEC_DEBUG
	{
		int i;
		IPSEC6_DEBUG("IV is 0x");
		if (sysctl_ipsec_debug_ipv6) {
			for (i=0; i < sa->esp_algo.cx->ci->ivsize ; i++) {
				printk(KERN_DEBUG "%x", (u8)(sa->esp_algo.iv[i]));
			}
		}
	}
#endif /* CONFIG_IPSEC_DEBUG */
	sa->esp_algo.cx->ci->encrypt_atomic_iv(sa->esp_algo.cx, srcdata,
					(u8 *)&esphdr->enc_data + sa->esp_algo.cx->ci->ivsize, encsize, sa->esp_algo.iv);
	memcpy(esphdr->enc_data, sa->esp_algo.iv, sa->esp_algo.cx->ci->ivsize);
	kfree(srcdata);
	srcdata=NULL;
	/* copy last block for next IV (src: enc_data + ivsize + encsize - ivsize) */
	memcpy(sa->esp_algo.iv, esphdr->enc_data + encsize, sa->esp_algo.cx->ci->ivsize);
	/* if CONFIG_IPSEC_DEBUG isn't defined here is finish of encryption process */

	if(sa->auth_algo.algo){
		authdata = kmalloc(sa->auth_algo.dx->di->blocksize, GFP_ATOMIC);
		if (!authdata) {
			if (net_ratelimit())
				printk(KERN_WARNING "ipsec6_out_enc: Out of memory.\n");
			kfree(esphdr);
			goto unlock_finish;
			return;
		}
		memset(authdata, 0, sa->auth_algo.dx->di->blocksize);
		sa->auth_algo.dx->di->hmac_atomic(sa->auth_algo.dx,
				sa->auth_algo.key,
				sa->auth_algo.key_len,
				(char*)esphdr, totalsize-hashsize, authdata);
		memcpy(&((char*)esphdr)[8 + sa->esp_algo.cx->ci->ivsize + encsize],
			authdata, sa->auth_algo.digest_len);

		kfree(authdata);
	}	

	if (!sa->fuse_time) {
		sa->fuse_time = jiffies;
		sa->lifetime_c.usetime = (sa->fuse_time)/HZ;
		ipsec_sa_mod_timer(sa);
		IPSEC6_DEBUG("set fuse_time = %lu\n", sa->fuse_time);
	}
	sa->lifetime_c.bytes += totalsize;
	IPSEC6_DEBUG("sa->lifetime_c.bytes=%-9u %-9u\n",	/* XXX: %-18Lu */
			(__u32)((sa->lifetime_c.bytes) >> 32), (__u32)(sa->lifetime_c.bytes));
	if (sa->lifetime_c.bytes >= sa->lifetime_s.bytes && sa->lifetime_s.bytes) {
		sa->state = SADB_SASTATE_DYING;
		IPSEC6_DEBUG("change sa state DYING\n");
	} 
	if (sa->lifetime_c.bytes >= sa->lifetime_h.bytes && sa->lifetime_h.bytes) {
		sa->state = SADB_SASTATE_DEAD;
		IPSEC6_DEBUG("change sa state DEAD\n");
	}

	write_unlock_bh(&sa->lock);
	ipsec_sa_put(sa);

	authdata = NULL;
	/* Set return values */
	*newdata = esphdr;
	*newlength = totalsize;
	return;

unlock_finish:
	write_unlock_bh(&sa->lock);
	ipsec_sa_put(sa);
	return;
}

void ipsec6_out_finish(struct ipv6_txoptions *opt, struct ipsec_sp *policy)
{

	if (opt) {
		kfree(opt);
	}

	if (policy) {
		ipsec_sp_put(policy);
	}
}

static int ipsec6_output_check_core(struct selector *selector, struct ipsec_sp **policy_ptr)
{
	int error = 0;
	struct ipsec_sp *policy = NULL;
	int result = IPSEC_ACTION_BYPASS; 	/* default */

	IPSEC6_DEBUG("called\n");

	if (!selector) {
		IPSEC6_DEBUG("selector is NULL\n");
		error = -EINVAL;
		goto err;
	}
	
	policy = ipsec_sp_get(selector);
	if (!policy) { /* not match ! */
		IPSEC6_DEBUG("no policy exists.\n");
		result = IPSEC_ACTION_BYPASS;
		goto err;
	}

	read_lock_bh(&policy->lock);
	if (policy->policy_action == IPSEC_POLICY_DROP) {
		result = IPSEC_ACTION_DROP;
		read_unlock_bh(&policy->lock);
		goto err;
	}  else if (policy->policy_action == IPSEC_POLICY_BYPASS) {
		result = IPSEC_ACTION_BYPASS;
		read_unlock_bh(&policy->lock);
		goto err;
	}
	
	/* policy must then be to apply ipsec */
	if (policy->auth_sa_idx) {
		if (policy->auth_sa_idx->sa) {
			read_lock_bh(&policy->auth_sa_idx->sa->lock);
			switch (policy->auth_sa_idx->sa->state) {
			case SADB_SASTATE_MATURE:
			case SADB_SASTATE_DYING:
				result |= IPSEC_ACTION_AUTH;
				break;
			default:
				result = IPSEC_ACTION_DROP;
			}
			read_unlock_bh(&policy->auth_sa_idx->sa->lock);
		} else {
			read_unlock_bh(&policy->lock);
			write_lock_bh(&policy->lock);
			/* check and see if another process attached an sa to
			 * the policy while we were acquiring the write lock.
			 * Note: refcnt guarantees policy is still in memory.
			 */
			if (!policy->auth_sa_idx->sa)
				policy->auth_sa_idx->sa = sadb_find_by_sa_index(policy->auth_sa_idx);
			write_unlock_bh(&policy->lock); 
			read_lock_bh(&policy->lock);
			if (policy->auth_sa_idx->sa) 
				result |= IPSEC_ACTION_AUTH;
		 	else 
				/* SADB_ACQUIRE message should be thrown up to KMd */
				result = IPSEC_ACTION_DROP;
		}
	}

	if (policy->esp_sa_idx) {
		if (policy->esp_sa_idx->sa) {
			read_lock_bh(&policy->esp_sa_idx->sa->lock);
			switch (policy->esp_sa_idx->sa->state) {
			case SADB_SASTATE_MATURE:
			case SADB_SASTATE_DYING:
				result |= IPSEC_ACTION_ESP;
				break;
			default:
				result = IPSEC_ACTION_DROP;
			}
			read_unlock_bh(&policy->esp_sa_idx->sa->lock);
		} else {
			read_unlock_bh(&policy->lock);
			write_lock_bh(&policy->lock);
			/* check and see if another process attached an sa to
			 * the policy while we were acquiring the write lock.
			 * Note: refcnt guarantees policy is still in memory.
			 */
			if (!policy->esp_sa_idx->sa)
				policy->esp_sa_idx->sa = sadb_find_by_sa_index(policy->esp_sa_idx);
			write_unlock_bh(&policy->lock);
			read_lock_bh(&policy->lock);
			if (policy->esp_sa_idx->sa) 
				result |= IPSEC_ACTION_ESP;
			 else 
				/* SADB_ACQUIRE message should be thrown up to KMd */
				result = IPSEC_ACTION_DROP;
		}
	}

	if (policy->comp_sa_idx) {
		if (policy->comp_sa_idx->sa) {
			read_lock_bh(&policy->comp_sa_idx->sa->lock);
			switch (policy->comp_sa_idx->sa->state) {
			case SADB_SASTATE_MATURE:
			case SADB_SASTATE_DYING:
				result |= IPSEC_ACTION_COMP;
				break;
			default:
				result |= IPSEC_ACTION_DROP;
			}
			read_unlock_bh(&policy->comp_sa_idx->sa->lock);
		} else {
			read_unlock_bh(&policy->lock);
			write_lock_bh(&policy->lock);
			/* check and see if another process attached an sa to
			 * the policy while we were acquiring the write lock.
			 * Note: refcnt guarantees policy is still in memory.
			 */
			if (!policy->comp_sa_idx->sa)
				policy->comp_sa_idx->sa = sadb_find_by_sa_index(policy->comp_sa_idx);
			write_unlock_bh(&policy->lock);
			read_lock_bh(&policy->lock);
			if (policy->comp_sa_idx->sa) 
				result |= IPSEC_ACTION_COMP;
			else 
				/* SADB_ACUIRE message should be thrown up to KMd */
				result |= IPSEC_ACTION_DROP;
		}
	}

	*policy_ptr= policy;
	read_unlock_bh(&policy->lock);
	IPSEC6_DEBUG("end\n");	

err:
	return result;
}

int ipsec6_output_check(struct sock *sk, struct flowi *fl, const u8 *data, struct ipsec_sp **policy_ptr)
{
	struct in6_addr *saddr,*daddr;
	u16 sport,dport;
	unsigned char proto;
	struct selector selector;
	int result = IPSEC_ACTION_BYPASS; 	/* default */

	IPSEC6_DEBUG("called\n");
	if (!sk && !fl) {
		printk(KERN_ERR "flowi and sock are NULL\n");
		result = -EINVAL;
		goto err;
	}
	
	if (fl && fl->fl6_src) {
		saddr = fl->fl6_src; 
	} else {
		if (sk) {
			saddr = &sk->net_pinfo.af_inet6.saddr; 
		} else {
			result = -EINVAL;
			printk(KERN_ERR "sock is null\n");
			goto err;
		}
	}

	if (fl && fl->fl6_dst) {
		daddr = fl->fl6_dst; 
	} else {
		if (sk) {
			daddr = &sk->net_pinfo.af_inet6.daddr; 
		} else { 
			result = -EINVAL;
			printk(KERN_ERR "flowi and sock are NULL\n");
			goto err;
		}
	}

	if (fl) { 
		sport=fl->uli_u.ports.sport;
		dport=fl->uli_u.ports.dport;
		proto=fl->proto;
	} else if (sk) {
		sport=sk->sport;
		dport=sk->dport;
		proto=sk->protocol;
	} else {
		result = -EINVAL;
		printk(KERN_ERR "flowi and sock are NULL\n");
		goto err;
	}

	/* for ISKAMP see RFC2408 */
	if (proto == IPPROTO_UDP && 
	    sport == htons(500) && dport == htons(500)) {
		result = IPSEC_ACTION_BYPASS; 	/* default */
		goto err;
	}

	/* XXX have to decide to the policy of ICMP messages -mk*/
	if (proto != IPPROTO_TCP && proto != IPPROTO_UDP) {
		sport = 0;
		dport = 0;
	}

	/* XXX config  port policy */
	memset(&selector, 0, sizeof(struct selector));


#ifdef CONFIG_IPV6_IPSEC_TUNNEL
	if (proto == IPPROTO_IPV6) {
		selector.mode = IPSEC_MODE_TUNNEL;
	} else {
#endif
		((struct sockaddr_in6 *)&selector.src)->sin6_port = sport;	
		((struct sockaddr_in6 *)&selector.dst)->sin6_port = dport;	
#ifdef CONFIG_IPV6_IPSEC_TUNNEL
	}
	selector.proto = proto;

	if (selector.mode == IPSEC_MODE_TUNNEL) {
		const struct ipv6hdr *h = (struct ipv6hdr*) data;
		((struct sockaddr_in6 *)&selector.src)->sin6_family = AF_INET6;
		ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.src)->sin6_addr,
			       &h->saddr);
		((struct sockaddr_in6 *)&selector.dst)->sin6_family = AF_INET6;
		ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.dst)->sin6_addr,
			       &h->daddr);
	} else { /* IPSEC_MODE_TRANSPORT */
#endif
		((struct sockaddr_in6 *)&selector.src)->sin6_family = AF_INET6;
		ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.src)->sin6_addr,
			       saddr);
		((struct sockaddr_in6 *)&selector.dst)->sin6_family = AF_INET6;
		ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.dst)->sin6_addr,
			       daddr);
#ifdef CONFIG_IPV6_IPSEC_TUNNEL
	}
#endif

	selector.prefixlen_d = 128;
	selector.prefixlen_s = 128;


#ifdef CONFIG_IPSEC_DEBUG
	{
		char buf[64];
		IPSEC6_DEBUG("original src addr: %s\n", in6_ntop(saddr, buf, sizeof(buf)));
		IPSEC6_DEBUG("original src port: %u\n", ntohs(sport));
		IPSEC6_DEBUG("original dst addr: %s\n", in6_ntop(daddr, buf, sizeof(buf)));
		IPSEC6_DEBUG("original dst port: %u\n", ntohs(dport));

		IPSEC6_DEBUG("selector src addr: %s\n", 
				in6_ntop( &((struct sockaddr_in6 *)&selector.src)->sin6_addr, buf, sizeof(buf)));
		IPSEC6_DEBUG("selector src port: %u\n", 
				ntohs(((struct sockaddr_in6 *)&selector.src)->sin6_port));
		IPSEC6_DEBUG("selector dst addr: %s\n", 
				in6_ntop( &((struct sockaddr_in6 *)&selector.dst)->sin6_addr, buf, sizeof(buf)));
		IPSEC6_DEBUG("selector dst port: %u\n", 
				ntohs(((struct sockaddr_in6 *)&selector.dst)->sin6_port));
		IPSEC6_DEBUG("selector proto: %u\n", selector.proto);
	}
#endif /* CONFIG_IPSEC_DEBUG */

	result = ipsec6_output_check_core(&selector, policy_ptr);

 err:
		return result;
}


int ipsec6_ndisc_check(struct in6_addr *saddr, struct in6_addr *daddr, struct ipsec_sp **policy_ptr)
{
	struct selector selector;
	int result = IPSEC_ACTION_BYPASS; 	/* default */

	IPSEC6_DEBUG("called\n");


	/* XXX config  port policy */
	memset(&selector, 0, sizeof(struct selector));

	((struct sockaddr_in6 *)&selector.src)->sin6_family = AF_INET6;
	ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.src)->sin6_addr,
		       saddr);
	((struct sockaddr_in6 *)&selector.dst)->sin6_family = AF_INET6;
	ipv6_addr_copy(&((struct sockaddr_in6 *)&selector.dst)->sin6_addr,
		       daddr);
	selector.proto = IPPROTO_ICMPV6;
	selector.prefixlen_d = 128;
	selector.prefixlen_s = 128;

#ifdef CONFIG_IPSEC_DEBUG
	{
		char buf[64];
		IPSEC6_DEBUG("original dst addr: %s\n", in6_ntop(daddr, buf, sizeof(buf)));
		IPSEC6_DEBUG("original src addr: %s\n", in6_ntop(saddr, buf, sizeof(buf)));

		IPSEC6_DEBUG("selector dst addr: %s\n", 
				in6_ntop( &((struct sockaddr_in6 *)&selector.dst)->sin6_addr, buf, sizeof(buf)));
		IPSEC6_DEBUG("selector src addr: %s\n", 
				in6_ntop( &((struct sockaddr_in6 *)&selector.src)->sin6_addr, buf, sizeof(buf)));
		IPSEC6_DEBUG("selector proto: %u\n", selector.proto);
	}
#endif /* CONFIG_IPSEC_DEBUG */

	result = ipsec6_output_check_core(&selector, policy_ptr);

	return result;
}
