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 * UDPv4 GSO support 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci 962306a36Sopenharmony_ci#include <linux/skbuff.h> 1062306a36Sopenharmony_ci#include <net/gro.h> 1162306a36Sopenharmony_ci#include <net/gso.h> 1262306a36Sopenharmony_ci#include <net/udp.h> 1362306a36Sopenharmony_ci#include <net/protocol.h> 1462306a36Sopenharmony_ci#include <net/inet_common.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistatic struct sk_buff *__skb_udp_tunnel_segment(struct sk_buff *skb, 1762306a36Sopenharmony_ci netdev_features_t features, 1862306a36Sopenharmony_ci struct sk_buff *(*gso_inner_segment)(struct sk_buff *skb, 1962306a36Sopenharmony_ci netdev_features_t features), 2062306a36Sopenharmony_ci __be16 new_protocol, bool is_ipv6) 2162306a36Sopenharmony_ci{ 2262306a36Sopenharmony_ci int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb); 2362306a36Sopenharmony_ci bool remcsum, need_csum, offload_csum, gso_partial; 2462306a36Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 2562306a36Sopenharmony_ci struct udphdr *uh = udp_hdr(skb); 2662306a36Sopenharmony_ci u16 mac_offset = skb->mac_header; 2762306a36Sopenharmony_ci __be16 protocol = skb->protocol; 2862306a36Sopenharmony_ci u16 mac_len = skb->mac_len; 2962306a36Sopenharmony_ci int udp_offset, outer_hlen; 3062306a36Sopenharmony_ci __wsum partial; 3162306a36Sopenharmony_ci bool need_ipsec; 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci if (unlikely(!pskb_may_pull(skb, tnl_hlen))) 3462306a36Sopenharmony_ci goto out; 3562306a36Sopenharmony_ci 3662306a36Sopenharmony_ci /* Adjust partial header checksum to negate old length. 3762306a36Sopenharmony_ci * We cannot rely on the value contained in uh->len as it is 3862306a36Sopenharmony_ci * possible that the actual value exceeds the boundaries of the 3962306a36Sopenharmony_ci * 16 bit length field due to the header being added outside of an 4062306a36Sopenharmony_ci * IP or IPv6 frame that was already limited to 64K - 1. 4162306a36Sopenharmony_ci */ 4262306a36Sopenharmony_ci if (skb_shinfo(skb)->gso_type & SKB_GSO_PARTIAL) 4362306a36Sopenharmony_ci partial = (__force __wsum)uh->len; 4462306a36Sopenharmony_ci else 4562306a36Sopenharmony_ci partial = (__force __wsum)htonl(skb->len); 4662306a36Sopenharmony_ci partial = csum_sub(csum_unfold(uh->check), partial); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci /* setup inner skb. */ 4962306a36Sopenharmony_ci skb->encapsulation = 0; 5062306a36Sopenharmony_ci SKB_GSO_CB(skb)->encap_level = 0; 5162306a36Sopenharmony_ci __skb_pull(skb, tnl_hlen); 5262306a36Sopenharmony_ci skb_reset_mac_header(skb); 5362306a36Sopenharmony_ci skb_set_network_header(skb, skb_inner_network_offset(skb)); 5462306a36Sopenharmony_ci skb_set_transport_header(skb, skb_inner_transport_offset(skb)); 5562306a36Sopenharmony_ci skb->mac_len = skb_inner_network_offset(skb); 5662306a36Sopenharmony_ci skb->protocol = new_protocol; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci need_csum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_UDP_TUNNEL_CSUM); 5962306a36Sopenharmony_ci skb->encap_hdr_csum = need_csum; 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci remcsum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_TUNNEL_REMCSUM); 6262306a36Sopenharmony_ci skb->remcsum_offload = remcsum; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci need_ipsec = skb_dst(skb) && dst_xfrm(skb_dst(skb)); 6562306a36Sopenharmony_ci /* Try to offload checksum if possible */ 6662306a36Sopenharmony_ci offload_csum = !!(need_csum && 6762306a36Sopenharmony_ci !need_ipsec && 6862306a36Sopenharmony_ci (skb->dev->features & 6962306a36Sopenharmony_ci (is_ipv6 ? (NETIF_F_HW_CSUM | NETIF_F_IPV6_CSUM) : 7062306a36Sopenharmony_ci (NETIF_F_HW_CSUM | NETIF_F_IP_CSUM)))); 7162306a36Sopenharmony_ci 7262306a36Sopenharmony_ci features &= skb->dev->hw_enc_features; 7362306a36Sopenharmony_ci if (need_csum) 7462306a36Sopenharmony_ci features &= ~NETIF_F_SCTP_CRC; 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci /* The only checksum offload we care about from here on out is the 7762306a36Sopenharmony_ci * outer one so strip the existing checksum feature flags and 7862306a36Sopenharmony_ci * instead set the flag based on our outer checksum offload value. 7962306a36Sopenharmony_ci */ 8062306a36Sopenharmony_ci if (remcsum) { 8162306a36Sopenharmony_ci features &= ~NETIF_F_CSUM_MASK; 8262306a36Sopenharmony_ci if (!need_csum || offload_csum) 8362306a36Sopenharmony_ci features |= NETIF_F_HW_CSUM; 8462306a36Sopenharmony_ci } 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci /* segment inner packet. */ 8762306a36Sopenharmony_ci segs = gso_inner_segment(skb, features); 8862306a36Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) { 8962306a36Sopenharmony_ci skb_gso_error_unwind(skb, protocol, tnl_hlen, mac_offset, 9062306a36Sopenharmony_ci mac_len); 9162306a36Sopenharmony_ci goto out; 9262306a36Sopenharmony_ci } 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL); 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_ci outer_hlen = skb_tnl_header_len(skb); 9762306a36Sopenharmony_ci udp_offset = outer_hlen - tnl_hlen; 9862306a36Sopenharmony_ci skb = segs; 9962306a36Sopenharmony_ci do { 10062306a36Sopenharmony_ci unsigned int len; 10162306a36Sopenharmony_ci 10262306a36Sopenharmony_ci if (remcsum) 10362306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_NONE; 10462306a36Sopenharmony_ci 10562306a36Sopenharmony_ci /* Set up inner headers if we are offloading inner checksum */ 10662306a36Sopenharmony_ci if (skb->ip_summed == CHECKSUM_PARTIAL) { 10762306a36Sopenharmony_ci skb_reset_inner_headers(skb); 10862306a36Sopenharmony_ci skb->encapsulation = 1; 10962306a36Sopenharmony_ci } 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_ci skb->mac_len = mac_len; 11262306a36Sopenharmony_ci skb->protocol = protocol; 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_ci __skb_push(skb, outer_hlen); 11562306a36Sopenharmony_ci skb_reset_mac_header(skb); 11662306a36Sopenharmony_ci skb_set_network_header(skb, mac_len); 11762306a36Sopenharmony_ci skb_set_transport_header(skb, udp_offset); 11862306a36Sopenharmony_ci len = skb->len - udp_offset; 11962306a36Sopenharmony_ci uh = udp_hdr(skb); 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci /* If we are only performing partial GSO the inner header 12262306a36Sopenharmony_ci * will be using a length value equal to only one MSS sized 12362306a36Sopenharmony_ci * segment instead of the entire frame. 12462306a36Sopenharmony_ci */ 12562306a36Sopenharmony_ci if (gso_partial && skb_is_gso(skb)) { 12662306a36Sopenharmony_ci uh->len = htons(skb_shinfo(skb)->gso_size + 12762306a36Sopenharmony_ci SKB_GSO_CB(skb)->data_offset + 12862306a36Sopenharmony_ci skb->head - (unsigned char *)uh); 12962306a36Sopenharmony_ci } else { 13062306a36Sopenharmony_ci uh->len = htons(len); 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci if (!need_csum) 13462306a36Sopenharmony_ci continue; 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci uh->check = ~csum_fold(csum_add(partial, 13762306a36Sopenharmony_ci (__force __wsum)htonl(len))); 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci if (skb->encapsulation || !offload_csum) { 14062306a36Sopenharmony_ci uh->check = gso_make_checksum(skb, ~uh->check); 14162306a36Sopenharmony_ci if (uh->check == 0) 14262306a36Sopenharmony_ci uh->check = CSUM_MANGLED_0; 14362306a36Sopenharmony_ci } else { 14462306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_PARTIAL; 14562306a36Sopenharmony_ci skb->csum_start = skb_transport_header(skb) - skb->head; 14662306a36Sopenharmony_ci skb->csum_offset = offsetof(struct udphdr, check); 14762306a36Sopenharmony_ci } 14862306a36Sopenharmony_ci } while ((skb = skb->next)); 14962306a36Sopenharmony_ciout: 15062306a36Sopenharmony_ci return segs; 15162306a36Sopenharmony_ci} 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_cistruct sk_buff *skb_udp_tunnel_segment(struct sk_buff *skb, 15462306a36Sopenharmony_ci netdev_features_t features, 15562306a36Sopenharmony_ci bool is_ipv6) 15662306a36Sopenharmony_ci{ 15762306a36Sopenharmony_ci const struct net_offload __rcu **offloads; 15862306a36Sopenharmony_ci __be16 protocol = skb->protocol; 15962306a36Sopenharmony_ci const struct net_offload *ops; 16062306a36Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 16162306a36Sopenharmony_ci struct sk_buff *(*gso_inner_segment)(struct sk_buff *skb, 16262306a36Sopenharmony_ci netdev_features_t features); 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci rcu_read_lock(); 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci switch (skb->inner_protocol_type) { 16762306a36Sopenharmony_ci case ENCAP_TYPE_ETHER: 16862306a36Sopenharmony_ci protocol = skb->inner_protocol; 16962306a36Sopenharmony_ci gso_inner_segment = skb_mac_gso_segment; 17062306a36Sopenharmony_ci break; 17162306a36Sopenharmony_ci case ENCAP_TYPE_IPPROTO: 17262306a36Sopenharmony_ci offloads = is_ipv6 ? inet6_offloads : inet_offloads; 17362306a36Sopenharmony_ci ops = rcu_dereference(offloads[skb->inner_ipproto]); 17462306a36Sopenharmony_ci if (!ops || !ops->callbacks.gso_segment) 17562306a36Sopenharmony_ci goto out_unlock; 17662306a36Sopenharmony_ci gso_inner_segment = ops->callbacks.gso_segment; 17762306a36Sopenharmony_ci break; 17862306a36Sopenharmony_ci default: 17962306a36Sopenharmony_ci goto out_unlock; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci segs = __skb_udp_tunnel_segment(skb, features, gso_inner_segment, 18362306a36Sopenharmony_ci protocol, is_ipv6); 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ciout_unlock: 18662306a36Sopenharmony_ci rcu_read_unlock(); 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_ci return segs; 18962306a36Sopenharmony_ci} 19062306a36Sopenharmony_ciEXPORT_SYMBOL(skb_udp_tunnel_segment); 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_cistatic void __udpv4_gso_segment_csum(struct sk_buff *seg, 19362306a36Sopenharmony_ci __be32 *oldip, __be32 *newip, 19462306a36Sopenharmony_ci __be16 *oldport, __be16 *newport) 19562306a36Sopenharmony_ci{ 19662306a36Sopenharmony_ci struct udphdr *uh; 19762306a36Sopenharmony_ci struct iphdr *iph; 19862306a36Sopenharmony_ci 19962306a36Sopenharmony_ci if (*oldip == *newip && *oldport == *newport) 20062306a36Sopenharmony_ci return; 20162306a36Sopenharmony_ci 20262306a36Sopenharmony_ci uh = udp_hdr(seg); 20362306a36Sopenharmony_ci iph = ip_hdr(seg); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci if (uh->check) { 20662306a36Sopenharmony_ci inet_proto_csum_replace4(&uh->check, seg, *oldip, *newip, 20762306a36Sopenharmony_ci true); 20862306a36Sopenharmony_ci inet_proto_csum_replace2(&uh->check, seg, *oldport, *newport, 20962306a36Sopenharmony_ci false); 21062306a36Sopenharmony_ci if (!uh->check) 21162306a36Sopenharmony_ci uh->check = CSUM_MANGLED_0; 21262306a36Sopenharmony_ci } 21362306a36Sopenharmony_ci *oldport = *newport; 21462306a36Sopenharmony_ci 21562306a36Sopenharmony_ci csum_replace4(&iph->check, *oldip, *newip); 21662306a36Sopenharmony_ci *oldip = *newip; 21762306a36Sopenharmony_ci} 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_cistatic struct sk_buff *__udpv4_gso_segment_list_csum(struct sk_buff *segs) 22062306a36Sopenharmony_ci{ 22162306a36Sopenharmony_ci struct sk_buff *seg; 22262306a36Sopenharmony_ci struct udphdr *uh, *uh2; 22362306a36Sopenharmony_ci struct iphdr *iph, *iph2; 22462306a36Sopenharmony_ci 22562306a36Sopenharmony_ci seg = segs; 22662306a36Sopenharmony_ci uh = udp_hdr(seg); 22762306a36Sopenharmony_ci iph = ip_hdr(seg); 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if ((udp_hdr(seg)->dest == udp_hdr(seg->next)->dest) && 23062306a36Sopenharmony_ci (udp_hdr(seg)->source == udp_hdr(seg->next)->source) && 23162306a36Sopenharmony_ci (ip_hdr(seg)->daddr == ip_hdr(seg->next)->daddr) && 23262306a36Sopenharmony_ci (ip_hdr(seg)->saddr == ip_hdr(seg->next)->saddr)) 23362306a36Sopenharmony_ci return segs; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci while ((seg = seg->next)) { 23662306a36Sopenharmony_ci uh2 = udp_hdr(seg); 23762306a36Sopenharmony_ci iph2 = ip_hdr(seg); 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci __udpv4_gso_segment_csum(seg, 24062306a36Sopenharmony_ci &iph2->saddr, &iph->saddr, 24162306a36Sopenharmony_ci &uh2->source, &uh->source); 24262306a36Sopenharmony_ci __udpv4_gso_segment_csum(seg, 24362306a36Sopenharmony_ci &iph2->daddr, &iph->daddr, 24462306a36Sopenharmony_ci &uh2->dest, &uh->dest); 24562306a36Sopenharmony_ci } 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci return segs; 24862306a36Sopenharmony_ci} 24962306a36Sopenharmony_ci 25062306a36Sopenharmony_cistatic struct sk_buff *__udp_gso_segment_list(struct sk_buff *skb, 25162306a36Sopenharmony_ci netdev_features_t features, 25262306a36Sopenharmony_ci bool is_ipv6) 25362306a36Sopenharmony_ci{ 25462306a36Sopenharmony_ci unsigned int mss = skb_shinfo(skb)->gso_size; 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci skb = skb_segment_list(skb, features, skb_mac_header_len(skb)); 25762306a36Sopenharmony_ci if (IS_ERR(skb)) 25862306a36Sopenharmony_ci return skb; 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_ci udp_hdr(skb)->len = htons(sizeof(struct udphdr) + mss); 26162306a36Sopenharmony_ci 26262306a36Sopenharmony_ci return is_ipv6 ? skb : __udpv4_gso_segment_list_csum(skb); 26362306a36Sopenharmony_ci} 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistruct sk_buff *__udp_gso_segment(struct sk_buff *gso_skb, 26662306a36Sopenharmony_ci netdev_features_t features, bool is_ipv6) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci struct sock *sk = gso_skb->sk; 26962306a36Sopenharmony_ci unsigned int sum_truesize = 0; 27062306a36Sopenharmony_ci struct sk_buff *segs, *seg; 27162306a36Sopenharmony_ci struct udphdr *uh; 27262306a36Sopenharmony_ci unsigned int mss; 27362306a36Sopenharmony_ci bool copy_dtor; 27462306a36Sopenharmony_ci __sum16 check; 27562306a36Sopenharmony_ci __be16 newlen; 27662306a36Sopenharmony_ci 27762306a36Sopenharmony_ci mss = skb_shinfo(gso_skb)->gso_size; 27862306a36Sopenharmony_ci if (gso_skb->len <= sizeof(*uh) + mss) 27962306a36Sopenharmony_ci return ERR_PTR(-EINVAL); 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci if (skb_gso_ok(gso_skb, features | NETIF_F_GSO_ROBUST)) { 28262306a36Sopenharmony_ci /* Packet is from an untrusted source, reset gso_segs. */ 28362306a36Sopenharmony_ci skb_shinfo(gso_skb)->gso_segs = DIV_ROUND_UP(gso_skb->len - sizeof(*uh), 28462306a36Sopenharmony_ci mss); 28562306a36Sopenharmony_ci return NULL; 28662306a36Sopenharmony_ci } 28762306a36Sopenharmony_ci 28862306a36Sopenharmony_ci if (skb_shinfo(gso_skb)->gso_type & SKB_GSO_FRAGLIST) 28962306a36Sopenharmony_ci return __udp_gso_segment_list(gso_skb, features, is_ipv6); 29062306a36Sopenharmony_ci 29162306a36Sopenharmony_ci skb_pull(gso_skb, sizeof(*uh)); 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci /* clear destructor to avoid skb_segment assigning it to tail */ 29462306a36Sopenharmony_ci copy_dtor = gso_skb->destructor == sock_wfree; 29562306a36Sopenharmony_ci if (copy_dtor) 29662306a36Sopenharmony_ci gso_skb->destructor = NULL; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci segs = skb_segment(gso_skb, features); 29962306a36Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) { 30062306a36Sopenharmony_ci if (copy_dtor) 30162306a36Sopenharmony_ci gso_skb->destructor = sock_wfree; 30262306a36Sopenharmony_ci return segs; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* GSO partial and frag_list segmentation only requires splitting 30662306a36Sopenharmony_ci * the frame into an MSS multiple and possibly a remainder, both 30762306a36Sopenharmony_ci * cases return a GSO skb. So update the mss now. 30862306a36Sopenharmony_ci */ 30962306a36Sopenharmony_ci if (skb_is_gso(segs)) 31062306a36Sopenharmony_ci mss *= skb_shinfo(segs)->gso_segs; 31162306a36Sopenharmony_ci 31262306a36Sopenharmony_ci seg = segs; 31362306a36Sopenharmony_ci uh = udp_hdr(seg); 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci /* preserve TX timestamp flags and TS key for first segment */ 31662306a36Sopenharmony_ci skb_shinfo(seg)->tskey = skb_shinfo(gso_skb)->tskey; 31762306a36Sopenharmony_ci skb_shinfo(seg)->tx_flags |= 31862306a36Sopenharmony_ci (skb_shinfo(gso_skb)->tx_flags & SKBTX_ANY_TSTAMP); 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* compute checksum adjustment based on old length versus new */ 32162306a36Sopenharmony_ci newlen = htons(sizeof(*uh) + mss); 32262306a36Sopenharmony_ci check = csum16_add(csum16_sub(uh->check, uh->len), newlen); 32362306a36Sopenharmony_ci 32462306a36Sopenharmony_ci for (;;) { 32562306a36Sopenharmony_ci if (copy_dtor) { 32662306a36Sopenharmony_ci seg->destructor = sock_wfree; 32762306a36Sopenharmony_ci seg->sk = sk; 32862306a36Sopenharmony_ci sum_truesize += seg->truesize; 32962306a36Sopenharmony_ci } 33062306a36Sopenharmony_ci 33162306a36Sopenharmony_ci if (!seg->next) 33262306a36Sopenharmony_ci break; 33362306a36Sopenharmony_ci 33462306a36Sopenharmony_ci uh->len = newlen; 33562306a36Sopenharmony_ci uh->check = check; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if (seg->ip_summed == CHECKSUM_PARTIAL) 33862306a36Sopenharmony_ci gso_reset_checksum(seg, ~check); 33962306a36Sopenharmony_ci else 34062306a36Sopenharmony_ci uh->check = gso_make_checksum(seg, ~check) ? : 34162306a36Sopenharmony_ci CSUM_MANGLED_0; 34262306a36Sopenharmony_ci 34362306a36Sopenharmony_ci seg = seg->next; 34462306a36Sopenharmony_ci uh = udp_hdr(seg); 34562306a36Sopenharmony_ci } 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ci /* last packet can be partial gso_size, account for that in checksum */ 34862306a36Sopenharmony_ci newlen = htons(skb_tail_pointer(seg) - skb_transport_header(seg) + 34962306a36Sopenharmony_ci seg->data_len); 35062306a36Sopenharmony_ci check = csum16_add(csum16_sub(uh->check, uh->len), newlen); 35162306a36Sopenharmony_ci 35262306a36Sopenharmony_ci uh->len = newlen; 35362306a36Sopenharmony_ci uh->check = check; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (seg->ip_summed == CHECKSUM_PARTIAL) 35662306a36Sopenharmony_ci gso_reset_checksum(seg, ~check); 35762306a36Sopenharmony_ci else 35862306a36Sopenharmony_ci uh->check = gso_make_checksum(seg, ~check) ? : CSUM_MANGLED_0; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci /* update refcount for the packet */ 36162306a36Sopenharmony_ci if (copy_dtor) { 36262306a36Sopenharmony_ci int delta = sum_truesize - gso_skb->truesize; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci /* In some pathological cases, delta can be negative. 36562306a36Sopenharmony_ci * We need to either use refcount_add() or refcount_sub_and_test() 36662306a36Sopenharmony_ci */ 36762306a36Sopenharmony_ci if (likely(delta >= 0)) 36862306a36Sopenharmony_ci refcount_add(delta, &sk->sk_wmem_alloc); 36962306a36Sopenharmony_ci else 37062306a36Sopenharmony_ci WARN_ON_ONCE(refcount_sub_and_test(-delta, &sk->sk_wmem_alloc)); 37162306a36Sopenharmony_ci } 37262306a36Sopenharmony_ci return segs; 37362306a36Sopenharmony_ci} 37462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__udp_gso_segment); 37562306a36Sopenharmony_ci 37662306a36Sopenharmony_cistatic struct sk_buff *udp4_ufo_fragment(struct sk_buff *skb, 37762306a36Sopenharmony_ci netdev_features_t features) 37862306a36Sopenharmony_ci{ 37962306a36Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 38062306a36Sopenharmony_ci unsigned int mss; 38162306a36Sopenharmony_ci __wsum csum; 38262306a36Sopenharmony_ci struct udphdr *uh; 38362306a36Sopenharmony_ci struct iphdr *iph; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci if (skb->encapsulation && 38662306a36Sopenharmony_ci (skb_shinfo(skb)->gso_type & 38762306a36Sopenharmony_ci (SKB_GSO_UDP_TUNNEL|SKB_GSO_UDP_TUNNEL_CSUM))) { 38862306a36Sopenharmony_ci segs = skb_udp_tunnel_segment(skb, features, false); 38962306a36Sopenharmony_ci goto out; 39062306a36Sopenharmony_ci } 39162306a36Sopenharmony_ci 39262306a36Sopenharmony_ci if (!(skb_shinfo(skb)->gso_type & (SKB_GSO_UDP | SKB_GSO_UDP_L4))) 39362306a36Sopenharmony_ci goto out; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci if (!pskb_may_pull(skb, sizeof(struct udphdr))) 39662306a36Sopenharmony_ci goto out; 39762306a36Sopenharmony_ci 39862306a36Sopenharmony_ci if (skb_shinfo(skb)->gso_type & SKB_GSO_UDP_L4) 39962306a36Sopenharmony_ci return __udp_gso_segment(skb, features, false); 40062306a36Sopenharmony_ci 40162306a36Sopenharmony_ci mss = skb_shinfo(skb)->gso_size; 40262306a36Sopenharmony_ci if (unlikely(skb->len <= mss)) 40362306a36Sopenharmony_ci goto out; 40462306a36Sopenharmony_ci 40562306a36Sopenharmony_ci /* Do software UFO. Complete and fill in the UDP checksum as 40662306a36Sopenharmony_ci * HW cannot do checksum of UDP packets sent as multiple 40762306a36Sopenharmony_ci * IP fragments. 40862306a36Sopenharmony_ci */ 40962306a36Sopenharmony_ci 41062306a36Sopenharmony_ci uh = udp_hdr(skb); 41162306a36Sopenharmony_ci iph = ip_hdr(skb); 41262306a36Sopenharmony_ci 41362306a36Sopenharmony_ci uh->check = 0; 41462306a36Sopenharmony_ci csum = skb_checksum(skb, 0, skb->len, 0); 41562306a36Sopenharmony_ci uh->check = udp_v4_check(skb->len, iph->saddr, iph->daddr, csum); 41662306a36Sopenharmony_ci if (uh->check == 0) 41762306a36Sopenharmony_ci uh->check = CSUM_MANGLED_0; 41862306a36Sopenharmony_ci 41962306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_UNNECESSARY; 42062306a36Sopenharmony_ci 42162306a36Sopenharmony_ci /* If there is no outer header we can fake a checksum offload 42262306a36Sopenharmony_ci * due to the fact that we have already done the checksum in 42362306a36Sopenharmony_ci * software prior to segmenting the frame. 42462306a36Sopenharmony_ci */ 42562306a36Sopenharmony_ci if (!skb->encap_hdr_csum) 42662306a36Sopenharmony_ci features |= NETIF_F_HW_CSUM; 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci /* Fragment the skb. IP headers of the fragments are updated in 42962306a36Sopenharmony_ci * inet_gso_segment() 43062306a36Sopenharmony_ci */ 43162306a36Sopenharmony_ci segs = skb_segment(skb, features); 43262306a36Sopenharmony_ciout: 43362306a36Sopenharmony_ci return segs; 43462306a36Sopenharmony_ci} 43562306a36Sopenharmony_ci 43662306a36Sopenharmony_cistatic int skb_gro_receive_list(struct sk_buff *p, struct sk_buff *skb) 43762306a36Sopenharmony_ci{ 43862306a36Sopenharmony_ci if (unlikely(p->len + skb->len >= 65536)) 43962306a36Sopenharmony_ci return -E2BIG; 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_ci if (NAPI_GRO_CB(p)->last == p) 44262306a36Sopenharmony_ci skb_shinfo(p)->frag_list = skb; 44362306a36Sopenharmony_ci else 44462306a36Sopenharmony_ci NAPI_GRO_CB(p)->last->next = skb; 44562306a36Sopenharmony_ci 44662306a36Sopenharmony_ci skb_pull(skb, skb_gro_offset(skb)); 44762306a36Sopenharmony_ci 44862306a36Sopenharmony_ci NAPI_GRO_CB(p)->last = skb; 44962306a36Sopenharmony_ci NAPI_GRO_CB(p)->count++; 45062306a36Sopenharmony_ci p->data_len += skb->len; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci /* sk owenrship - if any - completely transferred to the aggregated packet */ 45362306a36Sopenharmony_ci skb->destructor = NULL; 45462306a36Sopenharmony_ci p->truesize += skb->truesize; 45562306a36Sopenharmony_ci p->len += skb->len; 45662306a36Sopenharmony_ci 45762306a36Sopenharmony_ci NAPI_GRO_CB(skb)->same_flow = 1; 45862306a36Sopenharmony_ci 45962306a36Sopenharmony_ci return 0; 46062306a36Sopenharmony_ci} 46162306a36Sopenharmony_ci 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci#define UDP_GRO_CNT_MAX 64 46462306a36Sopenharmony_cistatic struct sk_buff *udp_gro_receive_segment(struct list_head *head, 46562306a36Sopenharmony_ci struct sk_buff *skb) 46662306a36Sopenharmony_ci{ 46762306a36Sopenharmony_ci struct udphdr *uh = udp_gro_udphdr(skb); 46862306a36Sopenharmony_ci struct sk_buff *pp = NULL; 46962306a36Sopenharmony_ci struct udphdr *uh2; 47062306a36Sopenharmony_ci struct sk_buff *p; 47162306a36Sopenharmony_ci unsigned int ulen; 47262306a36Sopenharmony_ci int ret = 0; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci /* requires non zero csum, for symmetry with GSO */ 47562306a36Sopenharmony_ci if (!uh->check) { 47662306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 47762306a36Sopenharmony_ci return NULL; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci /* Do not deal with padded or malicious packets, sorry ! */ 48162306a36Sopenharmony_ci ulen = ntohs(uh->len); 48262306a36Sopenharmony_ci if (ulen <= sizeof(*uh) || ulen != skb_gro_len(skb)) { 48362306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 48462306a36Sopenharmony_ci return NULL; 48562306a36Sopenharmony_ci } 48662306a36Sopenharmony_ci /* pull encapsulating udp header */ 48762306a36Sopenharmony_ci skb_gro_pull(skb, sizeof(struct udphdr)); 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci list_for_each_entry(p, head, list) { 49062306a36Sopenharmony_ci if (!NAPI_GRO_CB(p)->same_flow) 49162306a36Sopenharmony_ci continue; 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci uh2 = udp_hdr(p); 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* Match ports only, as csum is always non zero */ 49662306a36Sopenharmony_ci if ((*(u32 *)&uh->source != *(u32 *)&uh2->source)) { 49762306a36Sopenharmony_ci NAPI_GRO_CB(p)->same_flow = 0; 49862306a36Sopenharmony_ci continue; 49962306a36Sopenharmony_ci } 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ci if (NAPI_GRO_CB(skb)->is_flist != NAPI_GRO_CB(p)->is_flist) { 50262306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 50362306a36Sopenharmony_ci return p; 50462306a36Sopenharmony_ci } 50562306a36Sopenharmony_ci 50662306a36Sopenharmony_ci /* Terminate the flow on len mismatch or if it grow "too much". 50762306a36Sopenharmony_ci * Under small packet flood GRO count could elsewhere grow a lot 50862306a36Sopenharmony_ci * leading to excessive truesize values. 50962306a36Sopenharmony_ci * On len mismatch merge the first packet shorter than gso_size, 51062306a36Sopenharmony_ci * otherwise complete the GRO packet. 51162306a36Sopenharmony_ci */ 51262306a36Sopenharmony_ci if (ulen > ntohs(uh2->len)) { 51362306a36Sopenharmony_ci pp = p; 51462306a36Sopenharmony_ci } else { 51562306a36Sopenharmony_ci if (NAPI_GRO_CB(skb)->is_flist) { 51662306a36Sopenharmony_ci if (!pskb_may_pull(skb, skb_gro_offset(skb))) { 51762306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 51862306a36Sopenharmony_ci return NULL; 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci if ((skb->ip_summed != p->ip_summed) || 52162306a36Sopenharmony_ci (skb->csum_level != p->csum_level)) { 52262306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 52362306a36Sopenharmony_ci return NULL; 52462306a36Sopenharmony_ci } 52562306a36Sopenharmony_ci ret = skb_gro_receive_list(p, skb); 52662306a36Sopenharmony_ci } else { 52762306a36Sopenharmony_ci skb_gro_postpull_rcsum(skb, uh, 52862306a36Sopenharmony_ci sizeof(struct udphdr)); 52962306a36Sopenharmony_ci 53062306a36Sopenharmony_ci ret = skb_gro_receive(p, skb); 53162306a36Sopenharmony_ci } 53262306a36Sopenharmony_ci } 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci if (ret || ulen != ntohs(uh2->len) || 53562306a36Sopenharmony_ci NAPI_GRO_CB(p)->count >= UDP_GRO_CNT_MAX) 53662306a36Sopenharmony_ci pp = p; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci return pp; 53962306a36Sopenharmony_ci } 54062306a36Sopenharmony_ci 54162306a36Sopenharmony_ci /* mismatch, but we never need to flush */ 54262306a36Sopenharmony_ci return NULL; 54362306a36Sopenharmony_ci} 54462306a36Sopenharmony_ci 54562306a36Sopenharmony_cistruct sk_buff *udp_gro_receive(struct list_head *head, struct sk_buff *skb, 54662306a36Sopenharmony_ci struct udphdr *uh, struct sock *sk) 54762306a36Sopenharmony_ci{ 54862306a36Sopenharmony_ci struct sk_buff *pp = NULL; 54962306a36Sopenharmony_ci struct sk_buff *p; 55062306a36Sopenharmony_ci struct udphdr *uh2; 55162306a36Sopenharmony_ci unsigned int off = skb_gro_offset(skb); 55262306a36Sopenharmony_ci int flush = 1; 55362306a36Sopenharmony_ci 55462306a36Sopenharmony_ci /* we can do L4 aggregation only if the packet can't land in a tunnel 55562306a36Sopenharmony_ci * otherwise we could corrupt the inner stream 55662306a36Sopenharmony_ci */ 55762306a36Sopenharmony_ci NAPI_GRO_CB(skb)->is_flist = 0; 55862306a36Sopenharmony_ci if (!sk || !udp_sk(sk)->gro_receive) { 55962306a36Sopenharmony_ci if (skb->dev->features & NETIF_F_GRO_FRAGLIST) 56062306a36Sopenharmony_ci NAPI_GRO_CB(skb)->is_flist = sk ? !udp_test_bit(GRO_ENABLED, sk) : 1; 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci if ((!sk && (skb->dev->features & NETIF_F_GRO_UDP_FWD)) || 56362306a36Sopenharmony_ci (sk && udp_test_bit(GRO_ENABLED, sk)) || NAPI_GRO_CB(skb)->is_flist) 56462306a36Sopenharmony_ci return call_gro_receive(udp_gro_receive_segment, head, skb); 56562306a36Sopenharmony_ci 56662306a36Sopenharmony_ci /* no GRO, be sure flush the current packet */ 56762306a36Sopenharmony_ci goto out; 56862306a36Sopenharmony_ci } 56962306a36Sopenharmony_ci 57062306a36Sopenharmony_ci if (NAPI_GRO_CB(skb)->encap_mark || 57162306a36Sopenharmony_ci (uh->check && skb->ip_summed != CHECKSUM_PARTIAL && 57262306a36Sopenharmony_ci NAPI_GRO_CB(skb)->csum_cnt == 0 && 57362306a36Sopenharmony_ci !NAPI_GRO_CB(skb)->csum_valid)) 57462306a36Sopenharmony_ci goto out; 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_ci /* mark that this skb passed once through the tunnel gro layer */ 57762306a36Sopenharmony_ci NAPI_GRO_CB(skb)->encap_mark = 1; 57862306a36Sopenharmony_ci 57962306a36Sopenharmony_ci flush = 0; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci list_for_each_entry(p, head, list) { 58262306a36Sopenharmony_ci if (!NAPI_GRO_CB(p)->same_flow) 58362306a36Sopenharmony_ci continue; 58462306a36Sopenharmony_ci 58562306a36Sopenharmony_ci uh2 = (struct udphdr *)(p->data + off); 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Match ports and either checksums are either both zero 58862306a36Sopenharmony_ci * or nonzero. 58962306a36Sopenharmony_ci */ 59062306a36Sopenharmony_ci if ((*(u32 *)&uh->source != *(u32 *)&uh2->source) || 59162306a36Sopenharmony_ci (!uh->check ^ !uh2->check)) { 59262306a36Sopenharmony_ci NAPI_GRO_CB(p)->same_flow = 0; 59362306a36Sopenharmony_ci continue; 59462306a36Sopenharmony_ci } 59562306a36Sopenharmony_ci } 59662306a36Sopenharmony_ci 59762306a36Sopenharmony_ci skb_gro_pull(skb, sizeof(struct udphdr)); /* pull encapsulating udp header */ 59862306a36Sopenharmony_ci skb_gro_postpull_rcsum(skb, uh, sizeof(struct udphdr)); 59962306a36Sopenharmony_ci pp = call_gro_receive_sk(udp_sk(sk)->gro_receive, sk, head, skb); 60062306a36Sopenharmony_ci 60162306a36Sopenharmony_ciout: 60262306a36Sopenharmony_ci skb_gro_flush_final(skb, pp, flush); 60362306a36Sopenharmony_ci return pp; 60462306a36Sopenharmony_ci} 60562306a36Sopenharmony_ciEXPORT_SYMBOL(udp_gro_receive); 60662306a36Sopenharmony_ci 60762306a36Sopenharmony_cistatic struct sock *udp4_gro_lookup_skb(struct sk_buff *skb, __be16 sport, 60862306a36Sopenharmony_ci __be16 dport) 60962306a36Sopenharmony_ci{ 61062306a36Sopenharmony_ci const struct iphdr *iph = skb_gro_network_header(skb); 61162306a36Sopenharmony_ci struct net *net = dev_net(skb->dev); 61262306a36Sopenharmony_ci int iif, sdif; 61362306a36Sopenharmony_ci 61462306a36Sopenharmony_ci inet_get_iif_sdif(skb, &iif, &sdif); 61562306a36Sopenharmony_ci 61662306a36Sopenharmony_ci return __udp4_lib_lookup(net, iph->saddr, sport, 61762306a36Sopenharmony_ci iph->daddr, dport, iif, 61862306a36Sopenharmony_ci sdif, net->ipv4.udp_table, NULL); 61962306a36Sopenharmony_ci} 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE 62262306a36Sopenharmony_cistruct sk_buff *udp4_gro_receive(struct list_head *head, struct sk_buff *skb) 62362306a36Sopenharmony_ci{ 62462306a36Sopenharmony_ci struct udphdr *uh = udp_gro_udphdr(skb); 62562306a36Sopenharmony_ci struct sock *sk = NULL; 62662306a36Sopenharmony_ci struct sk_buff *pp; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci if (unlikely(!uh)) 62962306a36Sopenharmony_ci goto flush; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci /* Don't bother verifying checksum if we're going to flush anyway. */ 63262306a36Sopenharmony_ci if (NAPI_GRO_CB(skb)->flush) 63362306a36Sopenharmony_ci goto skip; 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci if (skb_gro_checksum_validate_zero_check(skb, IPPROTO_UDP, uh->check, 63662306a36Sopenharmony_ci inet_gro_compute_pseudo)) 63762306a36Sopenharmony_ci goto flush; 63862306a36Sopenharmony_ci else if (uh->check) 63962306a36Sopenharmony_ci skb_gro_checksum_try_convert(skb, IPPROTO_UDP, 64062306a36Sopenharmony_ci inet_gro_compute_pseudo); 64162306a36Sopenharmony_ciskip: 64262306a36Sopenharmony_ci NAPI_GRO_CB(skb)->is_ipv6 = 0; 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci if (static_branch_unlikely(&udp_encap_needed_key)) 64562306a36Sopenharmony_ci sk = udp4_gro_lookup_skb(skb, uh->source, uh->dest); 64662306a36Sopenharmony_ci 64762306a36Sopenharmony_ci pp = udp_gro_receive(head, skb, uh, sk); 64862306a36Sopenharmony_ci return pp; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ciflush: 65162306a36Sopenharmony_ci NAPI_GRO_CB(skb)->flush = 1; 65262306a36Sopenharmony_ci return NULL; 65362306a36Sopenharmony_ci} 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_cistatic int udp_gro_complete_segment(struct sk_buff *skb) 65662306a36Sopenharmony_ci{ 65762306a36Sopenharmony_ci struct udphdr *uh = udp_hdr(skb); 65862306a36Sopenharmony_ci 65962306a36Sopenharmony_ci skb->csum_start = (unsigned char *)uh - skb->head; 66062306a36Sopenharmony_ci skb->csum_offset = offsetof(struct udphdr, check); 66162306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_PARTIAL; 66262306a36Sopenharmony_ci 66362306a36Sopenharmony_ci skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; 66462306a36Sopenharmony_ci skb_shinfo(skb)->gso_type |= SKB_GSO_UDP_L4; 66562306a36Sopenharmony_ci 66662306a36Sopenharmony_ci if (skb->encapsulation) 66762306a36Sopenharmony_ci skb->inner_transport_header = skb->transport_header; 66862306a36Sopenharmony_ci 66962306a36Sopenharmony_ci return 0; 67062306a36Sopenharmony_ci} 67162306a36Sopenharmony_ci 67262306a36Sopenharmony_ciint udp_gro_complete(struct sk_buff *skb, int nhoff, 67362306a36Sopenharmony_ci udp_lookup_t lookup) 67462306a36Sopenharmony_ci{ 67562306a36Sopenharmony_ci __be16 newlen = htons(skb->len - nhoff); 67662306a36Sopenharmony_ci struct udphdr *uh = (struct udphdr *)(skb->data + nhoff); 67762306a36Sopenharmony_ci struct sock *sk; 67862306a36Sopenharmony_ci int err; 67962306a36Sopenharmony_ci 68062306a36Sopenharmony_ci uh->len = newlen; 68162306a36Sopenharmony_ci 68262306a36Sopenharmony_ci sk = INDIRECT_CALL_INET(lookup, udp6_lib_lookup_skb, 68362306a36Sopenharmony_ci udp4_lib_lookup_skb, skb, uh->source, uh->dest); 68462306a36Sopenharmony_ci if (sk && udp_sk(sk)->gro_complete) { 68562306a36Sopenharmony_ci skb_shinfo(skb)->gso_type = uh->check ? SKB_GSO_UDP_TUNNEL_CSUM 68662306a36Sopenharmony_ci : SKB_GSO_UDP_TUNNEL; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci /* clear the encap mark, so that inner frag_list gro_complete 68962306a36Sopenharmony_ci * can take place 69062306a36Sopenharmony_ci */ 69162306a36Sopenharmony_ci NAPI_GRO_CB(skb)->encap_mark = 0; 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_ci /* Set encapsulation before calling into inner gro_complete() 69462306a36Sopenharmony_ci * functions to make them set up the inner offsets. 69562306a36Sopenharmony_ci */ 69662306a36Sopenharmony_ci skb->encapsulation = 1; 69762306a36Sopenharmony_ci err = udp_sk(sk)->gro_complete(sk, skb, 69862306a36Sopenharmony_ci nhoff + sizeof(struct udphdr)); 69962306a36Sopenharmony_ci } else { 70062306a36Sopenharmony_ci err = udp_gro_complete_segment(skb); 70162306a36Sopenharmony_ci } 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci if (skb->remcsum_offload) 70462306a36Sopenharmony_ci skb_shinfo(skb)->gso_type |= SKB_GSO_TUNNEL_REMCSUM; 70562306a36Sopenharmony_ci 70662306a36Sopenharmony_ci return err; 70762306a36Sopenharmony_ci} 70862306a36Sopenharmony_ciEXPORT_SYMBOL(udp_gro_complete); 70962306a36Sopenharmony_ci 71062306a36Sopenharmony_ciINDIRECT_CALLABLE_SCOPE int udp4_gro_complete(struct sk_buff *skb, int nhoff) 71162306a36Sopenharmony_ci{ 71262306a36Sopenharmony_ci const struct iphdr *iph = ip_hdr(skb); 71362306a36Sopenharmony_ci struct udphdr *uh = (struct udphdr *)(skb->data + nhoff); 71462306a36Sopenharmony_ci 71562306a36Sopenharmony_ci /* do fraglist only if there is no outer UDP encap (or we already processed it) */ 71662306a36Sopenharmony_ci if (NAPI_GRO_CB(skb)->is_flist && !NAPI_GRO_CB(skb)->encap_mark) { 71762306a36Sopenharmony_ci uh->len = htons(skb->len - nhoff); 71862306a36Sopenharmony_ci 71962306a36Sopenharmony_ci skb_shinfo(skb)->gso_type |= (SKB_GSO_FRAGLIST|SKB_GSO_UDP_L4); 72062306a36Sopenharmony_ci skb_shinfo(skb)->gso_segs = NAPI_GRO_CB(skb)->count; 72162306a36Sopenharmony_ci 72262306a36Sopenharmony_ci if (skb->ip_summed == CHECKSUM_UNNECESSARY) { 72362306a36Sopenharmony_ci if (skb->csum_level < SKB_MAX_CSUM_LEVEL) 72462306a36Sopenharmony_ci skb->csum_level++; 72562306a36Sopenharmony_ci } else { 72662306a36Sopenharmony_ci skb->ip_summed = CHECKSUM_UNNECESSARY; 72762306a36Sopenharmony_ci skb->csum_level = 0; 72862306a36Sopenharmony_ci } 72962306a36Sopenharmony_ci 73062306a36Sopenharmony_ci return 0; 73162306a36Sopenharmony_ci } 73262306a36Sopenharmony_ci 73362306a36Sopenharmony_ci if (uh->check) 73462306a36Sopenharmony_ci uh->check = ~udp_v4_check(skb->len - nhoff, iph->saddr, 73562306a36Sopenharmony_ci iph->daddr, 0); 73662306a36Sopenharmony_ci 73762306a36Sopenharmony_ci return udp_gro_complete(skb, nhoff, udp4_lib_lookup_skb); 73862306a36Sopenharmony_ci} 73962306a36Sopenharmony_ci 74062306a36Sopenharmony_cistatic const struct net_offload udpv4_offload = { 74162306a36Sopenharmony_ci .callbacks = { 74262306a36Sopenharmony_ci .gso_segment = udp4_ufo_fragment, 74362306a36Sopenharmony_ci .gro_receive = udp4_gro_receive, 74462306a36Sopenharmony_ci .gro_complete = udp4_gro_complete, 74562306a36Sopenharmony_ci }, 74662306a36Sopenharmony_ci}; 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_ciint __init udpv4_offload_init(void) 74962306a36Sopenharmony_ci{ 75062306a36Sopenharmony_ci return inet_add_offload(&udpv4_offload, IPPROTO_UDP); 75162306a36Sopenharmony_ci} 752