162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *	IPV6 GSO/GRO offload support
462306a36Sopenharmony_ci *	Linux INET6 implementation
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci *      TCPv6 GSO/GRO support
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci#include <linux/indirect_call_wrapper.h>
962306a36Sopenharmony_ci#include <linux/skbuff.h>
1062306a36Sopenharmony_ci#include <net/gro.h>
1162306a36Sopenharmony_ci#include <net/protocol.h>
1262306a36Sopenharmony_ci#include <net/tcp.h>
1362306a36Sopenharmony_ci#include <net/ip6_checksum.h>
1462306a36Sopenharmony_ci#include "ip6_offload.h"
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE
1762306a36Sopenharmony_cistruct sk_buff *tcp6_gro_receive(struct list_head *head, struct sk_buff *skb)
1862306a36Sopenharmony_ci{
1962306a36Sopenharmony_ci	/* Don't bother verifying checksum if we're going to flush anyway. */
2062306a36Sopenharmony_ci	if (!NAPI_GRO_CB(skb)->flush &&
2162306a36Sopenharmony_ci	    skb_gro_checksum_validate(skb, IPPROTO_TCP,
2262306a36Sopenharmony_ci				      ip6_gro_compute_pseudo)) {
2362306a36Sopenharmony_ci		NAPI_GRO_CB(skb)->flush = 1;
2462306a36Sopenharmony_ci		return NULL;
2562306a36Sopenharmony_ci	}
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	return tcp_gro_receive(head, skb);
2862306a36Sopenharmony_ci}
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE int tcp6_gro_complete(struct sk_buff *skb, int thoff)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	const struct ipv6hdr *iph = ipv6_hdr(skb);
3362306a36Sopenharmony_ci	struct tcphdr *th = tcp_hdr(skb);
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	th->check = ~tcp_v6_check(skb->len - thoff, &iph->saddr,
3662306a36Sopenharmony_ci				  &iph->daddr, 0);
3762306a36Sopenharmony_ci	skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6;
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	tcp_gro_complete(skb);
4062306a36Sopenharmony_ci	return 0;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic struct sk_buff *tcp6_gso_segment(struct sk_buff *skb,
4462306a36Sopenharmony_ci					netdev_features_t features)
4562306a36Sopenharmony_ci{
4662306a36Sopenharmony_ci	struct tcphdr *th;
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci	if (!(skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6))
4962306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	if (!pskb_may_pull(skb, sizeof(*th)))
5262306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci	if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
5562306a36Sopenharmony_ci		const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
5662306a36Sopenharmony_ci		struct tcphdr *th = tcp_hdr(skb);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci		/* Set up pseudo header, usually expect stack to have done
5962306a36Sopenharmony_ci		 * this.
6062306a36Sopenharmony_ci		 */
6162306a36Sopenharmony_ci
6262306a36Sopenharmony_ci		th->check = 0;
6362306a36Sopenharmony_ci		skb->ip_summed = CHECKSUM_PARTIAL;
6462306a36Sopenharmony_ci		__tcp_v6_send_check(skb, &ipv6h->saddr, &ipv6h->daddr);
6562306a36Sopenharmony_ci	}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	return tcp_gso_segment(skb, features);
6862306a36Sopenharmony_ci}
6962306a36Sopenharmony_cistatic const struct net_offload tcpv6_offload = {
7062306a36Sopenharmony_ci	.callbacks = {
7162306a36Sopenharmony_ci		.gso_segment	=	tcp6_gso_segment,
7262306a36Sopenharmony_ci		.gro_receive	=	tcp6_gro_receive,
7362306a36Sopenharmony_ci		.gro_complete	=	tcp6_gro_complete,
7462306a36Sopenharmony_ci	},
7562306a36Sopenharmony_ci};
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ciint __init tcpv6_offload_init(void)
7862306a36Sopenharmony_ci{
7962306a36Sopenharmony_ci	return inet6_add_offload(&tcpv6_offload, IPPROTO_TCP);
8062306a36Sopenharmony_ci}
81