aboutsummaryrefslogtreecommitdiffstats
path: root/net/sched/act_csum.c
diff options
context:
space:
mode:
authorGrégoire Baron <baronchon@n7mm.org>2010-08-18 09:10:35 -0400
committerDavid S. Miller <davem@davemloft.net>2010-08-20 04:42:59 -0400
commiteb4d40654505e47aa9d2035bb97f631fa61d14b4 (patch)
treebed2a1c5026e9797c0ed48c67c48b050e7f07e3c /net/sched/act_csum.c
parent49e8ab03ebcacd8e37660ffec20c0c46721a2800 (diff)
net/sched: add ACT_CSUM action to update packets checksums
net/sched: add ACT_CSUM action to update packets checksums ACT_CSUM can be called just after ACT_PEDIT in order to re-compute some altered checksums in IPv4 and IPv6 packets. The following checksums are supported by this patch: - IPv4: IPv4 header, ICMP, IGMP, TCP, UDP & UDPLite - IPv6: ICMPv6, TCP, UDP & UDPLite It's possible to request in the same action to update different kind of checksums, if the packets flow mix TCP, UDP and UDPLite, ... An example of usage is done in the associated iproute2 patch. Version 3 changes: - remove useless goto instructions - improve IPv6 hop options decoding Version 2 changes: - coding style correction - remove useless arguments of some functions - use stack in tcf_csum_dump() - add tcf_csum_skb_nextlayer() to factor code Signed-off-by: Gregoire Baron <baronchon@n7mm.org> Acked-by: jamal <hadi@cyberus.ca> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net/sched/act_csum.c')
-rw-r--r--net/sched/act_csum.c595
1 files changed, 595 insertions, 0 deletions
diff --git a/net/sched/act_csum.c b/net/sched/act_csum.c
new file mode 100644
index 000000000000..58d7f36949da
--- /dev/null
+++ b/net/sched/act_csum.c
@@ -0,0 +1,595 @@
1/*
2 * Checksum updating actions
3 *
4 * Copyright (c) 2010 Gregoire Baron <baronchon@n7mm.org>
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2 of the License, or (at your option)
9 * any later version.
10 *
11 */
12
13#include <linux/types.h>
14#include <linux/init.h>
15#include <linux/kernel.h>
16#include <linux/module.h>
17#include <linux/spinlock.h>
18
19#include <linux/netlink.h>
20#include <net/netlink.h>
21#include <linux/rtnetlink.h>
22
23#include <linux/skbuff.h>
24
25#include <net/ip.h>
26#include <net/ipv6.h>
27#include <net/icmp.h>
28#include <linux/icmpv6.h>
29#include <linux/igmp.h>
30#include <net/tcp.h>
31#include <net/udp.h>
32
33#include <net/act_api.h>
34
35#include <linux/tc_act/tc_csum.h>
36#include <net/tc_act/tc_csum.h>
37
38#define CSUM_TAB_MASK 15
39static struct tcf_common *tcf_csum_ht[CSUM_TAB_MASK + 1];
40static u32 csum_idx_gen;
41static DEFINE_RWLOCK(csum_lock);
42
43static struct tcf_hashinfo csum_hash_info = {
44 .htab = tcf_csum_ht,
45 .hmask = CSUM_TAB_MASK,
46 .lock = &csum_lock,
47};
48
49static const struct nla_policy csum_policy[TCA_CSUM_MAX + 1] = {
50 [TCA_CSUM_PARMS] = { .len = sizeof(struct tc_csum), },
51};
52
53static int tcf_csum_init(struct nlattr *nla, struct nlattr *est,
54 struct tc_action *a, int ovr, int bind)
55{
56 struct nlattr *tb[TCA_CSUM_MAX + 1];
57 struct tc_csum *parm;
58 struct tcf_common *pc;
59 struct tcf_csum *p;
60 int ret = 0, err;
61
62 if (nla == NULL)
63 return -EINVAL;
64
65 err = nla_parse_nested(tb, TCA_CSUM_MAX, nla,csum_policy);
66 if (err < 0)
67 return err;
68
69 if (tb[TCA_CSUM_PARMS] == NULL)
70 return -EINVAL;
71 parm = nla_data(tb[TCA_CSUM_PARMS]);
72
73 pc = tcf_hash_check(parm->index, a, bind, &csum_hash_info);
74 if (!pc) {
75 pc = tcf_hash_create(parm->index, est, a, sizeof(*p), bind, &csum_idx_gen, &csum_hash_info);
76 if (IS_ERR(pc))
77 return PTR_ERR(pc);
78 p = to_tcf_csum(pc);
79 ret = ACT_P_CREATED;
80 } else {
81 p = to_tcf_csum(pc);
82 if (!ovr) {
83 tcf_hash_release(pc, bind, &csum_hash_info);
84 return -EEXIST;
85 }
86 }
87
88 spin_lock_bh(&p->tcf_lock);
89 p->tcf_action = parm->action;
90 p->update_flags = parm->update_flags;
91 spin_unlock_bh(&p->tcf_lock);
92
93 if (ret == ACT_P_CREATED)
94 tcf_hash_insert(pc, &csum_hash_info);
95
96 return ret;
97}
98
99static int tcf_csum_cleanup(struct tc_action *a, int bind)
100{
101 struct tcf_csum *p = a->priv;
102 return tcf_hash_release(&p->common, bind, &csum_hash_info);
103}
104
105/**
106 * tcf_csum_skb_nextlayer - Get next layer pointer
107 * @skb: sk_buff to use
108 * @ihl: previous summed headers length
109 * @ipl: complete packet length
110 * @jhl: next header length
111 *
112 * Check the expected next layer availability in the specified sk_buff.
113 * Return the next layer pointer if pass, NULL otherwise.
114 */
115static void *tcf_csum_skb_nextlayer(struct sk_buff *skb,
116 unsigned int ihl, unsigned int ipl,
117 unsigned int jhl)
118{
119 int ntkoff = skb_network_offset(skb);
120 int hl = ihl + jhl;
121
122 if (!pskb_may_pull(skb, ipl + ntkoff) || (ipl < hl) ||
123 (skb_cloned(skb) &&
124 !skb_clone_writable(skb, hl + ntkoff) &&
125 pskb_expand_head(skb, 0, 0, GFP_ATOMIC)))
126 return NULL;
127 else
128 return (void *)(skb_network_header(skb) + ihl);
129}
130
131static int tcf_csum_ipv4_icmp(struct sk_buff *skb,
132 unsigned int ihl, unsigned int ipl)
133{
134 struct icmphdr *icmph;
135
136 icmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmph));
137 if (icmph == NULL)
138 return 0;
139
140 icmph->checksum = 0;
141 skb->csum = csum_partial(icmph, ipl - ihl, 0);
142 icmph->checksum = csum_fold(skb->csum);
143
144 skb->ip_summed = CHECKSUM_NONE;
145
146 return 1;
147}
148
149static int tcf_csum_ipv4_igmp(struct sk_buff *skb,
150 unsigned int ihl, unsigned int ipl)
151{
152 struct igmphdr *igmph;
153
154 igmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*igmph));
155 if (igmph == NULL)
156 return 0;
157
158 igmph->csum = 0;
159 skb->csum = csum_partial(igmph, ipl - ihl, 0);
160 igmph->csum = csum_fold(skb->csum);
161
162 skb->ip_summed = CHECKSUM_NONE;
163
164 return 1;
165}
166
167static int tcf_csum_ipv6_icmp(struct sk_buff *skb, struct ipv6hdr *ip6h,
168 unsigned int ihl, unsigned int ipl)
169{
170 struct icmp6hdr *icmp6h;
171
172 icmp6h = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmp6h));
173 if (icmp6h == NULL)
174 return 0;
175
176 icmp6h->icmp6_cksum = 0;
177 skb->csum = csum_partial(icmp6h, ipl - ihl, 0);
178 icmp6h->icmp6_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
179 ipl - ihl, IPPROTO_ICMPV6,
180 skb->csum);
181
182 skb->ip_summed = CHECKSUM_NONE;
183
184 return 1;
185}
186
187static int tcf_csum_ipv4_tcp(struct sk_buff *skb, struct iphdr *iph,
188 unsigned int ihl, unsigned int ipl)
189{
190 struct tcphdr *tcph;
191
192 tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
193 if (tcph == NULL)
194 return 0;
195
196 tcph->check = 0;
197 skb->csum = csum_partial(tcph, ipl - ihl, 0);
198 tcph->check = tcp_v4_check(ipl - ihl,
199 iph->saddr, iph->daddr, skb->csum);
200
201 skb->ip_summed = CHECKSUM_NONE;
202
203 return 1;
204}
205
206static int tcf_csum_ipv6_tcp(struct sk_buff *skb, struct ipv6hdr *ip6h,
207 unsigned int ihl, unsigned int ipl)
208{
209 struct tcphdr *tcph;
210
211 tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
212 if (tcph == NULL)
213 return 0;
214
215 tcph->check = 0;
216 skb->csum = csum_partial(tcph, ipl - ihl, 0);
217 tcph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
218 ipl - ihl, IPPROTO_TCP,
219 skb->csum);
220
221 skb->ip_summed = CHECKSUM_NONE;
222
223 return 1;
224}
225
226static int tcf_csum_ipv4_udp(struct sk_buff *skb, struct iphdr *iph,
227 unsigned int ihl, unsigned int ipl, int udplite)
228{
229 struct udphdr *udph;
230 u16 ul;
231
232 /* Support both UDP and UDPLITE checksum algorithms,
233 * Don't use udph->len to get the real length without any protocol check,
234 * UDPLITE uses udph->len for another thing,
235 * Use iph->tot_len, or just ipl.
236 */
237
238 udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
239 if (udph == NULL)
240 return 0;
241
242 ul = ntohs(udph->len);
243
244 if (udplite || udph->check) {
245
246 udph->check = 0;
247
248 if (udplite) {
249 if (ul == 0)
250 skb->csum = csum_partial(udph, ipl - ihl, 0);
251
252 else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
253 skb->csum = csum_partial(udph, ul, 0);
254
255 else
256 goto ignore_obscure_skb;
257 } else {
258 if (ul != ipl - ihl)
259 goto ignore_obscure_skb;
260
261 skb->csum = csum_partial(udph, ul, 0);
262 }
263
264 udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
265 ul, iph->protocol,
266 skb->csum);
267
268 if (!udph->check)
269 udph->check = CSUM_MANGLED_0;
270 }
271
272 skb->ip_summed = CHECKSUM_NONE;
273
274ignore_obscure_skb:
275 return 1;
276}
277
278static int tcf_csum_ipv6_udp(struct sk_buff *skb, struct ipv6hdr *ip6h,
279 unsigned int ihl, unsigned int ipl, int udplite)
280{
281 struct udphdr *udph;
282 u16 ul;
283
284 /* Support both UDP and UDPLITE checksum algorithms,
285 * Don't use udph->len to get the real length without any protocol check,
286 * UDPLITE uses udph->len for another thing,
287 * Use ip6h->payload_len + sizeof(*ip6h) ... , or just ipl.
288 */
289
290 udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
291 if (udph == NULL)
292 return 0;
293
294 ul = ntohs(udph->len);
295
296 udph->check = 0;
297
298 if (udplite) {
299 if (ul == 0)
300 skb->csum = csum_partial(udph, ipl - ihl, 0);
301
302 else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
303 skb->csum = csum_partial(udph, ul, 0);
304
305 else
306 goto ignore_obscure_skb;
307 } else {
308 if (ul != ipl - ihl)
309 goto ignore_obscure_skb;
310
311 skb->csum = csum_partial(udph, ul, 0);
312 }
313
314 udph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, ul,
315 udplite ? IPPROTO_UDPLITE : IPPROTO_UDP,
316 skb->csum);
317
318 if (!udph->check)
319 udph->check = CSUM_MANGLED_0;
320
321 skb->ip_summed = CHECKSUM_NONE;
322
323ignore_obscure_skb:
324 return 1;
325}
326
327static int tcf_csum_ipv4(struct sk_buff *skb, u32 update_flags)
328{
329 struct iphdr *iph;
330 int ntkoff;
331
332 ntkoff = skb_network_offset(skb);
333
334 if (!pskb_may_pull(skb, sizeof(*iph) + ntkoff))
335 goto fail;
336
337 iph = ip_hdr(skb);
338
339 switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) {
340 case IPPROTO_ICMP:
341 if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
342 if (!tcf_csum_ipv4_icmp(skb,
343 iph->ihl * 4, ntohs(iph->tot_len)))
344 goto fail;
345 break;
346 case IPPROTO_IGMP:
347 if (update_flags & TCA_CSUM_UPDATE_FLAG_IGMP)
348 if (!tcf_csum_ipv4_igmp(skb,
349 iph->ihl * 4, ntohs(iph->tot_len)))
350 goto fail;
351 break;
352 case IPPROTO_TCP:
353 if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
354 if (!tcf_csum_ipv4_tcp(skb, iph,
355 iph->ihl * 4, ntohs(iph->tot_len)))
356 goto fail;
357 break;
358 case IPPROTO_UDP:
359 if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
360 if (!tcf_csum_ipv4_udp(skb, iph,
361 iph->ihl * 4, ntohs(iph->tot_len), 0))
362 goto fail;
363 break;
364 case IPPROTO_UDPLITE:
365 if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
366 if (!tcf_csum_ipv4_udp(skb, iph,
367 iph->ihl * 4, ntohs(iph->tot_len), 1))
368 goto fail;
369 break;
370 }
371
372 if (update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) {
373 if (skb_cloned(skb) &&
374 !skb_clone_writable(skb, sizeof(*iph) + ntkoff) &&
375 pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
376 goto fail;
377
378 ip_send_check(iph);
379 }
380
381 return 1;
382
383fail:
384 return 0;
385}
386
387static int tcf_csum_ipv6_hopopts(struct ipv6_opt_hdr *ip6xh,
388 unsigned int ixhl, unsigned int *pl)
389{
390 int off, len, optlen;
391 unsigned char *xh = (void *)ip6xh;
392
393 off = sizeof(*ip6xh);
394 len = ixhl - off;
395
396 while (len > 1) {
397 switch (xh[off])
398 {
399 case IPV6_TLV_PAD0:
400 optlen = 1;
401 break;
402 case IPV6_TLV_JUMBO:
403 optlen = xh[off + 1] + 2;
404 if (optlen != 6 || len < 6 || (off & 3) != 2)
405 /* wrong jumbo option length/alignment */
406 return 0;
407 *pl = ntohl(*(__be32 *)(xh + off + 2));
408 goto done;
409 default:
410 optlen = xh[off + 1] + 2;
411 if (optlen > len)
412 /* ignore obscure options */
413 goto done;
414 break;
415 }
416 off += optlen;
417 len -= optlen;
418 }
419
420done:
421 return 1;
422}
423
424static int tcf_csum_ipv6(struct sk_buff *skb, u32 update_flags)
425{
426 struct ipv6hdr *ip6h;
427 struct ipv6_opt_hdr *ip6xh;
428 unsigned int hl, ixhl;
429 unsigned int pl;
430 int ntkoff;
431 u8 nexthdr;
432
433 ntkoff = skb_network_offset(skb);
434
435 hl = sizeof(*ip6h);
436
437 if (!pskb_may_pull(skb, hl + ntkoff))
438 goto fail;
439
440 ip6h = ipv6_hdr(skb);
441
442 pl = ntohs(ip6h->payload_len);
443 nexthdr = ip6h->nexthdr;
444
445 do {
446 switch (nexthdr) {
447 case NEXTHDR_FRAGMENT:
448 goto ignore_skb;
449 case NEXTHDR_ROUTING:
450 case NEXTHDR_HOP:
451 case NEXTHDR_DEST:
452 if (!pskb_may_pull(skb, hl + sizeof(*ip6xh) + ntkoff))
453 goto fail;
454 ip6xh = (void *)(skb_network_header(skb) + hl);
455 ixhl = ipv6_optlen(ip6xh);
456 if (!pskb_may_pull(skb, hl + ixhl + ntkoff))
457 goto fail;
458 if ((nexthdr == NEXTHDR_HOP) &&
459 !(tcf_csum_ipv6_hopopts(ip6xh, ixhl, &pl)))
460 goto fail;
461 nexthdr = ip6xh->nexthdr;
462 hl += ixhl;
463 break;
464 case IPPROTO_ICMPV6:
465 if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
466 if (!tcf_csum_ipv6_icmp(skb, ip6h,
467 hl, pl + sizeof(*ip6h)))
468 goto fail;
469 goto done;
470 case IPPROTO_TCP:
471 if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
472 if (!tcf_csum_ipv6_tcp(skb, ip6h,
473 hl, pl + sizeof(*ip6h)))
474 goto fail;
475 goto done;
476 case IPPROTO_UDP:
477 if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
478 if (!tcf_csum_ipv6_udp(skb, ip6h,
479 hl, pl + sizeof(*ip6h), 0))
480 goto fail;
481 goto done;
482 case IPPROTO_UDPLITE:
483 if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
484 if (!tcf_csum_ipv6_udp(skb, ip6h,
485 hl, pl + sizeof(*ip6h), 1))
486 goto fail;
487 goto done;
488 default:
489 goto ignore_skb;
490 }
491 } while (pskb_may_pull(skb, hl + 1 + ntkoff));
492
493done:
494ignore_skb:
495 return 1;
496
497fail:
498 return 0;
499}
500
501static int tcf_csum(struct sk_buff *skb,
502 struct tc_action *a, struct tcf_result *res)
503{
504 struct tcf_csum *p = a->priv;
505 int action;
506 u32 update_flags;
507
508 spin_lock(&p->tcf_lock);
509 p->tcf_tm.lastuse = jiffies;
510 p->tcf_bstats.bytes += qdisc_pkt_len(skb);
511 p->tcf_bstats.packets++;
512 action = p->tcf_action;
513 update_flags = p->update_flags;
514 spin_unlock(&p->tcf_lock);
515
516 if (unlikely(action == TC_ACT_SHOT))
517 goto drop;
518
519 switch (skb->protocol) {
520 case cpu_to_be16(ETH_P_IP):
521 if (!tcf_csum_ipv4(skb, update_flags))
522 goto drop;
523 break;
524 case cpu_to_be16(ETH_P_IPV6):
525 if (!tcf_csum_ipv6(skb, update_flags))
526 goto drop;
527 break;
528 }
529
530 return action;
531
532drop:
533 spin_lock(&p->tcf_lock);
534 p->tcf_qstats.drops++;
535 spin_unlock(&p->tcf_lock);
536 return TC_ACT_SHOT;
537}
538
539static int tcf_csum_dump(struct sk_buff *skb,
540 struct tc_action *a, int bind, int ref)
541{
542 unsigned char *b = skb_tail_pointer(skb);
543 struct tcf_csum *p = a->priv;
544 struct tc_csum opt = {
545 .update_flags = p->update_flags,
546
547 .index = p->tcf_index,
548 .action = p->tcf_action,
549 .refcnt = p->tcf_refcnt - ref,
550 .bindcnt = p->tcf_bindcnt - bind,
551 };
552 struct tcf_t t;
553
554 NLA_PUT(skb, TCA_CSUM_PARMS, sizeof(opt), &opt);
555 t.install = jiffies_to_clock_t(jiffies - p->tcf_tm.install);
556 t.lastuse = jiffies_to_clock_t(jiffies - p->tcf_tm.lastuse);
557 t.expires = jiffies_to_clock_t(p->tcf_tm.expires);
558 NLA_PUT(skb, TCA_CSUM_TM, sizeof(t), &t);
559
560 return skb->len;
561
562nla_put_failure:
563 nlmsg_trim(skb, b);
564 return -1;
565}
566
567static struct tc_action_ops act_csum_ops = {
568 .kind = "csum",
569 .hinfo = &csum_hash_info,
570 .type = TCA_ACT_CSUM,
571 .capab = TCA_CAP_NONE,
572 .owner = THIS_MODULE,
573 .act = tcf_csum,
574 .dump = tcf_csum_dump,
575 .cleanup = tcf_csum_cleanup,
576 .lookup = tcf_hash_search,
577 .init = tcf_csum_init,
578 .walk = tcf_generic_walker
579};
580
581MODULE_DESCRIPTION("Checksum updating actions");
582MODULE_LICENSE("GPL");
583
584static int __init csum_init_module(void)
585{
586 return tcf_register_action(&act_csum_ops);
587}
588
589static void __exit csum_cleanup_module(void)
590{
591 tcf_unregister_action(&act_csum_ops);
592}
593
594module_init(csum_init_module);
595module_exit(csum_cleanup_module);