Subject: Re: [PATCH] IPv6 IPsec support

From: Kunihiro Ishiguro <[email-masked]>
Date: Wed, 19 Feb 2003 15:10:53 -0800

This will not be useful other than Miyazaki/Kanda.  I've applied
miyazaki's patch then try to diff against local code.

o xfrm6_selector_match() fix

o No need of option field mutation in xfrm6_rcv().  It is moved to
  ah.c.

o Setting Routing Header's segment_lefts to 0 is wrong.  Let's let it be.

o xfrm6_rcv() try to figure out he is processing AH or ESP by
  ipv6hdr->protocol...  But when other extenstion header exits this
  could be wrong.  Initial protocol value is passed from the caller.

o Some cosmetic change.

And this patch include

o Not removing AH header

o Mutation field provisioning

But there changes are no needed.  Miyazaki, would you mind to take a
look into this?  Have fun ;-).
--
Kunihiro Ishiguro

diff -ruN linux-2.5.62.orig/include/net/ipv6.h linux-2.5.62/include/net/ipv6.h
--- linux-2.5.62.orig/include/net/ipv6.h	2003-02-14 15:52:28.000000000 -0800
+++ linux-2.5.62/include/net/ipv6.h	2003-02-19 13:19:38.000000000 -0800
@@ -41,7 +41,7 @@
 
 #define NEXTHDR_MAX		255
 
-
+#define IP6OPT_MUTABLE  0x20
 
 #define IPV6_DEFAULT_HOPLIMIT   64
 #define IPV6_DEFAULT_MCASTHOPS	1
diff -ruN linux-2.5.62.orig/include/net/xfrm.h linux-2.5.62/include/net/xfrm.h
--- linux-2.5.62.orig/include/net/xfrm.h	2003-02-19 14:24:53.000000000 -0800
+++ linux-2.5.62/include/net/xfrm.h	2003-02-19 14:03:20.000000000 -0800
@@ -414,7 +414,7 @@
 extern void xfrm_replay_advance(struct xfrm_state *x, u32 seq);
 extern int xfrm_check_selectors(struct xfrm_state **x, int n, struct flowi *fl);
 extern int xfrm4_rcv(struct sk_buff *skb);
-extern int xfrm6_rcv(struct sk_buff *skb);
+extern int xfrm6_rcv(struct sk_buff *skb, u8 proto);
 extern int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir);
 extern int xfrm_user_policy(struct sock *sk, int optname, u8 *optval, int optlen);
 
@@ -452,11 +452,37 @@
 extern struct xfrm_algo_desc *xfrm_aalg_get_byname(char *name);
 extern struct xfrm_algo_desc *xfrm_ealg_get_byname(char *name);
 
