162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *	IPV4 GSO/GRO offload support
462306a36Sopenharmony_ci *	Linux INET implementation
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *	TCPv4 GSO/GRO support
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/indirect_call_wrapper.h>
1062306a36Sopenharmony_ci#include <linux/skbuff.h>
1162306a36Sopenharmony_ci#include <net/gro.h>
1262306a36Sopenharmony_ci#include <net/gso.h>
1362306a36Sopenharmony_ci#include <net/tcp.h>
1462306a36Sopenharmony_ci#include <net/protocol.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_cistatic void tcp_gso_tstamp(struct sk_buff *skb, unsigned int ts_seq,
1762306a36Sopenharmony_ci			   unsigned int seq, unsigned int mss)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	while (skb) {
2062306a36Sopenharmony_ci		if (before(ts_seq, seq + mss)) {
2162306a36Sopenharmony_ci			skb_shinfo(skb)->tx_flags |= SKBTX_SW_TSTAMP;
2262306a36Sopenharmony_ci			skb_shinfo(skb)->tskey = ts_seq;
2362306a36Sopenharmony_ci			return;
2462306a36Sopenharmony_ci		}
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci		skb = skb->next;
2762306a36Sopenharmony_ci		seq += mss;
2862306a36Sopenharmony_ci	}
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic struct sk_buff *tcp4_gso_segment(struct sk_buff *skb,
3262306a36Sopenharmony_ci					netdev_features_t features)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	if (!(skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4))
3562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(struct tcphdr)))
3862306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
4162306a36Sopenharmony_ci		const struct iphdr *iph = ip_hdr(skb);
4262306a36Sopenharmony_ci		struct tcphdr *th = tcp_hdr(skb);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci		/* Set up checksum pseudo header, usually expect stack to
4562306a36Sopenharmony_ci		 * have done this already.
4662306a36Sopenharmony_ci		 */
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci		th->check = 0;
4962306a36Sopenharmony_ci		skb->ip_summed = CHECKSUM_PARTIAL;
5062306a36Sopenharmony_ci		__tcp_v4_send_check(skb, iph->saddr, iph->daddr);
5162306a36Sopenharmony_ci	}
5262306a36Sopenharmony_ci
5362306a36Sopenharmony_ci	return tcp_gso_segment(skb, features);
5462306a36Sopenharmony_ci}
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistruct sk_buff *tcp_gso_segment(struct sk_buff *skb,
5762306a36Sopenharmony_ci				netdev_features_t features)
5862306a36Sopenharmony_ci{
5962306a36Sopenharmony_ci	struct sk_buff *segs = ERR_PTR(-EINVAL);
6062306a36Sopenharmony_ci	unsigned int sum_truesize = 0;
6162306a36Sopenharmony_ci	struct tcphdr *th;
6262306a36Sopenharmony_ci	unsigned int thlen;
6362306a36Sopenharmony_ci	unsigned int seq;
6462306a36Sopenharmony_ci	unsigned int oldlen;
6562306a36Sopenharmony_ci	unsigned int mss;
6662306a36Sopenharmony_ci	struct sk_buff *gso_skb = skb;
6762306a36Sopenharmony_ci	__sum16 newcheck;
6862306a36Sopenharmony_ci	bool ooo_okay, copy_destructor;
6962306a36Sopenharmony_ci	__wsum delta;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	th = tcp_hdr(skb);
7262306a36Sopenharmony_ci	thlen = th->doff * 4;
7362306a36Sopenharmony_ci	if (thlen < sizeof(*th))
7462306a36Sopenharmony_ci		goto out;
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_ci	if (!pskb_may_pull(skb, thlen))
7762306a36Sopenharmony_ci		goto out;
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_ci	oldlen = ~skb->len;
8062306a36Sopenharmony_ci	__skb_pull(skb, thlen);
8162306a36Sopenharmony_ci
8262306a36Sopenharmony_ci	mss = skb_shinfo(skb)->gso_size;
8362306a36Sopenharmony_ci	if (unlikely(skb->len <= mss))
8462306a36Sopenharmony_ci		goto out;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) {
8762306a36Sopenharmony_ci		/* Packet is from an untrusted source, reset gso_segs. */
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss);
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci		segs = NULL;
9262306a36Sopenharmony_ci		goto out;
9362306a36Sopenharmony_ci	}
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci	copy_destructor = gso_skb->destructor == tcp_wfree;
9662306a36Sopenharmony_ci	ooo_okay = gso_skb->ooo_okay;
9762306a36Sopenharmony_ci	/* All segments but the first should have ooo_okay cleared */
9862306a36Sopenharmony_ci	skb->ooo_okay = 0;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	segs = skb_segment(skb, features);
10162306a36Sopenharmony_ci	if (IS_ERR(segs))
10262306a36Sopenharmony_ci		goto out;
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	/* Only first segment might have ooo_okay set */
10562306a36Sopenharmony_ci	segs->ooo_okay = ooo_okay;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	/* GSO partial and frag_list segmentation only requires splitting
10862306a36Sopenharmony_ci	 * the frame into an MSS multiple and possibly a remainder, both
10962306a36Sopenharmony_ci	 * cases return a GSO skb. So update the mss now.
11062306a36Sopenharmony_ci	 */
11162306a36Sopenharmony_ci	if (skb_is_gso(segs))
11262306a36Sopenharmony_ci		mss *= skb_shinfo(segs)->gso_segs;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	delta = (__force __wsum)htonl(oldlen + thlen + mss);
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_ci	skb = segs;
11762306a36Sopenharmony_ci	th = tcp_hdr(skb);
11862306a36Sopenharmony_ci	seq = ntohl(th->seq);
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci	if (unlikely(skb_shinfo(gso_skb)->tx_flags & SKBTX_SW_TSTAMP))
12162306a36Sopenharmony_ci		tcp_gso_tstamp(segs, skb_shinfo(gso_skb)->tskey, seq, mss);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	newcheck = ~csum_fold(csum_add(csum_unfold(th->check), delta));
12462306a36Sopenharmony_ci
12562306a36Sopenharmony_ci	while (skb->next) {
12662306a36Sopenharmony_ci		th->fin = th->psh = 0;
12762306a36Sopenharmony_ci		th->check = newcheck;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci		if (skb->ip_summed == CHECKSUM_PARTIAL)
13062306a36Sopenharmony_ci			gso_reset_checksum(skb, ~th->check);
13162306a36Sopenharmony_ci		else
13262306a36Sopenharmony_ci			th->check = gso_make_checksum(skb, ~th->check);
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci		seq += mss;
13562306a36Sopenharmony_ci		if (copy_destructor) {
13662306a36Sopenharmony_ci			skb->destructor = gso_skb->destructor;
13762306a36Sopenharmony_ci			skb->sk = gso_skb->sk;
13862306a36Sopenharmony_ci			sum_truesize += skb->truesize;
13962306a36Sopenharmony_ci		}
14062306a36Sopenharmony_ci		skb = skb->next;
14162306a36Sopenharmony_ci		th = tcp_hdr(skb);
14262306a36Sopenharmony_ci
14362306a36Sopenharmony_ci		th->seq = htonl(seq);
14462306a36Sopenharmony_ci		th->cwr = 0;
14562306a36Sopenharmony_ci	}
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci	/* Following permits TCP Small Queues to work well with GSO :
14862306a36Sopenharmony_ci	 * The callback to TCP stack will be called at the time last frag
14962306a36Sopenharmony_ci	 * is freed at TX completion, and not right now when gso_skb
15062306a36Sopenharmony_ci	 * is freed by GSO engine
15162306a36Sopenharmony_ci	 */
15262306a36Sopenharmony_ci	if (copy_destructor) {
15362306a36Sopenharmony_ci		int delta;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci		swap(gso_skb->sk, skb->sk);
15662306a36Sopenharmony_ci		swap(gso_skb->destructor, skb->destructor);
15762306a36Sopenharmony_ci		sum_truesize += skb->truesize;
15862306a36Sopenharmony_ci		delta = sum_truesize - gso_skb->truesize;
15962306a36Sopenharmony_ci		/* In some pathological cases, delta can be negative.
16062306a36Sopenharmony_ci		 * We need to either use refcount_add() or refcount_sub_and_test()
16162306a36Sopenharmony_ci		 */
16262306a36Sopenharmony_ci		if (likely(delta >= 0))
16362306a36Sopenharmony_ci			refcount_add(delta, &skb->sk->sk_wmem_alloc);
16462306a36Sopenharmony_ci		else
16562306a36Sopenharmony_ci			WARN_ON_ONCE(refcount_sub_and_test(-delta, &skb->sk->sk_wmem_alloc));
16662306a36Sopenharmony_ci	}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	delta = (__force __wsum)htonl(oldlen +
16962306a36Sopenharmony_ci				      (skb_tail_pointer(skb) -
17062306a36Sopenharmony_ci				       skb_transport_header(skb)) +
17162306a36Sopenharmony_ci				      skb->data_len);
17262306a36Sopenharmony_ci	th->check = ~csum_fold(csum_add(csum_unfold(th->check), delta));
17362306a36Sopenharmony_ci	if (skb->ip_summed == CHECKSUM_PARTIAL)
17462306a36Sopenharmony_ci		gso_reset_checksum(skb, ~th->check);
17562306a36Sopenharmony_ci	else
17662306a36Sopenharmony_ci		th->check = gso_make_checksum(skb, ~th->check);
17762306a36Sopenharmony_ciout:
17862306a36Sopenharmony_ci	return segs;
17962306a36Sopenharmony_ci}
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_cistruct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb)
18262306a36Sopenharmony_ci{
18362306a36Sopenharmony_ci	struct sk_buff *pp = NULL;
18462306a36Sopenharmony_ci	struct sk_buff *p;
18562306a36Sopenharmony_ci	struct tcphdr *th;
18662306a36Sopenharmony_ci	struct tcphdr *th2;
18762306a36Sopenharmony_ci	unsigned int len;
18862306a36Sopenharmony_ci	unsigned int thlen;
18962306a36Sopenharmony_ci	__be32 flags;
19062306a36Sopenharmony_ci	unsigned int mss = 1;
19162306a36Sopenharmony_ci	unsigned int hlen;
19262306a36Sopenharmony_ci	unsigned int off;
19362306a36Sopenharmony_ci	int flush = 1;
19462306a36Sopenharmony_ci	int i;
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	off = skb_gro_offset(skb);
19762306a36Sopenharmony_ci	hlen = off + sizeof(*th);
19862306a36Sopenharmony_ci	th = skb_gro_header(skb, hlen, off);
19962306a36Sopenharmony_ci	if (unlikely(!th))
20062306a36Sopenharmony_ci		goto out;
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	thlen = th->doff * 4;
20362306a36Sopenharmony_ci	if (thlen < sizeof(*th))
20462306a36Sopenharmony_ci		goto out;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	hlen = off + thlen;
20762306a36Sopenharmony_ci	if (skb_gro_header_hard(skb, hlen)) {
20862306a36Sopenharmony_ci		th = skb_gro_header_slow(skb, hlen, off);
20962306a36Sopenharmony_ci		if (unlikely(!th))
21062306a36Sopenharmony_ci			goto out;
21162306a36Sopenharmony_ci	}
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	skb_gro_pull(skb, thlen);
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	len = skb_gro_len(skb);
21662306a36Sopenharmony_ci	flags = tcp_flag_word(th);
21762306a36Sopenharmony_ci
21862306a36Sopenharmony_ci	list_for_each_entry(p, head, list) {
21962306a36Sopenharmony_ci		if (!NAPI_GRO_CB(p)->same_flow)
22062306a36Sopenharmony_ci			continue;
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci		th2 = tcp_hdr(p);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_ci		if (*(u32 *)&th->source ^ *(u32 *)&th2->source) {
22562306a36Sopenharmony_ci			NAPI_GRO_CB(p)->same_flow = 0;
22662306a36Sopenharmony_ci			continue;
22762306a36Sopenharmony_ci		}
22862306a36Sopenharmony_ci
22962306a36Sopenharmony_ci		goto found;
23062306a36Sopenharmony_ci	}
23162306a36Sopenharmony_ci	p = NULL;
23262306a36Sopenharmony_ci	goto out_check_final;
23362306a36Sopenharmony_ci
23462306a36Sopenharmony_cifound:
23562306a36Sopenharmony_ci	/* Include the IP ID check below from the inner most IP hdr */
23662306a36Sopenharmony_ci	flush = NAPI_GRO_CB(p)->flush;
23762306a36Sopenharmony_ci	flush |= (__force int)(flags & TCP_FLAG_CWR);
23862306a36Sopenharmony_ci	flush |= (__force int)((flags ^ tcp_flag_word(th2)) &
23962306a36Sopenharmony_ci		  ~(TCP_FLAG_CWR | TCP_FLAG_FIN | TCP_FLAG_PSH));
24062306a36Sopenharmony_ci	flush |= (__force int)(th->ack_seq ^ th2->ack_seq);
24162306a36Sopenharmony_ci	for (i = sizeof(*th); i < thlen; i += 4)
24262306a36Sopenharmony_ci		flush |= *(u32 *)((u8 *)th + i) ^
24362306a36Sopenharmony_ci			 *(u32 *)((u8 *)th2 + i);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	/* When we receive our second frame we can made a decision on if we
24662306a36Sopenharmony_ci	 * continue this flow as an atomic flow with a fixed ID or if we use
24762306a36Sopenharmony_ci	 * an incrementing ID.
24862306a36Sopenharmony_ci	 */
24962306a36Sopenharmony_ci	if (NAPI_GRO_CB(p)->flush_id != 1 ||
25062306a36Sopenharmony_ci	    NAPI_GRO_CB(p)->count != 1 ||
25162306a36Sopenharmony_ci	    !NAPI_GRO_CB(p)->is_atomic)
25262306a36Sopenharmony_ci		flush |= NAPI_GRO_CB(p)->flush_id;
25362306a36Sopenharmony_ci	else
25462306a36Sopenharmony_ci		NAPI_GRO_CB(p)->is_atomic = false;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	mss = skb_shinfo(p)->gso_size;
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_ci	/* If skb is a GRO packet, make sure its gso_size matches prior packet mss.
25962306a36Sopenharmony_ci	 * If it is a single frame, do not aggregate it if its length
26062306a36Sopenharmony_ci	 * is bigger than our mss.
26162306a36Sopenharmony_ci	 */
26262306a36Sopenharmony_ci	if (unlikely(skb_is_gso(skb)))
26362306a36Sopenharmony_ci		flush |= (mss != skb_shinfo(skb)->gso_size);
26462306a36Sopenharmony_ci	else
26562306a36Sopenharmony_ci		flush |= (len - 1) >= mss;
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_ci	flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq);
26862306a36Sopenharmony_ci#ifdef CONFIG_TLS_DEVICE
26962306a36Sopenharmony_ci	flush |= p->decrypted ^ skb->decrypted;
27062306a36Sopenharmony_ci#endif
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (flush || skb_gro_receive(p, skb)) {
27362306a36Sopenharmony_ci		mss = 1;
27462306a36Sopenharmony_ci		goto out_check_final;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ciout_check_final:
28062306a36Sopenharmony_ci	/* Force a flush if last segment is smaller than mss. */
28162306a36Sopenharmony_ci	if (unlikely(skb_is_gso(skb)))
28262306a36Sopenharmony_ci		flush = len != NAPI_GRO_CB(skb)->count * skb_shinfo(skb)->gso_size;
28362306a36Sopenharmony_ci	else
28462306a36Sopenharmony_ci		flush = len < mss;
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	flush |= (__force int)(flags & (TCP_FLAG_URG | TCP_FLAG_PSH |
28762306a36Sopenharmony_ci					TCP_FLAG_RST | TCP_FLAG_SYN |
28862306a36Sopenharmony_ci					TCP_FLAG_FIN));
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_ci	if (p && (!NAPI_GRO_CB(skb)->same_flow || flush))
29162306a36Sopenharmony_ci		pp = p;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ciout:
29462306a36Sopenharmony_ci	NAPI_GRO_CB(skb)->flush |= (flush != 0);
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_ci	return pp;
29762306a36Sopenharmony_ci}
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_civoid tcp_gro_complete(struct sk_buff *skb)
30062306a36Sopenharmony_ci{
30162306a36Sopenharmony_ci	struct tcphdr *th = tcp_hdr(skb);
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	skb->csum_start = (unsigned char *)th - skb->head;
30462306a36Sopenharmony_ci	skb->csum_offset = offsetof(struct tcphdr, check);
30562306a36Sopenharmony_ci	skb->ip_summed = CHECKSUM_PARTIAL;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	if (th->cwr)
31062306a36Sopenharmony_ci		skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (skb->encapsulation)
31362306a36Sopenharmony_ci		skb->inner_transport_header = skb->transport_header;
31462306a36Sopenharmony_ci}
31562306a36Sopenharmony_ciEXPORT_SYMBOL(tcp_gro_complete);
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE
31862306a36Sopenharmony_cistruct sk_buff *tcp4_gro_receive(struct list_head *head, struct sk_buff *skb)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	/* Don't bother verifying checksum if we're going to flush anyway. */
32162306a36Sopenharmony_ci	if (!NAPI_GRO_CB(skb)->flush &&
32262306a36Sopenharmony_ci	    skb_gro_checksum_validate(skb, IPPROTO_TCP,
32362306a36Sopenharmony_ci				      inet_gro_compute_pseudo)) {
32462306a36Sopenharmony_ci		NAPI_GRO_CB(skb)->flush = 1;
32562306a36Sopenharmony_ci		return NULL;
32662306a36Sopenharmony_ci	}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	return tcp_gro_receive(head, skb);
32962306a36Sopenharmony_ci}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE int tcp4_gro_complete(struct sk_buff *skb, int thoff)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	const struct iphdr *iph = ip_hdr(skb);
33462306a36Sopenharmony_ci	struct tcphdr *th = tcp_hdr(skb);
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	th->check = ~tcp_v4_check(skb->len - thoff, iph->saddr,
33762306a36Sopenharmony_ci				  iph->daddr, 0);
33862306a36Sopenharmony_ci	skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4;
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	if (NAPI_GRO_CB(skb)->is_atomic)
34162306a36Sopenharmony_ci		skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_FIXEDID;
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_ci	tcp_gro_complete(skb);
34462306a36Sopenharmony_ci	return 0;
34562306a36Sopenharmony_ci}
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic const struct net_offload tcpv4_offload = {
34862306a36Sopenharmony_ci	.callbacks = {
34962306a36Sopenharmony_ci		.gso_segment	=	tcp4_gso_segment,
35062306a36Sopenharmony_ci		.gro_receive	=	tcp4_gro_receive,
35162306a36Sopenharmony_ci		.gro_complete	=	tcp4_gro_complete,
35262306a36Sopenharmony_ci	},
35362306a36Sopenharmony_ci};
35462306a36Sopenharmony_ci
35562306a36Sopenharmony_ciint __init tcpv4_offload_init(void)
35662306a36Sopenharmony_ci{
35762306a36Sopenharmony_ci	return inet_add_offload(&tcpv4_offload, IPPROTO_TCP);
35862306a36Sopenharmony_ci}
359