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; | |
7a7ffbab | 31 | u16 mac_offset = skb->mac_header; |
c50cd357 DB |
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 | | |
cb32f511 ED |
43 | SKB_GSO_GRE | |
44 | SKB_GSO_IPIP))) | |
c50cd357 DB |
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 | ||
7a7ffbab WCC |
62 | if (unlikely(!pskb_may_pull(skb, ghl))) |
63 | goto out; | |
64 | ||
c50cd357 DB |
65 | /* setup inner skb. */ |
66 | skb->protocol = greh->protocol; | |
67 | skb->encapsulation = 0; | |
68 | ||
c50cd357 DB |
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); | |
7a7ffbab WCC |
77 | if (!segs || IS_ERR(segs)) { |
78 | skb_gso_error_unwind(skb, protocol, ghl, mac_offset, mac_len); | |
c50cd357 | 79 | goto out; |
7a7ffbab | 80 | } |
c50cd357 DB |
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) { | |
0c1072ae | 94 | kfree_skb_list(segs); |
c50cd357 DB |
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 | ||
cdbaa0bb AD |
107 | skb_reset_inner_headers(skb); |
108 | skb->encapsulation = 1; | |
109 | ||
c50cd357 DB |
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 | } |