+static __inline__ int addr_match(void *token1, void *token2, int prefixlen)
+{
+	__u32 *a1 = token1;
+	__u32 *a2 = token2;
+	int pdw;
+	int pbi;
+
+	pdw = prefixlen >> 5;	  /* num of whole __u32 in prefix */
+	pbi = prefixlen &  0x1f;  /* num of bits in incomplete u32 in prefix */
+
+	if (pdw)
+		if (memcmp(a1, a2, pdw << 2))
+			return 0;
+
+	if (pbi) {
+		__u32 mask;
+
+		mask = htonl((0xffffffff) << (32 - pbi));
+
+		if ((a1[pdw] ^ a2[pdw]) & mask)
+			return 0;
+	}
+
+	return 1;
+}
+
 static inline int
 xfrm6_selector_match(struct xfrm_selector *sel, struct flowi *fl)
 {
-	return  !memcmp(fl->fl6_dst, &sel->daddr, (sel->prefixlen_d)/8) &&
-		!memcmp(fl->fl6_src, &sel->saddr, (sel->prefixlen_s)/8) &&
+	return  !addr_match(fl->fl6_dst, &sel->daddr, sel->prefixlen_d) &&
+		!addr_match(fl->fl6_src, &sel->saddr, sel->prefixlen_s) &&
 		!((fl->uli_u.ports.dport^sel->dport)&sel->dport_mask) &&
 		!((fl->uli_u.ports.sport^sel->sport)&sel->sport_mask) &&
 		(fl->proto == sel->proto || !sel->proto) &&
diff -ruN -x '*.o' -x '*.cmd' -x '*.ko' -x '*.mod.c' linux-2.5.62.orig/net/ipv6/ah.c linux-2.5.62/net/ipv6/ah.c
--- linux-2.5.62.orig/net/ipv6/ah.c	2003-02-19 14:24:53.000000000 -0800
+++ linux-2.5.62/net/ipv6/ah.c	2003-02-19 14:31:12.000000000 -0800
@@ -32,7 +32,6 @@
 
 #define AH_HLEN_NOICV   12
 
-/* XXX no ipv6 ah specific */
 #define NIP6(addr) \
 	ntohs((addr).s6_addr16[0]),\
 	ntohs((addr).s6_addr16[1]),\
@@ -43,6 +42,214 @@
 	ntohs((addr).s6_addr16[6]),\
 	ntohs((addr).s6_addr16[7])
 
+static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
+{
+	u8 *opt = (u8 *)opthdr;
+	int len = ipv6_optlen(opthdr);
+	int off = 0;
+	int optlen = 0;
+
+	off += 2;
+	len -= 2;
+
+	while (len > 0) {
+
+		switch (opt[off]) {
+
+		case IPV6_TLV_PAD0:
+			optlen = 1;
+			break;
+		default:
+			if (len < 2) 
+				goto bad;
+			optlen = opt[off+1]+2;
+			if (len < optlen)
+				goto bad;
+			if (opt[off] & 0x20)
+				memset(&opt[off+2], 0, opt[off+1]);
+			break;
+		}
+
+		off += optlen;
+		len -= optlen;
+	}
+	if (len == 0)
+		return 1;
+
+bad:
+	return 0;
+}
+
+int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir)
+{
+	u16 offset = sizeof(struct ipv6hdr);
+	struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
+	unsigned int packet_len = skb->tail - skb->nh.raw;
+	u8 nexthdr = skb->nh.ipv6h->nexthdr;
+	u8 nextnexthdr = 0;
+
+	*nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw;
+
+	while (offset + 1 <= packet_len) {
+
+		switch (nexthdr) {
+
+		case NEXTHDR_HOP:
+			*nh_offset = offset;
+			offset += ipv6_optlen(exthdr);
+			if (!zero_out_mutable_opts(exthdr)) {
+				if (net_ratelimit())
+					printk(KERN_WARNING "overrun hopopts\n"); 
+				return 0;
+			}
+			nexthdr = exthdr->nexthdr;
+			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
+			break;
+
+		case NEXTHDR_ROUTING:
+			*nh_offset = offset;
+			offset += ipv6_optlen(exthdr);
+			nexthdr = exthdr->nexthdr;
+			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
+			break;
+
+		case NEXTHDR_DEST:
+			*nh_offset = offset;
+			offset += ipv6_optlen(exthdr);
+			if (!zero_out_mutable_opts(exthdr))  {
+				if (net_ratelimit())
+					printk(KERN_WARNING "overrun destopt\n"); 
+				return 0;
+			}
+			nexthdr = exthdr->nexthdr;
+			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
+			break;
+
+		case NEXTHDR_AUTH:
+			if (dir == XFRM_POLICY_OUT) {
+				memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0, 
+				       (((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2);
+			}
+			if (exthdr->nexthdr == NEXTHDR_DEST) {
+				offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2;
+				exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
+				nextnexthdr = exthdr->nexthdr;
+				if (!zero_out_mutable_opts(exthdr)) {
+					if (net_ratelimit())
+						printk(KERN_WARNING "overrun destopt\n");
+					return 0;
+				}
+			}
+			return nexthdr;
+		default:
+			return nexthdr;
+		}
+	}
+
+	return nexthdr;
+}
+
+static int ipv6_check_mutable_options(struct sk_buff *skb, struct ipv6hdr *hdr,
+				      struct inet6_skb_parm *opt)
+{
+	int lim;
+	u8 *optpnt;
+	u8 nexthdr = hdr->nexthdr;
+	int datalen = 0;
+
+	optpnt = (u8*)(hdr+1);
+	lim = ntohs(hdr->payload_len);
+
+	while (lim > 0) {
+		struct ipv6_opt_hdr *opthdr;
+		int hdrlen;
+
+		opthdr = (struct ipv6_opt_hdr*)optpnt;
+
+		switch(nexthdr) {
+		case NEXTHDR_HOP:
+			opt->hop = optpnt - skb->nh.raw;
+			hdrlen = ipv6_optlen(opthdr);
+			datalen += (hdrlen - 2);
+			break;
+		case NEXTHDR_DEST:
+			opt->dst1 = optpnt - skb->nh.raw;
+			hdrlen = ipv6_optlen(opthdr);
+			datalen += (hdrlen - 2);
+			break;
+		case NEXTHDR_ROUTING:
+		case NEXTHDR_FRAGMENT:
+		case NEXTHDR_NONE:
+			hdrlen = ipv6_optlen(opthdr);
+			break;
+		case NEXTHDR_AUTH:
+			hdrlen = (opthdr->hdrlen + 2) << 2;
+			break;
+		default:
+			goto out;
+		}
+		nexthdr = opthdr->nexthdr;
+		optpnt += hdrlen;
+		lim -= hdrlen;
+	}
+out:
+	return datalen;
+}
+
+static int ah6_set_option(u8 *opthdr, u8 **opt_data, int erase)
+{
+	u8 *optpnt = opthdr;
+	int len = ipv6_optlen((struct ipv6_opt_hdr*)opthdr);
+	int datalen;
+	int optlen;
+
+	optpnt += 2;
+	len -= 2;
+	datalen = len;
+
+	if (erase) {
+		memcpy(*opt_data, optpnt, datalen);
+
+		while (len > 0) {
+			if (optpnt[0] == IPV6_TLV_PAD0) {
+				optlen = 1;
+			} else {
+				if (len < 2)
+					return -1;
+				optlen = optpnt[1] + 2;
+				if (optlen > len)
+					return -1;
+				if (optpnt[0] & IP6OPT_MUTABLE)
+					memset(optpnt+2, 0, optpnt[1]);
+			}
+			optpnt += optlen;
+			len -= optlen;
+		}
+	} else {
+		memcpy(optpnt, *opt_data, datalen);
+	}
+
+	*opt_data += datalen;
+
+	return 0;
+}
+
+static inline void ah6_clear_mutable_options(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 *opt_data)
+{
+	if (opt->hop)
+		ah6_set_option(skb->nh.raw+opt->hop, &opt_data, 1);
+	if (opt->dst1)
+		ah6_set_option(skb->nh.raw+opt->dst1, &opt_data, 1);
+}
+
+static inline void ah6_restore_mutable_options(struct sk_buff *skb, struct inet6_skb_parm *opt, u8 *opt_data)
+{
+	if (opt->hop)
+		ah6_set_option(skb->nh.raw+opt->hop, &opt_data, 0);
+	if (opt->dst1)
+		ah6_set_option(skb->nh.raw+opt->dst1, &opt_data, 0);
+}
+
 int ah6_output(struct sk_buff *skb)
 {
 	int err;
@@ -50,6 +257,7 @@
 	struct dst_entry *dst = skb->dst;
 	struct xfrm_state *x  = dst->xfrm;
 	struct ipv6hdr *iph = NULL;
+	struct ipv6hdr *top_hdr;
 	struct ip_auth_hdr *ah;
 	struct ah_data *ahp;
 	u16 nh_offset = 0;
@@ -66,13 +274,13 @@
 
 	if (x->props.mode) {
 		iph = skb->nh.ipv6h;
-		skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
-		skb->nh.ipv6h->version = 6;
-		skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
-		skb->nh.ipv6h->nexthdr = IPPROTO_AH;
-		memcpy(&skb->nh.ipv6h->saddr, &x->props.saddr, sizeof(struct in6_addr));
-		memcpy(&skb->nh.ipv6h->daddr, &x->id.daddr, sizeof(struct in6_addr));
-		ah = (struct ip_auth_hdr*)(skb->nh.ipv6h+1);
+		top_hdr = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
+		top_hdr->version = 6;
+		top_hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+		top_hdr->nexthdr = IPPROTO_AH;
+		memcpy(&top_hdr->saddr, &x->props.saddr, sizeof(struct in6_addr));
+		memcpy(&top_hdr->daddr, &x->id.daddr, sizeof(struct in6_addr));
+		ah = (struct ip_auth_hdr*)(top_hdr+1);
 		ah->nexthdr = IPPROTO_IPV6;
 	} else {
 		hdr_len = skb->h.raw - skb->nh.raw;
@@ -82,42 +290,40 @@
 			goto error;
 		}
 		memcpy(iph, skb->data, hdr_len);
-		skb->nh.ipv6h = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
-		memcpy(skb->nh.ipv6h, iph, hdr_len);
+		top_hdr = (struct ipv6hdr*)skb_push(skb, x->props.header_len);
+		memcpy(top_hdr, iph, hdr_len);
 		nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_OUT);
 		if (nexthdr == 0)
 			goto error;
 
 		skb->nh.raw[nh_offset] = IPPROTO_AH;
-		skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+		top_hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
 		ah = (struct ip_auth_hdr*)(skb->nh.raw+hdr_len);
 		ah->nexthdr = nexthdr;
 	}
 
-	skb->nh.ipv6h->priority    = 0;
-	skb->nh.ipv6h->flow_lbl[0] = 0;
-	skb->nh.ipv6h->flow_lbl[1] = 0;
-	skb->nh.ipv6h->flow_lbl[2] = 0;
-	skb->nh.ipv6h->hop_limit    = 0;
+	skb->nh.ipv6h = top_hdr;
+	top_hdr->priority    = 0;
+	memset(top_hdr->flow_lbl, 0, 3);
+	top_hdr->hop_limit    = 0;
 
 	ahp = x->data;
 	ah->hdrlen  = (XFRM_ALIGN8(ahp->icv_trunc_len +
-		AH_HLEN_NOICV) >> 2) - 2;
+			AH_HLEN_NOICV) >> 2) - 2;
+
 	ah->reserved = 0;
 	ah->spi = x->id.spi;
 	ah->seq_no = htonl(++x->replay.oseq);
 	ahp->icv(ahp, skb, ah->auth_data);
 
 	if (x->props.mode) {
-		skb->nh.ipv6h->hop_limit   = iph->hop_limit;
-		skb->nh.ipv6h->priority    = iph->priority; 	
-		skb->nh.ipv6h->flow_lbl[0] = iph->flow_lbl[0];
-		skb->nh.ipv6h->flow_lbl[1] = iph->flow_lbl[1];
-		skb->nh.ipv6h->flow_lbl[2] = iph->flow_lbl[2];
+		top_hdr->priority = iph->priority; 	
+		memcpy(top_hdr->flow_lbl, iph->flow_lbl, 3);
+		top_hdr->hop_limit = iph->hop_limit;
 	} else {
-		memcpy(skb->nh.ipv6h, iph, hdr_len);
+		memcpy(top_hdr, iph, hdr_len);
 		skb->nh.raw[nh_offset] = IPPROTO_AH;
-		skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
+		top_hdr->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
 		kfree (iph);
 	}
 
