Commit c50cd357887acf9fd7af3a5d492911bd825555a2
Committed by
David S. Miller
1 parent
419076f59f
Exists in
smarc-imx_3.14.28_1.0.0_ga
and in
1 other branch
net: gre: move GSO functions to gre_offload
Similarly to TCP/UDP offloading, move all related GRE functions to gre_offload.c to make things more explicit and similar to the rest of the code. Suggested-by: Eric Dumazet <eric.dumazet@gmail.com> Signed-off-by: Daniel Borkmann <dborkman@redhat.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Showing 5 changed files with 546 additions and 514 deletions Side-by-side Diff
include/net/gre.h
... | ... | @@ -32,6 +32,10 @@ |
32 | 32 | |
33 | 33 | int gre_cisco_register(struct gre_cisco_protocol *proto); |
34 | 34 | int gre_cisco_unregister(struct gre_cisco_protocol *proto); |
35 | + | |
36 | +int gre_offload_init(void); | |
37 | +void gre_offload_exit(void); | |
38 | + | |
35 | 39 | void gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi, |
36 | 40 | int hdr_len); |
37 | 41 | struct sk_buff *gre_handle_offloads(struct sk_buff *skb, bool gre_csum); |
net/ipv4/Makefile
... | ... | @@ -19,6 +19,7 @@ |
19 | 19 | obj-$(CONFIG_IP_MULTIPLE_TABLES) += fib_rules.o |
20 | 20 | obj-$(CONFIG_IP_MROUTE) += ipmr.o |
21 | 21 | obj-$(CONFIG_NET_IPIP) += ipip.o |
22 | +gre-y := gre_demux.o gre_offload.o | |
22 | 23 | obj-$(CONFIG_NET_IPGRE_DEMUX) += gre.o |
23 | 24 | obj-$(CONFIG_NET_IPGRE) += ip_gre.o |
24 | 25 | obj-$(CONFIG_NET_IPVTI) += ip_vti.o |
net/ipv4/gre.c
1 | -/* | |
2 | - * GRE over IPv4 demultiplexer driver | |
3 | - * | |
4 | - * Authors: Dmitry Kozlov (xeb@mail.ru) | |
5 | - * | |
6 | - * This program is free software; you can redistribute it and/or | |
7 | - * modify it under the terms of the GNU General Public License | |
8 | - * as published by the Free Software Foundation; either version | |
9 | - * 2 of the License, or (at your option) any later version. | |
10 | - * | |
11 | - */ | |
12 | - | |
13 | -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | - | |
15 | -#include <linux/module.h> | |
16 | -#include <linux/if.h> | |
17 | -#include <linux/icmp.h> | |
18 | -#include <linux/kernel.h> | |
19 | -#include <linux/kmod.h> | |
20 | -#include <linux/skbuff.h> | |
21 | -#include <linux/in.h> | |
22 | -#include <linux/ip.h> | |
23 | -#include <linux/netdevice.h> | |
24 | -#include <linux/if_tunnel.h> | |
25 | -#include <linux/spinlock.h> | |
26 | -#include <net/protocol.h> | |
27 | -#include <net/gre.h> | |
28 | - | |
29 | -#include <net/icmp.h> | |
30 | -#include <net/route.h> | |
31 | -#include <net/xfrm.h> | |
32 | - | |
33 | -static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; | |
34 | -static struct gre_cisco_protocol __rcu *gre_cisco_proto_list[GRE_IP_PROTO_MAX]; | |
35 | - | |
36 | -int gre_add_protocol(const struct gre_protocol *proto, u8 version) | |
37 | -{ | |
38 | - if (version >= GREPROTO_MAX) | |
39 | - return -EINVAL; | |
40 | - | |
41 | - return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ? | |
42 | - 0 : -EBUSY; | |
43 | -} | |
44 | -EXPORT_SYMBOL_GPL(gre_add_protocol); | |
45 | - | |
46 | -int gre_del_protocol(const struct gre_protocol *proto, u8 version) | |
47 | -{ | |
48 | - int ret; | |
49 | - | |
50 | - if (version >= GREPROTO_MAX) | |
51 | - return -EINVAL; | |
52 | - | |
53 | - ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ? | |
54 | - 0 : -EBUSY; | |
55 | - | |
56 | - if (ret) | |
57 | - return ret; | |
58 | - | |
59 | - synchronize_rcu(); | |
60 | - return 0; | |
61 | -} | |
62 | -EXPORT_SYMBOL_GPL(gre_del_protocol); | |
63 | - | |
64 | -void gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi, | |
65 | - int hdr_len) | |
66 | -{ | |
67 | - struct gre_base_hdr *greh; | |
68 | - | |
69 | - skb_push(skb, hdr_len); | |
70 | - | |
71 | - greh = (struct gre_base_hdr *)skb->data; | |
72 | - greh->flags = tnl_flags_to_gre_flags(tpi->flags); | |
73 | - greh->protocol = tpi->proto; | |
74 | - | |
75 | - if (tpi->flags&(TUNNEL_KEY|TUNNEL_CSUM|TUNNEL_SEQ)) { | |
76 | - __be32 *ptr = (__be32 *)(((u8 *)greh) + hdr_len - 4); | |
77 | - | |
78 | - if (tpi->flags&TUNNEL_SEQ) { | |
79 | - *ptr = tpi->seq; | |
80 | - ptr--; | |
81 | - } | |
82 | - if (tpi->flags&TUNNEL_KEY) { | |
83 | - *ptr = tpi->key; | |
84 | - ptr--; | |
85 | - } | |
86 | - if (tpi->flags&TUNNEL_CSUM && | |
87 | - !(skb_shinfo(skb)->gso_type & SKB_GSO_GRE)) { | |
88 | - *ptr = 0; | |
89 | - *(__sum16 *)ptr = csum_fold(skb_checksum(skb, 0, | |
90 | - skb->len, 0)); | |
91 | - } | |
92 | - } | |
93 | -} | |
94 | -EXPORT_SYMBOL_GPL(gre_build_header); | |
95 | - | |
96 | -struct sk_buff *gre_handle_offloads(struct sk_buff *skb, bool gre_csum) | |
97 | -{ | |
98 | - int err; | |
99 | - | |
100 | - if (likely(!skb->encapsulation)) { | |
101 | - skb_reset_inner_headers(skb); | |
102 | - skb->encapsulation = 1; | |
103 | - } | |
104 | - | |
105 | - if (skb_is_gso(skb)) { | |
106 | - err = skb_unclone(skb, GFP_ATOMIC); | |
107 | - if (unlikely(err)) | |
108 | - goto error; | |
109 | - skb_shinfo(skb)->gso_type |= SKB_GSO_GRE; | |
110 | - return skb; | |
111 | - } else if (skb->ip_summed == CHECKSUM_PARTIAL && gre_csum) { | |
112 | - err = skb_checksum_help(skb); | |
113 | - if (unlikely(err)) | |
114 | - goto error; | |
115 | - } else if (skb->ip_summed != CHECKSUM_PARTIAL) | |
116 | - skb->ip_summed = CHECKSUM_NONE; | |
117 | - | |
118 | - return skb; | |
119 | -error: | |
120 | - kfree_skb(skb); | |
121 | - return ERR_PTR(err); | |
122 | -} | |
123 | -EXPORT_SYMBOL_GPL(gre_handle_offloads); | |
124 | - | |
125 | -static __sum16 check_checksum(struct sk_buff *skb) | |
126 | -{ | |
127 | - __sum16 csum = 0; | |
128 | - | |
129 | - switch (skb->ip_summed) { | |
130 | - case CHECKSUM_COMPLETE: | |
131 | - csum = csum_fold(skb->csum); | |
132 | - | |
133 | - if (!csum) | |
134 | - break; | |
135 | - /* Fall through. */ | |
136 | - | |
137 | - case CHECKSUM_NONE: | |
138 | - skb->csum = 0; | |
139 | - csum = __skb_checksum_complete(skb); | |
140 | - skb->ip_summed = CHECKSUM_COMPLETE; | |
141 | - break; | |
142 | - } | |
143 | - | |
144 | - return csum; | |
145 | -} | |
146 | - | |
147 | -static int parse_gre_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, | |
148 | - bool *csum_err) | |
149 | -{ | |
150 | - unsigned int ip_hlen = ip_hdrlen(skb); | |
151 | - const struct gre_base_hdr *greh; | |
152 | - __be32 *options; | |
153 | - int hdr_len; | |
154 | - | |
155 | - if (unlikely(!pskb_may_pull(skb, sizeof(struct gre_base_hdr)))) | |
156 | - return -EINVAL; | |
157 | - | |
158 | - greh = (struct gre_base_hdr *)(skb_network_header(skb) + ip_hlen); | |
159 | - if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING))) | |
160 | - return -EINVAL; | |
161 | - | |
162 | - tpi->flags = gre_flags_to_tnl_flags(greh->flags); | |
163 | - hdr_len = ip_gre_calc_hlen(tpi->flags); | |
164 | - | |
165 | - if (!pskb_may_pull(skb, hdr_len)) | |
166 | - return -EINVAL; | |
167 | - | |
168 | - greh = (struct gre_base_hdr *)(skb_network_header(skb) + ip_hlen); | |
169 | - tpi->proto = greh->protocol; | |
170 | - | |
171 | - options = (__be32 *)(greh + 1); | |
172 | - if (greh->flags & GRE_CSUM) { | |
173 | - if (check_checksum(skb)) { | |
174 | - *csum_err = true; | |
175 | - return -EINVAL; | |
176 | - } | |
177 | - options++; | |
178 | - } | |
179 | - | |
180 | - if (greh->flags & GRE_KEY) { | |
181 | - tpi->key = *options; | |
182 | - options++; | |
183 | - } else | |
184 | - tpi->key = 0; | |
185 | - | |
186 | - if (unlikely(greh->flags & GRE_SEQ)) { | |
187 | - tpi->seq = *options; | |
188 | - options++; | |
189 | - } else | |
190 | - tpi->seq = 0; | |
191 | - | |
192 | - /* WCCP version 1 and 2 protocol decoding. | |
193 | - * - Change protocol to IP | |
194 | - * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header | |
195 | - */ | |
196 | - if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) { | |
197 | - tpi->proto = htons(ETH_P_IP); | |
198 | - if ((*(u8 *)options & 0xF0) != 0x40) { | |
199 | - hdr_len += 4; | |
200 | - if (!pskb_may_pull(skb, hdr_len)) | |
201 | - return -EINVAL; | |
202 | - } | |
203 | - } | |
204 | - | |
205 | - return iptunnel_pull_header(skb, hdr_len, tpi->proto); | |
206 | -} | |
207 | - | |
208 | -static int gre_cisco_rcv(struct sk_buff *skb) | |
209 | -{ | |
210 | - struct tnl_ptk_info tpi; | |
211 | - int i; | |
212 | - bool csum_err = false; | |
213 | - | |
214 | - if (parse_gre_header(skb, &tpi, &csum_err) < 0) | |
215 | - goto drop; | |
216 | - | |
217 | - rcu_read_lock(); | |
218 | - for (i = 0; i < GRE_IP_PROTO_MAX; i++) { | |
219 | - struct gre_cisco_protocol *proto; | |
220 | - int ret; | |
221 | - | |
222 | - proto = rcu_dereference(gre_cisco_proto_list[i]); | |
223 | - if (!proto) | |
224 | - continue; | |
225 | - ret = proto->handler(skb, &tpi); | |
226 | - if (ret == PACKET_RCVD) { | |
227 | - rcu_read_unlock(); | |
228 | - return 0; | |
229 | - } | |
230 | - } | |
231 | - rcu_read_unlock(); | |
232 | - | |
233 | - icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); | |
234 | -drop: | |
235 | - kfree_skb(skb); | |
236 | - return 0; | |
237 | -} | |
238 | - | |
239 | -static void gre_cisco_err(struct sk_buff *skb, u32 info) | |
240 | -{ | |
241 | - /* All the routers (except for Linux) return only | |
242 | - * 8 bytes of packet payload. It means, that precise relaying of | |
243 | - * ICMP in the real Internet is absolutely infeasible. | |
244 | - * | |
245 | - * Moreover, Cisco "wise men" put GRE key to the third word | |
246 | - * in GRE header. It makes impossible maintaining even soft | |
247 | - * state for keyed | |
248 | - * GRE tunnels with enabled checksum. Tell them "thank you". | |
249 | - * | |
250 | - * Well, I wonder, rfc1812 was written by Cisco employee, | |
251 | - * what the hell these idiots break standards established | |
252 | - * by themselves??? | |
253 | - */ | |
254 | - | |
255 | - const int type = icmp_hdr(skb)->type; | |
256 | - const int code = icmp_hdr(skb)->code; | |
257 | - struct tnl_ptk_info tpi; | |
258 | - bool csum_err = false; | |
259 | - int i; | |
260 | - | |
261 | - if (parse_gre_header(skb, &tpi, &csum_err)) { | |
262 | - if (!csum_err) /* ignore csum errors. */ | |
263 | - return; | |
264 | - } | |
265 | - | |
266 | - if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) { | |
267 | - ipv4_update_pmtu(skb, dev_net(skb->dev), info, | |
268 | - skb->dev->ifindex, 0, IPPROTO_GRE, 0); | |
269 | - return; | |
270 | - } | |
271 | - if (type == ICMP_REDIRECT) { | |
272 | - ipv4_redirect(skb, dev_net(skb->dev), skb->dev->ifindex, 0, | |
273 | - IPPROTO_GRE, 0); | |
274 | - return; | |
275 | - } | |
276 | - | |
277 | - rcu_read_lock(); | |
278 | - for (i = 0; i < GRE_IP_PROTO_MAX; i++) { | |
279 | - struct gre_cisco_protocol *proto; | |
280 | - | |
281 | - proto = rcu_dereference(gre_cisco_proto_list[i]); | |
282 | - if (!proto) | |
283 | - continue; | |
284 | - | |
285 | - if (proto->err_handler(skb, info, &tpi) == PACKET_RCVD) | |
286 | - goto out; | |
287 | - | |
288 | - } | |
289 | -out: | |
290 | - rcu_read_unlock(); | |
291 | -} | |
292 | - | |
293 | -static int gre_rcv(struct sk_buff *skb) | |
294 | -{ | |
295 | - const struct gre_protocol *proto; | |
296 | - u8 ver; | |
297 | - int ret; | |
298 | - | |
299 | - if (!pskb_may_pull(skb, 12)) | |
300 | - goto drop; | |
301 | - | |
302 | - ver = skb->data[1]&0x7f; | |
303 | - if (ver >= GREPROTO_MAX) | |
304 | - goto drop; | |
305 | - | |
306 | - rcu_read_lock(); | |
307 | - proto = rcu_dereference(gre_proto[ver]); | |
308 | - if (!proto || !proto->handler) | |
309 | - goto drop_unlock; | |
310 | - ret = proto->handler(skb); | |
311 | - rcu_read_unlock(); | |
312 | - return ret; | |
313 | - | |
314 | -drop_unlock: | |
315 | - rcu_read_unlock(); | |
316 | -drop: | |
317 | - kfree_skb(skb); | |
318 | - return NET_RX_DROP; | |
319 | -} | |
320 | - | |
321 | -static void gre_err(struct sk_buff *skb, u32 info) | |
322 | -{ | |
323 | - const struct gre_protocol *proto; | |
324 | - const struct iphdr *iph = (const struct iphdr *)skb->data; | |
325 | - u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f; | |
326 | - | |
327 | - if (ver >= GREPROTO_MAX) | |
328 | - return; | |
329 | - | |
330 | - rcu_read_lock(); | |
331 | - proto = rcu_dereference(gre_proto[ver]); | |
332 | - if (proto && proto->err_handler) | |
333 | - proto->err_handler(skb, info); | |
334 | - rcu_read_unlock(); | |
335 | -} | |
336 | - | |
337 | -static struct sk_buff *gre_gso_segment(struct sk_buff *skb, | |
338 | - netdev_features_t features) | |
339 | -{ | |
340 | - struct sk_buff *segs = ERR_PTR(-EINVAL); | |
341 | - netdev_features_t enc_features; | |
342 | - int ghl = GRE_HEADER_SECTION; | |
343 | - struct gre_base_hdr *greh; | |
344 | - int mac_len = skb->mac_len; | |
345 | - __be16 protocol = skb->protocol; | |
346 | - int tnl_hlen; | |
347 | - bool csum; | |
348 | - | |
349 | - if (unlikely(skb_shinfo(skb)->gso_type & | |
350 | - ~(SKB_GSO_TCPV4 | | |
351 | - SKB_GSO_TCPV6 | | |
352 | - SKB_GSO_UDP | | |
353 | - SKB_GSO_DODGY | | |
354 | - SKB_GSO_TCP_ECN | | |
355 | - SKB_GSO_GRE))) | |
356 | - goto out; | |
357 | - | |
358 | - if (unlikely(!pskb_may_pull(skb, sizeof(*greh)))) | |
359 | - goto out; | |
360 | - | |
361 | - greh = (struct gre_base_hdr *)skb_transport_header(skb); | |
362 | - | |
363 | - if (greh->flags & GRE_KEY) | |
364 | - ghl += GRE_HEADER_SECTION; | |
365 | - if (greh->flags & GRE_SEQ) | |
366 | - ghl += GRE_HEADER_SECTION; | |
367 | - if (greh->flags & GRE_CSUM) { | |
368 | - ghl += GRE_HEADER_SECTION; | |
369 | - csum = true; | |
370 | - } else | |
371 | - csum = false; | |
372 | - | |
373 | - /* setup inner skb. */ | |
374 | - skb->protocol = greh->protocol; | |
375 | - skb->encapsulation = 0; | |
376 | - | |
377 | - if (unlikely(!pskb_may_pull(skb, ghl))) | |
378 | - goto out; | |
379 | - __skb_pull(skb, ghl); | |
380 | - skb_reset_mac_header(skb); | |
381 | - skb_set_network_header(skb, skb_inner_network_offset(skb)); | |
382 | - skb->mac_len = skb_inner_network_offset(skb); | |
383 | - | |
384 | - /* segment inner packet. */ | |
385 | - enc_features = skb->dev->hw_enc_features & netif_skb_features(skb); | |
386 | - segs = skb_mac_gso_segment(skb, enc_features); | |
387 | - if (!segs || IS_ERR(segs)) | |
388 | - goto out; | |
389 | - | |
390 | - skb = segs; | |
391 | - tnl_hlen = skb_tnl_header_len(skb); | |
392 | - do { | |
393 | - __skb_push(skb, ghl); | |
394 | - if (csum) { | |
395 | - __be32 *pcsum; | |
396 | - | |
397 | - if (skb_has_shared_frag(skb)) { | |
398 | - int err; | |
399 | - | |
400 | - err = __skb_linearize(skb); | |
401 | - if (err) { | |
402 | - kfree_skb(segs); | |
403 | - segs = ERR_PTR(err); | |
404 | - goto out; | |
405 | - } | |
406 | - } | |
407 | - | |
408 | - greh = (struct gre_base_hdr *)(skb->data); | |
409 | - pcsum = (__be32 *)(greh + 1); | |
410 | - *pcsum = 0; | |
411 | - *(__sum16 *)pcsum = csum_fold(skb_checksum(skb, 0, skb->len, 0)); | |
412 | - } | |
413 | - __skb_push(skb, tnl_hlen - ghl); | |
414 | - | |
415 | - skb_reset_mac_header(skb); | |
416 | - skb_set_network_header(skb, mac_len); | |
417 | - skb->mac_len = mac_len; | |
418 | - skb->protocol = protocol; | |
419 | - } while ((skb = skb->next)); | |
420 | -out: | |
421 | - return segs; | |
422 | -} | |
423 | - | |
424 | -static int gre_gso_send_check(struct sk_buff *skb) | |
425 | -{ | |
426 | - if (!skb->encapsulation) | |
427 | - return -EINVAL; | |
428 | - return 0; | |
429 | -} | |
430 | - | |
431 | -static const struct net_protocol net_gre_protocol = { | |
432 | - .handler = gre_rcv, | |
433 | - .err_handler = gre_err, | |
434 | - .netns_ok = 1, | |
435 | -}; | |
436 | - | |
437 | -static const struct net_offload gre_offload = { | |
438 | - .callbacks = { | |
439 | - .gso_send_check = gre_gso_send_check, | |
440 | - .gso_segment = gre_gso_segment, | |
441 | - }, | |
442 | -}; | |
443 | - | |
444 | -static const struct gre_protocol ipgre_protocol = { | |
445 | - .handler = gre_cisco_rcv, | |
446 | - .err_handler = gre_cisco_err, | |
447 | -}; | |
448 | - | |
449 | -int gre_cisco_register(struct gre_cisco_protocol *newp) | |
450 | -{ | |
451 | - struct gre_cisco_protocol **proto = (struct gre_cisco_protocol **) | |
452 | - &gre_cisco_proto_list[newp->priority]; | |
453 | - | |
454 | - return (cmpxchg(proto, NULL, newp) == NULL) ? 0 : -EBUSY; | |
455 | -} | |
456 | -EXPORT_SYMBOL_GPL(gre_cisco_register); | |
457 | - | |
458 | -int gre_cisco_unregister(struct gre_cisco_protocol *del_proto) | |
459 | -{ | |
460 | - struct gre_cisco_protocol **proto = (struct gre_cisco_protocol **) | |
461 | - &gre_cisco_proto_list[del_proto->priority]; | |
462 | - int ret; | |
463 | - | |
464 | - ret = (cmpxchg(proto, del_proto, NULL) == del_proto) ? 0 : -EINVAL; | |
465 | - | |
466 | - if (ret) | |
467 | - return ret; | |
468 | - | |
469 | - synchronize_net(); | |
470 | - return 0; | |
471 | -} | |
472 | -EXPORT_SYMBOL_GPL(gre_cisco_unregister); | |
473 | - | |
474 | -static int __init gre_init(void) | |
475 | -{ | |
476 | - pr_info("GRE over IPv4 demultiplexor driver\n"); | |
477 | - | |
478 | - if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) { | |
479 | - pr_err("can't add protocol\n"); | |
480 | - goto err; | |
481 | - } | |
482 | - | |
483 | - if (gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0) { | |
484 | - pr_info("%s: can't add ipgre handler\n", __func__); | |
485 | - goto err_gre; | |
486 | - } | |
487 | - | |
488 | - if (inet_add_offload(&gre_offload, IPPROTO_GRE)) { | |
489 | - pr_err("can't add protocol offload\n"); | |
490 | - goto err_gso; | |
491 | - } | |
492 | - | |
493 | - return 0; | |
494 | -err_gso: | |
495 | - gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO); | |
496 | -err_gre: | |
497 | - inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | |
498 | -err: | |
499 | - return -EAGAIN; | |
500 | -} | |
501 | - | |
502 | -static void __exit gre_exit(void) | |
503 | -{ | |
504 | - inet_del_offload(&gre_offload, IPPROTO_GRE); | |
505 | - gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO); | |
506 | - inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | |
507 | -} | |
508 | - | |
509 | -module_init(gre_init); | |
510 | -module_exit(gre_exit); | |
511 | - | |
512 | -MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver"); | |
513 | -MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)"); | |
514 | -MODULE_LICENSE("GPL"); |
net/ipv4/gre_demux.c
1 | +/* | |
2 | + * GRE over IPv4 demultiplexer driver | |
3 | + * | |
4 | + * Authors: Dmitry Kozlov (xeb@mail.ru) | |
5 | + * | |
6 | + * This program is free software; you can redistribute it and/or | |
7 | + * modify it under the terms of the GNU General Public License | |
8 | + * as published by the Free Software Foundation; either version | |
9 | + * 2 of the License, or (at your option) any later version. | |
10 | + * | |
11 | + */ | |
12 | + | |
13 | +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | |
14 | + | |
15 | +#include <linux/module.h> | |
16 | +#include <linux/if.h> | |
17 | +#include <linux/icmp.h> | |
18 | +#include <linux/kernel.h> | |
19 | +#include <linux/kmod.h> | |
20 | +#include <linux/skbuff.h> | |
21 | +#include <linux/in.h> | |
22 | +#include <linux/ip.h> | |
23 | +#include <linux/netdevice.h> | |
24 | +#include <linux/if_tunnel.h> | |
25 | +#include <linux/spinlock.h> | |
26 | +#include <net/protocol.h> | |
27 | +#include <net/gre.h> | |
28 | + | |
29 | +#include <net/icmp.h> | |
30 | +#include <net/route.h> | |
31 | +#include <net/xfrm.h> | |
32 | + | |
33 | +static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; | |
34 | +static struct gre_cisco_protocol __rcu *gre_cisco_proto_list[GRE_IP_PROTO_MAX]; | |
35 | + | |
36 | +int gre_add_protocol(const struct gre_protocol *proto, u8 version) | |
37 | +{ | |
38 | + if (version >= GREPROTO_MAX) | |
39 | + return -EINVAL; | |
40 | + | |
41 | + return (cmpxchg((const struct gre_protocol **)&gre_proto[version], NULL, proto) == NULL) ? | |
42 | + 0 : -EBUSY; | |
43 | +} | |
44 | +EXPORT_SYMBOL_GPL(gre_add_protocol); | |
45 | + | |
46 | +int gre_del_protocol(const struct gre_protocol *proto, u8 version) | |
47 | +{ | |
48 | + int ret; | |
49 | + | |
50 | + if (version >= GREPROTO_MAX) | |
51 | + return -EINVAL; | |
52 | + | |
53 | + ret = (cmpxchg((const struct gre_protocol **)&gre_proto[version], proto, NULL) == proto) ? | |
54 | + 0 : -EBUSY; | |
55 | + | |
56 | + if (ret) | |
57 | + return ret; | |
58 | + | |
59 | + synchronize_rcu(); | |
60 | + return 0; | |
61 | +} | |
62 | +EXPORT_SYMBOL_GPL(gre_del_protocol); | |
63 | + | |
64 | +void gre_build_header(struct sk_buff *skb, const struct tnl_ptk_info *tpi, | |
65 | + int hdr_len) | |
66 | +{ | |
67 | + struct gre_base_hdr *greh; | |
68 | + | |
69 | + skb_push(skb, hdr_len); | |
70 | + | |
71 | + greh = (struct gre_base_hdr *)skb->data; | |
72 | + greh->flags = tnl_flags_to_gre_flags(tpi->flags); | |
73 | + greh->protocol = tpi->proto; | |
74 | + | |
75 | + if (tpi->flags&(TUNNEL_KEY|TUNNEL_CSUM|TUNNEL_SEQ)) { | |
76 | + __be32 *ptr = (__be32 *)(((u8 *)greh) + hdr_len - 4); | |
77 | + | |
78 | + if (tpi->flags&TUNNEL_SEQ) { | |
79 | + *ptr = tpi->seq; | |
80 | + ptr--; | |
81 | + } | |
82 | + if (tpi->flags&TUNNEL_KEY) { | |
83 | + *ptr = tpi->key; | |
84 | + ptr--; | |
85 | + } | |
86 | + if (tpi->flags&TUNNEL_CSUM && | |
87 | + !(skb_shinfo(skb)->gso_type & SKB_GSO_GRE)) { | |
88 | + *ptr = 0; | |
89 | + *(__sum16 *)ptr = csum_fold(skb_checksum(skb, 0, | |
90 | + skb->len, 0)); | |
91 | + } | |
92 | + } | |
93 | +} | |
94 | +EXPORT_SYMBOL_GPL(gre_build_header); | |
95 | + | |
96 | +struct sk_buff *gre_handle_offloads(struct sk_buff *skb, bool gre_csum) | |
97 | +{ | |
98 | + int err; | |
99 | + | |
100 | + if (likely(!skb->encapsulation)) { | |
101 | + skb_reset_inner_headers(skb); | |
102 | + skb->encapsulation = 1; | |
103 | + } | |
104 | + | |
105 | + if (skb_is_gso(skb)) { | |
106 | + err = skb_unclone(skb, GFP_ATOMIC); | |
107 | + if (unlikely(err)) | |
108 | + goto error; | |
109 | + skb_shinfo(skb)->gso_type |= SKB_GSO_GRE; | |
110 | + return skb; | |
111 | + } else if (skb->ip_summed == CHECKSUM_PARTIAL && gre_csum) { | |
112 | + err = skb_checksum_help(skb); | |
113 | + if (unlikely(err)) | |
114 | + goto error; | |
115 | + } else if (skb->ip_summed != CHECKSUM_PARTIAL) | |
116 | + skb->ip_summed = CHECKSUM_NONE; | |
117 | + | |
118 | + return skb; | |
119 | +error: | |
120 | + kfree_skb(skb); | |
121 | + return ERR_PTR(err); | |
122 | +} | |
123 | +EXPORT_SYMBOL_GPL(gre_handle_offloads); | |
124 | + | |
125 | +static __sum16 check_checksum(struct sk_buff *skb) | |
126 | +{ | |
127 | + __sum16 csum = 0; | |
128 | + | |
129 | + switch (skb->ip_summed) { | |
130 | + case CHECKSUM_COMPLETE: | |
131 | + csum = csum_fold(skb->csum); | |
132 | + | |
133 | + if (!csum) | |
134 | + break; | |
135 | + /* Fall through. */ | |
136 | + | |
137 | + case CHECKSUM_NONE: | |
138 | + skb->csum = 0; | |
139 | + csum = __skb_checksum_complete(skb); | |
140 | + skb->ip_summed = CHECKSUM_COMPLETE; | |
141 | + break; | |
142 | + } | |
143 | + | |
144 | + return csum; | |
145 | +} | |
146 | + | |
147 | +static int parse_gre_header(struct sk_buff *skb, struct tnl_ptk_info *tpi, | |
148 | + bool *csum_err) | |
149 | +{ | |
150 | + unsigned int ip_hlen = ip_hdrlen(skb); | |
151 | + const struct gre_base_hdr *greh; | |
152 | + __be32 *options; | |
153 | + int hdr_len; | |
154 | + | |
155 | + if (unlikely(!pskb_may_pull(skb, sizeof(struct gre_base_hdr)))) | |
156 | + return -EINVAL; | |
157 | + | |
158 | + greh = (struct gre_base_hdr *)(skb_network_header(skb) + ip_hlen); | |
159 | + if (unlikely(greh->flags & (GRE_VERSION | GRE_ROUTING))) | |
160 | + return -EINVAL; | |
161 | + | |
162 | + tpi->flags = gre_flags_to_tnl_flags(greh->flags); | |
163 | + hdr_len = ip_gre_calc_hlen(tpi->flags); | |
164 | + | |
165 | + if (!pskb_may_pull(skb, hdr_len)) | |
166 | + return -EINVAL; | |
167 | + | |
168 | + greh = (struct gre_base_hdr *)(skb_network_header(skb) + ip_hlen); | |
169 | + tpi->proto = greh->protocol; | |
170 | + | |
171 | + options = (__be32 *)(greh + 1); | |
172 | + if (greh->flags & GRE_CSUM) { | |
173 | + if (check_checksum(skb)) { | |
174 | + *csum_err = true; | |
175 | + return -EINVAL; | |
176 | + } | |
177 | + options++; | |
178 | + } | |
179 | + | |
180 | + if (greh->flags & GRE_KEY) { | |
181 | + tpi->key = *options; | |
182 | + options++; | |
183 | + } else | |
184 | + tpi->key = 0; | |
185 | + | |
186 | + if (unlikely(greh->flags & GRE_SEQ)) { | |
187 | + tpi->seq = *options; | |
188 | + options++; | |
189 | + } else | |
190 | + tpi->seq = 0; | |
191 | + | |
192 | + /* WCCP version 1 and 2 protocol decoding. | |
193 | + * - Change protocol to IP | |
194 | + * - When dealing with WCCPv2, Skip extra 4 bytes in GRE header | |
195 | + */ | |
196 | + if (greh->flags == 0 && tpi->proto == htons(ETH_P_WCCP)) { | |
197 | + tpi->proto = htons(ETH_P_IP); | |
198 | + if ((*(u8 *)options & 0xF0) != 0x40) { | |
199 | + hdr_len += 4; | |
200 | + if (!pskb_may_pull(skb, hdr_len)) | |
201 | + return -EINVAL; | |
202 | + } | |
203 | + } | |
204 | + | |
205 | + return iptunnel_pull_header(skb, hdr_len, tpi->proto); | |
206 | +} | |
207 | + | |
208 | +static int gre_cisco_rcv(struct sk_buff *skb) | |
209 | +{ | |
210 | + struct tnl_ptk_info tpi; | |
211 | + int i; | |
212 | + bool csum_err = false; | |
213 | + | |
214 | + if (parse_gre_header(skb, &tpi, &csum_err) < 0) | |
215 | + goto drop; | |
216 | + | |
217 | + rcu_read_lock(); | |
218 | + for (i = 0; i < GRE_IP_PROTO_MAX; i++) { | |
219 | + struct gre_cisco_protocol *proto; | |
220 | + int ret; | |
221 | + | |
222 | + proto = rcu_dereference(gre_cisco_proto_list[i]); | |
223 | + if (!proto) | |
224 | + continue; | |
225 | + ret = proto->handler(skb, &tpi); | |
226 | + if (ret == PACKET_RCVD) { | |
227 | + rcu_read_unlock(); | |
228 | + return 0; | |
229 | + } | |
230 | + } | |
231 | + rcu_read_unlock(); | |
232 | + | |
233 | + icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0); | |
234 | +drop: | |
235 | + kfree_skb(skb); | |
236 | + return 0; | |
237 | +} | |
238 | + | |
239 | +static void gre_cisco_err(struct sk_buff *skb, u32 info) | |
240 | +{ | |
241 | + /* All the routers (except for Linux) return only | |
242 | + * 8 bytes of packet payload. It means, that precise relaying of | |
243 | + * ICMP in the real Internet is absolutely infeasible. | |
244 | + * | |
245 | + * Moreover, Cisco "wise men" put GRE key to the third word | |
246 | + * in GRE header. It makes impossible maintaining even soft | |
247 | + * state for keyed | |
248 | + * GRE tunnels with enabled checksum. Tell them "thank you". | |
249 | + * | |
250 | + * Well, I wonder, rfc1812 was written by Cisco employee, | |
251 | + * what the hell these idiots break standards established | |
252 | + * by themselves??? | |
253 | + */ | |
254 | + | |
255 | + const int type = icmp_hdr(skb)->type; | |
256 | + const int code = icmp_hdr(skb)->code; | |
257 | + struct tnl_ptk_info tpi; | |
258 | + bool csum_err = false; | |
259 | + int i; | |
260 | + | |
261 | + if (parse_gre_header(skb, &tpi, &csum_err)) { | |
262 | + if (!csum_err) /* ignore csum errors. */ | |
263 | + return; | |
264 | + } | |
265 | + | |
266 | + if (type == ICMP_DEST_UNREACH && code == ICMP_FRAG_NEEDED) { | |
267 | + ipv4_update_pmtu(skb, dev_net(skb->dev), info, | |
268 | + skb->dev->ifindex, 0, IPPROTO_GRE, 0); | |
269 | + return; | |
270 | + } | |
271 | + if (type == ICMP_REDIRECT) { | |
272 | + ipv4_redirect(skb, dev_net(skb->dev), skb->dev->ifindex, 0, | |
273 | + IPPROTO_GRE, 0); | |
274 | + return; | |
275 | + } | |
276 | + | |
277 | + rcu_read_lock(); | |
278 | + for (i = 0; i < GRE_IP_PROTO_MAX; i++) { | |
279 | + struct gre_cisco_protocol *proto; | |
280 | + | |
281 | + proto = rcu_dereference(gre_cisco_proto_list[i]); | |
282 | + if (!proto) | |
283 | + continue; | |
284 | + | |
285 | + if (proto->err_handler(skb, info, &tpi) == PACKET_RCVD) | |
286 | + goto out; | |
287 | + | |
288 | + } | |
289 | +out: | |
290 | + rcu_read_unlock(); | |
291 | +} | |
292 | + | |
293 | +static int gre_rcv(struct sk_buff *skb) | |
294 | +{ | |
295 | + const struct gre_protocol *proto; | |
296 | + u8 ver; | |
297 | + int ret; | |
298 | + | |
299 | + if (!pskb_may_pull(skb, 12)) | |
300 | + goto drop; | |
301 | + | |
302 | + ver = skb->data[1]&0x7f; | |
303 | + if (ver >= GREPROTO_MAX) | |
304 | + goto drop; | |
305 | + | |
306 | + rcu_read_lock(); | |
307 | + proto = rcu_dereference(gre_proto[ver]); | |
308 | + if (!proto || !proto->handler) | |
309 | + goto drop_unlock; | |
310 | + ret = proto->handler(skb); | |
311 | + rcu_read_unlock(); | |
312 | + return ret; | |
313 | + | |
314 | +drop_unlock: | |
315 | + rcu_read_unlock(); | |
316 | +drop: | |
317 | + kfree_skb(skb); | |
318 | + return NET_RX_DROP; | |
319 | +} | |
320 | + | |
321 | +static void gre_err(struct sk_buff *skb, u32 info) | |
322 | +{ | |
323 | + const struct gre_protocol *proto; | |
324 | + const struct iphdr *iph = (const struct iphdr *)skb->data; | |
325 | + u8 ver = skb->data[(iph->ihl<<2) + 1]&0x7f; | |
326 | + | |
327 | + if (ver >= GREPROTO_MAX) | |
328 | + return; | |
329 | + | |
330 | + rcu_read_lock(); | |
331 | + proto = rcu_dereference(gre_proto[ver]); | |
332 | + if (proto && proto->err_handler) | |
333 | + proto->err_handler(skb, info); | |
334 | + rcu_read_unlock(); | |
335 | +} | |
336 | + | |
337 | +static const struct net_protocol net_gre_protocol = { | |
338 | + .handler = gre_rcv, | |
339 | + .err_handler = gre_err, | |
340 | + .netns_ok = 1, | |
341 | +}; | |
342 | + | |
343 | +static const struct gre_protocol ipgre_protocol = { | |
344 | + .handler = gre_cisco_rcv, | |
345 | + .err_handler = gre_cisco_err, | |
346 | +}; | |
347 | + | |
348 | +int gre_cisco_register(struct gre_cisco_protocol *newp) | |
349 | +{ | |
350 | + struct gre_cisco_protocol **proto = (struct gre_cisco_protocol **) | |
351 | + &gre_cisco_proto_list[newp->priority]; | |
352 | + | |
353 | + return (cmpxchg(proto, NULL, newp) == NULL) ? 0 : -EBUSY; | |
354 | +} | |
355 | +EXPORT_SYMBOL_GPL(gre_cisco_register); | |
356 | + | |
357 | +int gre_cisco_unregister(struct gre_cisco_protocol *del_proto) | |
358 | +{ | |
359 | + struct gre_cisco_protocol **proto = (struct gre_cisco_protocol **) | |
360 | + &gre_cisco_proto_list[del_proto->priority]; | |
361 | + int ret; | |
362 | + | |
363 | + ret = (cmpxchg(proto, del_proto, NULL) == del_proto) ? 0 : -EINVAL; | |
364 | + | |
365 | + if (ret) | |
366 | + return ret; | |
367 | + | |
368 | + synchronize_net(); | |
369 | + return 0; | |
370 | +} | |
371 | +EXPORT_SYMBOL_GPL(gre_cisco_unregister); | |
372 | + | |
373 | +static int __init gre_init(void) | |
374 | +{ | |
375 | + pr_info("GRE over IPv4 demultiplexor driver\n"); | |
376 | + | |
377 | + if (inet_add_protocol(&net_gre_protocol, IPPROTO_GRE) < 0) { | |
378 | + pr_err("can't add protocol\n"); | |
379 | + goto err; | |
380 | + } | |
381 | + | |
382 | + if (gre_add_protocol(&ipgre_protocol, GREPROTO_CISCO) < 0) { | |
383 | + pr_info("%s: can't add ipgre handler\n", __func__); | |
384 | + goto err_gre; | |
385 | + } | |
386 | + | |
387 | + if (gre_offload_init()) { | |
388 | + pr_err("can't add protocol offload\n"); | |
389 | + goto err_gso; | |
390 | + } | |
391 | + | |
392 | + return 0; | |
393 | +err_gso: | |
394 | + gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO); | |
395 | +err_gre: | |
396 | + inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | |
397 | +err: | |
398 | + return -EAGAIN; | |
399 | +} | |
400 | + | |
401 | +static void __exit gre_exit(void) | |
402 | +{ | |
403 | + gre_offload_exit(); | |
404 | + | |
405 | + gre_del_protocol(&ipgre_protocol, GREPROTO_CISCO); | |
406 | + inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | |
407 | +} | |
408 | + | |
409 | +module_init(gre_init); | |
410 | +module_exit(gre_exit); | |
411 | + | |
412 | +MODULE_DESCRIPTION("GRE over IPv4 demultiplexer driver"); | |
413 | +MODULE_AUTHOR("D. Kozlov (xeb@mail.ru)"); | |
414 | +MODULE_LICENSE("GPL"); |
net/ipv4/gre_offload.c
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) { | |
90 | + kfree_skb(segs); | |
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 | +} |