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