@@ -139,42 +345,48 @@
 int ah6_input(struct xfrm_state *x, struct sk_buff *skb)
 {
 	int ah_hlen;
-	struct ipv6hdr *iph;
+	struct ipv6hdr *hdr;
 	struct ipv6_auth_hdr *ah;
 	struct ah_data *ahp;
-	unsigned char *tmp_hdr = NULL;
-	int hdr_len = skb->h.raw - skb->nh.raw;
-	u8 nexthdr = 0;
+	struct inet6_skb_parm opt;
+	char work_buf[8];
+	int optlen;
+	u8 *opt_data = NULL;
 
 	if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
 		goto out;
 
 	ah = (struct ipv6_auth_hdr*)skb->data;
-
 	ahp = x->data;
-        ah_hlen = (ah->hdrlen + 2) << 2;
+	ah_hlen = (ah->hdrlen + 2) << 2;
 
-        if (ah_hlen != XFRM_ALIGN8(ahp->icv_full_len + AH_HLEN_NOICV) &&
-            ah_hlen != XFRM_ALIGN8(ahp->icv_trunc_len + AH_HLEN_NOICV))
-                goto out;
+	if (ah_hlen != XFRM_ALIGN8(ahp->icv_full_len + AH_HLEN_NOICV) &&
+	    ah_hlen != XFRM_ALIGN8(ahp->icv_trunc_len + AH_HLEN_NOICV))
+		goto out;
 
 	if (!pskb_may_pull(skb, (ah->hdrlen+2)<<2))
 		goto out;
 
-	/* We are going to _remove_ AH header to keep sockets happy,
-	 * so... Later this can change. */
-	if (skb_cloned(skb) &&
-	    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
-		goto out;
-	tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
-	if (!tmp_hdr)
-		goto out;
-	memcpy(tmp_hdr, skb->nh.raw, hdr_len);
-	ah = (struct ipv6_auth_hdr*)skb->data;
-	iph = skb->nh.ipv6h;
+	hdr = skb->nh.ipv6h;
 
+	memcpy(work_buf, hdr, 8);
+	hdr->priority = 0;
+	memset(hdr->flow_lbl, 0, 3);
+	hdr->hop_limit = 0;
+
+	memset(&opt, 0, sizeof(struct inet6_skb_parm));
+	optlen = ipv6_check_mutable_options(skb, hdr, &opt);
+	if (optlen < 0)
+		goto out;
         {
 		u8 auth_data[ahp->icv_trunc_len];
+		
+		if (optlen) {
+			opt_data = kmalloc(optlen, GFP_ATOMIC);
+			if (!opt_data)
+				goto out;
+			ah6_clear_mutable_options(skb, &opt, opt_data);
+		}
 		memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
 		memset(ah->auth_data, 0, ahp->icv_trunc_len);
 		skb_push(skb, skb->data - skb->nh.raw);
@@ -185,22 +397,19 @@
 			x->stats.integrity_failed++;
 			goto free_out;
 		}
+		if (optlen) {
+			ah6_restore_mutable_options(skb, &opt, opt_data);
+			kfree (opt_data);
+		}
 	}
+	memcpy(hdr, work_buf, 8);
+	skb->h.raw += (ah->hdrlen+2)<<2;
+	skb->data = skb->h.raw;
 
-	nexthdr = ah->nexthdr;
-	skb->nh.raw = skb_pull(skb, (ah->hdrlen+2)<<2);
-	memcpy(skb->nh.raw, tmp_hdr, hdr_len);
-	skb->nh.ipv6h->payload_len = htons(skb->len - sizeof(struct ipv6hdr));
-	skb_pull(skb, hdr_len);
-	skb->h.raw = skb->data;
-
-
-	kfree(tmp_hdr);
-
-	return nexthdr;
-
+	return ah->nexthdr;
 free_out:
-	kfree(tmp_hdr);
+	if (optlen)
+		kfree(opt_data);
 out:
 	return -EINVAL;
 }
