1 // SPDX-License-Identifier: GPL-2.0-or-later
3 * IPV6 GSO/GRO offload support
4 * Linux INET6 implementation
6 * TCPv6 GSO/GRO support
8 #include <linux/indirect_call_wrapper.h>
9 #include <linux/skbuff.h>
10 #include <net/inet6_hashtables.h>
12 #include <net/protocol.h>
14 #include <net/ip6_checksum.h>
15 #include "ip6_offload.h"
17 static void tcp6_check_fraglist_gro(struct list_head
*head
, struct sk_buff
*skb
,
20 #if IS_ENABLED(CONFIG_IPV6)
21 const struct ipv6hdr
*hdr
;
27 if (likely(!(skb
->dev
->features
& NETIF_F_GRO_FRAGLIST
)))
30 p
= tcp_gro_lookup(head
, th
);
32 NAPI_GRO_CB(skb
)->is_flist
= NAPI_GRO_CB(p
)->is_flist
;
36 inet6_get_iif_sdif(skb
, &iif
, &sdif
);
37 hdr
= skb_gro_network_header(skb
);
38 net
= dev_net(skb
->dev
);
39 sk
= __inet6_lookup_established(net
, net
->ipv4
.tcp_death_row
.hashinfo
,
40 &hdr
->saddr
, th
->source
,
41 &hdr
->daddr
, ntohs(th
->dest
),
43 NAPI_GRO_CB(skb
)->is_flist
= !sk
;
46 #endif /* IS_ENABLED(CONFIG_IPV6) */
49 INDIRECT_CALLABLE_SCOPE
50 struct sk_buff
*tcp6_gro_receive(struct list_head
*head
, struct sk_buff
*skb
)
54 /* Don't bother verifying checksum if we're going to flush anyway. */
55 if (!NAPI_GRO_CB(skb
)->flush
&&
56 skb_gro_checksum_validate(skb
, IPPROTO_TCP
,
57 ip6_gro_compute_pseudo
))
60 th
= tcp_gro_pull_header(skb
);
64 tcp6_check_fraglist_gro(head
, skb
, th
);
66 return tcp_gro_receive(head
, skb
, th
);
69 NAPI_GRO_CB(skb
)->flush
= 1;
73 INDIRECT_CALLABLE_SCOPE
int tcp6_gro_complete(struct sk_buff
*skb
, int thoff
)
75 const u16 offset
= NAPI_GRO_CB(skb
)->network_offsets
[skb
->encapsulation
];
76 const struct ipv6hdr
*iph
= (struct ipv6hdr
*)(skb
->data
+ offset
);
77 struct tcphdr
*th
= tcp_hdr(skb
);
79 if (unlikely(NAPI_GRO_CB(skb
)->is_flist
)) {
80 skb_shinfo(skb
)->gso_type
|= SKB_GSO_FRAGLIST
| SKB_GSO_TCPV6
;
81 skb_shinfo(skb
)->gso_segs
= NAPI_GRO_CB(skb
)->count
;
83 __skb_incr_checksum_unnecessary(skb
);
88 th
->check
= ~tcp_v6_check(skb
->len
- thoff
, &iph
->saddr
,
90 skb_shinfo(skb
)->gso_type
|= SKB_GSO_TCPV6
;
92 tcp_gro_complete(skb
);
96 static void __tcpv6_gso_segment_csum(struct sk_buff
*seg
,
97 __be16
*oldport
, __be16 newport
)
101 if (*oldport
== newport
)
105 inet_proto_csum_replace2(&th
->check
, seg
, *oldport
, newport
, false);
109 static struct sk_buff
*__tcpv6_gso_segment_list_csum(struct sk_buff
*segs
)
111 const struct tcphdr
*th
;
112 const struct ipv6hdr
*iph
;
115 struct ipv6hdr
*iph2
;
120 th2
= tcp_hdr(seg
->next
);
121 iph2
= ipv6_hdr(seg
->next
);
123 if (!(*(const u32
*)&th
->source
^ *(const u32
*)&th2
->source
) &&
124 ipv6_addr_equal(&iph
->saddr
, &iph2
->saddr
) &&
125 ipv6_addr_equal(&iph
->daddr
, &iph2
->daddr
))
128 while ((seg
= seg
->next
)) {
130 iph2
= ipv6_hdr(seg
);
132 iph2
->saddr
= iph
->saddr
;
133 iph2
->daddr
= iph
->daddr
;
134 __tcpv6_gso_segment_csum(seg
, &th2
->source
, th
->source
);
135 __tcpv6_gso_segment_csum(seg
, &th2
->dest
, th
->dest
);
141 static struct sk_buff
*__tcp6_gso_segment_list(struct sk_buff
*skb
,
142 netdev_features_t features
)
144 skb
= skb_segment_list(skb
, features
, skb_mac_header_len(skb
));
148 return __tcpv6_gso_segment_list_csum(skb
);
151 static struct sk_buff
*tcp6_gso_segment(struct sk_buff
*skb
,
152 netdev_features_t features
)
156 if (!(skb_shinfo(skb
)->gso_type
& SKB_GSO_TCPV6
))
157 return ERR_PTR(-EINVAL
);
159 if (!pskb_may_pull(skb
, sizeof(*th
)))
160 return ERR_PTR(-EINVAL
);
162 if (skb_shinfo(skb
)->gso_type
& SKB_GSO_FRAGLIST
) {
163 struct tcphdr
*th
= tcp_hdr(skb
);
165 if (skb_pagelen(skb
) - th
->doff
* 4 == skb_shinfo(skb
)->gso_size
)
166 return __tcp6_gso_segment_list(skb
, features
);
168 skb
->ip_summed
= CHECKSUM_NONE
;
171 if (unlikely(skb
->ip_summed
!= CHECKSUM_PARTIAL
)) {
172 const struct ipv6hdr
*ipv6h
= ipv6_hdr(skb
);
173 struct tcphdr
*th
= tcp_hdr(skb
);
175 /* Set up pseudo header, usually expect stack to have done
180 skb
->ip_summed
= CHECKSUM_PARTIAL
;
181 __tcp_v6_send_check(skb
, &ipv6h
->saddr
, &ipv6h
->daddr
);
184 return tcp_gso_segment(skb
, features
);
187 int __init
tcpv6_offload_init(void)
189 net_hotdata
.tcpv6_offload
= (struct net_offload
) {
191 .gso_segment
= tcp6_gso_segment
,
192 .gro_receive
= tcp6_gro_receive
,
193 .gro_complete
= tcp6_gro_complete
,
196 return inet6_add_offload(&net_hotdata
.tcpv6_offload
, IPPROTO_TCP
);