18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * IPV4 GSO/GRO offload support 48c2ecf20Sopenharmony_ci * Linux INET implementation 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * GRE GSO support 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/skbuff.h> 108c2ecf20Sopenharmony_ci#include <linux/init.h> 118c2ecf20Sopenharmony_ci#include <net/protocol.h> 128c2ecf20Sopenharmony_ci#include <net/gre.h> 138c2ecf20Sopenharmony_ci 148c2ecf20Sopenharmony_cistatic struct sk_buff *gre_gso_segment(struct sk_buff *skb, 158c2ecf20Sopenharmony_ci netdev_features_t features) 168c2ecf20Sopenharmony_ci{ 178c2ecf20Sopenharmony_ci int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb); 188c2ecf20Sopenharmony_ci bool need_csum, need_recompute_csum, gso_partial; 198c2ecf20Sopenharmony_ci struct sk_buff *segs = ERR_PTR(-EINVAL); 208c2ecf20Sopenharmony_ci u16 mac_offset = skb->mac_header; 218c2ecf20Sopenharmony_ci __be16 protocol = skb->protocol; 228c2ecf20Sopenharmony_ci u16 mac_len = skb->mac_len; 238c2ecf20Sopenharmony_ci int gre_offset, outer_hlen; 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci if (!skb->encapsulation) 268c2ecf20Sopenharmony_ci goto out; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci if (unlikely(tnl_hlen < sizeof(struct gre_base_hdr))) 298c2ecf20Sopenharmony_ci goto out; 308c2ecf20Sopenharmony_ci 318c2ecf20Sopenharmony_ci if (unlikely(!pskb_may_pull(skb, tnl_hlen))) 328c2ecf20Sopenharmony_ci goto out; 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_ci /* setup inner skb. */ 358c2ecf20Sopenharmony_ci skb->encapsulation = 0; 368c2ecf20Sopenharmony_ci SKB_GSO_CB(skb)->encap_level = 0; 378c2ecf20Sopenharmony_ci __skb_pull(skb, tnl_hlen); 388c2ecf20Sopenharmony_ci skb_reset_mac_header(skb); 398c2ecf20Sopenharmony_ci skb_set_network_header(skb, skb_inner_network_offset(skb)); 408c2ecf20Sopenharmony_ci skb->mac_len = skb_inner_network_offset(skb); 418c2ecf20Sopenharmony_ci skb->protocol = skb->inner_protocol; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci need_csum = !!(skb_shinfo(skb)->gso_type & SKB_GSO_GRE_CSUM); 448c2ecf20Sopenharmony_ci need_recompute_csum = skb->csum_not_inet; 458c2ecf20Sopenharmony_ci skb->encap_hdr_csum = need_csum; 468c2ecf20Sopenharmony_ci 478c2ecf20Sopenharmony_ci features &= skb->dev->hw_enc_features; 488c2ecf20Sopenharmony_ci 498c2ecf20Sopenharmony_ci /* segment inner packet. */ 508c2ecf20Sopenharmony_ci segs = skb_mac_gso_segment(skb, features); 518c2ecf20Sopenharmony_ci if (IS_ERR_OR_NULL(segs)) { 528c2ecf20Sopenharmony_ci skb_gso_error_unwind(skb, protocol, tnl_hlen, mac_offset, 538c2ecf20Sopenharmony_ci mac_len); 548c2ecf20Sopenharmony_ci goto out; 558c2ecf20Sopenharmony_ci } 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci gso_partial = !!(skb_shinfo(segs)->gso_type & SKB_GSO_PARTIAL); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci outer_hlen = skb_tnl_header_len(skb); 608c2ecf20Sopenharmony_ci gre_offset = outer_hlen - tnl_hlen; 618c2ecf20Sopenharmony_ci skb = segs; 628c2ecf20Sopenharmony_ci do { 638c2ecf20Sopenharmony_ci struct gre_base_hdr *greh; 648c2ecf20Sopenharmony_ci __sum16 *pcsum; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_ci /* Set up inner headers if we are offloading inner checksum */ 678c2ecf20Sopenharmony_ci if (skb->ip_summed == CHECKSUM_PARTIAL) { 688c2ecf20Sopenharmony_ci skb_reset_inner_headers(skb); 698c2ecf20Sopenharmony_ci skb->encapsulation = 1; 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_ci skb->mac_len = mac_len; 738c2ecf20Sopenharmony_ci skb->protocol = protocol; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_ci __skb_push(skb, outer_hlen); 768c2ecf20Sopenharmony_ci skb_reset_mac_header(skb); 778c2ecf20Sopenharmony_ci skb_set_network_header(skb, mac_len); 788c2ecf20Sopenharmony_ci skb_set_transport_header(skb, gre_offset); 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_ci if (!need_csum) 818c2ecf20Sopenharmony_ci continue; 828c2ecf20Sopenharmony_ci 838c2ecf20Sopenharmony_ci greh = (struct gre_base_hdr *)skb_transport_header(skb); 848c2ecf20Sopenharmony_ci pcsum = (__sum16 *)(greh + 1); 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci if (gso_partial && skb_is_gso(skb)) { 878c2ecf20Sopenharmony_ci unsigned int partial_adj; 888c2ecf20Sopenharmony_ci 898c2ecf20Sopenharmony_ci /* Adjust checksum to account for the fact that 908c2ecf20Sopenharmony_ci * the partial checksum is based on actual size 918c2ecf20Sopenharmony_ci * whereas headers should be based on MSS size. 928c2ecf20Sopenharmony_ci */ 938c2ecf20Sopenharmony_ci partial_adj = skb->len + skb_headroom(skb) - 948c2ecf20Sopenharmony_ci SKB_GSO_CB(skb)->data_offset - 958c2ecf20Sopenharmony_ci skb_shinfo(skb)->gso_size; 968c2ecf20Sopenharmony_ci *pcsum = ~csum_fold((__force __wsum)htonl(partial_adj)); 978c2ecf20Sopenharmony_ci } else { 988c2ecf20Sopenharmony_ci *pcsum = 0; 998c2ecf20Sopenharmony_ci } 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci *(pcsum + 1) = 0; 1028c2ecf20Sopenharmony_ci if (need_recompute_csum && !skb_is_gso(skb)) { 1038c2ecf20Sopenharmony_ci __wsum csum; 1048c2ecf20Sopenharmony_ci 1058c2ecf20Sopenharmony_ci csum = skb_checksum(skb, gre_offset, 1068c2ecf20Sopenharmony_ci skb->len - gre_offset, 0); 1078c2ecf20Sopenharmony_ci *pcsum = csum_fold(csum); 1088c2ecf20Sopenharmony_ci } else { 1098c2ecf20Sopenharmony_ci *pcsum = gso_make_checksum(skb, 0); 1108c2ecf20Sopenharmony_ci } 1118c2ecf20Sopenharmony_ci } while ((skb = skb->next)); 1128c2ecf20Sopenharmony_ciout: 1138c2ecf20Sopenharmony_ci return segs; 1148c2ecf20Sopenharmony_ci} 1158c2ecf20Sopenharmony_ci 1168c2ecf20Sopenharmony_cistatic struct sk_buff *gre_gro_receive(struct list_head *head, 1178c2ecf20Sopenharmony_ci struct sk_buff *skb) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct sk_buff *pp = NULL; 1208c2ecf20Sopenharmony_ci struct sk_buff *p; 1218c2ecf20Sopenharmony_ci const struct gre_base_hdr *greh; 1228c2ecf20Sopenharmony_ci unsigned int hlen, grehlen; 1238c2ecf20Sopenharmony_ci unsigned int off; 1248c2ecf20Sopenharmony_ci int flush = 1; 1258c2ecf20Sopenharmony_ci struct packet_offload *ptype; 1268c2ecf20Sopenharmony_ci __be16 type; 1278c2ecf20Sopenharmony_ci 1288c2ecf20Sopenharmony_ci if (NAPI_GRO_CB(skb)->encap_mark) 1298c2ecf20Sopenharmony_ci goto out; 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci NAPI_GRO_CB(skb)->encap_mark = 1; 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci off = skb_gro_offset(skb); 1348c2ecf20Sopenharmony_ci hlen = off + sizeof(*greh); 1358c2ecf20Sopenharmony_ci greh = skb_gro_header_fast(skb, off); 1368c2ecf20Sopenharmony_ci if (skb_gro_header_hard(skb, hlen)) { 1378c2ecf20Sopenharmony_ci greh = skb_gro_header_slow(skb, hlen, off); 1388c2ecf20Sopenharmony_ci if (unlikely(!greh)) 1398c2ecf20Sopenharmony_ci goto out; 1408c2ecf20Sopenharmony_ci } 1418c2ecf20Sopenharmony_ci 1428c2ecf20Sopenharmony_ci /* Only support version 0 and K (key), C (csum) flags. Note that 1438c2ecf20Sopenharmony_ci * although the support for the S (seq#) flag can be added easily 1448c2ecf20Sopenharmony_ci * for GRO, this is problematic for GSO hence can not be enabled 1458c2ecf20Sopenharmony_ci * here because a GRO pkt may end up in the forwarding path, thus 1468c2ecf20Sopenharmony_ci * requiring GSO support to break it up correctly. 1478c2ecf20Sopenharmony_ci */ 1488c2ecf20Sopenharmony_ci if ((greh->flags & ~(GRE_KEY|GRE_CSUM)) != 0) 1498c2ecf20Sopenharmony_ci goto out; 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci /* We can only support GRE_CSUM if we can track the location of 1528c2ecf20Sopenharmony_ci * the GRE header. In the case of FOU/GUE we cannot because the 1538c2ecf20Sopenharmony_ci * outer UDP header displaces the GRE header leaving us in a state 1548c2ecf20Sopenharmony_ci * of limbo. 1558c2ecf20Sopenharmony_ci */ 1568c2ecf20Sopenharmony_ci if ((greh->flags & GRE_CSUM) && NAPI_GRO_CB(skb)->is_fou) 1578c2ecf20Sopenharmony_ci goto out; 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci type = greh->protocol; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci rcu_read_lock(); 1628c2ecf20Sopenharmony_ci ptype = gro_find_receive_by_type(type); 1638c2ecf20Sopenharmony_ci if (!ptype) 1648c2ecf20Sopenharmony_ci goto out_unlock; 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci grehlen = GRE_HEADER_SECTION; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci if (greh->flags & GRE_KEY) 1698c2ecf20Sopenharmony_ci grehlen += GRE_HEADER_SECTION; 1708c2ecf20Sopenharmony_ci 1718c2ecf20Sopenharmony_ci if (greh->flags & GRE_CSUM) 1728c2ecf20Sopenharmony_ci grehlen += GRE_HEADER_SECTION; 1738c2ecf20Sopenharmony_ci 1748c2ecf20Sopenharmony_ci hlen = off + grehlen; 1758c2ecf20Sopenharmony_ci if (skb_gro_header_hard(skb, hlen)) { 1768c2ecf20Sopenharmony_ci greh = skb_gro_header_slow(skb, hlen, off); 1778c2ecf20Sopenharmony_ci if (unlikely(!greh)) 1788c2ecf20Sopenharmony_ci goto out_unlock; 1798c2ecf20Sopenharmony_ci } 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci /* Don't bother verifying checksum if we're going to flush anyway. */ 1828c2ecf20Sopenharmony_ci if ((greh->flags & GRE_CSUM) && !NAPI_GRO_CB(skb)->flush) { 1838c2ecf20Sopenharmony_ci if (skb_gro_checksum_simple_validate(skb)) 1848c2ecf20Sopenharmony_ci goto out_unlock; 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci skb_gro_checksum_try_convert(skb, IPPROTO_GRE, 1878c2ecf20Sopenharmony_ci null_compute_pseudo); 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci 1908c2ecf20Sopenharmony_ci list_for_each_entry(p, head, list) { 1918c2ecf20Sopenharmony_ci const struct gre_base_hdr *greh2; 1928c2ecf20Sopenharmony_ci 1938c2ecf20Sopenharmony_ci if (!NAPI_GRO_CB(p)->same_flow) 1948c2ecf20Sopenharmony_ci continue; 1958c2ecf20Sopenharmony_ci 1968c2ecf20Sopenharmony_ci /* The following checks are needed to ensure only pkts 1978c2ecf20Sopenharmony_ci * from the same tunnel are considered for aggregation. 1988c2ecf20Sopenharmony_ci * The criteria for "the same tunnel" includes: 1998c2ecf20Sopenharmony_ci * 1) same version (we only support version 0 here) 2008c2ecf20Sopenharmony_ci * 2) same protocol (we only support ETH_P_IP for now) 2018c2ecf20Sopenharmony_ci * 3) same set of flags 2028c2ecf20Sopenharmony_ci * 4) same key if the key field is present. 2038c2ecf20Sopenharmony_ci */ 2048c2ecf20Sopenharmony_ci greh2 = (struct gre_base_hdr *)(p->data + off); 2058c2ecf20Sopenharmony_ci 2068c2ecf20Sopenharmony_ci if (greh2->flags != greh->flags || 2078c2ecf20Sopenharmony_ci greh2->protocol != greh->protocol) { 2088c2ecf20Sopenharmony_ci NAPI_GRO_CB(p)->same_flow = 0; 2098c2ecf20Sopenharmony_ci continue; 2108c2ecf20Sopenharmony_ci } 2118c2ecf20Sopenharmony_ci if (greh->flags & GRE_KEY) { 2128c2ecf20Sopenharmony_ci /* compare keys */ 2138c2ecf20Sopenharmony_ci if (*(__be32 *)(greh2+1) != *(__be32 *)(greh+1)) { 2148c2ecf20Sopenharmony_ci NAPI_GRO_CB(p)->same_flow = 0; 2158c2ecf20Sopenharmony_ci continue; 2168c2ecf20Sopenharmony_ci } 2178c2ecf20Sopenharmony_ci } 2188c2ecf20Sopenharmony_ci } 2198c2ecf20Sopenharmony_ci 2208c2ecf20Sopenharmony_ci skb_gro_pull(skb, grehlen); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci /* Adjusted NAPI_GRO_CB(skb)->csum after skb_gro_pull()*/ 2238c2ecf20Sopenharmony_ci skb_gro_postpull_rcsum(skb, greh, grehlen); 2248c2ecf20Sopenharmony_ci 2258c2ecf20Sopenharmony_ci pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); 2268c2ecf20Sopenharmony_ci flush = 0; 2278c2ecf20Sopenharmony_ci 2288c2ecf20Sopenharmony_ciout_unlock: 2298c2ecf20Sopenharmony_ci rcu_read_unlock(); 2308c2ecf20Sopenharmony_ciout: 2318c2ecf20Sopenharmony_ci skb_gro_flush_final(skb, pp, flush); 2328c2ecf20Sopenharmony_ci 2338c2ecf20Sopenharmony_ci return pp; 2348c2ecf20Sopenharmony_ci} 2358c2ecf20Sopenharmony_ci 2368c2ecf20Sopenharmony_cistatic int gre_gro_complete(struct sk_buff *skb, int nhoff) 2378c2ecf20Sopenharmony_ci{ 2388c2ecf20Sopenharmony_ci struct gre_base_hdr *greh = (struct gre_base_hdr *)(skb->data + nhoff); 2398c2ecf20Sopenharmony_ci struct packet_offload *ptype; 2408c2ecf20Sopenharmony_ci unsigned int grehlen = sizeof(*greh); 2418c2ecf20Sopenharmony_ci int err = -ENOENT; 2428c2ecf20Sopenharmony_ci __be16 type; 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci skb->encapsulation = 1; 2458c2ecf20Sopenharmony_ci skb_shinfo(skb)->gso_type = SKB_GSO_GRE; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci type = greh->protocol; 2488c2ecf20Sopenharmony_ci if (greh->flags & GRE_KEY) 2498c2ecf20Sopenharmony_ci grehlen += GRE_HEADER_SECTION; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci if (greh->flags & GRE_CSUM) 2528c2ecf20Sopenharmony_ci grehlen += GRE_HEADER_SECTION; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci rcu_read_lock(); 2558c2ecf20Sopenharmony_ci ptype = gro_find_complete_by_type(type); 2568c2ecf20Sopenharmony_ci if (ptype) 2578c2ecf20Sopenharmony_ci err = ptype->callbacks.gro_complete(skb, nhoff + grehlen); 2588c2ecf20Sopenharmony_ci 2598c2ecf20Sopenharmony_ci rcu_read_unlock(); 2608c2ecf20Sopenharmony_ci 2618c2ecf20Sopenharmony_ci skb_set_inner_mac_header(skb, nhoff + grehlen); 2628c2ecf20Sopenharmony_ci 2638c2ecf20Sopenharmony_ci return err; 2648c2ecf20Sopenharmony_ci} 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_cistatic const struct net_offload gre_offload = { 2678c2ecf20Sopenharmony_ci .callbacks = { 2688c2ecf20Sopenharmony_ci .gso_segment = gre_gso_segment, 2698c2ecf20Sopenharmony_ci .gro_receive = gre_gro_receive, 2708c2ecf20Sopenharmony_ci .gro_complete = gre_gro_complete, 2718c2ecf20Sopenharmony_ci }, 2728c2ecf20Sopenharmony_ci}; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_cistatic int __init gre_offload_init(void) 2758c2ecf20Sopenharmony_ci{ 2768c2ecf20Sopenharmony_ci int err; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci err = inet_add_offload(&gre_offload, IPPROTO_GRE); 2798c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_IPV6) 2808c2ecf20Sopenharmony_ci if (err) 2818c2ecf20Sopenharmony_ci return err; 2828c2ecf20Sopenharmony_ci 2838c2ecf20Sopenharmony_ci err = inet6_add_offload(&gre_offload, IPPROTO_GRE); 2848c2ecf20Sopenharmony_ci if (err) 2858c2ecf20Sopenharmony_ci inet_del_offload(&gre_offload, IPPROTO_GRE); 2868c2ecf20Sopenharmony_ci#endif 2878c2ecf20Sopenharmony_ci 2888c2ecf20Sopenharmony_ci return err; 2898c2ecf20Sopenharmony_ci} 2908c2ecf20Sopenharmony_cidevice_initcall(gre_offload_init); 291