@@ -208,22 +417,19 @@
 void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt, 
 	 int type, int code, int offset, __u32 info)
 {
-	struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
+	struct ipv6hdr *hdr = (struct ipv6hdr*)skb->data;
 	struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset);
 	struct xfrm_state *x;
 
-	if (type != ICMPV6_DEST_UNREACH ||
-	    type != ICMPV6_PKT_TOOBIG)
+	if (type != ICMPV6_DEST_UNREACH || type != ICMPV6_PKT_TOOBIG)
 		return;
 
-	x = xfrm6_state_lookup(&iph->daddr, ah->spi, IPPROTO_AH);
+	x = xfrm6_state_lookup(&hdr->daddr, ah->spi, IPPROTO_AH);
 	if (!x)
 		return;
-
 	printk(KERN_DEBUG "pmtu discvovery on SA AH/%08x/"
 			"%04x:%04x:%04x:%04x:%04x:%04x:%04x:%04x\n",
-	       ntohl(ah->spi), NIP6(iph->daddr));
-
+	       ntohl(ah->spi), NIP6(hdr->daddr));
 	xfrm_state_put(x);
 }
 
@@ -315,26 +521,29 @@
 	.output		= ah6_output
 };
 
+static inline int
+xfrm6_ah_rcv(struct sk_buff *skb)
+{
+	return xfrm6_rcv(skb, IPPROTO_AH);
+}
+
 static struct inet6_protocol ah6_protocol = {
-	.handler	=	xfrm6_rcv,
+	.handler	=	xfrm6_ah_rcv,
 	.err_handler	=	ah6_err,
 };
 
 int __init ah6_init(void)
 {
 	SET_MODULE_OWNER(&ah6_type);
-
 	if (xfrm6_register_type(&ah6_type) < 0) {
 		printk(KERN_INFO "ipv6 ah init: can't add xfrm type\n");
 		return -EAGAIN;
 	}
-
 	if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) {
 		printk(KERN_INFO "ipv6 ah init: can't add protocol\n");
 		xfrm6_unregister_type(&ah6_type);
 		return -EAGAIN;
 	}
