162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * IPV4 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/udp.h>
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic struct sk_buff *esp4_gro_receive(struct list_head *head,
3162306a36Sopenharmony_ci					struct sk_buff *skb)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	int offset = skb_gro_offset(skb);
3462306a36Sopenharmony_ci	struct xfrm_offload *xo;
3562306a36Sopenharmony_ci	struct xfrm_state *x;
3662306a36Sopenharmony_ci	__be32 seq;
3762306a36Sopenharmony_ci	__be32 spi;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	if (!pskb_pull(skb, offset))
4062306a36Sopenharmony_ci		return NULL;
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	if (xfrm_parse_spi(skb, IPPROTO_ESP, &spi, &seq) != 0)
4362306a36Sopenharmony_ci		goto out;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	xo = xfrm_offload(skb);
4662306a36Sopenharmony_ci	if (!xo || !(xo->flags & CRYPTO_DONE)) {
4762306a36Sopenharmony_ci		struct sec_path *sp = secpath_set(skb);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci		if (!sp)
5062306a36Sopenharmony_ci			goto out;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci		if (sp->len == XFRM_MAX_DEPTH)
5362306a36Sopenharmony_ci			goto out_reset;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci		x = xfrm_state_lookup(dev_net(skb->dev), skb->mark,
5662306a36Sopenharmony_ci				      (xfrm_address_t *)&ip_hdr(skb)->daddr,
5762306a36Sopenharmony_ci				      spi, IPPROTO_ESP, AF_INET);
5862306a36Sopenharmony_ci		if (!x)
5962306a36Sopenharmony_ci			goto out_reset;
6062306a36Sopenharmony_ci
6162306a36Sopenharmony_ci		skb->mark = xfrm_smark_get(skb->mark, x);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci		sp->xvec[sp->len++] = x;
6462306a36Sopenharmony_ci		sp->olen++;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci		xo = xfrm_offload(skb);
6762306a36Sopenharmony_ci		if (!xo)
6862306a36Sopenharmony_ci			goto out_reset;
6962306a36Sopenharmony_ci	}
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	xo->flags |= XFRM_GRO;
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	XFRM_TUNNEL_SKB_CB(skb)->tunnel.ip4 = NULL;
7462306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->family = AF_INET;
7562306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->daddroff = offsetof(struct iphdr, daddr);
7662306a36Sopenharmony_ci	XFRM_SPI_SKB_CB(skb)->seq = seq;
7762306a36Sopenharmony_ci
7862306a36Sopenharmony_ci	/* We don't need to handle errors from xfrm_input, it does all
7962306a36Sopenharmony_ci	 * the error handling and frees the resources on error. */
8062306a36Sopenharmony_ci	xfrm_input(skb, IPPROTO_ESP, spi, -2);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	return ERR_PTR(-EINPROGRESS);
8362306a36Sopenharmony_ciout_reset:
8462306a36Sopenharmony_ci	secpath_reset(skb);
8562306a36Sopenharmony_ciout:
8662306a36Sopenharmony_ci	skb_push(skb, offset);
8762306a36Sopenharmony_ci	NAPI_GRO_CB(skb)->same_flow = 0;
8862306a36Sopenharmony_ci	NAPI_GRO_CB(skb)->flush = 1;
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci	return NULL;
9162306a36Sopenharmony_ci}
9262306a36Sopenharmony_ci
9362306a36Sopenharmony_cistatic void esp4_gso_encap(struct xfrm_state *x, struct sk_buff *skb)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	struct ip_esp_hdr *esph;
9662306a36Sopenharmony_ci	struct iphdr *iph = ip_hdr(skb);
9762306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
9862306a36Sopenharmony_ci	int proto = iph->protocol;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	skb_push(skb, -skb_network_offset(skb));
10162306a36Sopenharmony_ci	esph = ip_esp_hdr(skb);
10262306a36Sopenharmony_ci	*skb_mac_header(skb) = IPPROTO_ESP;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	esph->spi = x->id.spi;
10562306a36Sopenharmony_ci	esph->seq_no = htonl(XFRM_SKB_CB(skb)->seq.output.low);
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	xo->proto = proto;
10862306a36Sopenharmony_ci}
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_cistatic struct sk_buff *xfrm4_tunnel_gso_segment(struct xfrm_state *x,
11162306a36Sopenharmony_ci						struct sk_buff *skb,
11262306a36Sopenharmony_ci						netdev_features_t features)
11362306a36Sopenharmony_ci{
11462306a36Sopenharmony_ci	__be16 type = x->inner_mode.family == AF_INET6 ? htons(ETH_P_IPV6)
11562306a36Sopenharmony_ci						       : htons(ETH_P_IP);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	return skb_eth_gso_segment(skb, features, type);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_cistatic struct sk_buff *xfrm4_transport_gso_segment(struct xfrm_state *x,
12162306a36Sopenharmony_ci						   struct sk_buff *skb,
12262306a36Sopenharmony_ci						   netdev_features_t features)
12362306a36Sopenharmony_ci{
12462306a36Sopenharmony_ci	const struct net_offload *ops;
12562306a36Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
12662306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	skb->transport_header += x->props.header_len;
12962306a36Sopenharmony_ci	ops = rcu_dereference(inet_offloads[xo->proto]);
13062306a36Sopenharmony_ci	if (likely(ops && ops->callbacks.gso_segment))
13162306a36Sopenharmony_ci		segs = ops->callbacks.gso_segment(skb, features);
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	return segs;
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic struct sk_buff *xfrm4_beet_gso_segment(struct xfrm_state *x,
13762306a36Sopenharmony_ci					      struct sk_buff *skb,
13862306a36Sopenharmony_ci					      netdev_features_t features)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
14162306a36Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
14262306a36Sopenharmony_ci	const struct net_offload *ops;
14362306a36Sopenharmony_ci	u8 proto = xo->proto;
14462306a36Sopenharmony_ci
14562306a36Sopenharmony_ci	skb->transport_header += x->props.header_len;
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	if (x->sel.family != AF_INET6) {
14862306a36Sopenharmony_ci		if (proto == IPPROTO_BEETPH) {
14962306a36Sopenharmony_ci			struct ip_beet_phdr *ph =
15062306a36Sopenharmony_ci				(struct ip_beet_phdr *)skb->data;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci			skb->transport_header += ph->hdrlen * 8;
15362306a36Sopenharmony_ci			proto = ph->nexthdr;
15462306a36Sopenharmony_ci		} else {
15562306a36Sopenharmony_ci			skb->transport_header -= IPV4_BEET_PHMAXLEN;
15662306a36Sopenharmony_ci		}
15762306a36Sopenharmony_ci	} else {
15862306a36Sopenharmony_ci		__be16 frag;
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci		skb->transport_header +=
16162306a36Sopenharmony_ci			ipv6_skip_exthdr(skb, 0, &proto, &frag);
16262306a36Sopenharmony_ci		if (proto == IPPROTO_TCP)
16362306a36Sopenharmony_ci			skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4;
16462306a36Sopenharmony_ci	}
16562306a36Sopenharmony_ci
16662306a36Sopenharmony_ci	if (proto == IPPROTO_IPV6)
16762306a36Sopenharmony_ci		skb_shinfo(skb)->gso_type |= SKB_GSO_IPXIP4;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	__skb_pull(skb, skb_transport_offset(skb));
17062306a36Sopenharmony_ci	ops = rcu_dereference(inet_offloads[proto]);
17162306a36Sopenharmony_ci	if (likely(ops && ops->callbacks.gso_segment))
17262306a36Sopenharmony_ci		segs = ops->callbacks.gso_segment(skb, features);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	return segs;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_cistatic struct sk_buff *xfrm4_outer_mode_gso_segment(struct xfrm_state *x,
17862306a36Sopenharmony_ci						    struct sk_buff *skb,
17962306a36Sopenharmony_ci						    netdev_features_t features)
18062306a36Sopenharmony_ci{
18162306a36Sopenharmony_ci	switch (x->outer_mode.encap) {
18262306a36Sopenharmony_ci	case XFRM_MODE_TUNNEL:
18362306a36Sopenharmony_ci		return xfrm4_tunnel_gso_segment(x, skb, features);
18462306a36Sopenharmony_ci	case XFRM_MODE_TRANSPORT:
18562306a36Sopenharmony_ci		return xfrm4_transport_gso_segment(x, skb, features);
18662306a36Sopenharmony_ci	case XFRM_MODE_BEET:
18762306a36Sopenharmony_ci		return xfrm4_beet_gso_segment(x, skb, features);
18862306a36Sopenharmony_ci	}
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	return ERR_PTR(-EOPNOTSUPP);
19162306a36Sopenharmony_ci}
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_cistatic struct sk_buff *esp4_gso_segment(struct sk_buff *skb,
19462306a36Sopenharmony_ci				        netdev_features_t features)
19562306a36Sopenharmony_ci{
19662306a36Sopenharmony_ci	struct xfrm_state *x;
19762306a36Sopenharmony_ci	struct ip_esp_hdr *esph;
19862306a36Sopenharmony_ci	struct crypto_aead *aead;
19962306a36Sopenharmony_ci	netdev_features_t esp_features = features;
20062306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
20162306a36Sopenharmony_ci	struct sec_path *sp;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (!xo)
20462306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	if (!(skb_shinfo(skb)->gso_type & SKB_GSO_ESP))
20762306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_ci	sp = skb_sec_path(skb);
21062306a36Sopenharmony_ci	x = sp->xvec[sp->len - 1];
21162306a36Sopenharmony_ci	aead = x->data;
21262306a36Sopenharmony_ci	esph = ip_esp_hdr(skb);
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	if (esph->spi != x->id.spi)
21562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(*esph) + crypto_aead_ivsize(aead)))
21862306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
21962306a36Sopenharmony_ci
22062306a36Sopenharmony_ci	__skb_pull(skb, sizeof(*esph) + crypto_aead_ivsize(aead));
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	skb->encap_hdr_csum = 1;
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci	if ((!(skb->dev->gso_partial_features & NETIF_F_HW_ESP) &&
22562306a36Sopenharmony_ci	     !(features & NETIF_F_HW_ESP)) || x->xso.dev != skb->dev)
22662306a36Sopenharmony_ci		esp_features = features & ~(NETIF_F_SG | NETIF_F_CSUM_MASK |
22762306a36Sopenharmony_ci					    NETIF_F_SCTP_CRC);
22862306a36Sopenharmony_ci	else if (!(features & NETIF_F_HW_ESP_TX_CSUM) &&
22962306a36Sopenharmony_ci		 !(skb->dev->gso_partial_features & NETIF_F_HW_ESP_TX_CSUM))
23062306a36Sopenharmony_ci		esp_features = features & ~(NETIF_F_CSUM_MASK |
23162306a36Sopenharmony_ci					    NETIF_F_SCTP_CRC);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	xo->flags |= XFRM_GSO_SEGMENT;
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	return xfrm4_outer_mode_gso_segment(x, skb, esp_features);
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic int esp_input_tail(struct xfrm_state *x, struct sk_buff *skb)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct crypto_aead *aead = x->data;
24162306a36Sopenharmony_ci	struct xfrm_offload *xo = xfrm_offload(skb);
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(struct ip_esp_hdr) + crypto_aead_ivsize(aead)))
24462306a36Sopenharmony_ci		return -EINVAL;
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	if (!(xo->flags & CRYPTO_DONE))
24762306a36Sopenharmony_ci		skb->ip_summed = CHECKSUM_NONE;
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	return esp_input_done2(skb, 0);
25062306a36Sopenharmony_ci}
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_cistatic int esp_xmit(struct xfrm_state *x, struct sk_buff *skb,  netdev_features_t features)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	int err;
25562306a36Sopenharmony_ci	int alen;
25662306a36Sopenharmony_ci	int blksize;
25762306a36Sopenharmony_ci	struct xfrm_offload *xo;
25862306a36Sopenharmony_ci	struct ip_esp_hdr *esph;
25962306a36Sopenharmony_ci	struct crypto_aead *aead;
26062306a36Sopenharmony_ci	struct esp_info esp;
26162306a36Sopenharmony_ci	bool hw_offload = true;
26262306a36Sopenharmony_ci	__u32 seq;
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci	esp.inplace = true;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	xo = xfrm_offload(skb);
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	if (!xo)
26962306a36Sopenharmony_ci		return -EINVAL;
27062306a36Sopenharmony_ci
27162306a36Sopenharmony_ci	if ((!(features & NETIF_F_HW_ESP) &&
27262306a36Sopenharmony_ci	     !(skb->dev->gso_partial_features & NETIF_F_HW_ESP)) ||
27362306a36Sopenharmony_ci	    x->xso.dev != skb->dev) {
27462306a36Sopenharmony_ci		xo->flags |= CRYPTO_FALLBACK;
27562306a36Sopenharmony_ci		hw_offload = false;
27662306a36Sopenharmony_ci	}
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_ci	esp.proto = xo->proto;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	/* skb is pure payload to encrypt */
28162306a36Sopenharmony_ci
28262306a36Sopenharmony_ci	aead = x->data;
28362306a36Sopenharmony_ci	alen = crypto_aead_authsize(aead);
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	esp.tfclen = 0;
28662306a36Sopenharmony_ci	/* XXX: Add support for tfc padding here. */
28762306a36Sopenharmony_ci
28862306a36Sopenharmony_ci	blksize = ALIGN(crypto_aead_blocksize(aead), 4);
28962306a36Sopenharmony_ci	esp.clen = ALIGN(skb->len + 2 + esp.tfclen, blksize);
29062306a36Sopenharmony_ci	esp.plen = esp.clen - skb->len - esp.tfclen;
29162306a36Sopenharmony_ci	esp.tailen = esp.tfclen + esp.plen + alen;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	esp.esph = ip_esp_hdr(skb);
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	if (!hw_offload || !skb_is_gso(skb)) {
29762306a36Sopenharmony_ci		esp.nfrags = esp_output_head(x, skb, &esp);
29862306a36Sopenharmony_ci		if (esp.nfrags < 0)
29962306a36Sopenharmony_ci			return esp.nfrags;
30062306a36Sopenharmony_ci	}
30162306a36Sopenharmony_ci
30262306a36Sopenharmony_ci	seq = xo->seq.low;
30362306a36Sopenharmony_ci
30462306a36Sopenharmony_ci	esph = esp.esph;
30562306a36Sopenharmony_ci	esph->spi = x->id.spi;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	skb_push(skb, -skb_network_offset(skb));
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (xo->flags & XFRM_GSO_SEGMENT) {
31062306a36Sopenharmony_ci		esph->seq_no = htonl(seq);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci		if (!skb_is_gso(skb))
31362306a36Sopenharmony_ci			xo->seq.low++;
31462306a36Sopenharmony_ci		else
31562306a36Sopenharmony_ci			xo->seq.low += skb_shinfo(skb)->gso_segs;
31662306a36Sopenharmony_ci	}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	if (xo->seq.low < seq)
31962306a36Sopenharmony_ci		xo->seq.hi++;
32062306a36Sopenharmony_ci
32162306a36Sopenharmony_ci	esp.seqno = cpu_to_be64(seq + ((u64)xo->seq.hi << 32));
32262306a36Sopenharmony_ci
32362306a36Sopenharmony_ci	ip_hdr(skb)->tot_len = htons(skb->len);
32462306a36Sopenharmony_ci	ip_send_check(ip_hdr(skb));
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	if (hw_offload) {
32762306a36Sopenharmony_ci		if (!skb_ext_add(skb, SKB_EXT_SEC_PATH))
32862306a36Sopenharmony_ci			return -ENOMEM;
32962306a36Sopenharmony_ci
33062306a36Sopenharmony_ci		xo = xfrm_offload(skb);
33162306a36Sopenharmony_ci		if (!xo)
33262306a36Sopenharmony_ci			return -EINVAL;
33362306a36Sopenharmony_ci
33462306a36Sopenharmony_ci		xo->flags |= XFRM_XMIT;
33562306a36Sopenharmony_ci		return 0;
33662306a36Sopenharmony_ci	}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_ci	err = esp_output_tail(x, skb, &esp);
33962306a36Sopenharmony_ci	if (err)
34062306a36Sopenharmony_ci		return err;
34162306a36Sopenharmony_ci
34262306a36Sopenharmony_ci	secpath_reset(skb);
34362306a36Sopenharmony_ci
34462306a36Sopenharmony_ci	if (skb_needs_linearize(skb, skb->dev->features) &&
34562306a36Sopenharmony_ci	    __skb_linearize(skb))
34662306a36Sopenharmony_ci		return -ENOMEM;
34762306a36Sopenharmony_ci	return 0;
34862306a36Sopenharmony_ci}
34962306a36Sopenharmony_ci
35062306a36Sopenharmony_cistatic const struct net_offload esp4_offload = {
35162306a36Sopenharmony_ci	.callbacks = {
35262306a36Sopenharmony_ci		.gro_receive = esp4_gro_receive,
35362306a36Sopenharmony_ci		.gso_segment = esp4_gso_segment,
35462306a36Sopenharmony_ci	},
35562306a36Sopenharmony_ci};
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_cistatic const struct xfrm_type_offload esp_type_offload = {
35862306a36Sopenharmony_ci	.owner		= THIS_MODULE,
35962306a36Sopenharmony_ci	.proto	     	= IPPROTO_ESP,
36062306a36Sopenharmony_ci	.input_tail	= esp_input_tail,
36162306a36Sopenharmony_ci	.xmit		= esp_xmit,
36262306a36Sopenharmony_ci	.encap		= esp4_gso_encap,
36362306a36Sopenharmony_ci};
36462306a36Sopenharmony_ci
36562306a36Sopenharmony_cistatic int __init esp4_offload_init(void)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	if (xfrm_register_type_offload(&esp_type_offload, AF_INET) < 0) {
36862306a36Sopenharmony_ci		pr_info("%s: can't add xfrm type offload\n", __func__);
36962306a36Sopenharmony_ci		return -EAGAIN;
37062306a36Sopenharmony_ci	}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	return inet_add_offload(&esp4_offload, IPPROTO_ESP);
37362306a36Sopenharmony_ci}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_cistatic void __exit esp4_offload_exit(void)
37662306a36Sopenharmony_ci{
37762306a36Sopenharmony_ci	xfrm_unregister_type_offload(&esp_type_offload, AF_INET);
37862306a36Sopenharmony_ci	inet_del_offload(&esp4_offload, IPPROTO_ESP);
37962306a36Sopenharmony_ci}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cimodule_init(esp4_offload_init);
38262306a36Sopenharmony_cimodule_exit(esp4_offload_exit);
38362306a36Sopenharmony_ciMODULE_LICENSE("GPL");
38462306a36Sopenharmony_ciMODULE_AUTHOR("Steffen Klassert <steffen.klassert@secunet.com>");
38562306a36Sopenharmony_ciMODULE_ALIAS_XFRM_OFFLOAD_TYPE(AF_INET, XFRM_PROTO_ESP);
38662306a36Sopenharmony_ciMODULE_DESCRIPTION("IPV4 GSO/GRO offload support");
387