Commit | Line | Data |
---|---|---|
c50cd357 DB |
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 | int mac_len = skb->mac_len; | |
32 | __be16 protocol = skb->protocol; | |
33 | int tnl_hlen; | |
34 | bool csum; | |
35 | ||
36 | if (unlikely(skb_shinfo(skb)->gso_type & | |
37 | ~(SKB_GSO_TCPV4 | | |
38 | SKB_GSO_TCPV6 | | |
39 | SKB_GSO_UDP | | |
40 | SKB_GSO_DODGY | | |
41 | SKB_GSO_TCP_ECN | | |
42 | SKB_GSO_GRE))) | |
43 | goto out; | |
44 | ||
45 | if (unlikely(!pskb_may_pull(skb, sizeof(*greh)))) | |
46 | goto out; | |
47 | ||
48 | greh = (struct gre_base_hdr *)skb_transport_header(skb); | |
49 | ||
50 | if (greh->flags & GRE_KEY) | |
51 | ghl += GRE_HEADER_SECTION; | |
52 | if (greh->flags & GRE_SEQ) | |
53 | ghl += GRE_HEADER_SECTION; | |
54 | if (greh->flags & GRE_CSUM) { | |
55 | ghl += GRE_HEADER_SECTION; | |
56 | csum = true; | |
57 | } else | |
58 | csum = false; | |
59 | ||
60 | /* setup inner skb. */ | |
61 | skb->protocol = greh->protocol; | |
62 | skb->encapsulation = 0; | |
63 | ||
64 | if (unlikely(!pskb_may_pull(skb, ghl))) | |
65 | goto out; | |
66 | ||
67 | __skb_pull(skb, ghl); | |
68 | skb_reset_mac_header(skb); | |
69 | skb_set_network_header(skb, skb_inner_network_offset(skb)); | |
70 | skb->mac_len = skb_inner_network_offset(skb); | |
71 | ||
72 | /* segment inner packet. */ | |
73 | enc_features = skb->dev->hw_enc_features & netif_skb_features(skb); | |
74 | segs = skb_mac_gso_segment(skb, enc_features); | |
75 | if (!segs || IS_ERR(segs)) | |
76 | goto out; | |
77 | ||
78 | skb = segs; | |
79 | tnl_hlen = skb_tnl_header_len(skb); | |
80 | do { | |
81 | __skb_push(skb, ghl); | |
82 | if (csum) { | |
83 | __be32 *pcsum; | |
84 | ||
85 | if (skb_has_shared_frag(skb)) { | |
86 | int err; | |
87 | ||
88 | err = __skb_linearize(skb); | |
89 | if (err) { | |
0c1072ae | 90 | kfree_skb_list(segs); |
c50cd357 DB |
91 | segs = ERR_PTR(err); |
92 | goto out; | |
93 | } | |
94 | } | |
95 | ||
96 | greh = (struct gre_base_hdr *)(skb->data); | |
97 | pcsum = (__be32 *)(greh + 1); | |
98 | *pcsum = 0; | |
99 | *(__sum16 *)pcsum = csum_fold(skb_checksum(skb, 0, skb->len, 0)); | |
100 | } | |
101 | __skb_push(skb, tnl_hlen - ghl); | |
102 | ||
103 | skb_reset_mac_header(skb); | |
104 | skb_set_network_header(skb, mac_len); | |
105 | skb->mac_len = mac_len; | |
106 | skb->protocol = protocol; | |
107 | } while ((skb = skb->next)); | |
108 | out: | |
109 | return segs; | |
110 | } | |
111 | ||
112 | static const struct net_offload gre_offload = { | |
113 | .callbacks = { | |
114 | .gso_send_check = gre_gso_send_check, | |
115 | .gso_segment = gre_gso_segment, | |
116 | }, | |
117 | }; | |
118 | ||
119 | int __init gre_offload_init(void) | |
120 | { | |
121 | return inet_add_offload(&gre_offload, IPPROTO_GRE); | |
122 | } | |
123 | ||
124 | void __exit gre_offload_exit(void) | |
125 | { | |
126 | inet_del_offload(&gre_offload, IPPROTO_GRE); | |
127 | } |