xref: /openbmc/linux/net/ipv6/ah6.c (revision 14bbd6a5)
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>
3881aded24SDavid S. Miller #include <net/ip6_route.h>
391da177e4SLinus Torvalds #include <net/icmp.h>
401da177e4SLinus Torvalds #include <net/ipv6.h>
4114c85021SArnaldo Carvalho de Melo #include <net/protocol.h>
421da177e4SLinus Torvalds #include <net/xfrm.h>
431da177e4SLinus Torvalds 
448631e9bdSSteffen Klassert #define IPV6HDR_BASELEN 8
458631e9bdSSteffen Klassert 
468631e9bdSSteffen Klassert struct tmp_ext {
4707a93626SAmerigo Wang #if IS_ENABLED(CONFIG_IPV6_MIP6)
488631e9bdSSteffen Klassert 		struct in6_addr saddr;
498631e9bdSSteffen Klassert #endif
508631e9bdSSteffen Klassert 		struct in6_addr daddr;
518631e9bdSSteffen Klassert 		char hdrs[0];
528631e9bdSSteffen Klassert };
538631e9bdSSteffen Klassert 
548631e9bdSSteffen Klassert struct ah_skb_cb {
558631e9bdSSteffen Klassert 	struct xfrm_skb_cb xfrm;
568631e9bdSSteffen Klassert 	void *tmp;
578631e9bdSSteffen Klassert };
588631e9bdSSteffen Klassert 
598631e9bdSSteffen Klassert #define AH_SKB_CB(__skb) ((struct ah_skb_cb *)&((__skb)->cb[0]))
608631e9bdSSteffen Klassert 
618631e9bdSSteffen Klassert static void *ah_alloc_tmp(struct crypto_ahash *ahash, int nfrags,
628631e9bdSSteffen Klassert 			  unsigned int size)
638631e9bdSSteffen Klassert {
648631e9bdSSteffen Klassert 	unsigned int len;
658631e9bdSSteffen Klassert 
668631e9bdSSteffen Klassert 	len = size + crypto_ahash_digestsize(ahash) +
678631e9bdSSteffen Klassert 	      (crypto_ahash_alignmask(ahash) &
688631e9bdSSteffen Klassert 	       ~(crypto_tfm_ctx_alignment() - 1));
698631e9bdSSteffen Klassert 
708631e9bdSSteffen Klassert 	len = ALIGN(len, crypto_tfm_ctx_alignment());
718631e9bdSSteffen Klassert 
728631e9bdSSteffen Klassert 	len += sizeof(struct ahash_request) + crypto_ahash_reqsize(ahash);
738631e9bdSSteffen Klassert 	len = ALIGN(len, __alignof__(struct scatterlist));
748631e9bdSSteffen Klassert 
758631e9bdSSteffen Klassert 	len += sizeof(struct scatterlist) * nfrags;
768631e9bdSSteffen Klassert 
778631e9bdSSteffen Klassert 	return kmalloc(len, GFP_ATOMIC);
788631e9bdSSteffen Klassert }
798631e9bdSSteffen Klassert 
808631e9bdSSteffen Klassert static inline struct tmp_ext *ah_tmp_ext(void *base)
818631e9bdSSteffen Klassert {
828631e9bdSSteffen Klassert 	return base + IPV6HDR_BASELEN;
838631e9bdSSteffen Klassert }
848631e9bdSSteffen Klassert 
858631e9bdSSteffen Klassert static inline u8 *ah_tmp_auth(u8 *tmp, unsigned int offset)
868631e9bdSSteffen Klassert {
878631e9bdSSteffen Klassert 	return tmp + offset;
888631e9bdSSteffen Klassert }
898631e9bdSSteffen Klassert 
908631e9bdSSteffen Klassert static inline u8 *ah_tmp_icv(struct crypto_ahash *ahash, void *tmp,
918631e9bdSSteffen Klassert 			     unsigned int offset)
928631e9bdSSteffen Klassert {
938631e9bdSSteffen Klassert 	return PTR_ALIGN((u8 *)tmp + offset, crypto_ahash_alignmask(ahash) + 1);
948631e9bdSSteffen Klassert }
958631e9bdSSteffen Klassert 
968631e9bdSSteffen Klassert static inline struct ahash_request *ah_tmp_req(struct crypto_ahash *ahash,
978631e9bdSSteffen Klassert 					       u8 *icv)
988631e9bdSSteffen Klassert {
998631e9bdSSteffen Klassert 	struct ahash_request *req;
1008631e9bdSSteffen Klassert 
1018631e9bdSSteffen Klassert 	req = (void *)PTR_ALIGN(icv + crypto_ahash_digestsize(ahash),
1028631e9bdSSteffen Klassert 				crypto_tfm_ctx_alignment());
1038631e9bdSSteffen Klassert 
1048631e9bdSSteffen Klassert 	ahash_request_set_tfm(req, ahash);
1058631e9bdSSteffen Klassert 
1068631e9bdSSteffen Klassert 	return req;
1078631e9bdSSteffen Klassert }
1088631e9bdSSteffen Klassert 
1098631e9bdSSteffen Klassert static inline struct scatterlist *ah_req_sg(struct crypto_ahash *ahash,
1108631e9bdSSteffen Klassert 					     struct ahash_request *req)
1118631e9bdSSteffen Klassert {
1128631e9bdSSteffen Klassert 	return (void *)ALIGN((unsigned long)(req + 1) +
1138631e9bdSSteffen Klassert 			     crypto_ahash_reqsize(ahash),
1148631e9bdSSteffen Klassert 			     __alignof__(struct scatterlist));
1158631e9bdSSteffen Klassert }
1168631e9bdSSteffen Klassert 
117a50feda5SEric Dumazet static bool zero_out_mutable_opts(struct ipv6_opt_hdr *opthdr)
1181da177e4SLinus Torvalds {
1191da177e4SLinus Torvalds 	u8 *opt = (u8 *)opthdr;
1201da177e4SLinus Torvalds 	int len = ipv6_optlen(opthdr);
1211da177e4SLinus Torvalds 	int off = 0;
1221da177e4SLinus Torvalds 	int optlen = 0;
1231da177e4SLinus Torvalds 
1241da177e4SLinus Torvalds 	off += 2;
1251da177e4SLinus Torvalds 	len -= 2;
1261da177e4SLinus Torvalds 
1271da177e4SLinus Torvalds 	while (len > 0) {
1281da177e4SLinus Torvalds 
1291da177e4SLinus Torvalds 		switch (opt[off]) {
1301da177e4SLinus Torvalds 
1311de5a71cSEldad Zack 		case IPV6_TLV_PAD1:
1321da177e4SLinus Torvalds 			optlen = 1;
1331da177e4SLinus Torvalds 			break;
1341da177e4SLinus Torvalds 		default:
1351da177e4SLinus Torvalds 			if (len < 2)
1361da177e4SLinus Torvalds 				goto bad;
1371da177e4SLinus Torvalds 			optlen = opt[off+1]+2;
1381da177e4SLinus Torvalds 			if (len < optlen)
1391da177e4SLinus Torvalds 				goto bad;
1401da177e4SLinus Torvalds 			if (opt[off] & 0x20)
1411da177e4SLinus Torvalds 				memset(&opt[off+2], 0, opt[off+1]);
1421da177e4SLinus Torvalds 			break;
1431da177e4SLinus Torvalds 		}
1441da177e4SLinus Torvalds 
1451da177e4SLinus Torvalds 		off += optlen;
1461da177e4SLinus Torvalds 		len -= optlen;
1471da177e4SLinus Torvalds 	}
1481da177e4SLinus Torvalds 	if (len == 0)
149a50feda5SEric Dumazet 		return true;
1501da177e4SLinus Torvalds 
1511da177e4SLinus Torvalds bad:
152a50feda5SEric Dumazet 	return false;
1531da177e4SLinus Torvalds }
1541da177e4SLinus Torvalds 
15507a93626SAmerigo Wang #if IS_ENABLED(CONFIG_IPV6_MIP6)
15627637df9SMasahide NAKAMURA /**
15727637df9SMasahide NAKAMURA  *	ipv6_rearrange_destopt - rearrange IPv6 destination options header
15827637df9SMasahide NAKAMURA  *	@iph: IPv6 header
15927637df9SMasahide NAKAMURA  *	@destopt: destionation options header
16027637df9SMasahide NAKAMURA  */
16127637df9SMasahide NAKAMURA static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *destopt)
16227637df9SMasahide NAKAMURA {
16327637df9SMasahide NAKAMURA 	u8 *opt = (u8 *)destopt;
16427637df9SMasahide NAKAMURA 	int len = ipv6_optlen(destopt);
16527637df9SMasahide NAKAMURA 	int off = 0;
16627637df9SMasahide NAKAMURA 	int optlen = 0;
16727637df9SMasahide NAKAMURA 
16827637df9SMasahide NAKAMURA 	off += 2;
16927637df9SMasahide NAKAMURA 	len -= 2;
17027637df9SMasahide NAKAMURA 
17127637df9SMasahide NAKAMURA 	while (len > 0) {
17227637df9SMasahide NAKAMURA 
17327637df9SMasahide NAKAMURA 		switch (opt[off]) {
17427637df9SMasahide NAKAMURA 
1751de5a71cSEldad Zack 		case IPV6_TLV_PAD1:
17627637df9SMasahide NAKAMURA 			optlen = 1;
17727637df9SMasahide NAKAMURA 			break;
17827637df9SMasahide NAKAMURA 		default:
17927637df9SMasahide NAKAMURA 			if (len < 2)
18027637df9SMasahide NAKAMURA 				goto bad;
18127637df9SMasahide NAKAMURA 			optlen = opt[off+1]+2;
18227637df9SMasahide NAKAMURA 			if (len < optlen)
18327637df9SMasahide NAKAMURA 				goto bad;
18427637df9SMasahide NAKAMURA 
18527637df9SMasahide NAKAMURA 			/* Rearrange the source address in @iph and the
18627637df9SMasahide NAKAMURA 			 * addresses in home address option for final source.
18727637df9SMasahide NAKAMURA 			 * See 11.3.2 of RFC 3775 for details.
18827637df9SMasahide NAKAMURA 			 */
18927637df9SMasahide NAKAMURA 			if (opt[off] == IPV6_TLV_HAO) {
19027637df9SMasahide NAKAMURA 				struct in6_addr final_addr;
19127637df9SMasahide NAKAMURA 				struct ipv6_destopt_hao *hao;
19227637df9SMasahide NAKAMURA 
19327637df9SMasahide NAKAMURA 				hao = (struct ipv6_destopt_hao *)&opt[off];
19427637df9SMasahide NAKAMURA 				if (hao->length != sizeof(hao->addr)) {
195e87cc472SJoe Perches 					net_warn_ratelimited("destopt hao: invalid header length: %u\n",
196e87cc472SJoe Perches 							     hao->length);
19727637df9SMasahide NAKAMURA 					goto bad;
19827637df9SMasahide NAKAMURA 				}
1994e3fd7a0SAlexey Dobriyan 				final_addr = hao->addr;
2004e3fd7a0SAlexey Dobriyan 				hao->addr = iph->saddr;
2014e3fd7a0SAlexey Dobriyan 				iph->saddr = final_addr;
20227637df9SMasahide NAKAMURA 			}
20327637df9SMasahide NAKAMURA 			break;
20427637df9SMasahide NAKAMURA 		}
20527637df9SMasahide NAKAMURA 
20627637df9SMasahide NAKAMURA 		off += optlen;
20727637df9SMasahide NAKAMURA 		len -= optlen;
20827637df9SMasahide NAKAMURA 	}
209e731c248SYOSHIFUJI Hideaki 	/* Note: ok if len == 0 */
21027637df9SMasahide NAKAMURA bad:
21127637df9SMasahide NAKAMURA 	return;
21227637df9SMasahide NAKAMURA }
213136ebf08SMasahide NAKAMURA #else
214136ebf08SMasahide NAKAMURA static void ipv6_rearrange_destopt(struct ipv6hdr *iph, struct ipv6_opt_hdr *destopt) {}
21527637df9SMasahide NAKAMURA #endif
21627637df9SMasahide NAKAMURA 
2171da177e4SLinus Torvalds /**
2181da177e4SLinus Torvalds  *	ipv6_rearrange_rthdr - rearrange IPv6 routing header
2191da177e4SLinus Torvalds  *	@iph: IPv6 header
2201da177e4SLinus Torvalds  *	@rthdr: routing header
2211da177e4SLinus Torvalds  *
2221da177e4SLinus Torvalds  *	Rearrange the destination address in @iph and the addresses in @rthdr
2231da177e4SLinus Torvalds  *	so that they appear in the order they will at the final destination.
2241da177e4SLinus Torvalds  *	See Appendix A2 of RFC 2402 for details.
2251da177e4SLinus Torvalds  */
2261da177e4SLinus Torvalds static void ipv6_rearrange_rthdr(struct ipv6hdr *iph, struct ipv6_rt_hdr *rthdr)
2271da177e4SLinus Torvalds {
2281da177e4SLinus Torvalds 	int segments, segments_left;
2291da177e4SLinus Torvalds 	struct in6_addr *addrs;
2301da177e4SLinus Torvalds 	struct in6_addr final_addr;
2311da177e4SLinus Torvalds 
2321da177e4SLinus Torvalds 	segments_left = rthdr->segments_left;
2331da177e4SLinus Torvalds 	if (segments_left == 0)
2341da177e4SLinus Torvalds 		return;
2351da177e4SLinus Torvalds 	rthdr->segments_left = 0;
2361da177e4SLinus Torvalds 
2371da177e4SLinus Torvalds 	/* The value of rthdr->hdrlen has been verified either by the system
2381da177e4SLinus Torvalds 	 * call if it is locally generated, or by ipv6_rthdr_rcv() for incoming
2391da177e4SLinus Torvalds 	 * packets.  So we can assume that it is even and that segments is
2401da177e4SLinus Torvalds 	 * greater than or equal to segments_left.
2411da177e4SLinus Torvalds 	 *
2421da177e4SLinus Torvalds 	 * For the same reason we can assume that this option is of type 0.
2431da177e4SLinus Torvalds 	 */
2441da177e4SLinus Torvalds 	segments = rthdr->hdrlen >> 1;
2451da177e4SLinus Torvalds 
2461da177e4SLinus Torvalds 	addrs = ((struct rt0_hdr *)rthdr)->addr;
2474e3fd7a0SAlexey Dobriyan 	final_addr = addrs[segments - 1];
2481da177e4SLinus Torvalds 
2491da177e4SLinus Torvalds 	addrs += segments - segments_left;
2501da177e4SLinus Torvalds 	memmove(addrs + 1, addrs, (segments_left - 1) * sizeof(*addrs));
2511da177e4SLinus Torvalds 
2524e3fd7a0SAlexey Dobriyan 	addrs[0] = iph->daddr;
2534e3fd7a0SAlexey Dobriyan 	iph->daddr = final_addr;
2541da177e4SLinus Torvalds }
2551da177e4SLinus Torvalds 
25627637df9SMasahide NAKAMURA static int ipv6_clear_mutable_options(struct ipv6hdr *iph, int len, int dir)
2571da177e4SLinus Torvalds {
2581da177e4SLinus Torvalds 	union {
2591da177e4SLinus Torvalds 		struct ipv6hdr *iph;
2601da177e4SLinus Torvalds 		struct ipv6_opt_hdr *opth;
2611da177e4SLinus Torvalds 		struct ipv6_rt_hdr *rth;
2621da177e4SLinus Torvalds 		char *raw;
2631da177e4SLinus Torvalds 	} exthdr = { .iph = iph };
2641da177e4SLinus Torvalds 	char *end = exthdr.raw + len;
2651da177e4SLinus Torvalds 	int nexthdr = iph->nexthdr;
2661da177e4SLinus Torvalds 
2671da177e4SLinus Torvalds 	exthdr.iph++;
2681da177e4SLinus Torvalds 
2691da177e4SLinus Torvalds 	while (exthdr.raw < end) {
2701da177e4SLinus Torvalds 		switch (nexthdr) {
27127637df9SMasahide NAKAMURA 		case NEXTHDR_DEST:
27227637df9SMasahide NAKAMURA 			if (dir == XFRM_POLICY_OUT)
27327637df9SMasahide NAKAMURA 				ipv6_rearrange_destopt(iph, exthdr.opth);
274e731c248SYOSHIFUJI Hideaki 		case NEXTHDR_HOP:
275e731c248SYOSHIFUJI Hideaki 			if (!zero_out_mutable_opts(exthdr.opth)) {
276e731c248SYOSHIFUJI Hideaki 				LIMIT_NETDEBUG(
277e731c248SYOSHIFUJI Hideaki 					KERN_WARNING "overrun %sopts\n",
278e731c248SYOSHIFUJI Hideaki 					nexthdr == NEXTHDR_HOP ?
279e731c248SYOSHIFUJI Hideaki 						"hop" : "dest");
280e731c248SYOSHIFUJI Hideaki 				return -EINVAL;
281e731c248SYOSHIFUJI Hideaki 			}
282e731c248SYOSHIFUJI Hideaki 			break;
2831da177e4SLinus Torvalds 
2841da177e4SLinus Torvalds 		case NEXTHDR_ROUTING:
2851da177e4SLinus Torvalds 			ipv6_rearrange_rthdr(iph, exthdr.rth);
2861da177e4SLinus Torvalds 			break;
2871da177e4SLinus Torvalds 
2881da177e4SLinus Torvalds 		default :
2891da177e4SLinus Torvalds 			return 0;
2901da177e4SLinus Torvalds 		}
2911da177e4SLinus Torvalds 
2921da177e4SLinus Torvalds 		nexthdr = exthdr.opth->nexthdr;
2931da177e4SLinus Torvalds 		exthdr.raw += ipv6_optlen(exthdr.opth);
2941da177e4SLinus Torvalds 	}
2951da177e4SLinus Torvalds 
2961da177e4SLinus Torvalds 	return 0;
2971da177e4SLinus Torvalds }
2981da177e4SLinus Torvalds 
2998631e9bdSSteffen Klassert static void ah6_output_done(struct crypto_async_request *base, int err)
3008631e9bdSSteffen Klassert {
3018631e9bdSSteffen Klassert 	int extlen;
3028631e9bdSSteffen Klassert 	u8 *iph_base;
3038631e9bdSSteffen Klassert 	u8 *icv;
3048631e9bdSSteffen Klassert 	struct sk_buff *skb = base->data;
3058631e9bdSSteffen Klassert 	struct xfrm_state *x = skb_dst(skb)->xfrm;
3068631e9bdSSteffen Klassert 	struct ah_data *ahp = x->data;
3078631e9bdSSteffen Klassert 	struct ipv6hdr *top_iph = ipv6_hdr(skb);
3088631e9bdSSteffen Klassert 	struct ip_auth_hdr *ah = ip_auth_hdr(skb);
3098631e9bdSSteffen Klassert 	struct tmp_ext *iph_ext;
3108631e9bdSSteffen Klassert 
3118631e9bdSSteffen Klassert 	extlen = skb_network_header_len(skb) - sizeof(struct ipv6hdr);
3128631e9bdSSteffen Klassert 	if (extlen)
3138631e9bdSSteffen Klassert 		extlen += sizeof(*iph_ext);
3148631e9bdSSteffen Klassert 
3158631e9bdSSteffen Klassert 	iph_base = AH_SKB_CB(skb)->tmp;
3168631e9bdSSteffen Klassert 	iph_ext = ah_tmp_ext(iph_base);
3178631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahp->ahash, iph_ext, extlen);
3188631e9bdSSteffen Klassert 
3198631e9bdSSteffen Klassert 	memcpy(ah->auth_data, icv, ahp->icv_trunc_len);
3208631e9bdSSteffen Klassert 	memcpy(top_iph, iph_base, IPV6HDR_BASELEN);
3218631e9bdSSteffen Klassert 
3228631e9bdSSteffen Klassert 	if (extlen) {
32307a93626SAmerigo Wang #if IS_ENABLED(CONFIG_IPV6_MIP6)
3248631e9bdSSteffen Klassert 		memcpy(&top_iph->saddr, iph_ext, extlen);
3258631e9bdSSteffen Klassert #else
3268631e9bdSSteffen Klassert 		memcpy(&top_iph->daddr, iph_ext, extlen);
3278631e9bdSSteffen Klassert #endif
3288631e9bdSSteffen Klassert 	}
3298631e9bdSSteffen Klassert 
3308631e9bdSSteffen Klassert 	kfree(AH_SKB_CB(skb)->tmp);
3318631e9bdSSteffen Klassert 	xfrm_output_resume(skb, err);
3328631e9bdSSteffen Klassert }
3338631e9bdSSteffen Klassert 
3341da177e4SLinus Torvalds static int ah6_output(struct xfrm_state *x, struct sk_buff *skb)
3351da177e4SLinus Torvalds {
3361da177e4SLinus Torvalds 	int err;
3378631e9bdSSteffen Klassert 	int nfrags;
3381da177e4SLinus Torvalds 	int extlen;
3398631e9bdSSteffen Klassert 	u8 *iph_base;
3408631e9bdSSteffen Klassert 	u8 *icv;
3418631e9bdSSteffen Klassert 	u8 nexthdr;
3428631e9bdSSteffen Klassert 	struct sk_buff *trailer;
3438631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
3448631e9bdSSteffen Klassert 	struct ahash_request *req;
3458631e9bdSSteffen Klassert 	struct scatterlist *sg;
3461da177e4SLinus Torvalds 	struct ipv6hdr *top_iph;
3471da177e4SLinus Torvalds 	struct ip_auth_hdr *ah;
3481da177e4SLinus Torvalds 	struct ah_data *ahp;
3498631e9bdSSteffen Klassert 	struct tmp_ext *iph_ext;
3508631e9bdSSteffen Klassert 
3518631e9bdSSteffen Klassert 	ahp = x->data;
3528631e9bdSSteffen Klassert 	ahash = ahp->ahash;
3538631e9bdSSteffen Klassert 
3548631e9bdSSteffen Klassert 	if ((err = skb_cow_data(skb, 0, &trailer)) < 0)
3558631e9bdSSteffen Klassert 		goto out;
3568631e9bdSSteffen Klassert 	nfrags = err;
3571da177e4SLinus Torvalds 
3587b277b1aSHerbert Xu 	skb_push(skb, -skb_network_offset(skb));
3598631e9bdSSteffen Klassert 	extlen = skb_network_header_len(skb) - sizeof(struct ipv6hdr);
3608631e9bdSSteffen Klassert 	if (extlen)
3618631e9bdSSteffen Klassert 		extlen += sizeof(*iph_ext);
3628631e9bdSSteffen Klassert 
3638631e9bdSSteffen Klassert 	err = -ENOMEM;
3648631e9bdSSteffen Klassert 	iph_base = ah_alloc_tmp(ahash, nfrags, IPV6HDR_BASELEN + extlen);
3658631e9bdSSteffen Klassert 	if (!iph_base)
3668631e9bdSSteffen Klassert 		goto out;
3678631e9bdSSteffen Klassert 
3688631e9bdSSteffen Klassert 	iph_ext = ah_tmp_ext(iph_base);
3698631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahash, iph_ext, extlen);
3708631e9bdSSteffen Klassert 	req = ah_tmp_req(ahash, icv);
3718631e9bdSSteffen Klassert 	sg = ah_req_sg(ahash, req);
3728631e9bdSSteffen Klassert 
3738631e9bdSSteffen Klassert 	ah = ip_auth_hdr(skb);
3748631e9bdSSteffen Klassert 	memset(ah->auth_data, 0, ahp->icv_trunc_len);
3758631e9bdSSteffen Klassert 
376007f0211SHerbert Xu 	top_iph = ipv6_hdr(skb);
3771da177e4SLinus Torvalds 	top_iph->payload_len = htons(skb->len - sizeof(*top_iph));
3781da177e4SLinus Torvalds 
379007f0211SHerbert Xu 	nexthdr = *skb_mac_header(skb);
380007f0211SHerbert Xu 	*skb_mac_header(skb) = IPPROTO_AH;
3811da177e4SLinus Torvalds 
3821da177e4SLinus Torvalds 	/* When there are no extension headers, we only need to save the first
3831da177e4SLinus Torvalds 	 * 8 bytes of the base IP header.
3841da177e4SLinus Torvalds 	 */
3858631e9bdSSteffen Klassert 	memcpy(iph_base, top_iph, IPV6HDR_BASELEN);
3861da177e4SLinus Torvalds 
3871da177e4SLinus Torvalds 	if (extlen) {
38807a93626SAmerigo Wang #if IS_ENABLED(CONFIG_IPV6_MIP6)
3898631e9bdSSteffen Klassert 		memcpy(iph_ext, &top_iph->saddr, extlen);
390e731c248SYOSHIFUJI Hideaki #else
3918631e9bdSSteffen Klassert 		memcpy(iph_ext, &top_iph->daddr, extlen);
392e731c248SYOSHIFUJI Hideaki #endif
39327637df9SMasahide NAKAMURA 		err = ipv6_clear_mutable_options(top_iph,
3948631e9bdSSteffen Klassert 						 extlen - sizeof(*iph_ext) +
39527637df9SMasahide NAKAMURA 						 sizeof(*top_iph),
39627637df9SMasahide NAKAMURA 						 XFRM_POLICY_OUT);
3971da177e4SLinus Torvalds 		if (err)
3988631e9bdSSteffen Klassert 			goto out_free;
3991da177e4SLinus Torvalds 	}
4001da177e4SLinus Torvalds 
4011da177e4SLinus Torvalds 	ah->nexthdr = nexthdr;
4021da177e4SLinus Torvalds 
4031da177e4SLinus Torvalds 	top_iph->priority    = 0;
4041da177e4SLinus Torvalds 	top_iph->flow_lbl[0] = 0;
4051da177e4SLinus Torvalds 	top_iph->flow_lbl[1] = 0;
4061da177e4SLinus Torvalds 	top_iph->flow_lbl[2] = 0;
4071da177e4SLinus Torvalds 	top_iph->hop_limit   = 0;
4081da177e4SLinus Torvalds 
40987bdc48dSHerbert Xu 	ah->hdrlen  = (XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len) >> 2) - 2;
4101da177e4SLinus Torvalds 
4111da177e4SLinus Torvalds 	ah->reserved = 0;
4121da177e4SLinus Torvalds 	ah->spi = x->id.spi;
4131ce3644aSSteffen Klassert 	ah->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output.low);
414b7c6538cSHerbert Xu 
4158631e9bdSSteffen Klassert 	sg_init_table(sg, nfrags);
4168631e9bdSSteffen Klassert 	skb_to_sgvec(skb, sg, 0, skb->len);
417b7c6538cSHerbert Xu 
4188631e9bdSSteffen Klassert 	ahash_request_set_crypt(req, sg, icv, skb->len);
4198631e9bdSSteffen Klassert 	ahash_request_set_callback(req, 0, ah6_output_done, skb);
4201da177e4SLinus Torvalds 
4218631e9bdSSteffen Klassert 	AH_SKB_CB(skb)->tmp = iph_base;
4228631e9bdSSteffen Klassert 
4238631e9bdSSteffen Klassert 	err = crypto_ahash_digest(req);
4248631e9bdSSteffen Klassert 	if (err) {
4258631e9bdSSteffen Klassert 		if (err == -EINPROGRESS)
4268631e9bdSSteffen Klassert 			goto out;
4278631e9bdSSteffen Klassert 
4288631e9bdSSteffen Klassert 		if (err == -EBUSY)
4298631e9bdSSteffen Klassert 			err = NET_XMIT_DROP;
4308631e9bdSSteffen Klassert 		goto out_free;
4311da177e4SLinus Torvalds 	}
4321da177e4SLinus Torvalds 
4338631e9bdSSteffen Klassert 	memcpy(ah->auth_data, icv, ahp->icv_trunc_len);
4348631e9bdSSteffen Klassert 	memcpy(top_iph, iph_base, IPV6HDR_BASELEN);
4358631e9bdSSteffen Klassert 
4368631e9bdSSteffen Klassert 	if (extlen) {
43707a93626SAmerigo Wang #if IS_ENABLED(CONFIG_IPV6_MIP6)
4388631e9bdSSteffen Klassert 		memcpy(&top_iph->saddr, iph_ext, extlen);
4398631e9bdSSteffen Klassert #else
4408631e9bdSSteffen Klassert 		memcpy(&top_iph->daddr, iph_ext, extlen);
4418631e9bdSSteffen Klassert #endif
4428631e9bdSSteffen Klassert 	}
4438631e9bdSSteffen Klassert 
4448631e9bdSSteffen Klassert out_free:
4458631e9bdSSteffen Klassert 	kfree(iph_base);
4468631e9bdSSteffen Klassert out:
4471da177e4SLinus Torvalds 	return err;
4481da177e4SLinus Torvalds }
4491da177e4SLinus Torvalds 
4508631e9bdSSteffen Klassert static void ah6_input_done(struct crypto_async_request *base, int err)
4518631e9bdSSteffen Klassert {
4528631e9bdSSteffen Klassert 	u8 *auth_data;
4538631e9bdSSteffen Klassert 	u8 *icv;
4548631e9bdSSteffen Klassert 	u8 *work_iph;
4558631e9bdSSteffen Klassert 	struct sk_buff *skb = base->data;
4568631e9bdSSteffen Klassert 	struct xfrm_state *x = xfrm_input_state(skb);
4578631e9bdSSteffen Klassert 	struct ah_data *ahp = x->data;
4588631e9bdSSteffen Klassert 	struct ip_auth_hdr *ah = ip_auth_hdr(skb);
4598631e9bdSSteffen Klassert 	int hdr_len = skb_network_header_len(skb);
4608631e9bdSSteffen Klassert 	int ah_hlen = (ah->hdrlen + 2) << 2;
4618631e9bdSSteffen Klassert 
4628631e9bdSSteffen Klassert 	work_iph = AH_SKB_CB(skb)->tmp;
4638631e9bdSSteffen Klassert 	auth_data = ah_tmp_auth(work_iph, hdr_len);
4648631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahp->ahash, auth_data, ahp->icv_trunc_len);
4658631e9bdSSteffen Klassert 
4668631e9bdSSteffen Klassert 	err = memcmp(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG: 0;
4678631e9bdSSteffen Klassert 	if (err)
4688631e9bdSSteffen Klassert 		goto out;
4698631e9bdSSteffen Klassert 
470b7ea81a5SNick Bowler 	err = ah->nexthdr;
471b7ea81a5SNick Bowler 
4728631e9bdSSteffen Klassert 	skb->network_header += ah_hlen;
4738631e9bdSSteffen Klassert 	memcpy(skb_network_header(skb), work_iph, hdr_len);
4748631e9bdSSteffen Klassert 	__skb_pull(skb, ah_hlen + hdr_len);
475a9403f8aSLi RongQing 	if (x->props.mode == XFRM_MODE_TUNNEL)
476a9403f8aSLi RongQing 		skb_reset_transport_header(skb);
477a9403f8aSLi RongQing 	else
4788631e9bdSSteffen Klassert 		skb_set_transport_header(skb, -hdr_len);
4798631e9bdSSteffen Klassert out:
4808631e9bdSSteffen Klassert 	kfree(AH_SKB_CB(skb)->tmp);
4818631e9bdSSteffen Klassert 	xfrm_input_resume(skb, err);
4828631e9bdSSteffen Klassert }
4838631e9bdSSteffen Klassert 
4848631e9bdSSteffen Klassert 
4858631e9bdSSteffen Klassert 
486e695633eSHerbert Xu static int ah6_input(struct xfrm_state *x, struct sk_buff *skb)
4871da177e4SLinus Torvalds {
4881da177e4SLinus Torvalds 	/*
4891da177e4SLinus Torvalds 	 * Before process AH
4901da177e4SLinus Torvalds 	 * [IPv6][Ext1][Ext2][AH][Dest][Payload]
4911da177e4SLinus Torvalds 	 * |<-------------->| hdr_len
4921da177e4SLinus Torvalds 	 *
4931da177e4SLinus Torvalds 	 * To erase AH:
4941da177e4SLinus Torvalds 	 * Keeping copy of cleared headers. After AH processing,
495b0e380b1SArnaldo Carvalho de Melo 	 * Moving the pointer of skb->network_header by using skb_pull as long
496b0e380b1SArnaldo Carvalho de Melo 	 * as AH header length. Then copy back the copy as long as hdr_len
4971da177e4SLinus Torvalds 	 * If destination header following AH exists, copy it into after [Ext2].
4981da177e4SLinus Torvalds 	 *
4991da177e4SLinus Torvalds 	 * |<>|[IPv6][Ext1][Ext2][Dest][Payload]
5001da177e4SLinus Torvalds 	 * There is offset of AH before IPv6 header after the process.
5011da177e4SLinus Torvalds 	 */
5021da177e4SLinus Torvalds 
5038631e9bdSSteffen Klassert 	u8 *auth_data;
5048631e9bdSSteffen Klassert 	u8 *icv;
5058631e9bdSSteffen Klassert 	u8 *work_iph;
5068631e9bdSSteffen Klassert 	struct sk_buff *trailer;
5078631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
5088631e9bdSSteffen Klassert 	struct ahash_request *req;
5098631e9bdSSteffen Klassert 	struct scatterlist *sg;
51087bdc48dSHerbert Xu 	struct ip_auth_hdr *ah;
5110660e03fSArnaldo Carvalho de Melo 	struct ipv6hdr *ip6h;
5121da177e4SLinus Torvalds 	struct ah_data *ahp;
5131da177e4SLinus Torvalds 	u16 hdr_len;
5141da177e4SLinus Torvalds 	u16 ah_hlen;
5151da177e4SLinus Torvalds 	int nexthdr;
5168631e9bdSSteffen Klassert 	int nfrags;
5178631e9bdSSteffen Klassert 	int err = -ENOMEM;
5181da177e4SLinus Torvalds 
5191da177e4SLinus Torvalds 	if (!pskb_may_pull(skb, sizeof(struct ip_auth_hdr)))
5201da177e4SLinus Torvalds 		goto out;
5211da177e4SLinus Torvalds 
5221da177e4SLinus Torvalds 	/* We are going to _remove_ AH header to keep sockets happy,
5231da177e4SLinus Torvalds 	 * so... Later this can change. */
52414bbd6a5SPravin B Shelar 	if (skb_unclone(skb, GFP_ATOMIC))
5251da177e4SLinus Torvalds 		goto out;
5261da177e4SLinus Torvalds 
5277aa68cb9SHerbert Xu 	skb->ip_summed = CHECKSUM_NONE;
5287aa68cb9SHerbert Xu 
5298631e9bdSSteffen Klassert 	hdr_len = skb_network_header_len(skb);
53087bdc48dSHerbert Xu 	ah = (struct ip_auth_hdr *)skb->data;
5311da177e4SLinus Torvalds 	ahp = x->data;
5328631e9bdSSteffen Klassert 	ahash = ahp->ahash;
5338631e9bdSSteffen Klassert 
5341da177e4SLinus Torvalds 	nexthdr = ah->nexthdr;
5351da177e4SLinus Torvalds 	ah_hlen = (ah->hdrlen + 2) << 2;
5361da177e4SLinus Torvalds 
53787bdc48dSHerbert Xu 	if (ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_full_len) &&
53887bdc48dSHerbert Xu 	    ah_hlen != XFRM_ALIGN8(sizeof(*ah) + ahp->icv_trunc_len))
5391da177e4SLinus Torvalds 		goto out;
5401da177e4SLinus Torvalds 
5411da177e4SLinus Torvalds 	if (!pskb_may_pull(skb, ah_hlen))
5421da177e4SLinus Torvalds 		goto out;
5431da177e4SLinus Torvalds 
5448631e9bdSSteffen Klassert 
5458631e9bdSSteffen Klassert 	if ((err = skb_cow_data(skb, 0, &trailer)) < 0)
5468631e9bdSSteffen Klassert 		goto out;
5478631e9bdSSteffen Klassert 	nfrags = err;
5488631e9bdSSteffen Klassert 
5494b0ef1f2SDang Hongwu 	ah = (struct ip_auth_hdr *)skb->data;
5504b0ef1f2SDang Hongwu 	ip6h = ipv6_hdr(skb);
5514b0ef1f2SDang Hongwu 
5524b0ef1f2SDang Hongwu 	skb_push(skb, hdr_len);
5534b0ef1f2SDang Hongwu 
5548631e9bdSSteffen Klassert 	work_iph = ah_alloc_tmp(ahash, nfrags, hdr_len + ahp->icv_trunc_len);
5558631e9bdSSteffen Klassert 	if (!work_iph)
5568631e9bdSSteffen Klassert 		goto out;
5578631e9bdSSteffen Klassert 
5588631e9bdSSteffen Klassert 	auth_data = ah_tmp_auth(work_iph, hdr_len);
5598631e9bdSSteffen Klassert 	icv = ah_tmp_icv(ahash, auth_data, ahp->icv_trunc_len);
5608631e9bdSSteffen Klassert 	req = ah_tmp_req(ahash, icv);
5618631e9bdSSteffen Klassert 	sg = ah_req_sg(ahash, req);
5628631e9bdSSteffen Klassert 
5638631e9bdSSteffen Klassert 	memcpy(work_iph, ip6h, hdr_len);
5648631e9bdSSteffen Klassert 	memcpy(auth_data, ah->auth_data, ahp->icv_trunc_len);
5658631e9bdSSteffen Klassert 	memset(ah->auth_data, 0, ahp->icv_trunc_len);
5668631e9bdSSteffen Klassert 
5670660e03fSArnaldo Carvalho de Melo 	if (ipv6_clear_mutable_options(ip6h, hdr_len, XFRM_POLICY_IN))
5688631e9bdSSteffen Klassert 		goto out_free;
5698631e9bdSSteffen Klassert 
5700660e03fSArnaldo Carvalho de Melo 	ip6h->priority    = 0;
5710660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[0] = 0;
5720660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[1] = 0;
5730660e03fSArnaldo Carvalho de Melo 	ip6h->flow_lbl[2] = 0;
5740660e03fSArnaldo Carvalho de Melo 	ip6h->hop_limit   = 0;
5751da177e4SLinus Torvalds 
5768631e9bdSSteffen Klassert 	sg_init_table(sg, nfrags);
5778631e9bdSSteffen Klassert 	skb_to_sgvec(skb, sg, 0, skb->len);
5781da177e4SLinus Torvalds 
5798631e9bdSSteffen Klassert 	ahash_request_set_crypt(req, sg, icv, skb->len);
5808631e9bdSSteffen Klassert 	ahash_request_set_callback(req, 0, ah6_input_done, skb);
5818631e9bdSSteffen Klassert 
5828631e9bdSSteffen Klassert 	AH_SKB_CB(skb)->tmp = work_iph;
5838631e9bdSSteffen Klassert 
5848631e9bdSSteffen Klassert 	err = crypto_ahash_digest(req);
5858631e9bdSSteffen Klassert 	if (err) {
5868631e9bdSSteffen Klassert 		if (err == -EINPROGRESS)
5878631e9bdSSteffen Klassert 			goto out;
5888631e9bdSSteffen Klassert 
5898631e9bdSSteffen Klassert 		goto out_free;
5900ebea8efSHerbert Xu 	}
5910ebea8efSHerbert Xu 
5928631e9bdSSteffen Klassert 	err = memcmp(icv, auth_data, ahp->icv_trunc_len) ? -EBADMSG: 0;
5930ebea8efSHerbert Xu 	if (err)
5948631e9bdSSteffen Klassert 		goto out_free;
5951da177e4SLinus Torvalds 
596b0e380b1SArnaldo Carvalho de Melo 	skb->network_header += ah_hlen;
5978631e9bdSSteffen Klassert 	memcpy(skb_network_header(skb), work_iph, hdr_len);
59831a4ab93SHerbert Xu 	__skb_pull(skb, ah_hlen + hdr_len);
5991da177e4SLinus Torvalds 
600a9403f8aSLi RongQing 	if (x->props.mode == XFRM_MODE_TUNNEL)
601a9403f8aSLi RongQing 		skb_reset_transport_header(skb);
602a9403f8aSLi RongQing 	else
603a9403f8aSLi RongQing 		skb_set_transport_header(skb, -hdr_len);
604a9403f8aSLi RongQing 
6058631e9bdSSteffen Klassert 	err = nexthdr;
6061da177e4SLinus Torvalds 
6078631e9bdSSteffen Klassert out_free:
6088631e9bdSSteffen Klassert 	kfree(work_iph);
6091da177e4SLinus Torvalds out:
61007d4ee58SHerbert Xu 	return err;
6111da177e4SLinus Torvalds }
6121da177e4SLinus Torvalds 
6131da177e4SLinus Torvalds static void ah6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
614d5fdd6baSBrian Haley 		    u8 type, u8 code, int offset, __be32 info)
6151da177e4SLinus Torvalds {
6164fb236baSAlexey Dobriyan 	struct net *net = dev_net(skb->dev);
6171da177e4SLinus Torvalds 	struct ipv6hdr *iph = (struct ipv6hdr*)skb->data;
6181da177e4SLinus Torvalds 	struct ip_auth_hdr *ah = (struct ip_auth_hdr*)(skb->data+offset);
6191da177e4SLinus Torvalds 	struct xfrm_state *x;
6201da177e4SLinus Torvalds 
6211da177e4SLinus Torvalds 	if (type != ICMPV6_DEST_UNREACH &&
622ec18d9a2SDavid S. Miller 	    type != ICMPV6_PKT_TOOBIG &&
623ec18d9a2SDavid S. Miller 	    type != NDISC_REDIRECT)
6241da177e4SLinus Torvalds 		return;
6251da177e4SLinus Torvalds 
626bd55775cSJamal Hadi Salim 	x = xfrm_state_lookup(net, skb->mark, (xfrm_address_t *)&iph->daddr, ah->spi, IPPROTO_AH, AF_INET6);
6271da177e4SLinus Torvalds 	if (!x)
6281da177e4SLinus Torvalds 		return;
6291da177e4SLinus Torvalds 
630ec18d9a2SDavid S. Miller 	if (type == NDISC_REDIRECT)
631ec18d9a2SDavid S. Miller 		ip6_redirect(skb, net, 0, 0);
632ec18d9a2SDavid S. Miller 	else
63381aded24SDavid S. Miller 		ip6_update_pmtu(skb, net, info, 0, 0);
6341da177e4SLinus Torvalds 	xfrm_state_put(x);
6351da177e4SLinus Torvalds }
6361da177e4SLinus Torvalds 
63772cb6962SHerbert Xu static int ah6_init_state(struct xfrm_state *x)
6381da177e4SLinus Torvalds {
6391da177e4SLinus Torvalds 	struct ah_data *ahp = NULL;
6401da177e4SLinus Torvalds 	struct xfrm_algo_desc *aalg_desc;
6418631e9bdSSteffen Klassert 	struct crypto_ahash *ahash;
6421da177e4SLinus Torvalds 
6431da177e4SLinus Torvalds 	if (!x->aalg)
6441da177e4SLinus Torvalds 		goto error;
6451da177e4SLinus Torvalds 
6461da177e4SLinus Torvalds 	if (x->encap)
6471da177e4SLinus Torvalds 		goto error;
6481da177e4SLinus Torvalds 
6490c600edaSIngo Oeser 	ahp = kzalloc(sizeof(*ahp), GFP_KERNEL);
6501da177e4SLinus Torvalds 	if (ahp == NULL)
6511da177e4SLinus Torvalds 		return -ENOMEM;
6521da177e4SLinus Torvalds 
6538631e9bdSSteffen Klassert 	ahash = crypto_alloc_ahash(x->aalg->alg_name, 0, 0);
6548631e9bdSSteffen Klassert 	if (IS_ERR(ahash))
6551da177e4SLinus Torvalds 		goto error;
65607d4ee58SHerbert Xu 
6578631e9bdSSteffen Klassert 	ahp->ahash = ahash;
6588631e9bdSSteffen Klassert 	if (crypto_ahash_setkey(ahash, x->aalg->alg_key,
659bc31d3b2SHerbert Xu 			       (x->aalg->alg_key_len + 7) / 8))
66007d4ee58SHerbert Xu 		goto error;
6611da177e4SLinus Torvalds 
6621da177e4SLinus Torvalds 	/*
6631da177e4SLinus Torvalds 	 * Lookup the algorithm description maintained by xfrm_algo,
6641da177e4SLinus Torvalds 	 * verify crypto transform properties, and store information
6651da177e4SLinus Torvalds 	 * we need for AH processing.  This lookup cannot fail here
66607d4ee58SHerbert Xu 	 * after a successful crypto_alloc_hash().
6671da177e4SLinus Torvalds 	 */
6681da177e4SLinus Torvalds 	aalg_desc = xfrm_aalg_get_byname(x->aalg->alg_name, 0);
6691da177e4SLinus Torvalds 	BUG_ON(!aalg_desc);
6701da177e4SLinus Torvalds 
6711da177e4SLinus Torvalds 	if (aalg_desc->uinfo.auth.icv_fullbits/8 !=
6728631e9bdSSteffen Klassert 	    crypto_ahash_digestsize(ahash)) {
673f3213831SJoe Perches 		pr_info("AH: %s digestsize %u != %hu\n",
6748631e9bdSSteffen Klassert 			x->aalg->alg_name, crypto_ahash_digestsize(ahash),
6751da177e4SLinus Torvalds 			aalg_desc->uinfo.auth.icv_fullbits/8);
6761da177e4SLinus Torvalds 		goto error;
6771da177e4SLinus Torvalds 	}
6781da177e4SLinus Torvalds 
6791da177e4SLinus Torvalds 	ahp->icv_full_len = aalg_desc->uinfo.auth.icv_fullbits/8;
6808f8a088cSMartin Willi 	ahp->icv_trunc_len = x->aalg->alg_trunc_len/8;
6811da177e4SLinus Torvalds 
6821da177e4SLinus Torvalds 	BUG_ON(ahp->icv_trunc_len > MAX_AH_AUTH_LEN);
6831da177e4SLinus Torvalds 
68487bdc48dSHerbert Xu 	x->props.header_len = XFRM_ALIGN8(sizeof(struct ip_auth_hdr) +
68587bdc48dSHerbert Xu 					  ahp->icv_trunc_len);
686ca68145fSHerbert Xu 	switch (x->props.mode) {
687ca68145fSHerbert Xu 	case XFRM_MODE_BEET:
688ca68145fSHerbert Xu 	case XFRM_MODE_TRANSPORT:
689ca68145fSHerbert Xu 		break;
690ca68145fSHerbert Xu 	case XFRM_MODE_TUNNEL:
6911da177e4SLinus Torvalds 		x->props.header_len += sizeof(struct ipv6hdr);
692ea2c47b4SMasahide NAKAMURA 		break;
693ca68145fSHerbert Xu 	default:
694ca68145fSHerbert Xu 		goto error;
695ca68145fSHerbert Xu 	}
6961da177e4SLinus Torvalds 	x->data = ahp;
6971da177e4SLinus Torvalds 
6981da177e4SLinus Torvalds 	return 0;
6991da177e4SLinus Torvalds 
7001da177e4SLinus Torvalds error:
7011da177e4SLinus Torvalds 	if (ahp) {
7028631e9bdSSteffen Klassert 		crypto_free_ahash(ahp->ahash);
7031da177e4SLinus Torvalds 		kfree(ahp);
7041da177e4SLinus Torvalds 	}
7051da177e4SLinus Torvalds 	return -EINVAL;
7061da177e4SLinus Torvalds }
7071da177e4SLinus Torvalds 
7081da177e4SLinus Torvalds static void ah6_destroy(struct xfrm_state *x)
7091da177e4SLinus Torvalds {
7101da177e4SLinus Torvalds 	struct ah_data *ahp = x->data;
7111da177e4SLinus Torvalds 
7121da177e4SLinus Torvalds 	if (!ahp)
7131da177e4SLinus Torvalds 		return;
7141da177e4SLinus Torvalds 
7158631e9bdSSteffen Klassert 	crypto_free_ahash(ahp->ahash);
7161da177e4SLinus Torvalds 	kfree(ahp);
7171da177e4SLinus Torvalds }
7181da177e4SLinus Torvalds 
719533cb5b0SEric Dumazet static const struct xfrm_type ah6_type =
7201da177e4SLinus Torvalds {
7211da177e4SLinus Torvalds 	.description	= "AH6",
7221da177e4SLinus Torvalds 	.owner		= THIS_MODULE,
7231da177e4SLinus Torvalds 	.proto	     	= IPPROTO_AH,
724436a0a40SHerbert Xu 	.flags		= XFRM_TYPE_REPLAY_PROT,
7251da177e4SLinus Torvalds 	.init_state	= ah6_init_state,
7261da177e4SLinus Torvalds 	.destructor	= ah6_destroy,
7271da177e4SLinus Torvalds 	.input		= ah6_input,
728aee5adb4SMasahide NAKAMURA 	.output		= ah6_output,
729aee5adb4SMasahide NAKAMURA 	.hdr_offset	= xfrm6_find_1stfragopt,
7301da177e4SLinus Torvalds };
7311da177e4SLinus Torvalds 
73241135cc8SAlexey Dobriyan static const struct inet6_protocol ah6_protocol = {
7331da177e4SLinus Torvalds 	.handler	=	xfrm6_rcv,
7341da177e4SLinus Torvalds 	.err_handler	=	ah6_err,
7351da177e4SLinus Torvalds 	.flags		=	INET6_PROTO_NOPOLICY,
7361da177e4SLinus Torvalds };
7371da177e4SLinus Torvalds 
7381da177e4SLinus Torvalds static int __init ah6_init(void)
7391da177e4SLinus Torvalds {
7401da177e4SLinus Torvalds 	if (xfrm_register_type(&ah6_type, AF_INET6) < 0) {
741f3213831SJoe Perches 		pr_info("%s: can't add xfrm type\n", __func__);
7421da177e4SLinus Torvalds 		return -EAGAIN;
7431da177e4SLinus Torvalds 	}
7441da177e4SLinus Torvalds 
7451da177e4SLinus Torvalds 	if (inet6_add_protocol(&ah6_protocol, IPPROTO_AH) < 0) {
746f3213831SJoe Perches 		pr_info("%s: can't add protocol\n", __func__);
7471da177e4SLinus Torvalds 		xfrm_unregister_type(&ah6_type, AF_INET6);
7481da177e4SLinus Torvalds 		return -EAGAIN;
7491da177e4SLinus Torvalds 	}
7501da177e4SLinus Torvalds 
7511da177e4SLinus Torvalds 	return 0;
7521da177e4SLinus Torvalds }
7531da177e4SLinus Torvalds 
7541da177e4SLinus Torvalds static void __exit ah6_fini(void)
7551da177e4SLinus Torvalds {
7561da177e4SLinus Torvalds 	if (inet6_del_protocol(&ah6_protocol, IPPROTO_AH) < 0)
757f3213831SJoe Perches 		pr_info("%s: can't remove protocol\n", __func__);
7581da177e4SLinus Torvalds 
7591da177e4SLinus Torvalds 	if (xfrm_unregister_type(&ah6_type, AF_INET6) < 0)
760f3213831SJoe Perches 		pr_info("%s: can't remove xfrm type\n", __func__);
7611da177e4SLinus Torvalds 
7621da177e4SLinus Torvalds }
7631da177e4SLinus Torvalds 
7641da177e4SLinus Torvalds module_init(ah6_init);
7651da177e4SLinus Torvalds module_exit(ah6_fini);
7661da177e4SLinus Torvalds 
7671da177e4SLinus Torvalds MODULE_LICENSE("GPL");
768d3d6dd3aSMasahide NAKAMURA MODULE_ALIAS_XFRM_TYPE(AF_INET6, XFRM_PROTO_AH);
769