162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IPV6 GSO/GRO offload support
462306a36Sopenharmony_ci * Linux INET implementation
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright (C) 2016 secunet Security Networks AG
762306a36Sopenharmony_ci * Author: Steffen Klassert <steffen.klassert@secunet.com>
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * ESP GRO support
1062306a36Sopenharmony_ci */
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/skbuff.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <net/protocol.h>
1562306a36Sopenharmony_ci#include <crypto/aead.h>
1662306a36Sopenharmony_ci#include <crypto/authenc.h>
1762306a36Sopenharmony_ci#include <linux/err.h>
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <net/gro.h>
2062306a36Sopenharmony_ci#include <net/gso.h>
2162306a36Sopenharmony_ci#include <net/ip.h>
2262306a36Sopenharmony_ci#include <net/xfrm.h>
2362306a36Sopenharmony_ci#include <net/esp.h>
2462306a36Sopenharmony_ci#include <linux/scatterlist.h>
2562306a36Sopenharmony_ci#include <linux/kernel.h>
2662306a36Sopenharmony_ci#include <linux/slab.h>
2762306a36Sopenharmony_ci#include <linux/spinlock.h>
2862306a36Sopenharmony_ci#include <net/ip6_route.h>
2962306a36Sopenharmony_ci#include <net/ipv6.h>
3062306a36Sopenharmony_ci#include <linux/icmpv6.h>
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic __u16 esp6_nexthdr_esp_offset(struct ipv6hdr *ipv6_hdr, int nhlen)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	int off = sizeof(struct ipv6hdr);
3562306a36Sopenharmony_ci	struct ipv6_opt_hdr *exthdr;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (likely(ipv6_hdr->nexthdr == NEXTHDR_ESP))
3862306a36Sopenharmony_ci		return offsetof(struct ipv6hdr, nexthdr);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	while (off < nhlen) {
4162306a36Sopenharmony_ci		exthdr = (void *)ipv6_hdr + off;
4262306a36Sopenharmony_ci		if (exthdr->nexthdr == NEXTHDR_ESP)
4362306a36Sopenharmony_ci			return off;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci		off += ipv6_optlen(exthdr);
4662306a36Sopenharmony_ci	}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	return 0;
4962306a36Sopenharmony_ci}
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_cistatic struct sk_buff *esp6_gro_receive(struct list_head *head,
5262306a36Sopenharmony_ci					struct sk_buff *skb)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	int offset = skb_gro_offset(skb);
5562306a36Sopenharmony_ci	struct xfrm_offload *xo;
5662306a36Sopenharmony_ci	struct xfrm_state *x;
5762306a36Sopenharmony_ci	__be32 seq;
5862306a36Sopenharmony_ci	__be32 spi;
5962306a36Sopenharmony_ci	int nhoff;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci	if (!pskb_pull(skb, offset))
6262306a36Sopenharmony_ci		return NULL;
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	if (xfrm_parse_spi(skb, IPPROTO_ESP, &spi, &seq) != 0)
6562306a36Sopenharmony_ci		goto out;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	xo = xfrm_offload(skb);
6862306a36Sopenharmony_ci	if (!xo || !(xo->flags & CRYPTO_DONE)) {
6962306a36Sopenharmony_ci		struct sec_path *sp = secpath_set(skb);
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci		if (!sp)
7262306a36Sopenharmony_ci			goto out;
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_ci		if (sp->len == XFRM_MAX_DEPTH)
7562306a36Sopenharmony_ci			goto out_reset;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci		x = xfrm_state_lookup(dev_net(skb->dev), skb->mark,
7862306a36Sopenharmony_ci				      (xfrm_address_t *)&ipv6_hdr(skb)->daddr,
7962306a36Sopenharmony_ci				      spi, IPPROTO_ESP, AF_INET6);
8062306a36Sopenharmony_ci		if (!x)
8162306a36Sopenharmony_ci			goto out_reset;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci		skb->mark = xfrm_smark_get(skb->mark, x);
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci		sp->xvec[sp->len++] = x;
8662306a36Sopenharmony_ci		sp->olen++;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci		xo = xfrm_offload(skb);
8962306a36Sopenharmony_ci		if (!xo)
9062306a36Sopenharmony_ci			goto out_reset;
9162306a36Sopenharmony_ci	}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_ci	xo->flags |= XFRM_GRO;
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	nhoff = esp6_nexthdr_esp_offset(ipv6_hdr(skb), offset);
9662306a36Sopenharmony_ci	if (!nhoff)
9762306a36Sopenharmony_ci		goto out;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	IP6CB(skb)->nhoff = nhoff;
10062306a36Sopenharmony_ci	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip6 = NULL;
10162306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->family = AF_INET6;
10262306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct ipv6hdr, daddr);
10362306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->seq = seq;
10462306a36Sopenharmony_ci
10562306a36Sopenharmony_ci	/* We don't need to handle errors from xfrm_input, it does all
10662306a36Sopenharmony_ci	 * the error handling and frees the resources on error. */
10762306a36Sopenharmony_ci	xfrm_input(skb, IPPROTO_ESP, spi, -2);
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	return ERR_PTR(-EINPROGRESS);
11062306a36Sopenharmony_ciout_reset:
11162306a36Sopenharmony_ci	secpath_reset(skb);
11262306a36Sopenharmony_ciout:
11362306a36Sopenharmony_ci	skb_push(skb, offset);
11462306a36Sopenharmony_ci	NAPI_GRO_CB(skb)->same_flow = 0;
11562306a36Sopenharmony_ci	NAPI_GRO_CB(skb)->flush = 1;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return NULL;
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic void esp6_gso_encap(struct xfrm_state *x, struct sk_buff *skb)
12162306a36Sopenharmony_ci{
12262306a36Sopenharmony_ci	struct ip_esp_hdr *esph;
12362306a36Sopenharmony_ci	struct ipv6hdr *iph = ipv6_hdr(skb);
12462306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
12562306a36Sopenharmony_ci	u8 proto = iph->nexthdr;
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci	skb_push(skb, -skb_network_offset(skb));
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	if (x->outer_mode.encap == XFRM_MODE_TRANSPORT) {
13062306a36Sopenharmony_ci		__be16 frag;
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci		ipv6_skip_exthdr(skb, sizeof(struct ipv6hdr), &proto, &frag);
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	esph = ip_esp_hdr(skb);
13662306a36Sopenharmony_ci	*skb_mac_header(skb) = IPPROTO_ESP;
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_ci	esph->spi = x->id.spi;
13962306a36Sopenharmony_ci	esph->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output.low);
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci	xo->proto = proto;
14262306a36Sopenharmony_ci}
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic struct sk_buff *xfrm6_tunnel_gso_segment(struct xfrm_state *x,
14562306a36Sopenharmony_ci						struct sk_buff *skb,
14662306a36Sopenharmony_ci						netdev_features_t features)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	__be16 type = x->inner_mode.family == AF_INET ? htons(ETH_P_IP)
14962306a36Sopenharmony_ci						      : htons(ETH_P_IPV6);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ci	return skb_eth_gso_segment(skb, features, type);
15262306a36Sopenharmony_ci}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_cistatic struct sk_buff *xfrm6_transport_gso_segment(struct xfrm_state *x,
15562306a36Sopenharmony_ci						   struct sk_buff *skb,
15662306a36Sopenharmony_ci						   netdev_features_t features)
15762306a36Sopenharmony_ci{
15862306a36Sopenharmony_ci	const struct net_offload *ops;
15962306a36Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
16062306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci	skb->transport_header += x->props.header_len;
16362306a36Sopenharmony_ci	ops = rcu_dereference(inet6_offloads[xo->proto]);
16462306a36Sopenharmony_ci	if (likely(ops && ops->callbacks.gso_segment))
16562306a36Sopenharmony_ci		segs = ops->callbacks.gso_segment(skb, features);
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	return segs;
16862306a36Sopenharmony_ci}
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_cistatic struct sk_buff *xfrm6_beet_gso_segment(struct xfrm_state *x,
17162306a36Sopenharmony_ci					      struct sk_buff *skb,
17262306a36Sopenharmony_ci					      netdev_features_t features)
17362306a36Sopenharmony_ci{
17462306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
17562306a36Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
17662306a36Sopenharmony_ci	const struct net_offload *ops;
17762306a36Sopenharmony_ci	u8 proto = xo->proto;
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	skb->transport_header += x->props.header_len;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	if (x->sel.family != AF_INET6) {
18262306a36Sopenharmony_ci		skb->transport_header -=
18362306a36Sopenharmony_ci			(sizeof(struct ipv6hdr) - sizeof(struct iphdr));
18462306a36Sopenharmony_ci
18562306a36Sopenharmony_ci		if (proto == IPPROTO_BEETPH) {
18662306a36Sopenharmony_ci			struct ip_beet_phdr *ph =
18762306a36Sopenharmony_ci				(struct ip_beet_phdr *)skb->data;
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_ci			skb->transport_header += ph->hdrlen * 8;
19062306a36Sopenharmony_ci			proto = ph->nexthdr;
19162306a36Sopenharmony_ci		} else {
19262306a36Sopenharmony_ci			skb->transport_header -= IPV4_BEET_PHMAXLEN;
19362306a36Sopenharmony_ci		}
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci		if (proto == IPPROTO_TCP)
19662306a36Sopenharmony_ci			skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6;
19762306a36Sopenharmony_ci	} else {
19862306a36Sopenharmony_ci		__be16 frag;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		skb->transport_header +=
20162306a36Sopenharmony_ci			ipv6_skip_exthdr(skb, 0, &proto, &frag);
20262306a36Sopenharmony_ci	}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	if (proto == IPPROTO_IPIP)
20562306a36Sopenharmony_ci		skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP6;
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci	__skb_pull(skb, skb_transport_offset(skb));
20862306a36Sopenharmony_ci	ops = rcu_dereference(inet6_offloads[proto]);
20962306a36Sopenharmony_ci	if (likely(ops && ops->callbacks.gso_segment))
21062306a36Sopenharmony_ci		segs = ops->callbacks.gso_segment(skb, features);
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	return segs;
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic struct sk_buff *xfrm6_outer_mode_gso_segment(struct xfrm_state *x,
21662306a36Sopenharmony_ci						    struct sk_buff *skb,
21762306a36Sopenharmony_ci						    netdev_features_t features)
21862306a36Sopenharmony_ci{
21962306a36Sopenharmony_ci	switch (x->outer_mode.encap) {
22062306a36Sopenharmony_ci	case XFRM_MODE_TUNNEL:
22162306a36Sopenharmony_ci		return xfrm6_tunnel_gso_segment(x, skb, features);
22262306a36Sopenharmony_ci	case XFRM_MODE_TRANSPORT:
22362306a36Sopenharmony_ci		return xfrm6_transport_gso_segment(x, skb, features);
22462306a36Sopenharmony_ci	case XFRM_MODE_BEET:
22562306a36Sopenharmony_ci		return xfrm6_beet_gso_segment(x, skb, features);
22662306a36Sopenharmony_ci	}
22762306a36Sopenharmony_ci
22862306a36Sopenharmony_ci	return ERR_PTR(-EOPNOTSUPP);
22962306a36Sopenharmony_ci}
23062306a36Sopenharmony_ci
23162306a36Sopenharmony_cistatic struct sk_buff *esp6_gso_segment(struct sk_buff *skb,
23262306a36Sopenharmony_ci				        netdev_features_t features)
23362306a36Sopenharmony_ci{
23462306a36Sopenharmony_ci	struct xfrm_state *x;
23562306a36Sopenharmony_ci	struct ip_esp_hdr *esph;
23662306a36Sopenharmony_ci	struct crypto_aead *aead;
23762306a36Sopenharmony_ci	netdev_features_t esp_features = features;
23862306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
23962306a36Sopenharmony_ci	struct sec_path *sp;
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci	if (!xo)
24262306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	if (!(skb_shinfo(skb)->gso_type & SKB_GSO_ESP))
24562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	sp = skb_sec_path(skb);
24862306a36Sopenharmony_ci	x = sp->xvec[sp->len - 1];
24962306a36Sopenharmony_ci	aead = x->data;
25062306a36Sopenharmony_ci	esph = ip_esp_hdr(skb);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	if (esph->spi != x->id.spi)
25362306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(*esph) + crypto_aead_ivsize(aead)))
25662306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	__skb_pull(skb, sizeof(*esph) + crypto_aead_ivsize(aead));
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	skb->encap_hdr_csum = 1;
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	if (!(features & NETIF_F_HW_ESP) || x->xso.dev != skb->dev)
26362306a36Sopenharmony_ci		esp_features = features & ~(NETIF_F_SG | NETIF_F_CSUM_MASK |
26462306a36Sopenharmony_ci					    NETIF_F_SCTP_CRC);
26562306a36Sopenharmony_ci	else if (!(features & NETIF_F_HW_ESP_TX_CSUM))
26662306a36Sopenharmony_ci		esp_features = features & ~(NETIF_F_CSUM_MASK |
26762306a36Sopenharmony_ci					    NETIF_F_SCTP_CRC);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_ci	xo->flags |= XFRM_GSO_SEGMENT;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	return xfrm6_outer_mode_gso_segment(x, skb, esp_features);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic int esp6_input_tail(struct xfrm_state *x, struct sk_buff *skb)
27562306a36Sopenharmony_ci{
27662306a36Sopenharmony_ci	struct crypto_aead *aead = x->data;
27762306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr) + crypto_aead_ivsize(aead)))
28062306a36Sopenharmony_ci		return -EINVAL;
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	if (!(xo->flags & CRYPTO_DONE))
28362306a36Sopenharmony_ci		skb->ip_summed = CHECKSUM_NONE;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	return esp6_input_done2(skb, 0);
28662306a36Sopenharmony_ci}
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_cistatic int esp6_xmit(struct xfrm_state *x, struct sk_buff *skb,  netdev_features_t features)
28962306a36Sopenharmony_ci{
29062306a36Sopenharmony_ci	int len;
29162306a36Sopenharmony_ci	int err;
29262306a36Sopenharmony_ci	int alen;
29362306a36Sopenharmony_ci	int blksize;
29462306a36Sopenharmony_ci	struct xfrm_offload *xo;
29562306a36Sopenharmony_ci	struct crypto_aead *aead;
29662306a36Sopenharmony_ci	struct esp_info esp;
29762306a36Sopenharmony_ci	bool hw_offload = true;
29862306a36Sopenharmony_ci	__u32 seq;
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	esp.inplace = true;
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	xo = xfrm_offload(skb);
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	if (!xo)
30562306a36Sopenharmony_ci		return -EINVAL;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (!(features & NETIF_F_HW_ESP) || x->xso.dev != skb->dev) {
30862306a36Sopenharmony_ci		xo->flags |= CRYPTO_FALLBACK;
30962306a36Sopenharmony_ci		hw_offload = false;
31062306a36Sopenharmony_ci	}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	esp.proto = xo->proto;
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	/* skb is pure payload to encrypt */
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	aead = x->data;
31762306a36Sopenharmony_ci	alen = crypto_aead_authsize(aead);
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	esp.tfclen = 0;
32062306a36Sopenharmony_ci	/* XXX: Add support for tfc padding here. */
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	blksize = ALIGN(crypto_aead_blocksize(aead), 4);
32362306a36Sopenharmony_ci	esp.clen = ALIGN(skb->len + 2 + esp.tfclen, blksize);
32462306a36Sopenharmony_ci	esp.plen = esp.clen - skb->len - esp.tfclen;
32562306a36Sopenharmony_ci	esp.tailen = esp.tfclen + esp.plen + alen;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	if (!hw_offload || !skb_is_gso(skb)) {
32862306a36Sopenharmony_ci		esp.nfrags = esp6_output_head(x, skb, &esp);
32962306a36Sopenharmony_ci		if (esp.nfrags < 0)
33062306a36Sopenharmony_ci			return esp.nfrags;
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	seq = xo->seq.low;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	esp.esph = ip_esp_hdr(skb);
33662306a36Sopenharmony_ci	esp.esph->spi = x->id.spi;
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	skb_push(skb, -skb_network_offset(skb));
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (xo->flags & XFRM_GSO_SEGMENT) {
34162306a36Sopenharmony_ci		esp.esph->seq_no = htonl(seq);
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci		if (!skb_is_gso(skb))
34462306a36Sopenharmony_ci			xo->seq.low++;
34562306a36Sopenharmony_ci		else
34662306a36Sopenharmony_ci			xo->seq.low += skb_shinfo(skb)->gso_segs;
34762306a36Sopenharmony_ci	}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	if (xo->seq.low < seq)
35062306a36Sopenharmony_ci		xo->seq.hi++;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	esp.seqno = cpu_to_be64(xo->seq.low + ((u64)xo->seq.hi << 32));
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	len = skb->len - sizeof(struct ipv6hdr);
35562306a36Sopenharmony_ci	if (len > IPV6_MAXPLEN)
35662306a36Sopenharmony_ci		len = 0;
35762306a36Sopenharmony_ci
35862306a36Sopenharmony_ci	ipv6_hdr(skb)->payload_len = htons(len);
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_ci	if (hw_offload) {
36162306a36Sopenharmony_ci		if (!skb_ext_add(skb, SKB_EXT_SEC_PATH))
36262306a36Sopenharmony_ci			return -ENOMEM;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci		xo = xfrm_offload(skb);
36562306a36Sopenharmony_ci		if (!xo)
36662306a36Sopenharmony_ci			return -EINVAL;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci		xo->flags |= XFRM_XMIT;
36962306a36Sopenharmony_ci		return 0;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	err = esp6_output_tail(x, skb, &esp);
37362306a36Sopenharmony_ci	if (err)
37462306a36Sopenharmony_ci		return err;
37562306a36Sopenharmony_ci
37662306a36Sopenharmony_ci	secpath_reset(skb);
37762306a36Sopenharmony_ci
37862306a36Sopenharmony_ci	if (skb_needs_linearize(skb, skb->dev->features) &&
37962306a36Sopenharmony_ci	    __skb_linearize(skb))
38062306a36Sopenharmony_ci		return -ENOMEM;
38162306a36Sopenharmony_ci	return 0;
38262306a36Sopenharmony_ci}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic const struct net_offload esp6_offload = {
38562306a36Sopenharmony_ci	.callbacks = {
38662306a36Sopenharmony_ci		.gro_receive = esp6_gro_receive,
38762306a36Sopenharmony_ci		.gso_segment = esp6_gso_segment,
38862306a36Sopenharmony_ci	},
38962306a36Sopenharmony_ci};
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_cistatic const struct xfrm_type_offload esp6_type_offload = {
39262306a36Sopenharmony_ci	.owner		= THIS_MODULE,
39362306a36Sopenharmony_ci	.proto	     	= IPPROTO_ESP,
39462306a36Sopenharmony_ci	.input_tail	= esp6_input_tail,
39562306a36Sopenharmony_ci	.xmit		= esp6_xmit,
39662306a36Sopenharmony_ci	.encap		= esp6_gso_encap,
39762306a36Sopenharmony_ci};
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_cistatic int __init esp6_offload_init(void)
40062306a36Sopenharmony_ci{
40162306a36Sopenharmony_ci	if (xfrm_register_type_offload(&esp6_type_offload, AF_INET6) < 0) {
40262306a36Sopenharmony_ci		pr_info("%s: can't add xfrm type offload\n", __func__);
40362306a36Sopenharmony_ci		return -EAGAIN;
40462306a36Sopenharmony_ci	}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	return inet6_add_offload(&esp6_offload, IPPROTO_ESP);
40762306a36Sopenharmony_ci}
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_cistatic void __exit esp6_offload_exit(void)
41062306a36Sopenharmony_ci{
41162306a36Sopenharmony_ci	xfrm_unregister_type_offload(&esp6_type_offload, AF_INET6);
41262306a36Sopenharmony_ci	inet6_del_offload(&esp6_offload, IPPROTO_ESP);
41362306a36Sopenharmony_ci}
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_cimodule_init(esp6_offload_init);
41662306a36Sopenharmony_cimodule_exit(esp6_offload_exit);
41762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
41862306a36Sopenharmony_ciMODULE_AUTHOR("Steffen Klassert <steffen.klassert@secunet.com>");
41962306a36Sopenharmony_ciMODULE_ALIAS_XFRM_OFFLOAD_TYPE(AF_INET6, XFRM_PROTO_ESP);
42062306a36Sopenharmony_ciMODULE_DESCRIPTION("IPV6 GSO/GRO offload support");
421