12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 228850dc7SDaniel Borkmann /* 328850dc7SDaniel Borkmann * IPV4 GSO/GRO offload support 428850dc7SDaniel Borkmann * Linux INET implementation 528850dc7SDaniel Borkmann * 628850dc7SDaniel Borkmann * TCPv4 GSO/GRO support 728850dc7SDaniel Borkmann */ 828850dc7SDaniel Borkmann 9028e0a47SPaolo Abeni #include <linux/indirect_call_wrapper.h> 1028850dc7SDaniel Borkmann #include <linux/skbuff.h> 114721031cSEric Dumazet #include <net/gro.h> 12d457a0e3SEric Dumazet #include <net/gso.h> 1328850dc7SDaniel Borkmann #include <net/tcp.h> 1428850dc7SDaniel Borkmann #include <net/protocol.h> 1528850dc7SDaniel Borkmann 16f066e2b0SWillem de Bruijn static void tcp_gso_tstamp(struct sk_buff *skb, unsigned int ts_seq, 17f066e2b0SWillem de Bruijn unsigned int seq, unsigned int mss) 184ed2d765SWillem de Bruijn { 194ed2d765SWillem de Bruijn while (skb) { 20f066e2b0SWillem de Bruijn if (before(ts_seq, seq + mss)) { 21f066e2b0SWillem de Bruijn skb_shinfo(skb)->tx_flags |= SKBTX_SW_TSTAMP; 224ed2d765SWillem de Bruijn skb_shinfo(skb)->tskey = ts_seq; 234ed2d765SWillem de Bruijn return; 244ed2d765SWillem de Bruijn } 254ed2d765SWillem de Bruijn 264ed2d765SWillem de Bruijn skb = skb->next; 274ed2d765SWillem de Bruijn seq += mss; 284ed2d765SWillem de Bruijn } 294ed2d765SWillem de Bruijn } 304ed2d765SWillem de Bruijn 31bee88cd5SFelix Fietkau static void __tcpv4_gso_segment_csum(struct sk_buff *seg, 32bee88cd5SFelix Fietkau __be32 *oldip, __be32 newip, 33bee88cd5SFelix Fietkau __be16 *oldport, __be16 newport) 34bee88cd5SFelix Fietkau { 35bee88cd5SFelix Fietkau struct tcphdr *th; 36bee88cd5SFelix Fietkau struct iphdr *iph; 37bee88cd5SFelix Fietkau 38bee88cd5SFelix Fietkau if (*oldip == newip && *oldport == newport) 39bee88cd5SFelix Fietkau return; 40bee88cd5SFelix Fietkau 41bee88cd5SFelix Fietkau th = tcp_hdr(seg); 42bee88cd5SFelix Fietkau iph = ip_hdr(seg); 43bee88cd5SFelix Fietkau 44bee88cd5SFelix Fietkau inet_proto_csum_replace4(&th->check, seg, *oldip, newip, true); 45bee88cd5SFelix Fietkau inet_proto_csum_replace2(&th->check, seg, *oldport, newport, false); 46bee88cd5SFelix Fietkau *oldport = newport; 47bee88cd5SFelix Fietkau 48bee88cd5SFelix Fietkau csum_replace4(&iph->check, *oldip, newip); 49bee88cd5SFelix Fietkau *oldip = newip; 50bee88cd5SFelix Fietkau } 51bee88cd5SFelix Fietkau 52bee88cd5SFelix Fietkau static struct sk_buff *__tcpv4_gso_segment_list_csum(struct sk_buff *segs) 53bee88cd5SFelix Fietkau { 54bee88cd5SFelix Fietkau const struct tcphdr *th; 55bee88cd5SFelix Fietkau const struct iphdr *iph; 56bee88cd5SFelix Fietkau struct sk_buff *seg; 57bee88cd5SFelix Fietkau struct tcphdr *th2; 58bee88cd5SFelix Fietkau struct iphdr *iph2; 59bee88cd5SFelix Fietkau 60bee88cd5SFelix Fietkau seg = segs; 61bee88cd5SFelix Fietkau th = tcp_hdr(seg); 62bee88cd5SFelix Fietkau iph = ip_hdr(seg); 63bee88cd5SFelix Fietkau th2 = tcp_hdr(seg->next); 64bee88cd5SFelix Fietkau iph2 = ip_hdr(seg->next); 65bee88cd5SFelix Fietkau 66bee88cd5SFelix Fietkau if (!(*(const u32 *)&th->source ^ *(const u32 *)&th2->source) && 67bee88cd5SFelix Fietkau iph->daddr == iph2->daddr && iph->saddr == iph2->saddr) 68bee88cd5SFelix Fietkau return segs; 69bee88cd5SFelix Fietkau 70bee88cd5SFelix Fietkau while ((seg = seg->next)) { 71bee88cd5SFelix Fietkau th2 = tcp_hdr(seg); 72bee88cd5SFelix Fietkau iph2 = ip_hdr(seg); 73bee88cd5SFelix Fietkau 74bee88cd5SFelix Fietkau __tcpv4_gso_segment_csum(seg, 75bee88cd5SFelix Fietkau &iph2->saddr, iph->saddr, 76bee88cd5SFelix Fietkau &th2->source, th->source); 77bee88cd5SFelix Fietkau __tcpv4_gso_segment_csum(seg, 78bee88cd5SFelix Fietkau &iph2->daddr, iph->daddr, 79bee88cd5SFelix Fietkau &th2->dest, th->dest); 80bee88cd5SFelix Fietkau } 81bee88cd5SFelix Fietkau 82bee88cd5SFelix Fietkau return segs; 83bee88cd5SFelix Fietkau } 84bee88cd5SFelix Fietkau 85bee88cd5SFelix Fietkau static struct sk_buff *__tcp4_gso_segment_list(struct sk_buff *skb, 86bee88cd5SFelix Fietkau netdev_features_t features) 87bee88cd5SFelix Fietkau { 88bee88cd5SFelix Fietkau skb = skb_segment_list(skb, features, skb_mac_header_len(skb)); 89bee88cd5SFelix Fietkau if (IS_ERR(skb)) 90bee88cd5SFelix Fietkau return skb; 91bee88cd5SFelix Fietkau 92bee88cd5SFelix Fietkau return __tcpv4_gso_segment_list_csum(skb); 93bee88cd5SFelix Fietkau } 94bee88cd5SFelix Fietkau 9574abc20cSEric Dumazet static struct sk_buff *tcp4_gso_segment(struct sk_buff *skb, 96d020f8f7STom Herbert netdev_features_t features) 97d020f8f7STom Herbert { 98121d57afSWillem de Bruijn if (!(skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4)) 99121d57afSWillem de Bruijn return ERR_PTR(-EINVAL); 100121d57afSWillem de Bruijn 101d020f8f7STom Herbert if (!pskb_may_pull(skb, sizeof(struct tcphdr))) 102d020f8f7STom Herbert return ERR_PTR(-EINVAL); 103d020f8f7STom Herbert 104bee88cd5SFelix Fietkau if (skb_shinfo(skb)->gso_type & SKB_GSO_FRAGLIST) 105bee88cd5SFelix Fietkau return __tcp4_gso_segment_list(skb, features); 106bee88cd5SFelix Fietkau 107d020f8f7STom Herbert if (unlikely(skb->ip_summed != CHECKSUM_PARTIAL)) { 108d020f8f7STom Herbert const struct iphdr *iph = ip_hdr(skb); 109d020f8f7STom Herbert struct tcphdr *th = tcp_hdr(skb); 110d020f8f7STom Herbert 111d020f8f7STom Herbert /* Set up checksum pseudo header, usually expect stack to 112d020f8f7STom Herbert * have done this already. 113d020f8f7STom Herbert */ 114d020f8f7STom Herbert 115d020f8f7STom Herbert th->check = 0; 116d020f8f7STom Herbert skb->ip_summed = CHECKSUM_PARTIAL; 117d020f8f7STom Herbert __tcp_v4_send_check(skb, iph->saddr, iph->daddr); 118d020f8f7STom Herbert } 119d020f8f7STom Herbert 120d020f8f7STom Herbert return tcp_gso_segment(skb, features); 121d020f8f7STom Herbert } 122d020f8f7STom Herbert 12328be6e07SEric Dumazet struct sk_buff *tcp_gso_segment(struct sk_buff *skb, 12428850dc7SDaniel Borkmann netdev_features_t features) 12528850dc7SDaniel Borkmann { 12628850dc7SDaniel Borkmann struct sk_buff *segs = ERR_PTR(-EINVAL); 1270d08c42cSEric Dumazet unsigned int sum_truesize = 0; 12828850dc7SDaniel Borkmann struct tcphdr *th; 12928850dc7SDaniel Borkmann unsigned int thlen; 13028850dc7SDaniel Borkmann unsigned int seq; 13128850dc7SDaniel Borkmann unsigned int oldlen; 13228850dc7SDaniel Borkmann unsigned int mss; 13328850dc7SDaniel Borkmann struct sk_buff *gso_skb = skb; 13428850dc7SDaniel Borkmann __sum16 newcheck; 13528850dc7SDaniel Borkmann bool ooo_okay, copy_destructor; 13682a01ab3SEric Dumazet __wsum delta; 13728850dc7SDaniel Borkmann 13828850dc7SDaniel Borkmann th = tcp_hdr(skb); 13928850dc7SDaniel Borkmann thlen = th->doff * 4; 14028850dc7SDaniel Borkmann if (thlen < sizeof(*th)) 14128850dc7SDaniel Borkmann goto out; 14228850dc7SDaniel Borkmann 14328850dc7SDaniel Borkmann if (!pskb_may_pull(skb, thlen)) 14428850dc7SDaniel Borkmann goto out; 14528850dc7SDaniel Borkmann 14682a01ab3SEric Dumazet oldlen = ~skb->len; 14728850dc7SDaniel Borkmann __skb_pull(skb, thlen); 14828850dc7SDaniel Borkmann 149a7eea416SEric Dumazet mss = skb_shinfo(skb)->gso_size; 15028850dc7SDaniel Borkmann if (unlikely(skb->len <= mss)) 15128850dc7SDaniel Borkmann goto out; 15228850dc7SDaniel Borkmann 15328850dc7SDaniel Borkmann if (skb_gso_ok(skb, features | NETIF_F_GSO_ROBUST)) { 15428850dc7SDaniel Borkmann /* Packet is from an untrusted source, reset gso_segs. */ 15528850dc7SDaniel Borkmann 15628850dc7SDaniel Borkmann skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss); 15728850dc7SDaniel Borkmann 15828850dc7SDaniel Borkmann segs = NULL; 15928850dc7SDaniel Borkmann goto out; 16028850dc7SDaniel Borkmann } 16128850dc7SDaniel Borkmann 16228850dc7SDaniel Borkmann copy_destructor = gso_skb->destructor == tcp_wfree; 16328850dc7SDaniel Borkmann ooo_okay = gso_skb->ooo_okay; 16428850dc7SDaniel Borkmann /* All segments but the first should have ooo_okay cleared */ 16528850dc7SDaniel Borkmann skb->ooo_okay = 0; 16628850dc7SDaniel Borkmann 16728850dc7SDaniel Borkmann segs = skb_segment(skb, features); 16828850dc7SDaniel Borkmann if (IS_ERR(segs)) 16928850dc7SDaniel Borkmann goto out; 17028850dc7SDaniel Borkmann 17128850dc7SDaniel Borkmann /* Only first segment might have ooo_okay set */ 17228850dc7SDaniel Borkmann segs->ooo_okay = ooo_okay; 17328850dc7SDaniel Borkmann 17407b26c94SSteffen Klassert /* GSO partial and frag_list segmentation only requires splitting 17507b26c94SSteffen Klassert * the frame into an MSS multiple and possibly a remainder, both 17607b26c94SSteffen Klassert * cases return a GSO skb. So update the mss now. 17707b26c94SSteffen Klassert */ 17807b26c94SSteffen Klassert if (skb_is_gso(segs)) 17907b26c94SSteffen Klassert mss *= skb_shinfo(segs)->gso_segs; 18007b26c94SSteffen Klassert 18182a01ab3SEric Dumazet delta = (__force __wsum)htonl(oldlen + thlen + mss); 18228850dc7SDaniel Borkmann 18328850dc7SDaniel Borkmann skb = segs; 18428850dc7SDaniel Borkmann th = tcp_hdr(skb); 18528850dc7SDaniel Borkmann seq = ntohl(th->seq); 18628850dc7SDaniel Borkmann 1874ed2d765SWillem de Bruijn if (unlikely(skb_shinfo(gso_skb)->tx_flags & SKBTX_SW_TSTAMP)) 1884ed2d765SWillem de Bruijn tcp_gso_tstamp(segs, skb_shinfo(gso_skb)->tskey, seq, mss); 1894ed2d765SWillem de Bruijn 19082a01ab3SEric Dumazet newcheck = ~csum_fold(csum_add(csum_unfold(th->check), delta)); 19128850dc7SDaniel Borkmann 192802ab55aSAlexander Duyck while (skb->next) { 19328850dc7SDaniel Borkmann th->fin = th->psh = 0; 19428850dc7SDaniel Borkmann th->check = newcheck; 19528850dc7SDaniel Borkmann 19608b64fccSAlexander Duyck if (skb->ip_summed == CHECKSUM_PARTIAL) 19708b64fccSAlexander Duyck gso_reset_checksum(skb, ~th->check); 19808b64fccSAlexander Duyck else 199e9c3a24bSTom Herbert th->check = gso_make_checksum(skb, ~th->check); 20028850dc7SDaniel Borkmann 20128850dc7SDaniel Borkmann seq += mss; 20228850dc7SDaniel Borkmann if (copy_destructor) { 20328850dc7SDaniel Borkmann skb->destructor = gso_skb->destructor; 20428850dc7SDaniel Borkmann skb->sk = gso_skb->sk; 2050d08c42cSEric Dumazet sum_truesize += skb->truesize; 20628850dc7SDaniel Borkmann } 20728850dc7SDaniel Borkmann skb = skb->next; 20828850dc7SDaniel Borkmann th = tcp_hdr(skb); 20928850dc7SDaniel Borkmann 21028850dc7SDaniel Borkmann th->seq = htonl(seq); 21128850dc7SDaniel Borkmann th->cwr = 0; 212802ab55aSAlexander Duyck } 21328850dc7SDaniel Borkmann 21428850dc7SDaniel Borkmann /* Following permits TCP Small Queues to work well with GSO : 21528850dc7SDaniel Borkmann * The callback to TCP stack will be called at the time last frag 21628850dc7SDaniel Borkmann * is freed at TX completion, and not right now when gso_skb 21728850dc7SDaniel Borkmann * is freed by GSO engine 21828850dc7SDaniel Borkmann */ 21928850dc7SDaniel Borkmann if (copy_destructor) { 2207ec318feSEric Dumazet int delta; 2217ec318feSEric Dumazet 22228850dc7SDaniel Borkmann swap(gso_skb->sk, skb->sk); 22328850dc7SDaniel Borkmann swap(gso_skb->destructor, skb->destructor); 2240d08c42cSEric Dumazet sum_truesize += skb->truesize; 2257ec318feSEric Dumazet delta = sum_truesize - gso_skb->truesize; 2267ec318feSEric Dumazet /* In some pathological cases, delta can be negative. 2277ec318feSEric Dumazet * We need to either use refcount_add() or refcount_sub_and_test() 2287ec318feSEric Dumazet */ 2297ec318feSEric Dumazet if (likely(delta >= 0)) 2307ec318feSEric Dumazet refcount_add(delta, &skb->sk->sk_wmem_alloc); 2317ec318feSEric Dumazet else 2327ec318feSEric Dumazet WARN_ON_ONCE(refcount_sub_and_test(-delta, &skb->sk->sk_wmem_alloc)); 23328850dc7SDaniel Borkmann } 23428850dc7SDaniel Borkmann 23582a01ab3SEric Dumazet delta = (__force __wsum)htonl(oldlen + 23682a01ab3SEric Dumazet (skb_tail_pointer(skb) - 23728850dc7SDaniel Borkmann skb_transport_header(skb)) + 23828850dc7SDaniel Borkmann skb->data_len); 23982a01ab3SEric Dumazet th->check = ~csum_fold(csum_add(csum_unfold(th->check), delta)); 24008b64fccSAlexander Duyck if (skb->ip_summed == CHECKSUM_PARTIAL) 24108b64fccSAlexander Duyck gso_reset_checksum(skb, ~th->check); 24208b64fccSAlexander Duyck else 243e9c3a24bSTom Herbert th->check = gso_make_checksum(skb, ~th->check); 24428850dc7SDaniel Borkmann out: 24528850dc7SDaniel Borkmann return segs; 24628850dc7SDaniel Borkmann } 24728850dc7SDaniel Borkmann 24880e85fbdSFelix Fietkau struct sk_buff *tcp_gro_lookup(struct list_head *head, struct tcphdr *th) 24980e85fbdSFelix Fietkau { 25080e85fbdSFelix Fietkau struct tcphdr *th2; 25180e85fbdSFelix Fietkau struct sk_buff *p; 25280e85fbdSFelix Fietkau 25380e85fbdSFelix Fietkau list_for_each_entry(p, head, list) { 25480e85fbdSFelix Fietkau if (!NAPI_GRO_CB(p)->same_flow) 25580e85fbdSFelix Fietkau continue; 25680e85fbdSFelix Fietkau 25780e85fbdSFelix Fietkau th2 = tcp_hdr(p); 25880e85fbdSFelix Fietkau if (*(u32 *)&th->source ^ *(u32 *)&th2->source) { 25980e85fbdSFelix Fietkau NAPI_GRO_CB(p)->same_flow = 0; 26080e85fbdSFelix Fietkau continue; 26180e85fbdSFelix Fietkau } 26280e85fbdSFelix Fietkau 26380e85fbdSFelix Fietkau return p; 26480e85fbdSFelix Fietkau } 26580e85fbdSFelix Fietkau 26680e85fbdSFelix Fietkau return NULL; 26780e85fbdSFelix Fietkau } 26880e85fbdSFelix Fietkau 2697516b27cSFelix Fietkau struct tcphdr *tcp_gro_pull_header(struct sk_buff *skb) 27028850dc7SDaniel Borkmann { 2717516b27cSFelix Fietkau unsigned int thlen, hlen, off; 27228850dc7SDaniel Borkmann struct tcphdr *th; 27328850dc7SDaniel Borkmann 27428850dc7SDaniel Borkmann off = skb_gro_offset(skb); 27528850dc7SDaniel Borkmann hlen = off + sizeof(*th); 27635ffb665SRichard Gobert th = skb_gro_header(skb, hlen, off); 27728850dc7SDaniel Borkmann if (unlikely(!th)) 2787516b27cSFelix Fietkau return NULL; 27928850dc7SDaniel Borkmann 28028850dc7SDaniel Borkmann thlen = th->doff * 4; 28128850dc7SDaniel Borkmann if (thlen < sizeof(*th)) 2827516b27cSFelix Fietkau return NULL; 28328850dc7SDaniel Borkmann 28428850dc7SDaniel Borkmann hlen = off + thlen; 28593e16ea0SEric Dumazet if (!skb_gro_may_pull(skb, hlen)) { 28628850dc7SDaniel Borkmann th = skb_gro_header_slow(skb, hlen, off); 28728850dc7SDaniel Borkmann if (unlikely(!th)) 2887516b27cSFelix Fietkau return NULL; 28928850dc7SDaniel Borkmann } 29028850dc7SDaniel Borkmann 29128850dc7SDaniel Borkmann skb_gro_pull(skb, thlen); 29228850dc7SDaniel Borkmann 2937516b27cSFelix Fietkau return th; 2947516b27cSFelix Fietkau } 2957516b27cSFelix Fietkau 2967516b27cSFelix Fietkau struct sk_buff *tcp_gro_receive(struct list_head *head, struct sk_buff *skb, 2977516b27cSFelix Fietkau struct tcphdr *th) 2987516b27cSFelix Fietkau { 2997516b27cSFelix Fietkau unsigned int thlen = th->doff * 4; 3007516b27cSFelix Fietkau struct sk_buff *pp = NULL; 3017516b27cSFelix Fietkau struct sk_buff *p; 3027516b27cSFelix Fietkau struct tcphdr *th2; 3037516b27cSFelix Fietkau unsigned int len; 3047516b27cSFelix Fietkau __be32 flags; 3057516b27cSFelix Fietkau unsigned int mss = 1; 3067516b27cSFelix Fietkau int flush = 1; 3077516b27cSFelix Fietkau int i; 3087516b27cSFelix Fietkau 30928850dc7SDaniel Borkmann len = skb_gro_len(skb); 31028850dc7SDaniel Borkmann flags = tcp_flag_word(th); 31128850dc7SDaniel Borkmann 31280e85fbdSFelix Fietkau p = tcp_gro_lookup(head, th); 31380e85fbdSFelix Fietkau if (!p) 31428850dc7SDaniel Borkmann goto out_check_final; 31528850dc7SDaniel Borkmann 316bf5a755fSJerry Chu /* Include the IP ID check below from the inner most IP hdr */ 31780e85fbdSFelix Fietkau th2 = tcp_hdr(p); 3181530545eSAlexander Duyck flush = NAPI_GRO_CB(p)->flush; 31928850dc7SDaniel Borkmann flush |= (__force int)(flags & TCP_FLAG_CWR); 32028850dc7SDaniel Borkmann flush |= (__force int)((flags ^ tcp_flag_word(th2)) & 32128850dc7SDaniel Borkmann ~(TCP_FLAG_CWR | TCP_FLAG_FIN | TCP_FLAG_PSH)); 32228850dc7SDaniel Borkmann flush |= (__force int)(th->ack_seq ^ th2->ack_seq); 32328850dc7SDaniel Borkmann for (i = sizeof(*th); i < thlen; i += 4) 32428850dc7SDaniel Borkmann flush |= *(u32 *)((u8 *)th + i) ^ 32528850dc7SDaniel Borkmann *(u32 *)((u8 *)th2 + i); 32628850dc7SDaniel Borkmann 3271530545eSAlexander Duyck /* When we receive our second frame we can made a decision on if we 3281530545eSAlexander Duyck * continue this flow as an atomic flow with a fixed ID or if we use 3291530545eSAlexander Duyck * an incrementing ID. 3301530545eSAlexander Duyck */ 3311530545eSAlexander Duyck if (NAPI_GRO_CB(p)->flush_id != 1 || 3321530545eSAlexander Duyck NAPI_GRO_CB(p)->count != 1 || 3331530545eSAlexander Duyck !NAPI_GRO_CB(p)->is_atomic) 3341530545eSAlexander Duyck flush |= NAPI_GRO_CB(p)->flush_id; 3351530545eSAlexander Duyck else 3361530545eSAlexander Duyck NAPI_GRO_CB(p)->is_atomic = false; 3371530545eSAlexander Duyck 338a7eea416SEric Dumazet mss = skb_shinfo(p)->gso_size; 33928850dc7SDaniel Borkmann 3405eddb249SCoco Li /* If skb is a GRO packet, make sure its gso_size matches prior packet mss. 3415eddb249SCoco Li * If it is a single frame, do not aggregate it if its length 3425eddb249SCoco Li * is bigger than our mss. 3435eddb249SCoco Li */ 3445eddb249SCoco Li if (unlikely(skb_is_gso(skb))) 3455eddb249SCoco Li flush |= (mss != skb_shinfo(skb)->gso_size); 3465eddb249SCoco Li else 34728850dc7SDaniel Borkmann flush |= (len - 1) >= mss; 3485eddb249SCoco Li 34928850dc7SDaniel Borkmann flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq); 3509f06f87fSJakub Kicinski flush |= skb_cmp_decrypted(p, skb); 35128850dc7SDaniel Borkmann 3528d95dc47SFelix Fietkau if (unlikely(NAPI_GRO_CB(p)->is_flist)) { 3538d95dc47SFelix Fietkau flush |= (__force int)(flags ^ tcp_flag_word(th2)); 3548d95dc47SFelix Fietkau flush |= skb->ip_summed != p->ip_summed; 3558d95dc47SFelix Fietkau flush |= skb->csum_level != p->csum_level; 3568d95dc47SFelix Fietkau flush |= NAPI_GRO_CB(p)->count >= 64; 3578d95dc47SFelix Fietkau 3588d95dc47SFelix Fietkau if (flush || skb_gro_receive_list(p, skb)) 3598d95dc47SFelix Fietkau mss = 1; 3608d95dc47SFelix Fietkau 3618d95dc47SFelix Fietkau goto out_check_final; 3628d95dc47SFelix Fietkau } 3638d95dc47SFelix Fietkau 364d4546c25SDavid Miller if (flush || skb_gro_receive(p, skb)) { 36528850dc7SDaniel Borkmann mss = 1; 36628850dc7SDaniel Borkmann goto out_check_final; 36728850dc7SDaniel Borkmann } 36828850dc7SDaniel Borkmann 36928850dc7SDaniel Borkmann tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH); 37028850dc7SDaniel Borkmann 37128850dc7SDaniel Borkmann out_check_final: 3725eddb249SCoco Li /* Force a flush if last segment is smaller than mss. */ 3735eddb249SCoco Li if (unlikely(skb_is_gso(skb))) 3745eddb249SCoco Li flush = len != NAPI_GRO_CB(skb)->count * skb_shinfo(skb)->gso_size; 3755eddb249SCoco Li else 37628850dc7SDaniel Borkmann flush = len < mss; 3775eddb249SCoco Li 37828850dc7SDaniel Borkmann flush |= (__force int)(flags & (TCP_FLAG_URG | TCP_FLAG_PSH | 37928850dc7SDaniel Borkmann TCP_FLAG_RST | TCP_FLAG_SYN | 38028850dc7SDaniel Borkmann TCP_FLAG_FIN)); 38128850dc7SDaniel Borkmann 38228850dc7SDaniel Borkmann if (p && (!NAPI_GRO_CB(skb)->same_flow || flush)) 383d4546c25SDavid Miller pp = p; 38428850dc7SDaniel Borkmann 385bf5a755fSJerry Chu NAPI_GRO_CB(skb)->flush |= (flush != 0); 38628850dc7SDaniel Borkmann 38728850dc7SDaniel Borkmann return pp; 38828850dc7SDaniel Borkmann } 38928850dc7SDaniel Borkmann 390b1f2abcfSParav Pandit void tcp_gro_complete(struct sk_buff *skb) 39128850dc7SDaniel Borkmann { 39228850dc7SDaniel Borkmann struct tcphdr *th = tcp_hdr(skb); 3938f78010bSEric Dumazet struct skb_shared_info *shinfo; 3948f78010bSEric Dumazet 3958f78010bSEric Dumazet if (skb->encapsulation) 3968f78010bSEric Dumazet skb->inner_transport_header = skb->transport_header; 39728850dc7SDaniel Borkmann 398299603e8SJerry Chu skb->csum_start = (unsigned char *)th - skb->head; 39928850dc7SDaniel Borkmann skb->csum_offset = offsetof(struct tcphdr, check); 40028850dc7SDaniel Borkmann skb->ip_summed = CHECKSUM_PARTIAL; 40128850dc7SDaniel Borkmann 4028f78010bSEric Dumazet shinfo = skb_shinfo(skb); 4038f78010bSEric Dumazet shinfo->gso_segs = NAPI_GRO_CB(skb)->count; 40428850dc7SDaniel Borkmann 40528850dc7SDaniel Borkmann if (th->cwr) 4068f78010bSEric Dumazet shinfo->gso_type |= SKB_GSO_TCP_ECN; 40728850dc7SDaniel Borkmann } 40828850dc7SDaniel Borkmann EXPORT_SYMBOL(tcp_gro_complete); 40928850dc7SDaniel Borkmann 410c9d1d23eSFelix Fietkau static void tcp4_check_fraglist_gro(struct list_head *head, struct sk_buff *skb, 411c9d1d23eSFelix Fietkau struct tcphdr *th) 412c9d1d23eSFelix Fietkau { 413c9d1d23eSFelix Fietkau const struct iphdr *iph; 414c9d1d23eSFelix Fietkau struct sk_buff *p; 415c9d1d23eSFelix Fietkau struct sock *sk; 416c9d1d23eSFelix Fietkau struct net *net; 417c9d1d23eSFelix Fietkau int iif, sdif; 418c9d1d23eSFelix Fietkau 419c9d1d23eSFelix Fietkau if (likely(!(skb->dev->features & NETIF_F_GRO_FRAGLIST))) 420c9d1d23eSFelix Fietkau return; 421c9d1d23eSFelix Fietkau 422c9d1d23eSFelix Fietkau p = tcp_gro_lookup(head, th); 423c9d1d23eSFelix Fietkau if (p) { 424c9d1d23eSFelix Fietkau NAPI_GRO_CB(skb)->is_flist = NAPI_GRO_CB(p)->is_flist; 425c9d1d23eSFelix Fietkau return; 426c9d1d23eSFelix Fietkau } 427c9d1d23eSFelix Fietkau 428c9d1d23eSFelix Fietkau inet_get_iif_sdif(skb, &iif, &sdif); 429c9d1d23eSFelix Fietkau iph = skb_gro_network_header(skb); 430c9d1d23eSFelix Fietkau net = dev_net(skb->dev); 431c9d1d23eSFelix Fietkau sk = __inet_lookup_established(net, net->ipv4.tcp_death_row.hashinfo, 432c9d1d23eSFelix Fietkau iph->saddr, th->source, 433c9d1d23eSFelix Fietkau iph->daddr, ntohs(th->dest), 434c9d1d23eSFelix Fietkau iif, sdif); 435c9d1d23eSFelix Fietkau NAPI_GRO_CB(skb)->is_flist = !sk; 436c9d1d23eSFelix Fietkau if (sk) 437c9d1d23eSFelix Fietkau sock_put(sk); 438c9d1d23eSFelix Fietkau } 439c9d1d23eSFelix Fietkau 440028e0a47SPaolo Abeni INDIRECT_CALLABLE_SCOPE 441028e0a47SPaolo Abeni struct sk_buff *tcp4_gro_receive(struct list_head *head, struct sk_buff *skb) 44228850dc7SDaniel Borkmann { 4437516b27cSFelix Fietkau struct tcphdr *th; 4447516b27cSFelix Fietkau 445cc5c00bbSHerbert Xu /* Don't bother verifying checksum if we're going to flush anyway. */ 446149d0774STom Herbert if (!NAPI_GRO_CB(skb)->flush && 447149d0774STom Herbert skb_gro_checksum_validate(skb, IPPROTO_TCP, 4487516b27cSFelix Fietkau inet_gro_compute_pseudo)) 4497516b27cSFelix Fietkau goto flush; 4507516b27cSFelix Fietkau 4517516b27cSFelix Fietkau th = tcp_gro_pull_header(skb); 4527516b27cSFelix Fietkau if (!th) 4537516b27cSFelix Fietkau goto flush; 4547516b27cSFelix Fietkau 455c9d1d23eSFelix Fietkau tcp4_check_fraglist_gro(head, skb, th); 456c9d1d23eSFelix Fietkau 4577516b27cSFelix Fietkau return tcp_gro_receive(head, skb, th); 4587516b27cSFelix Fietkau 4597516b27cSFelix Fietkau flush: 46028850dc7SDaniel Borkmann NAPI_GRO_CB(skb)->flush = 1; 46128850dc7SDaniel Borkmann return NULL; 46228850dc7SDaniel Borkmann } 46328850dc7SDaniel Borkmann 464028e0a47SPaolo Abeni INDIRECT_CALLABLE_SCOPE int tcp4_gro_complete(struct sk_buff *skb, int thoff) 46528850dc7SDaniel Borkmann { 466*186b1ea7SRichard Gobert const u16 offset = NAPI_GRO_CB(skb)->network_offsets[skb->encapsulation]; 467*186b1ea7SRichard Gobert const struct iphdr *iph = (struct iphdr *)(skb->data + offset); 46828850dc7SDaniel Borkmann struct tcphdr *th = tcp_hdr(skb); 46928850dc7SDaniel Borkmann 4708d95dc47SFelix Fietkau if (unlikely(NAPI_GRO_CB(skb)->is_flist)) { 4718d95dc47SFelix Fietkau skb_shinfo(skb)->gso_type |= SKB_GSO_FRAGLIST | SKB_GSO_TCPV4; 4728d95dc47SFelix Fietkau skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; 4738d95dc47SFelix Fietkau 4748d95dc47SFelix Fietkau __skb_incr_checksum_unnecessary(skb); 4758d95dc47SFelix Fietkau 4768d95dc47SFelix Fietkau return 0; 4778d95dc47SFelix Fietkau } 4788d95dc47SFelix Fietkau 479299603e8SJerry Chu th->check = ~tcp_v4_check(skb->len - thoff, iph->saddr, 480299603e8SJerry Chu iph->daddr, 0); 48128850dc7SDaniel Borkmann 4828f78010bSEric Dumazet skb_shinfo(skb)->gso_type |= SKB_GSO_TCPV4 | 4838f78010bSEric Dumazet (NAPI_GRO_CB(skb)->is_atomic * SKB_GSO_TCP_FIXEDID); 4841530545eSAlexander Duyck 485b1f2abcfSParav Pandit tcp_gro_complete(skb); 486b1f2abcfSParav Pandit return 0; 48728850dc7SDaniel Borkmann } 48828850dc7SDaniel Borkmann 4890139806eSEric Dumazet int __init tcpv4_offload_init(void) 4900139806eSEric Dumazet { 4910139806eSEric Dumazet net_hotdata.tcpv4_offload = (struct net_offload) { 49228850dc7SDaniel Borkmann .callbacks = { 493d020f8f7STom Herbert .gso_segment = tcp4_gso_segment, 49428850dc7SDaniel Borkmann .gro_receive = tcp4_gro_receive, 49528850dc7SDaniel Borkmann .gro_complete = tcp4_gro_complete, 49628850dc7SDaniel Borkmann }, 49728850dc7SDaniel Borkmann }; 4980139806eSEric Dumazet return inet_add_offload(&net_hotdata.tcpv4_offload, IPPROTO_TCP); 49928850dc7SDaniel Borkmann } 500