xref: /openbmc/linux/net/ipv6/ah6.c (revision 1de5a71c)
11da177e4SLinus Torvalds /*
21da177e4SLinus Torvalds  * Copyright (C)2002 USAGI/WIDE Project
31da177e4SLinus Torvalds  *
41da177e4SLinus Torvalds  * This program is free software; you can redistribute it and/or modify
51da177e4SLinus Torvalds  * it under the terms of the GNU General Public License as published by
61da177e4SLinus Torvalds  * the Free Software Foundation; either version 2 of the License, or
71da177e4SLinus Torvalds  * (at your option) any later version.
81da177e4SLinus Torvalds  *
91da177e4SLinus Torvalds  * This program is distributed in the hope that it will be useful,
101da177e4SLinus Torvalds  * but WITHOUT ANY WARRANTY; without even the implied warranty of
111da177e4SLinus Torvalds  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
121da177e4SLinus Torvalds  * GNU General Public License for more details.
131da177e4SLinus Torvalds  *
141da177e4SLinus Torvalds  * You should have received a copy of the GNU General Public License
151da177e4SLinus Torvalds  * along with this program; if not, write to the Free Software
161da177e4SLinus Torvalds  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
171da177e4SLinus Torvalds  *
181da177e4SLinus Torvalds  * Authors
191da177e4SLinus Torvalds  *
201da177e4SLinus Torvalds  *	Mitsuru KANDA @USAGI       : IPv6 Support
211da177e4SLinus Torvalds  * 	Kazunori MIYAZAWA @USAGI   :
221da177e4SLinus Torvalds  * 	Kunihiro Ishiguro <kunihiro@ipinfusion.com>
231da177e4SLinus Torvalds  *
241da177e4SLinus Torvalds  * 	This file is derived from net/ipv4/ah.c.
251da177e4SLinus Torvalds  */
261da177e4SLinus Torvalds 
27f3213831SJoe Perches #define pr_fmt(fmt) "IPv6: " fmt
28f3213831SJoe Perches 
298631e9bdSSteffen Klassert #include <crypto/hash.h>
301da177e4SLinus Torvalds #include <linux/module.h>
315a0e3ad6STejun Heo #include <linux/slab.h>
321da177e4SLinus Torvalds #include <net/ip.h>
331da177e4SLinus Torvalds #include <net/ah.h>
341da177e4SLinus Torvalds #include <linux/crypto.h>
351da177e4SLinus Torvalds #include <linux/pfkeyv2.h>
361da177e4SLinus Torvalds #include <linux/string.h>
378631e9bdSSteffen Klassert #include <linux/scatterlist.h>
381da177e4SLinus Torvalds #include <net/icmp.h>
391da177e4SLinus Torvalds #include <net/ipv6.h>
4014c85021SArnaldo Carvalho de Melo #include <net/protocol.h>
411da177e4SLinus Torvalds #include <net/xfrm.h>
421da177e4SLinus Torvalds 
438631e9bdSSteffen Klassert #define IPV6HDR_BASELEN 8
448631e9bdSSteffen Klassert 
458631e9bdSSteffen Klassert struct tmp_ext {
468631e9bdSSteffen Klassert #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
478631e9bdSSteffen Klassert 		struct in6_addr saddr;
488631e9bdSSteffen Klassert #endif
498631e9bdSSteffen Klassert 		struct in6_addr daddr;
508631e9bdSSteffen Klassert 		char hdrs[0];
518631e9bdSSteffen Klassert };
528631e9bdSSteffen Klassert 
538631e9bdSSteffen Klassert struct ah_skb_cb {
548631e9bdSSteffen Klassert 	struct xfrm_skb_cb xfrm;
558631e9bdSSteffen Klassert 	void *tmp;
568631e9bdSSteffen Klassert };
578631e9bdSSteffen Klassert 
588631e9bdSSteffen Klassert #define AH_SKB_CB(__skb) ((struct ah_skb_cb *)&((__skb)->cb[0]))
598631e9bdSSteffen Klassert 
608631e9bdSSteffen Klassert static void *ah_alloc_tmp(struct crypto_ahash *ahash, int nfrags,
618631e9bdSSteffen Klassert 			  unsigned int size)
628631e9bdSSteffen Klassert {
638631e9bdSSteffen Klassert 	unsigned int len;
648631e9bdSSteffen Klassert 
658631e9bdSSteffen Klassert 	len = size + crypto_ahash_digestsize(ahash) +
668631e9bdSSteffen Klassert 	      (crypto_ahash_alignmask(ahash) &
678631e9bdSSteffen Klassert 	       ~(crypto_tfm_ctx_alignment() - 1));
688631e9bdSSteffen Klassert 
698631e9bdSSteffen Klassert 	len = ALIGN(len, crypto_tfm_ctx_alignment());
708631e9bdSSteffen Klassert 
718631e9bdSSteffen Klassert 	len += sizeof(struct ahash_request) + crypto_ahash_reqsize(ahash);
728631e9bdSSteffen Klassert 	len = ALIGN(len, __alignof__(struct scatterlist));
738631e9bdSSteffen Klassert 
748631e9bdSSteffen Klassert 	len += sizeof(struct scatterlist) * nfrags;
758631e9bdSSteffen Klassert 
768631e9bdSSteffen Klassert 	return kmalloc(len, GFP_ATOMIC);
778631e9bdSSteffen Klassert }
788631e9bdSSteffen Klassert 
798631e9bdSSteffen Klassert static inline struct tmp_ext *ah_tmp_ext(void *base)
808631e9bdSSteffen Klassert {
818631e9bdSSteffen Klassert 	return base + IPV6HDR_BASELEN;
828631e9bdSSteffen Klassert }
838631e9bdSSteffen Klassert 
848631e9bdSSteffen Klassert static inline u8 *ah_tmp_auth(u8 *tmp, unsigned int offset)
858631e9bdSSteffen Klassert {
868631e9bdSSteffen Klassert 	return tmp + offset;
878631e9bdSSteffen Klassert }
888631e9bdSSteffen Klassert 
898631e9bdSSteffen Klassert static inline u8 *ah_tmp_icv(struct crypto_ahash *ahash, void *tmp,
908631e9bdSSteffen Klassert 			     unsigned int offset)
918631e9bdSSteffen Klassert {
928631e9bdSSteffen Klassert 	return PTR_ALIGN((u8 *)tmp + offset, crypto_ahash_alignmask(ahash) + 1);
938631e9bdSSteffen Klassert }
948631e9bdSSteffen Klassert 
958631e9bdSSteffen Klassert static inline struct ahash_request *ah_tmp_req(struct crypto_ahash *ahash,
968631e9bdSSteffen Klassert 					       u8 *icv)
978631e9bdSSteffen Klassert {
988631e9bdSSteffen Klassert 	struct ahash_request *req;
998631e9bdSSteffen Klassert 
1008631e9bdSSteffen Klassert 	req = (void *)PTR_ALIGN(icv + crypto_ahash_digestsize(ahash),
1018631e9bdSSteffen Klassert 				crypto_tfm_ctx_alignment());
1028631e9bdSSteffen Klassert 
1038631e9bdSSteffen Klassert 	ahash_request_set_tfm(req, ahash);
1048631e9bdSSteffen Klassert 
1058631e9bdSSteffen Klassert 	return req;
1068631e9bdSSteffen Klassert }
1078631e9bdSSteffen Klassert 
1088631e9bdSSteffen Klassert static inline struct scatterlist *ah_req_sg(struct crypto_ahash *ahash,
1098631e9bdSSteffen Klassert 					     struct ahash_request *req)
1108631e9bdSSteffen Klassert {
1118631e9bdSSteffen Klassert 	return (void *)ALIGN((unsigned long)(req + 1) +
1128631e9bdSSteffen Klassert 			     crypto_ahash_reqsize(ahash),
1138631e9bdSSteffen Klassert 			     __alignof__(struct scatterlist));
1148631e9bdSSteffen Klassert }
1158631e9bdSSteffen Klassert 
1161da177e4SLinus Torvalds static int zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
1171da177e4SLinus Torvalds {
1181da177e4SLinus Torvalds 	u8 *opt = (u8 *)opthdr;
1191da177e4SLinus Torvalds 	int len = ipv6_optlen(opthdr);
1201da177e4SLinus Torvalds 	int off = 0;
1211da177e4SLinus Torvalds 	int optlen = 0;
1221da177e4SLinus Torvalds 
1231da177e4SLinus Torvalds 	off += 2;
1241da177e4SLinus Torvalds 	len -= 2;
1251da177e4SLinus Torvalds 
1261da177e4SLinus Torvalds 	while (len > 0) {
1271da177e4SLinus Torvalds 
1281da177e4SLinus Torvalds 		switch (opt[off]) {
1291da177e4SLinus Torvalds 
1301de5a71cSEldad Zack 		case IPV6_TLV_PAD1:
1311da177e4SLinus Torvalds 			optlen = 1;
1321da177e4SLinus Torvalds 			break;
1331da177e4SLinus Torvalds 		default:
1341da177e4SLinus Torvalds 			if (len < 2)
1351da177e4SLinus Torvalds 				goto bad;
1361da177e4SLinus Torvalds 			optlen = opt[off+1]+2;
1371da177e4SLinus Torvalds 			if (len < optlen)
1381da177e4SLinus Torvalds 				goto bad;
1391da177e4SLinus Torvalds 			if (opt[off] & 0x20)
1401da177e4SLinus Torvalds 				memset(&opt[off+2], 0, opt[off+1]);
1411da177e4SLinus Torvalds 			break;
1421da177e4SLinus Torvalds 		}
1431da177e4SLinus Torvalds 
1441da177e4SLinus Torvalds 		off += optlen;
1451da177e4SLinus Torvalds 		len -= optlen;
1461da177e4SLinus Torvalds 	}
1471da177e4SLinus Torvalds 	if (len == 0)
1481da177e4SLinus Torvalds 		return 1;
1491da177e4SLinus Torvalds 
1501da177e4SLinus Torvalds bad:
1511da177e4SLinus Torvalds 	return 0;
1521da177e4SLinus Torvalds }
1531da177e4SLinus Torvalds 
15459fbb3a6SMasahide NAKAMURA #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
15527637df9SMasahide NAKAMURA /**
15627637df9SMasahide NAKAMURA  *	ipv6_rearrange_destopt - rearrange IPv6 destination options header
15727637df9SMasahide NAKAMURA  *	@iph: IPv6 header
15827637df9SMasahide NAKAMURA  *	@destopt: destionation options header
15927637df9SMasahide NAKAMURA  */
16027637df9SMasahide NAKAMURA static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *destopt)
16127637df9SMasahide NAKAMURA {
16227637df9SMasahide NAKAMURA 	u8 *opt = (u8 *)destopt;
16327637df9SMasahide NAKAMURA 	int len = ipv6_optlen(destopt);
16427637df9SMasahide NAKAMURA 	int off = 0;
16527637df9SMasahide NAKAMURA 	int optlen = 0;
16627637df9SMasahide NAKAMURA 
16727637df9SMasahide NAKAMURA 	off += 2;
16827637df9SMasahide NAKAMURA 	len -= 2;
16927637df9SMasahide NAKAMURA 
17027637df9SMasahide NAKAMURA 	while (len > 0) {
17127637df9SMasahide NAKAMURA 
17227637df9SMasahide NAKAMURA 		switch (opt[off]) {
17327637df9SMasahide NAKAMURA 
1741de5a71cSEldad Zack 		case IPV6_TLV_PAD1:
17527637df9SMasahide NAKAMURA 			optlen = 1;
17627637df9SMasahide NAKAMURA 			break;
17727637df9SMasahide NAKAMURA 		default:
17827637df9SMasahide NAKAMURA 			if (len < 2)
17927637df9SMasahide NAKAMURA 				goto bad;
18027637df9SMasahide NAKAMURA 			optlen = opt[off+1]+2;
18127637df9SMasahide NAKAMURA 			if (len < optlen)
18227637df9SMasahide NAKAMURA 				goto bad;
18327637df9SMasahide NAKAMURA 
18427637df9SMasahide NAKAMURA 			/* Rearrange the source address in @iph and the
18527637df9SMasahide NAKAMURA 			 * addresses in home address option for final source.
18627637df9SMasahide NAKAMURA 			 * See 11.3.2 of RFC 3775 for details.
18727637df9SMasahide NAKAMURA 			 */
18827637df9SMasahide NAKAMURA 			if (opt[off] == IPV6_TLV_HAO) {
18927637df9SMasahide NAKAMURA 				struct in6_addr final_addr;
19027637df9SMasahide NAKAMURA 				struct ipv6_destopt_hao *hao;
19127637df9SMasahide NAKAMURA 
19227637df9SMasahide NAKAMURA 				hao = (struct ipv6_destopt_hao *)&opt[off];
19327637df9SMasahide NAKAMURA 				if (hao->length != sizeof(hao->addr)) {
194e87cc472SJoe Perches 					net_warn_ratelimited("destopt hao: invalid header length: %u\n",
195e87cc472SJoe Perches 							     hao->length);
19627637df9SMasahide NAKAMURA 					goto bad;
19727637df9SMasahide NAKAMURA 				}
1984e3fd7a0SAlexey Dobriyan 				final_addr = hao->addr;
1994e3fd7a0SAlexey Dobriyan 				hao->addr = iph->saddr;
2004e3fd7a0SAlexey Dobriyan 				iph->saddr = final_addr;
20127637df9SMasahide NAKAMURA 			}
20227637df9SMasahide NAKAMURA 			break;
20327637df9SMasahide NAKAMURA 		}
20427637df9SMasahide NAKAMURA 
20527637df9SMasahide NAKAMURA 		off += optlen;
20627637df9SMasahide NAKAMURA 		len -= optlen;
20727637df9SMasahide NAKAMURA 	}
208e731c248SYOSHIFUJI Hideaki 	/* Note: ok if len == 0 */
20927637df9SMasahide NAKAMURA bad:
21027637df9SMasahide NAKAMURA 	return;
21127637df9SMasahide NAKAMURA }
212136ebf08SMasahide NAKAMURA #else
213136ebf08SMasahide NAKAMURA static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *destopt) {}
21427637df9SMasahide NAKAMURA #endif
21527637df9SMasahide NAKAMURA 
2161da177e4SLinus Torvalds /**
2171da177e4SLinus Torvalds  *	ipv6_rearrange_rthdr - rearrange IPv6 routing header
2181da177e4SLinus Torvalds  *	@iph: IPv6 header
2191da177e4SLinus Torvalds  *	@rthdr: routing header
2201da177e4SLinus Torvalds  *
2211da177e4SLinus Torvalds  *	Rearrange the destination address in @iph and the addresses in @rthdr
2221da177e4SLinus Torvalds  *	so that they appear in the order they will at the final destination.
2231da177e4SLinus Torvalds  *	See Appendix A2 of RFC 2402 for details.
2241da177e4SLinus Torvalds  */
2251da177e4SLinus Torvalds static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
2261da177e4SLinus Torvalds {
2271da177e4SLinus Torvalds 	int segments, segments_left;
2281da177e4SLinus Torvalds 	struct in6_addr *addrs;
2291da177e4SLinus Torvalds 	struct in6_addr final_addr;
2301da177e4SLinus Torvalds 
2311da177e4SLinus Torvalds 	segments_left = rthdr->segments_left;
2321da177e4SLinus Torvalds 	if (segments_left == 0)
2331da177e4SLinus Torvalds 		return;
2341da177e4SLinus Torvalds 	rthdr->segments_left = 0;
2351da177e4SLinus Torvalds 
2361da177e4SLinus Torvalds 	/* The value of rthdr->hdrlen has been verified either by the system
2371da177e4SLinus Torvalds 	 * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming
2381da177e4SLinus Torvalds 	 * packets.  So we can assume that it is even and that segments is
2391da177e4SLinus Torvalds 	 * greater than or equal to segments_left.
2401da177e4SLinus Torvalds 	 *
2411da177e4SLinus Torvalds 	 * For the same reason we can assume that this option is of type 0.
2421da177e4SLinus Torvalds 	 */
2431da177e4SLinus Torvalds 	segments = rthdr->hdrlen >> 1;
2441da177e4SLinus Torvalds 
2451da177e4SLinus Torvalds 	addrs = ((struct rt0_hdr *)rthdr)->addr;
2464e3fd7a0SAlexey Dobriyan 	final_addr = addrs[segments - 1];
2471da177e4SLinus Torvalds 
2481da177e4SLinus Torvalds 	addrs += segments - segments_left;
2491da177e4SLinus Torvalds 	memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs));
2501da177e4SLinus Torvalds 
2514e3fd7a0SAlexey Dobriyan 	addrs[0] = iph->daddr;
2524e3fd7a0SAlexey Dobriyan 	iph->daddr = final_addr;
2531da177e4SLinus Torvalds }
2541da177e4SLinus Torvalds 
25527637df9SMasahide NAKAMURA static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir)
2561da177e4SLinus Torvalds {
2571da177e4SLinus Torvalds 	union {
2581da177e4SLinus Torvalds 		struct ipv6hdr *iph;
2591da177e4SLinus Torvalds 		struct ipv6_opt_hdr *opth;
2601da177e4SLinus Torvalds 		struct ipv6_rt_hdr *rth;
2611da177e4SLinus Torvalds 		char *raw;
2621da177e4SLinus Torvalds 	} exthdr = { .iph = iph };
2631da177e4SLinus Torvalds 	char *end = exthdr.raw + len;
2641da177e4SLinus Torvalds 	int nexthdr = iph->nexthdr;
2651da177e4SLinus Torvalds 
2661da177e4SLinus Torvalds 	exthdr.iph++;
2671da177e4SLinus Torvalds 
2681da177e4SLinus Torvalds 	while (exthdr.raw < end) {
2691da177e4SLinus Torvalds 		switch (nexthdr) {
27027637df9SMasahide NAKAMURA 		case NEXTHDR_DEST:
27127637df9SMasahide NAKAMURA 			if (dir == XFRM_POLICY_OUT)
27227637df9SMasahide NAKAMURA 				ipv6_rearrange_destopt(iph, exthdr.opth);
273e731c248SYOSHIFUJI Hideaki 		case NEXTHDR_HOP:
274e731c248SYOSHIFUJI Hideaki 			if (!zero_out_mutable_opts(exthdr.opth)) {
275e731c248SYOSHIFUJI Hideaki 				LIMIT_NETDEBUG(
276e731c248SYOSHIFUJI Hideaki 					KERN_WARNING "overrun %sopts\n",
277e731c248SYOSHIFUJI Hideaki 					nexthdr == NEXTHDR_HOP ?
278e731c248SYOSHIFUJI Hideaki 						"hop" : "dest");
279e731c248SYOSHIFUJI Hideaki 				return -EINVAL;
280e731c248SYOSHIFUJI Hideaki 			}
281e731c248SYOSHIFUJI Hideaki 			break;
2821da177e4SLinus Torvalds 
2831da177e4SLinus Torvalds 		case NEXTHDR_ROUTING:
2841da177e4SLinus Torvalds 			ipv6_rearrange_rthdr(iph, exthdr.rth);
2851da177e4SLinus Torvalds 			break;
2861da177e4SLinus Torvalds 
2871da177e4SLinus Torvalds 		default :
2881da177e4SLinus Torvalds 			return 0;
2891da177e4SLinus Torvalds 		}
2901da177e4SLinus Torvalds 
2911da177e4SLinus Torvalds 		nexthdr = exthdr.opth->nexthdr;
2921da177e4SLinus Torvalds 		exthdr.raw += ipv6_optlen(exthdr.opth);
2931da177e4SLinus Torvalds 	}
2941da177e4SLinus Torvalds 
2951da177e4SLinus Torvalds 	return 0;
2961da177e4SLinus Torvalds }
2971da177e4SLinus Torvalds 
2988631e9bdSSteffen Klassert static void ah6_output_done(struct crypto_async_request *base, int err)
2998631e9bdSSteffen Klassert {
3008631e9bdSSteffen Klassert 	int extlen;
3018631e9bdSSteffen Klassert 	u8 *iph_base;
3028631e9bdSSteffen Klassert 	u8 *icv;
3038631e9bdSSteffen Klassert 	struct sk_buff *skb = base->data;
3048631e9bdSSteffen Klassert 	struct xfrm_state *x = skb_dst(skb)->xfrm;
3058631e9bdSSteffen Klassert 	struct ah_data *ahp = x->data;
3068631e9bdSSteffen Klassert 	struct ipv6hdr *top_iph = ipv6_hdr(skb);
3078631e9bdSSteffen Klassert 	struct ip_auth_hdr *ah = ip_auth_hdr(skb);
3088631e9bdSSteffen Klassert 	struct tmp_ext *iph_ext;
3098631e9bdSSteffen Klassert 
3108631e9bdSSteffen Klassert 	extlen = skb_network_header_len(skb) - sizeof(struct ipv6hdr);
3118631e9bdSSteffen Klassert 	if (extlen)
3128631e9bdSSteffen Klassert 		extlen += sizeof(*iph_ext);
3138631e9bdSSteffen Klassert 
3148631e9bdSSteffen Klassert 	iph_base = AH_SKB_CB(skb)->tmp;
3158631e9bdSSteffen Klassert 	iph_ext = ah_tmp_ext(iph_base);
3168631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahp->ahash, iph_ext, extlen);
3178631e9bdSSteffen Klassert 
3188631e9bdSSteffen Klassert 	memcpy(ah->auth_data, icv, ahp->icv_trunc_len);
3198631e9bdSSteffen Klassert 	memcpy(top_iph, iph_base, IPV6HDR_BASELEN);
3208631e9bdSSteffen Klassert 
3218631e9bdSSteffen Klassert 	if (extlen) {
3228631e9bdSSteffen Klassert #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
3238631e9bdSSteffen Klassert 		memcpy(&top_iph->saddr, iph_ext, extlen);
3248631e9bdSSteffen Klassert #else
3258631e9bdSSteffen Klassert 		memcpy(&top_iph->daddr, iph_ext, extlen);
3268631e9bdSSteffen Klassert #endif
3278631e9bdSSteffen Klassert 	}
3288631e9bdSSteffen Klassert 
3298631e9bdSSteffen Klassert 	kfree(AH_SKB_CB(skb)->tmp);
3308631e9bdSSteffen Klassert 	xfrm_output_resume(skb, err);
3318631e9bdSSteffen Klassert }
3328631e9bdSSteffen Klassert 
3331da177e4SLinus Torvalds static int ah6_output(struct xfrm_state *x, struct sk_buff *skb)
3341da177e4SLinus Torvalds {
3351da177e4SLinus Torvalds 	int err;
3368631e9bdSSteffen Klassert 	int nfrags;
3371da177e4SLinus Torvalds 	int extlen;
3388631e9bdSSteffen Klassert 	u8 *iph_base;
3398631e9bdSSteffen Klassert 	u8 *icv;
3408631e9bdSSteffen Klassert 	u8 nexthdr;
3418631e9bdSSteffen Klassert 	struct sk_buff *trailer;
3428631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
3438631e9bdSSteffen Klassert 	struct ahash_request *req;
3448631e9bdSSteffen Klassert 	struct scatterlist *sg;
3451da177e4SLinus Torvalds 	struct ipv6hdr *top_iph;
3461da177e4SLinus Torvalds 	struct ip_auth_hdr *ah;
3471da177e4SLinus Torvalds 	struct ah_data *ahp;
3488631e9bdSSteffen Klassert 	struct tmp_ext *iph_ext;
3498631e9bdSSteffen Klassert 
3508631e9bdSSteffen Klassert 	ahp = x->data;
3518631e9bdSSteffen Klassert 	ahash = ahp->ahash;
3528631e9bdSSteffen Klassert 
3538631e9bdSSteffen Klassert 	if ((err = skb_cow_data(skb, 0, &trailer)) < 0)
3548631e9bdSSteffen Klassert 		goto out;
3558631e9bdSSteffen Klassert 	nfrags = err;
3561da177e4SLinus Torvalds 
3577b277b1aSHerbert Xu 	skb_push(skb, -skb_network_offset(skb));
3588631e9bdSSteffen Klassert 	extlen = skb_network_header_len(skb) - sizeof(struct ipv6hdr);
3598631e9bdSSteffen Klassert 	if (extlen)
3608631e9bdSSteffen Klassert 		extlen += sizeof(*iph_ext);
3618631e9bdSSteffen Klassert 
3628631e9bdSSteffen Klassert 	err = -ENOMEM;
3638631e9bdSSteffen Klassert 	iph_base = ah_alloc_tmp(ahash, nfrags, IPV6HDR_BASELEN + extlen);
3648631e9bdSSteffen Klassert 	if (!iph_base)
3658631e9bdSSteffen Klassert 		goto out;
3668631e9bdSSteffen Klassert 
3678631e9bdSSteffen Klassert 	iph_ext = ah_tmp_ext(iph_base);
3688631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahash, iph_ext, extlen);
3698631e9bdSSteffen Klassert 	req = ah_tmp_req(ahash, icv);
3708631e9bdSSteffen Klassert 	sg = ah_req_sg(ahash, req);
3718631e9bdSSteffen Klassert 
3728631e9bdSSteffen Klassert 	ah = ip_auth_hdr(skb);
3738631e9bdSSteffen Klassert 	memset(ah->auth_data, 0, ahp->icv_trunc_len);
3748631e9bdSSteffen Klassert 
375007f0211SHerbert Xu 	top_iph = ipv6_hdr(skb);
3761da177e4SLinus Torvalds 	top_iph->payload_len = htons(skb->len - sizeof(*top_iph));
3771da177e4SLinus Torvalds 
378007f0211SHerbert Xu 	nexthdr = *skb_mac_header(skb);
379007f0211SHerbert Xu 	*skb_mac_header(skb) = IPPROTO_AH;
3801da177e4SLinus Torvalds 
3811da177e4SLinus Torvalds 	/* When there are no extension headers, we only need to save the first
3821da177e4SLinus Torvalds 	 * 8 bytes of the base IP header.
3831da177e4SLinus Torvalds 	 */
3848631e9bdSSteffen Klassert 	memcpy(iph_base, top_iph, IPV6HDR_BASELEN);
3851da177e4SLinus Torvalds 
3861da177e4SLinus Torvalds 	if (extlen) {
38759fbb3a6SMasahide NAKAMURA #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
3888631e9bdSSteffen Klassert 		memcpy(iph_ext, &top_iph->saddr, extlen);
389e731c248SYOSHIFUJI Hideaki #else
3908631e9bdSSteffen Klassert 		memcpy(iph_ext, &top_iph->daddr, extlen);
391e731c248SYOSHIFUJI Hideaki #endif
39227637df9SMasahide NAKAMURA 		err = ipv6_clear_mutable_options(top_iph,
3938631e9bdSSteffen Klassert 						 extlen - sizeof(*iph_ext) +
39427637df9SMasahide NAKAMURA 						 sizeof(*top_iph),
39527637df9SMasahide NAKAMURA 						 XFRM_POLICY_OUT);
3961da177e4SLinus Torvalds 		if (err)
3978631e9bdSSteffen Klassert 			goto out_free;
3981da177e4SLinus Torvalds 	}
3991da177e4SLinus Torvalds 
4001da177e4SLinus Torvalds 	ah->nexthdr = nexthdr;
4011da177e4SLinus Torvalds 
4021da177e4SLinus Torvalds 	top_iph->priority    = 0;
4031da177e4SLinus Torvalds 	top_iph->flow_lbl[0] = 0;
4041da177e4SLinus Torvalds 	top_iph->flow_lbl[1] = 0;
4051da177e4SLinus Torvalds 	top_iph->flow_lbl[2] = 0;
4061da177e4SLinus Torvalds 	top_iph->hop_limit   = 0;
4071da177e4SLinus Torvalds 
40887bdc48dSHerbert Xu 	ah->hdrlen  = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2;
4091da177e4SLinus Torvalds 
4101da177e4SLinus Torvalds 	ah->reserved = 0;
4111da177e4SLinus Torvalds 	ah->spi = x->id.spi;
4121ce3644aSSteffen Klassert 	ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output.low);
413b7c6538cSHerbert Xu 
4148631e9bdSSteffen Klassert 	sg_init_table(sg, nfrags);
4158631e9bdSSteffen Klassert 	skb_to_sgvec(skb, sg, 0, skb->len);
416b7c6538cSHerbert Xu 
4178631e9bdSSteffen Klassert 	ahash_request_set_crypt(req, sg, icv, skb->len);
4188631e9bdSSteffen Klassert 	ahash_request_set_callback(req, 0, ah6_output_done, skb);
4191da177e4SLinus Torvalds 
4208631e9bdSSteffen Klassert 	AH_SKB_CB(skb)->tmp = iph_base;
4218631e9bdSSteffen Klassert 
4228631e9bdSSteffen Klassert 	err = crypto_ahash_digest(req);
4238631e9bdSSteffen Klassert 	if (err) {
4248631e9bdSSteffen Klassert 		if (err == -EINPROGRESS)
4258631e9bdSSteffen Klassert 			goto out;
4268631e9bdSSteffen Klassert 
4278631e9bdSSteffen Klassert 		if (err == -EBUSY)
4288631e9bdSSteffen Klassert 			err = NET_XMIT_DROP;
4298631e9bdSSteffen Klassert 		goto out_free;
4301da177e4SLinus Torvalds 	}
4311da177e4SLinus Torvalds 
4328631e9bdSSteffen Klassert 	memcpy(ah->auth_data, icv, ahp->icv_trunc_len);
4338631e9bdSSteffen Klassert 	memcpy(top_iph, iph_base, IPV6HDR_BASELEN);
4348631e9bdSSteffen Klassert 
4358631e9bdSSteffen Klassert 	if (extlen) {
4368631e9bdSSteffen Klassert #if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
4378631e9bdSSteffen Klassert 		memcpy(&top_iph->saddr, iph_ext, extlen);
4388631e9bdSSteffen Klassert #else
4398631e9bdSSteffen Klassert 		memcpy(&top_iph->daddr, iph_ext, extlen);
4408631e9bdSSteffen Klassert #endif
4418631e9bdSSteffen Klassert 	}
4428631e9bdSSteffen Klassert 
4438631e9bdSSteffen Klassert out_free:
4448631e9bdSSteffen Klassert 	kfree(iph_base);
4458631e9bdSSteffen Klassert out:
4461da177e4SLinus Torvalds 	return err;
4471da177e4SLinus Torvalds }
4481da177e4SLinus Torvalds 
4498631e9bdSSteffen Klassert static void ah6_input_done(struct crypto_async_request *base, int err)
4508631e9bdSSteffen Klassert {
4518631e9bdSSteffen Klassert 	u8 *auth_data;
4528631e9bdSSteffen Klassert 	u8 *icv;
4538631e9bdSSteffen Klassert 	u8 *work_iph;
4548631e9bdSSteffen Klassert 	struct sk_buff *skb = base->data;
4558631e9bdSSteffen Klassert 	struct xfrm_state *x = xfrm_input_state(skb);
4568631e9bdSSteffen Klassert 	struct ah_data *ahp = x->data;
4578631e9bdSSteffen Klassert 	struct ip_auth_hdr *ah = ip_auth_hdr(skb);
4588631e9bdSSteffen Klassert 	int hdr_len = skb_network_header_len(skb);
4598631e9bdSSteffen Klassert 	int ah_hlen = (ah->hdrlen + 2) << 2;
4608631e9bdSSteffen Klassert 
4618631e9bdSSteffen Klassert 	work_iph = AH_SKB_CB(skb)->tmp;
4628631e9bdSSteffen Klassert 	auth_data = ah_tmp_auth(work_iph, hdr_len);
4638631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahp->ahash, auth_data, ahp->icv_trunc_len);
4648631e9bdSSteffen Klassert 
4658631e9bdSSteffen Klassert 	err = memcmp(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG: 0;
4668631e9bdSSteffen Klassert 	if (err)
4678631e9bdSSteffen Klassert 		goto out;
4688631e9bdSSteffen Klassert 
469b7ea81a5SNick Bowler 	err = ah->nexthdr;
470b7ea81a5SNick Bowler 
4718631e9bdSSteffen Klassert 	skb->network_header += ah_hlen;
4728631e9bdSSteffen Klassert 	memcpy(skb_network_header(skb), work_iph, hdr_len);
4738631e9bdSSteffen Klassert 	__skb_pull(skb, ah_hlen + hdr_len);
4748631e9bdSSteffen Klassert 	skb_set_transport_header(skb, -hdr_len);
4758631e9bdSSteffen Klassert out:
4768631e9bdSSteffen Klassert 	kfree(AH_SKB_CB(skb)->tmp);
4778631e9bdSSteffen Klassert 	xfrm_input_resume(skb, err);
4788631e9bdSSteffen Klassert }
4798631e9bdSSteffen Klassert 
4808631e9bdSSteffen Klassert 
4818631e9bdSSteffen Klassert 
482e695633eSHerbert Xu static int ah6_input(struct xfrm_state *x, struct sk_buff *skb)
4831da177e4SLinus Torvalds {
4841da177e4SLinus Torvalds 	/*
4851da177e4SLinus Torvalds 	 * Before process AH
4861da177e4SLinus Torvalds 	 * [IPv6][Ext1][Ext2][AH][Dest][Payload]
4871da177e4SLinus Torvalds 	 * |<-------------->| hdr_len
4881da177e4SLinus Torvalds 	 *
4891da177e4SLinus Torvalds 	 * To erase AH:
4901da177e4SLinus Torvalds 	 * Keeping copy of cleared headers. After AH processing,
491b0e380b1SArnaldo Carvalho de Melo 	 * Moving the pointer of skb->network_header by using skb_pull as long
492b0e380b1SArnaldo Carvalho de Melo 	 * as AH header length. Then copy back the copy as long as hdr_len
4931da177e4SLinus Torvalds 	 * If destination header following AH exists, copy it into after [Ext2].
4941da177e4SLinus Torvalds 	 *
4951da177e4SLinus Torvalds 	 * |<>|[IPv6][Ext1][Ext2][Dest][Payload]
4961da177e4SLinus Torvalds 	 * There is offset of AH before IPv6 header after the process.
4971da177e4SLinus Torvalds 	 */
4981da177e4SLinus Torvalds 
4998631e9bdSSteffen Klassert 	u8 *auth_data;
5008631e9bdSSteffen Klassert 	u8 *icv;
5018631e9bdSSteffen Klassert 	u8 *work_iph;
5028631e9bdSSteffen Klassert 	struct sk_buff *trailer;
5038631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
5048631e9bdSSteffen Klassert 	struct ahash_request *req;
5058631e9bdSSteffen Klassert 	struct scatterlist *sg;
50687bdc48dSHerbert Xu 	struct ip_auth_hdr *ah;
5070660e03fSArnaldo Carvalho de Melo 	struct ipv6hdr *ip6h;
5081da177e4SLinus Torvalds 	struct ah_data *ahp;
5091da177e4SLinus Torvalds 	u16 hdr_len;
5101da177e4SLinus Torvalds 	u16 ah_hlen;
5111da177e4SLinus Torvalds 	int nexthdr;
5128631e9bdSSteffen Klassert 	int nfrags;
5138631e9bdSSteffen Klassert 	int err = -ENOMEM;
5141da177e4SLinus Torvalds 
5151da177e4SLinus Torvalds 	if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
5161da177e4SLinus Torvalds 		goto out;
5171da177e4SLinus Torvalds 
5181da177e4SLinus Torvalds 	/* We are going to _remove_ AH header to keep sockets happy,
5191da177e4SLinus Torvalds 	 * so... Later this can change. */
5201da177e4SLinus Torvalds 	if (skb_cloned(skb) &&
5211da177e4SLinus Torvalds 	    pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
5221da177e4SLinus Torvalds 		goto out;
5231da177e4SLinus Torvalds 
5247aa68cb9SHerbert Xu 	skb->ip_summed = CHECKSUM_NONE;
5257aa68cb9SHerbert Xu 
5268631e9bdSSteffen Klassert 	hdr_len = skb_network_header_len(skb);
52787bdc48dSHerbert Xu 	ah = (struct ip_auth_hdr *)skb->data;
5281da177e4SLinus Torvalds 	ahp = x->data;
5298631e9bdSSteffen Klassert 	ahash = ahp->ahash;
5308631e9bdSSteffen Klassert 
5311da177e4SLinus Torvalds 	nexthdr = ah->nexthdr;
5321da177e4SLinus Torvalds 	ah_hlen = (ah->hdrlen + 2) << 2;
5331da177e4SLinus Torvalds 
53487bdc48dSHerbert Xu 	if (ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_full_len) &&
53587bdc48dSHerbert Xu 	    ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len))
5361da177e4SLinus Torvalds 		goto out;
5371da177e4SLinus Torvalds 
5381da177e4SLinus Torvalds 	if (!pskb_may_pull(skb, ah_hlen))
5391da177e4SLinus Torvalds 		goto out;
5401da177e4SLinus Torvalds 
5418631e9bdSSteffen Klassert 
5428631e9bdSSteffen Klassert 	if ((err = skb_cow_data(skb, 0, &trailer)) < 0)
5438631e9bdSSteffen Klassert 		goto out;
5448631e9bdSSteffen Klassert 	nfrags = err;
5458631e9bdSSteffen Klassert 
5464b0ef1f2SDang Hongwu 	ah = (struct ip_auth_hdr *)skb->data;
5474b0ef1f2SDang Hongwu 	ip6h = ipv6_hdr(skb);
5484b0ef1f2SDang Hongwu 
5494b0ef1f2SDang Hongwu 	skb_push(skb, hdr_len);
5504b0ef1f2SDang Hongwu 
5518631e9bdSSteffen Klassert 	work_iph = ah_alloc_tmp(ahash, nfrags, hdr_len + ahp->icv_trunc_len);
5528631e9bdSSteffen Klassert 	if (!work_iph)
5538631e9bdSSteffen Klassert 		goto out;
5548631e9bdSSteffen Klassert 
5558631e9bdSSteffen Klassert 	auth_data = ah_tmp_auth(work_iph, hdr_len);
5568631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahash, auth_data, ahp->icv_trunc_len);
5578631e9bdSSteffen Klassert 	req = ah_tmp_req(ahash, icv);
5588631e9bdSSteffen Klassert 	sg = ah_req_sg(ahash, req);
5598631e9bdSSteffen Klassert 
5608631e9bdSSteffen Klassert 	memcpy(work_iph, ip6h, hdr_len);
5618631e9bdSSteffen Klassert 	memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
5628631e9bdSSteffen Klassert 	memset(ah->auth_data, 0, ahp->icv_trunc_len);
5638631e9bdSSteffen Klassert 
5640660e03fSArnaldo Carvalho de Melo 	if (ipv6_clear_mutable_options(ip6h, hdr_len, XFRM_POLICY_IN))
5658631e9bdSSteffen Klassert 		goto out_free;
5668631e9bdSSteffen Klassert 
5670660e03fSArnaldo Carvalho de Melo 	ip6h->priority    = 0;
5680660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[0] = 0;
5690660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[1] = 0;
5700660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[2] = 0;
5710660e03fSArnaldo Carvalho de Melo 	ip6h->hop_limit   = 0;
5721da177e4SLinus Torvalds 
5738631e9bdSSteffen Klassert 	sg_init_table(sg, nfrags);
5748631e9bdSSteffen Klassert 	skb_to_sgvec(skb, sg, 0, skb->len);
5751da177e4SLinus Torvalds 
5768631e9bdSSteffen Klassert 	ahash_request_set_crypt(req, sg, icv, skb->len);
5778631e9bdSSteffen Klassert 	ahash_request_set_callback(req, 0, ah6_input_done, skb);
5788631e9bdSSteffen Klassert 
5798631e9bdSSteffen Klassert 	AH_SKB_CB(skb)->tmp = work_iph;
5808631e9bdSSteffen Klassert 
5818631e9bdSSteffen Klassert 	err = crypto_ahash_digest(req);
5828631e9bdSSteffen Klassert 	if (err) {
5838631e9bdSSteffen Klassert 		if (err == -EINPROGRESS)
5848631e9bdSSteffen Klassert 			goto out;
5858631e9bdSSteffen Klassert 
5868631e9bdSSteffen Klassert 		goto out_free;
5870ebea8efSHerbert Xu 	}
5880ebea8efSHerbert Xu 
5898631e9bdSSteffen Klassert 	err = memcmp(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG: 0;
5900ebea8efSHerbert Xu 	if (err)
5918631e9bdSSteffen Klassert 		goto out_free;
5921da177e4SLinus Torvalds 
593b0e380b1SArnaldo Carvalho de Melo 	skb->network_header += ah_hlen;
5948631e9bdSSteffen Klassert 	memcpy(skb_network_header(skb), work_iph, hdr_len);
595b0e380b1SArnaldo Carvalho de Melo 	skb->transport_header = skb->network_header;
59631a4ab93SHerbert Xu 	__skb_pull(skb, ah_hlen + hdr_len);
5971da177e4SLinus Torvalds 
5988631e9bdSSteffen Klassert 	err = nexthdr;
5991da177e4SLinus Torvalds 
6008631e9bdSSteffen Klassert out_free:
6018631e9bdSSteffen Klassert 	kfree(work_iph);
6021da177e4SLinus Torvalds out:
60307d4ee58SHerbert Xu 	return err;
6041da177e4SLinus Torvalds }
6051da177e4SLinus Torvalds 
6061da177e4SLinus Torvalds static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
607d5fdd6baSBrian Haley 		    u8 type, u8 code, int offset, __be32 info)
6081da177e4SLinus Torvalds {
6094fb236baSAlexey Dobriyan 	struct net *net = dev_net(skb->dev);
6101da177e4SLinus Torvalds 	struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
6111da177e4SLinus Torvalds 	struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset);
6121da177e4SLinus Torvalds 	struct xfrm_state *x;
6131da177e4SLinus Torvalds 
6141da177e4SLinus Torvalds 	if (type != ICMPV6_DEST_UNREACH &&
6151da177e4SLinus Torvalds 	    type != ICMPV6_PKT_TOOBIG)
6161da177e4SLinus Torvalds 		return;
6171da177e4SLinus Torvalds 
618bd55775cSJamal Hadi Salim 	x = xfrm_state_lookup(net, skb->mark, (xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6);
6191da177e4SLinus Torvalds 	if (!x)
6201da177e4SLinus Torvalds 		return;
6211da177e4SLinus Torvalds 
6225b095d98SHarvey Harrison 	NETDEBUG(KERN_DEBUG "pmtu discovery on SA AH/%08x/%pI6\n",
6230c6ce78aSHarvey Harrison 		 ntohl(ah->spi), &iph->daddr);
6241da177e4SLinus Torvalds 
6251da177e4SLinus Torvalds 	xfrm_state_put(x);
6261da177e4SLinus Torvalds }
6271da177e4SLinus Torvalds 
62872cb6962SHerbert Xu static int ah6_init_state(struct xfrm_state *x)
6291da177e4SLinus Torvalds {
6301da177e4SLinus Torvalds 	struct ah_data *ahp = NULL;
6311da177e4SLinus Torvalds 	struct xfrm_algo_desc *aalg_desc;
6328631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
6331da177e4SLinus Torvalds 
6341da177e4SLinus Torvalds 	if (!x->aalg)
6351da177e4SLinus Torvalds 		goto error;
6361da177e4SLinus Torvalds 
6371da177e4SLinus Torvalds 	if (x->encap)
6381da177e4SLinus Torvalds 		goto error;
6391da177e4SLinus Torvalds 
6400c600edaSIngo Oeser 	ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);
6411da177e4SLinus Torvalds 	if (ahp == NULL)
6421da177e4SLinus Torvalds 		return -ENOMEM;
6431da177e4SLinus Torvalds 
6448631e9bdSSteffen Klassert 	ahash = crypto_alloc_ahash(x->aalg->alg_name, 0, 0);
6458631e9bdSSteffen Klassert 	if (IS_ERR(ahash))
6461da177e4SLinus Torvalds 		goto error;
64707d4ee58SHerbert Xu 
6488631e9bdSSteffen Klassert 	ahp->ahash = ahash;
6498631e9bdSSteffen Klassert 	if (crypto_ahash_setkey(ahash, x->aalg->alg_key,
650bc31d3b2SHerbert Xu 			       (x->aalg->alg_key_len + 7) / 8))
65107d4ee58SHerbert Xu 		goto error;
6521da177e4SLinus Torvalds 
6531da177e4SLinus Torvalds 	/*
6541da177e4SLinus Torvalds 	 * Lookup the algorithm description maintained by xfrm_algo,
6551da177e4SLinus Torvalds 	 * verify crypto transform properties, and store information
6561da177e4SLinus Torvalds 	 * we need for AH processing.  This lookup cannot fail here
65707d4ee58SHerbert Xu 	 * after a successful crypto_alloc_hash().
6581da177e4SLinus Torvalds 	 */
6591da177e4SLinus Torvalds 	aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
6601da177e4SLinus Torvalds 	BUG_ON(!aalg_desc);
6611da177e4SLinus Torvalds 
6621da177e4SLinus Torvalds 	if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
6638631e9bdSSteffen Klassert 	    crypto_ahash_digestsize(ahash)) {
664f3213831SJoe Perches 		pr_info("AH: %s digestsize %u != %hu\n",
6658631e9bdSSteffen Klassert 			x->aalg->alg_name, crypto_ahash_digestsize(ahash),
6661da177e4SLinus Torvalds 			aalg_desc->uinfo.auth.icv_fullbits/8);
6671da177e4SLinus Torvalds 		goto error;
6681da177e4SLinus Torvalds 	}
6691da177e4SLinus Torvalds 
6701da177e4SLinus Torvalds 	ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
6718f8a088cSMartin Willi 	ahp->icv_trunc_len = x->aalg->alg_trunc_len/8;
6721da177e4SLinus Torvalds 
6731da177e4SLinus Torvalds 	BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN);
6741da177e4SLinus Torvalds 
67587bdc48dSHerbert Xu 	x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) +
67687bdc48dSHerbert Xu 					  ahp->icv_trunc_len);
677ca68145fSHerbert Xu 	switch (x->props.mode) {
678ca68145fSHerbert Xu 	case XFRM_MODE_BEET:
679ca68145fSHerbert Xu 	case XFRM_MODE_TRANSPORT:
680ca68145fSHerbert Xu 		break;
681ca68145fSHerbert Xu 	case XFRM_MODE_TUNNEL:
6821da177e4SLinus Torvalds 		x->props.header_len += sizeof(struct ipv6hdr);
683ea2c47b4SMasahide NAKAMURA 		break;
684ca68145fSHerbert Xu 	default:
685ca68145fSHerbert Xu 		goto error;
686ca68145fSHerbert Xu 	}
6871da177e4SLinus Torvalds 	x->data = ahp;
6881da177e4SLinus Torvalds 
6891da177e4SLinus Torvalds 	return 0;
6901da177e4SLinus Torvalds 
6911da177e4SLinus Torvalds error:
6921da177e4SLinus Torvalds 	if (ahp) {
6938631e9bdSSteffen Klassert 		crypto_free_ahash(ahp->ahash);
6941da177e4SLinus Torvalds 		kfree(ahp);
6951da177e4SLinus Torvalds 	}
6961da177e4SLinus Torvalds 	return -EINVAL;
6971da177e4SLinus Torvalds }
6981da177e4SLinus Torvalds 
6991da177e4SLinus Torvalds static void ah6_destroy(struct xfrm_state *x)
7001da177e4SLinus Torvalds {
7011da177e4SLinus Torvalds 	struct ah_data *ahp = x->data;
7021da177e4SLinus Torvalds 
7031da177e4SLinus Torvalds 	if (!ahp)
7041da177e4SLinus Torvalds 		return;
7051da177e4SLinus Torvalds 
7068631e9bdSSteffen Klassert 	crypto_free_ahash(ahp->ahash);
7071da177e4SLinus Torvalds 	kfree(ahp);
7081da177e4SLinus Torvalds }
7091da177e4SLinus Torvalds 
710533cb5b0SEric Dumazet static const struct xfrm_type ah6_type =
7111da177e4SLinus Torvalds {
7121da177e4SLinus Torvalds 	.description	= "AH6",
7131da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
7141da177e4SLinus Torvalds 	.proto	     	= IPPROTO_AH,
715436a0a40SHerbert Xu 	.flags		= XFRM_TYPE_REPLAY_PROT,
7161da177e4SLinus Torvalds 	.init_state	= ah6_init_state,
7171da177e4SLinus Torvalds 	.destructor	= ah6_destroy,
7181da177e4SLinus Torvalds 	.input		= ah6_input,
719aee5adb4SMasahide NAKAMURA 	.output		= ah6_output,
720aee5adb4SMasahide NAKAMURA 	.hdr_offset	= xfrm6_find_1stfragopt,
7211da177e4SLinus Torvalds };
7221da177e4SLinus Torvalds 
72341135cc8SAlexey Dobriyan static const struct inet6_protocol ah6_protocol = {
7241da177e4SLinus Torvalds 	.handler	=	xfrm6_rcv,
7251da177e4SLinus Torvalds 	.err_handler	=	ah6_err,
7261da177e4SLinus Torvalds 	.flags		=	INET6_PROTO_NOPOLICY,
7271da177e4SLinus Torvalds };
7281da177e4SLinus Torvalds 
7291da177e4SLinus Torvalds static int __init ah6_init(void)
7301da177e4SLinus Torvalds {
7311da177e4SLinus Torvalds 	if (xfrm_register_type(&ah6_type, AF_INET6) < 0) {
732f3213831SJoe Perches 		pr_info("%s: can't add xfrm type\n", __func__);
7331da177e4SLinus Torvalds 		return -EAGAIN;
7341da177e4SLinus Torvalds 	}
7351da177e4SLinus Torvalds 
7361da177e4SLinus Torvalds 	if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) {
737f3213831SJoe Perches 		pr_info("%s: can't add protocol\n", __func__);
7381da177e4SLinus Torvalds 		xfrm_unregister_type(&ah6_type, AF_INET6);
7391da177e4SLinus Torvalds 		return -EAGAIN;
7401da177e4SLinus Torvalds 	}
7411da177e4SLinus Torvalds 
7421da177e4SLinus Torvalds 	return 0;
7431da177e4SLinus Torvalds }
7441da177e4SLinus Torvalds 
7451da177e4SLinus Torvalds static void __exit ah6_fini(void)
7461da177e4SLinus Torvalds {
7471da177e4SLinus Torvalds 	if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0)
748f3213831SJoe Perches 		pr_info("%s: can't remove protocol\n", __func__);
7491da177e4SLinus Torvalds 
7501da177e4SLinus Torvalds 	if (xfrm_unregister_type(&ah6_type, AF_INET6) < 0)
751f3213831SJoe Perches 		pr_info("%s: can't remove xfrm type\n", __func__);
7521da177e4SLinus Torvalds 
7531da177e4SLinus Torvalds }
7541da177e4SLinus Torvalds 
7551da177e4SLinus Torvalds module_init(ah6_init);
7561da177e4SLinus Torvalds module_exit(ah6_fini);
7571da177e4SLinus Torvalds 
7581da177e4SLinus Torvalds MODULE_LICENSE("GPL");
759d3d6dd3aSMasahide NAKAMURA MODULE_ALIAS_XFRM_TYPE(AF_INET6, XFRM_PROTO_AH);
760