diff options
author | Pravin B Shelar <pshelar@nicira.com> | 2013-02-14 09:02:41 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2013-02-15 15:17:11 -0500 |
commit | 68c331631143f5f039baac99a650e0b9e1ea02b6 (patch) | |
tree | c69d73c5599aab5e92a8c99bc5343c9fc9ffbbd8 /net/ipv4/gre.c | |
parent | 05e8ef4ab2d8087d360e814d14da20b9f7fb2283 (diff) |
v4 GRE: Add TCP segmentation offload for GRE
Following patch adds GRE protocol offload handler so that
skb_gso_segment() can segment GRE packets.
SKB GSO CB is added to keep track of total header length so that
skb_segment can push entire header. e.g. in case of GRE, skb_segment
need to push inner and outer headers to every segment.
New NETIF_F_GRE_GSO feature is added for devices which support HW
GRE TSO offload. Currently none of devices support it therefore GRE GSO
always fall backs to software GSO.
[ Compute pkt_len before ip_local_out() invocation. -DaveM ]
Signed-off-by: Pravin B Shelar <pshelar@nicira.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/ipv4/gre.c')
-rw-r--r-- | net/ipv4/gre.c | 118 |
1 files changed, 118 insertions, 0 deletions
diff --git a/net/ipv4/gre.c b/net/ipv4/gre.c index 42a491055c76..7a4c710c4cdd 100644 --- a/net/ipv4/gre.c +++ b/net/ipv4/gre.c | |||
@@ -19,6 +19,7 @@ | |||
19 | #include <linux/in.h> | 19 | #include <linux/in.h> |
20 | #include <linux/ip.h> | 20 | #include <linux/ip.h> |
21 | #include <linux/netdevice.h> | 21 | #include <linux/netdevice.h> |
22 | #include <linux/if_tunnel.h> | ||
22 | #include <linux/spinlock.h> | 23 | #include <linux/spinlock.h> |
23 | #include <net/protocol.h> | 24 | #include <net/protocol.h> |
24 | #include <net/gre.h> | 25 | #include <net/gre.h> |
@@ -26,6 +27,11 @@ | |||
26 | 27 | ||
27 | static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; | 28 | static const struct gre_protocol __rcu *gre_proto[GREPROTO_MAX] __read_mostly; |
28 | static DEFINE_SPINLOCK(gre_proto_lock); | 29 | static DEFINE_SPINLOCK(gre_proto_lock); |
30 | struct gre_base_hdr { | ||
31 | __be16 flags; | ||
32 | __be16 protocol; | ||
33 | }; | ||
34 | #define GRE_HEADER_SECTION 4 | ||
29 | 35 | ||
30 | int gre_add_protocol(const struct gre_protocol *proto, u8 version) | 36 | int gre_add_protocol(const struct gre_protocol *proto, u8 version) |
31 | { | 37 | { |
@@ -112,12 +118,117 @@ static void gre_err(struct sk_buff *skb, u32 info) | |||
112 | rcu_read_unlock(); | 118 | rcu_read_unlock(); |
113 | } | 119 | } |
114 | 120 | ||
121 | static struct sk_buff *gre_gso_segment(struct sk_buff *skb, | ||
122 | netdev_features_t features) | ||
123 | { | ||
124 | struct sk_buff *segs = ERR_PTR(-EINVAL); | ||
125 | netdev_features_t enc_features; | ||
126 | int ghl = GRE_HEADER_SECTION; | ||
127 | struct gre_base_hdr *greh; | ||
128 | int mac_len = skb->mac_len; | ||
129 | int tnl_hlen; | ||
130 | bool csum; | ||
131 | |||
132 | if (unlikely(skb_shinfo(skb)->gso_type & | ||
133 | ~(SKB_GSO_TCPV4 | | ||
134 | SKB_GSO_TCPV6 | | ||
135 | SKB_GSO_UDP | | ||
136 | SKB_GSO_DODGY | | ||
137 | SKB_GSO_TCP_ECN | | ||
138 | SKB_GSO_GRE))) | ||
139 | goto out; | ||
140 | |||
141 | if (unlikely(!pskb_may_pull(skb, sizeof(*greh)))) | ||
142 | goto out; | ||
143 | |||
144 | greh = (struct gre_base_hdr *)skb_transport_header(skb); | ||
145 | |||
146 | if (greh->flags & GRE_KEY) | ||
147 | ghl += GRE_HEADER_SECTION; | ||
148 | if (greh->flags & GRE_SEQ) | ||
149 | ghl += GRE_HEADER_SECTION; | ||
150 | if (greh->flags & GRE_CSUM) { | ||
151 | ghl += GRE_HEADER_SECTION; | ||
152 | csum = true; | ||
153 | } else | ||
154 | csum = false; | ||
155 | |||
156 | /* setup inner skb. */ | ||
157 | if (greh->protocol == htons(ETH_P_TEB)) { | ||
158 | struct ethhdr *eth = eth_hdr(skb); | ||
159 | skb->protocol = eth->h_proto; | ||
160 | } else { | ||
161 | skb->protocol = greh->protocol; | ||
162 | } | ||
163 | |||
164 | skb->encapsulation = 0; | ||
165 | |||
166 | if (unlikely(!pskb_may_pull(skb, ghl))) | ||
167 | goto out; | ||
168 | __skb_pull(skb, ghl); | ||
169 | skb_reset_mac_header(skb); | ||
170 | skb_set_network_header(skb, skb_inner_network_offset(skb)); | ||
171 | skb->mac_len = skb_inner_network_offset(skb); | ||
172 | |||
173 | /* segment inner packet. */ | ||
174 | enc_features = skb->dev->hw_enc_features & netif_skb_features(skb); | ||
175 | segs = skb_mac_gso_segment(skb, enc_features); | ||
176 | if (!segs || IS_ERR(segs)) | ||
177 | goto out; | ||
178 | |||
179 | skb = segs; | ||
180 | tnl_hlen = skb_tnl_header_len(skb); | ||
181 | do { | ||
182 | __skb_push(skb, ghl); | ||
183 | if (csum) { | ||
184 | __be32 *pcsum; | ||
185 | |||
186 | if (skb_has_shared_frag(skb)) { | ||
187 | int err; | ||
188 | |||
189 | err = __skb_linearize(skb); | ||
190 | if (err) { | ||
191 | kfree_skb(segs); | ||
192 | segs = ERR_PTR(err); | ||
193 | goto out; | ||
194 | } | ||
195 | } | ||
196 | |||
197 | greh = (struct gre_base_hdr *)(skb->data); | ||
198 | pcsum = (__be32 *)(greh + 1); | ||
199 | *pcsum = 0; | ||
200 | *(__sum16 *)pcsum = csum_fold(skb_checksum(skb, 0, skb->len, 0)); | ||
201 | } | ||
202 | __skb_push(skb, tnl_hlen - ghl); | ||
203 | |||
204 | skb_reset_mac_header(skb); | ||
205 | skb_set_network_header(skb, mac_len); | ||
206 | skb->mac_len = mac_len; | ||
207 | } while ((skb = skb->next)); | ||
208 | out: | ||
209 | return segs; | ||
210 | } | ||
211 | |||
212 | static int gre_gso_send_check(struct sk_buff *skb) | ||
213 | { | ||
214 | if (!skb->encapsulation) | ||
215 | return -EINVAL; | ||
216 | return 0; | ||
217 | } | ||
218 | |||
115 | static const struct net_protocol net_gre_protocol = { | 219 | static const struct net_protocol net_gre_protocol = { |
116 | .handler = gre_rcv, | 220 | .handler = gre_rcv, |
117 | .err_handler = gre_err, | 221 | .err_handler = gre_err, |
118 | .netns_ok = 1, | 222 | .netns_ok = 1, |
119 | }; | 223 | }; |
120 | 224 | ||
225 | static const struct net_offload gre_offload = { | ||
226 | .callbacks = { | ||
227 | .gso_send_check = gre_gso_send_check, | ||
228 | .gso_segment = gre_gso_segment, | ||
229 | }, | ||
230 | }; | ||
231 | |||
121 | static int __init gre_init(void) | 232 | static int __init gre_init(void) |
122 | { | 233 | { |
123 | pr_info("GRE over IPv4 demultiplexor driver\n"); | 234 | pr_info("GRE over IPv4 demultiplexor driver\n"); |
@@ -127,11 +238,18 @@ static int __init gre_init(void) | |||
127 | return -EAGAIN; | 238 | return -EAGAIN; |
128 | } | 239 | } |
129 | 240 | ||
241 | if (inet_add_offload(&gre_offload, IPPROTO_GRE)) { | ||
242 | pr_err("can't add protocol offload\n"); | ||
243 | inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | ||
244 | return -EAGAIN; | ||
245 | } | ||
246 | |||
130 | return 0; | 247 | return 0; |
131 | } | 248 | } |
132 | 249 | ||
133 | static void __exit gre_exit(void) | 250 | static void __exit gre_exit(void) |
134 | { | 251 | { |
252 | inet_del_offload(&gre_offload, IPPROTO_GRE); | ||
135 | inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); | 253 | inet_del_protocol(&net_gre_protocol, IPPROTO_GRE); |
136 | } | 254 | } |
137 | 255 | ||