-
 	return 0;
 }
 
@@ -342,10 +551,8 @@
 {
 	if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0)
 		printk(KERN_INFO "ipv6 ah close: can't remove protocol\n");
-
 	if (xfrm6_unregister_type(&ah6_type) < 0)
 		printk(KERN_INFO "ipv6 ah close: can't remove xfrm type\n");
-
 }
 
 module_init(ah6_init);
diff -ruN -x '*.o' -x '*.cmd' -x '*.ko' -x '*.mod.c' linux-2.5.62.orig/net/ipv6/esp.c linux-2.5.62/net/ipv6/esp.c
--- linux-2.5.62.orig/net/ipv6/esp.c	2003-02-19 14:24:53.000000000 -0800
+++ linux-2.5.62/net/ipv6/esp.c	2003-02-19 14:20:43.000000000 -0800
@@ -35,10 +35,6 @@
 #include <linux/icmpv6.h>
 
 #define MAX_SG_ONSTACK 4
-#if 0
-typedef void (icv_update_fn_t)(struct crypto_tfm *,
-			struct scatterlist *, unsigned int);
-#endif
 
 /* XXX no ipv6 esp specific */
 #define NIP6(addr) \
@@ -545,8 +541,14 @@
 	.output		= esp6_output
 };
 
+static inline int
+xfrm6_esp_rcv(struct sk_buff *skb)
+{
+	return xfrm6_rcv(skb, IPPROTO_ESP);
+}
+
 static struct inet6_protocol esp6_protocol = {
-	.handler 	=	xfrm6_rcv,
+	.handler 	=	xfrm6_esp_rcv,
 	.err_handler	=	esp6_err,
 };
 
