1 /* 2 * IPV4 GSO/GRO offload support 3 * Linux INET implementation 4 * 5 * This program is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU General Public License 7 * as published by the Free Software Foundation; either version 8 * 2 of the License, or (at your option) any later version. 9 * 10 * GRE GSO support 11 */ 12 13 #include <linux/skbuff.h> 14 #include <net/protocol.h> 15 #include <net/gre.h> 16 17 static int gre_gso_send_check(struct sk_buff *skb) 18 { 19 if (!skb->encapsulation) 20 return -EINVAL; 21 return 0; 22 } 23 24 static struct sk_buff *gre_gso_segment(struct sk_buff *skb, 25 netdev_features_t features) 26 { 27 struct sk_buff *segs = ERR_PTR(-EINVAL); 28 netdev_features_t enc_features; 29 int ghl = GRE_HEADER_SECTION; 30 struct gre_base_hdr *greh; 31 u16 mac_offset = skb->mac_header; 32 int mac_len = skb->mac_len; 33 __be16 protocol = skb->protocol; 34 int tnl_hlen; 35 bool csum; 36 37 if (unlikely(skb_shinfo(skb)->gso_type & 38 ~(SKB_GSO_TCPV4 | 39 SKB_GSO_TCPV6 | 40 SKB_GSO_UDP | 41 SKB_GSO_DODGY | 42 SKB_GSO_TCP_ECN | 43 SKB_GSO_GRE | 44 SKB_GSO_IPIP))) 45 goto out; 46 47 if (unlikely(!pskb_may_pull(skb, sizeof(*greh)))) 48 goto out; 49 50 greh = (struct gre_base_hdr *)skb_transport_header(skb); 51 52 if (greh->flags & GRE_KEY) 53 ghl += GRE_HEADER_SECTION; 54 if (greh->flags & GRE_SEQ) 55 ghl += GRE_HEADER_SECTION; 56 if (greh->flags & GRE_CSUM) { 57 ghl += GRE_HEADER_SECTION; 58 csum = true; 59 } else 60 csum = false; 61 62 if (unlikely(!pskb_may_pull(skb, ghl))) 63 goto out; 64 65 /* setup inner skb. */ 66 skb->protocol = greh->protocol; 67 skb->encapsulation = 0; 68 69 __skb_pull(skb, ghl); 70 skb_reset_mac_header(skb); 71 skb_set_network_header(skb, skb_inner_network_offset(skb)); 72 skb->mac_len = skb_inner_network_offset(skb); 73 74 /* segment inner packet. */ 75 enc_features = skb->dev->hw_enc_features & netif_skb_features(skb); 76 segs = skb_mac_gso_segment(skb, enc_features); 77 if (!segs || IS_ERR(segs)) { 78 skb_gso_error_unwind(skb, protocol, ghl, mac_offset, mac_len); 79 goto out; 80 } 81 82 skb = segs; 83 tnl_hlen = skb_tnl_header_len(skb); 84 do { 85 __skb_push(skb, ghl); 86 if (csum) { 87 __be32 *pcsum; 88 89 if (skb_has_shared_frag(skb)) { 90 int err; 91 92 err = __skb_linearize(skb); 93 if (err) { 94 kfree_skb_list(segs); 95 segs = ERR_PTR(err); 96 goto out; 97 } 98 } 99 100 greh = (struct gre_base_hdr *)(skb->data); 101 pcsum = (__be32 *)(greh + 1); 102 *pcsum = 0; 103 *(__sum16 *)pcsum = csum_fold(skb_checksum(skb, 0, skb->len, 0)); 104 } 105 __skb_push(skb, tnl_hlen - ghl); 106 107 skb_reset_inner_headers(skb); 108 skb->encapsulation = 1; 109 110 skb_reset_mac_header(skb); 111 skb_set_network_header(skb, mac_len); 112 skb->mac_len = mac_len; 113 skb->protocol = protocol; 114 } while ((skb = skb->next)); 115 out: 116 return segs; 117 } 118 119 static const struct net_offload gre_offload = { 120 .callbacks = { 121 .gso_send_check = gre_gso_send_check, 122 .gso_segment = gre_gso_segment, 123 }, 124 }; 125 126 int __init gre_offload_init(void) 127 { 128 return inet_add_offload(&gre_offload, IPPROTO_GRE); 129 } 130 131 void __exit gre_offload_exit(void) 132 { 133 inet_del_offload(&gre_offload, IPPROTO_GRE); 134 } 135