12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
28663e02aSVlad Yasevich /*
38663e02aSVlad Yasevich * IPV6 GSO/GRO offload support
48663e02aSVlad Yasevich * Linux INET6 implementation
58663e02aSVlad Yasevich *
68663e02aSVlad Yasevich * TCPv6 GSO/GRO support
78663e02aSVlad Yasevich */
8028e0a47SPaolo Abeni #include <linux/indirect_call_wrapper.h>
98663e02aSVlad Yasevich #include <linux/skbuff.h>
104721031cSEric Dumazet #include <net/gro.h>
118663e02aSVlad Yasevich #include <net/protocol.h>
128663e02aSVlad Yasevich #include <net/tcp.h>
138663e02aSVlad Yasevich #include <net/ip6_checksum.h>
148663e02aSVlad Yasevich #include "ip6_offload.h"
158663e02aSVlad Yasevich
16028e0a47SPaolo Abeni INDIRECT_CALLABLE_SCOPE
tcp6_gro_receive(struct list_head * head,struct sk_buff * skb)17028e0a47SPaolo Abeni struct sk_buff *tcp6_gro_receive(struct list_head *head, struct sk_buff *skb)
188663e02aSVlad Yasevich {
19cc5c00bbSHerbert Xu /* Don't bother verifying checksum if we're going to flush anyway. */
20149d0774STom Herbert if (!NAPI_GRO_CB(skb)->flush &&
21149d0774STom Herbert skb_gro_checksum_validate(skb, IPPROTO_TCP,
22149d0774STom Herbert ip6_gro_compute_pseudo)) {
238663e02aSVlad Yasevich NAPI_GRO_CB(skb)->flush = 1;
248663e02aSVlad Yasevich return NULL;
258663e02aSVlad Yasevich }
268663e02aSVlad Yasevich
278663e02aSVlad Yasevich return tcp_gro_receive(head, skb);
288663e02aSVlad Yasevich }
298663e02aSVlad Yasevich
tcp6_gro_complete(struct sk_buff * skb,int thoff)30028e0a47SPaolo Abeni INDIRECT_CALLABLE_SCOPE int tcp6_gro_complete(struct sk_buff *skb, int thoff)
318663e02aSVlad Yasevich {
328663e02aSVlad Yasevich const struct ipv6hdr *iph = ipv6_hdr(skb);
338663e02aSVlad Yasevich struct tcphdr *th = tcp_hdr(skb);
348663e02aSVlad Yasevich
35299603e8SJerry Chu th->check = ~tcp_v6_check(skb->len - thoff, &iph->saddr,
36299603e8SJerry Chu &iph->daddr, 0);
37c3caf119SJerry Chu skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV6;
388663e02aSVlad Yasevich
39*b1f2abcfSParav Pandit tcp_gro_complete(skb);
40*b1f2abcfSParav Pandit return 0;
418663e02aSVlad Yasevich }
428663e02aSVlad Yasevich
tcp6_gso_segment(struct sk_buff * skb,netdev_features_t features)4374abc20cSEric Dumazet static struct sk_buff *tcp6_gso_segment(struct sk_buff *skb,
44d020f8f7STom Herbert netdev_features_t features)
45d020f8f7STom Herbert {
46d020f8f7STom Herbert struct tcphdr *th;
47d020f8f7STom Herbert
48121d57afSWillem de Bruijn if (!(skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6))
49121d57afSWillem de Bruijn return ERR_PTR(-EINVAL);
50121d57afSWillem de Bruijn
51d020f8f7STom Herbert if (!pskb_may_pull(skb, sizeof(*th)))
52d020f8f7STom Herbert return ERR_PTR(-EINVAL);
53d020f8f7STom Herbert
54d020f8f7STom Herbert if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) {
55d020f8f7STom Herbert const struct ipv6hdr *ipv6h = ipv6_hdr(skb);
56d020f8f7STom Herbert struct tcphdr *th = tcp_hdr(skb);
57d020f8f7STom Herbert
58d020f8f7STom Herbert /* Set up pseudo header, usually expect stack to have done
59d020f8f7STom Herbert * this.
60d020f8f7STom Herbert */
61d020f8f7STom Herbert
62d020f8f7STom Herbert th->check = 0;
63d020f8f7STom Herbert skb->ip_summed = CHECKSUM_PARTIAL;
64d020f8f7STom Herbert __tcp_v6_send_check(skb, &ipv6h->saddr, &ipv6h->daddr);
65d020f8f7STom Herbert }
66d020f8f7STom Herbert
67d020f8f7STom Herbert return tcp_gso_segment(skb, features);
68d020f8f7STom Herbert }
698663e02aSVlad Yasevich static const struct net_offload tcpv6_offload = {
70f191a1d1SVlad Yasevich .callbacks = {
71d020f8f7STom Herbert .gso_segment = tcp6_gso_segment,
728663e02aSVlad Yasevich .gro_receive = tcp6_gro_receive,
738663e02aSVlad Yasevich .gro_complete = tcp6_gro_complete,
74f191a1d1SVlad Yasevich },
758663e02aSVlad Yasevich };
768663e02aSVlad Yasevich
tcpv6_offload_init(void)778663e02aSVlad Yasevich int __init tcpv6_offload_init(void)
788663e02aSVlad Yasevich {
798663e02aSVlad Yasevich return inet6_add_offload(&tcpv6_offload, IPPROTO_TCP);
808663e02aSVlad Yasevich }
81