diff -ruN -x '*.o' -x '*.cmd' -x '*.ko' -x '*.mod.c' linux-2.5.62.orig/net/ipv6/xfrm_input.c linux-2.5.62/net/ipv6/xfrm_input.c
--- linux-2.5.62.orig/net/ipv6/xfrm_input.c	2003-02-19 14:24:53.000000000 -0800
+++ linux-2.5.62/net/ipv6/xfrm_input.c	2003-02-19 14:06:49.000000000 -0800
@@ -30,11 +30,11 @@
 
 /* Fetch spi and seq frpm ipsec header */
 
-static int xfrm6_parse_spi(struct sk_buff *skb, u8 nexthdr, u32 *spi, u32 *seq)
+static int xfrm6_parse_spi(struct sk_buff *skb, u8 proto, u32 *spi, u32 *seq)
 {
 	int offset, offset_seq;
 
-	switch (nexthdr) {
+	switch (proto) {
 	case IPPROTO_AH:
 		offset = offsetof(struct ip_auth_hdr, spi);
 		offset_seq = offsetof(struct ip_auth_hdr, seq_no);
@@ -61,115 +61,7 @@
 	return 0;
 }
 
-static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
-{
-	u8 *opt = (u8 *)opthdr;
-	int len = ipv6_optlen(opthdr);
-	int off = 0;
-	int optlen = 0;
-
-	off += 2;
-	len -= 2;
-
-	while (len > 0) {
-
-		switch (opt[off]) {
-
-		case IPV6_TLV_PAD0:
-			optlen = 1;
-			break;
-		default:
-			if (len < 2) 
-				goto bad;
-			optlen = opt[off+1]+2;
-			if (len < optlen)
-				goto bad;
-			if (opt[off] & 0x20)
-				memset(&opt[off+2], 0, opt[off+1]);
-			break;
-		}
-
-		off += optlen;
-		len -= optlen;
-	}
-	if (len == 0)
-		return 1;
-
-bad:
-	return 0;
-}
-
-int xfrm6_clear_mutable_options(struct sk_buff *skb, u16 *nh_offset, int dir)
-{
-	u16 offset = sizeof(struct ipv6hdr);
-	struct ipv6_opt_hdr *exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
-	unsigned int packet_len = skb->tail - skb->nh.raw;
-	u8 nexthdr = skb->nh.ipv6h->nexthdr;
-	u8 nextnexthdr = 0;
-
-	*nh_offset = ((unsigned char *)&skb->nh.ipv6h->nexthdr) - skb->nh.raw;
-
-	while (offset + 1 <= packet_len) {
-
-		switch (nexthdr) {
-
-		case NEXTHDR_HOP:
-			*nh_offset = offset;
-			offset += ipv6_optlen(exthdr);
-			if (!zero_out_mutable_opts(exthdr)) {
-				if (net_ratelimit())
-					printk(KERN_WARNING "overrun hopopts\n"); 
-				return 0;
-			}
-			nexthdr = exthdr->nexthdr;
-			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
-			break;
-
-		case NEXTHDR_ROUTING:
-			*nh_offset = offset;
-			offset += ipv6_optlen(exthdr);
-			((struct ipv6_rt_hdr*)exthdr)->segments_left = 0; 
-			nexthdr = exthdr->nexthdr;
-			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
-			break;
-
-		case NEXTHDR_DEST:
-			*nh_offset = offset;
-			offset += ipv6_optlen(exthdr);
-			if (!zero_out_mutable_opts(exthdr))  {
-				if (net_ratelimit())
-					printk(KERN_WARNING "overrun destopt\n"); 
-				return 0;
-			}
-			nexthdr = exthdr->nexthdr;
-			exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
-			break;
-
-		case NEXTHDR_AUTH:
-			if (dir == XFRM_POLICY_OUT) {
-				memset(((struct ipv6_auth_hdr*)exthdr)->auth_data, 0, 
-				       (((struct ipv6_auth_hdr*)exthdr)->hdrlen - 1) << 2);
-			}
-			if (exthdr->nexthdr == NEXTHDR_DEST) {
-				offset += (((struct ipv6_auth_hdr*)exthdr)->hdrlen + 2) << 2;
-				exthdr = (struct ipv6_opt_hdr*)(skb->nh.raw + offset);
-				nextnexthdr = exthdr->nexthdr;
-				if (!zero_out_mutable_opts(exthdr)) {
-					if (net_ratelimit())
-						printk(KERN_WARNING "overrun destopt\n");
-					return 0;
-				}
-			}
-			return nexthdr;
-		default:
-			return nexthdr;
-		}
-	}
-
-	return nexthdr;
-}
-
-int xfrm6_rcv(struct sk_buff *skb)
+int xfrm6_rcv(struct sk_buff *skb, u8 proto)
 {
 	int err;
 	u32 spi, seq;
@@ -177,32 +69,10 @@
 	struct xfrm_state *x;
 	int xfrm_nr = 0;
 	int decaps = 0;
-	struct ipv6hdr *hdr = skb->nh.ipv6h;
-	unsigned char *tmp_hdr = NULL;
-	int hdr_len = 0;
 	u16 nh_offset = 0;
 	u8 nexthdr = 0;
 
-	if (hdr->nexthdr == IPPROTO_AH || hdr->nexthdr == IPPROTO_ESP) {
-		nh_offset = ((unsigned char*)&skb->nh.ipv6h->nexthdr) - skb->nh.raw;
-		hdr_len = sizeof(struct ipv6hdr);
-	} else {
-		hdr_len = skb->h.raw - skb->nh.raw;
-	}
-
-	tmp_hdr = kmalloc(hdr_len, GFP_ATOMIC);
-	if (!tmp_hdr)
-		goto drop;
-	memcpy(tmp_hdr, skb->nh.raw, hdr_len);
-
-	nexthdr = xfrm6_clear_mutable_options(skb, &nh_offset, XFRM_POLICY_IN);
-	hdr->priority    = 0;
-	hdr->flow_lbl[0] = 0;
-	hdr->flow_lbl[1] = 0;
-	hdr->flow_lbl[2] = 0;
-	hdr->hop_limit   = 0;
-
-	if ((err = xfrm6_parse_spi(skb, nexthdr, &spi, &seq)) != 0)
+	if ((err = xfrm6_parse_spi(skb, proto, &spi, &seq)) != 0)
 		goto drop;
 	
 	do {
@@ -211,9 +81,10 @@
 		if (xfrm_nr == XFRM_MAX_DEPTH)
 			goto drop;
 
-		x = xfrm6_state_lookup(&iph->daddr, spi, nexthdr);
+		x = xfrm6_state_lookup(&iph->daddr, spi, proto);
 		if (x == NULL)
 			goto drop;
+
 		spin_lock(&x->lock);
 		if (unlikely(x->km.state != XFRM_STATE_VALID))
 			goto drop_unlock;
@@ -221,8 +92,8 @@
 		if (x->props.replay_window && xfrm_replay_check(x, seq))
 			goto drop_unlock;
 
-		nexthdr = x->type->input(x, skb);
-		if (nexthdr <= 0)
+		proto = x->type->input(x, skb);
+		if (proto <= 0)
 			goto drop_unlock;
 
 		if (x->props.replay_window)
@@ -237,13 +108,13 @@
 
 		iph = skb->nh.ipv6h; /* ??? */ 
 
-		if (nexthdr == NEXTHDR_DEST) {
+		if (proto == NEXTHDR_DEST) {
 			if (!pskb_may_pull(skb, (skb->h.raw-skb->data)+8) ||
 		    	!pskb_may_pull(skb, (skb->h.raw-skb->data)+((skb->h.raw[1]+1)<<3))) {
 				err = -EINVAL;
 				goto drop;
 			}
-			nexthdr = skb->h.raw[0];
+			proto = skb->h.raw[0];
 			nh_offset = skb->h.raw - skb->nh.raw;
 			skb_pull(skb, (skb->h.raw[1]+1)<<3);
 			skb->h.raw = skb->data;
@@ -258,14 +129,10 @@
 			break;
 		}
 
-		if ((err = xfrm6_parse_spi(skb, nexthdr, &spi, &seq)) < 0)
+		if ((err = xfrm6_parse_spi(skb, proto, &spi, &seq)) < 0)
 			goto drop;
 	} while (!err);
 
-	memcpy(skb->nh.raw, tmp_hdr, hdr_len);
-	skb->nh.raw[nh_offset] = nexthdr;
-	skb->nh.ipv6h->payload_len = htons(hdr_len + skb->len - sizeof(struct ipv6hdr));
-
 	/* Allocate new secpath or COW existing one. */
 	if (!skb->sp || atomic_read(&skb->sp->refcnt) != 1) {
 		struct sec_path *sp;
@@ -295,14 +162,13 @@
 		netif_rx(skb);
 		return 0;
 	} else {
-		return -nexthdr;
+		return -proto;
 	}
 
 drop_unlock:
 	spin_unlock(&x->lock);
 	xfrm_state_put(x);
 drop:
-	if (tmp_hdr) kfree(tmp_hdr);
 	while (--xfrm_nr >= 0)
 		xfrm_state_put(xfrm_vec[xfrm_nr]);
 	kfree_skb(skb);
diff -ruN -x '*.o' -x '*.cmd' -x '*.ko' -x '*.mod.c' linux-2.5.62.orig/net/ipv6/xfrm_policy.c linux-2.5.62/net/ipv6/xfrm_policy.c
--- linux-2.5.62.orig/net/ipv6/xfrm_policy.c	2003-02-19 14:24:53.000000000 -0800
+++ linux-2.5.62/net/ipv6/xfrm_policy.c	2003-02-19 02:50:41.000000000 -0800
@@ -229,7 +229,7 @@
 	read_lock_bh(&xfrm_policy_lock);
 	for (pol = xfrm_policy_list[dir]; pol; pol = pol->next) {
 		struct xfrm_selector *sel = &pol->selector;
-		if (pol->family != AF_INET6) continue);
+		if (pol->family != AF_INET6) continue;
 		if (xfrm6_selector_match(sel, fl)) {
 			atomic_inc(&pol->refcnt);
 			break;