blob: 58d7f36949da42333f52a3a6e074814fa8f0a99a [file] [log] [blame]
Grégoire Baroneb4d4062010-08-18 13:10:35 +00001/*
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);