act_csum.c 15.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
/*
 * Checksum updating actions
 *
 * Copyright (c) 2010 Gregoire Baron <baronchon@n7mm.org>
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 */

#include <linux/types.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/spinlock.h>

#include <linux/netlink.h>
#include <net/netlink.h>
#include <linux/rtnetlink.h>

#include <linux/skbuff.h>

#include <net/ip.h>
#include <net/ipv6.h>
#include <net/icmp.h>
#include <linux/icmpv6.h>
#include <linux/igmp.h>
#include <net/tcp.h>
#include <net/udp.h>
32
#include <net/ip6_checksum.h>
33
#include <net/sctp/checksum.h>
34 35 36 37 38 39 40 41 42 43

#include <net/act_api.h>

#include <linux/tc_act/tc_csum.h>
#include <net/tc_act/tc_csum.h>

static const struct nla_policy csum_policy[TCA_CSUM_MAX + 1] = {
	[TCA_CSUM_PARMS] = { .len = sizeof(struct tc_csum), },
};

44
static unsigned int csum_net_id;
45
static struct tc_action_ops act_csum_ops;
46 47

static int tcf_csum_init(struct net *net, struct nlattr *nla,
48
			 struct nlattr *est, struct tc_action **a, int ovr,
49
			 int bind, struct netlink_ext_ack *extack)
50
{
51
	struct tc_action_net *tn = net_generic(net, csum_net_id);
52
	struct tcf_csum_params *params_old, *params_new;
53 54 55 56 57 58 59 60
	struct nlattr *tb[TCA_CSUM_MAX + 1];
	struct tc_csum *parm;
	struct tcf_csum *p;
	int ret = 0, err;

	if (nla == NULL)
		return -EINVAL;

61
	err = nla_parse_nested(tb, TCA_CSUM_MAX, nla, csum_policy, NULL);
62 63 64 65 66 67 68
	if (err < 0)
		return err;

	if (tb[TCA_CSUM_PARMS] == NULL)
		return -EINVAL;
	parm = nla_data(tb[TCA_CSUM_PARMS]);

69 70
	if (!tcf_idr_check(tn, parm->index, a, bind)) {
		ret = tcf_idr_create(tn, parm->index, est, a,
71
				     &act_csum_ops, bind, true);
72 73
		if (ret)
			return ret;
74 75
		ret = ACT_P_CREATED;
	} else {
76 77
		if (bind)/* dont override defaults */
			return 0;
78
		tcf_idr_release(*a, bind);
79
		if (!ovr)
80 81 82
			return -EEXIST;
	}

83
	p = to_tcf_csum(*a);
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
	ASSERT_RTNL();

	params_new = kzalloc(sizeof(*params_new), GFP_KERNEL);
	if (unlikely(!params_new)) {
		if (ret == ACT_P_CREATED)
			tcf_idr_release(*a, bind);
		return -ENOMEM;
	}
	params_old = rtnl_dereference(p->params);

	params_new->action = parm->action;
	params_new->update_flags = parm->update_flags;
	rcu_assign_pointer(p->params, params_new);
	if (params_old)
		kfree_rcu(params_old, rcu);
99 100

	if (ret == ACT_P_CREATED)
101
		tcf_idr_insert(tn, *a);
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123

	return ret;
}

/**
 * tcf_csum_skb_nextlayer - Get next layer pointer
 * @skb: sk_buff to use
 * @ihl: previous summed headers length
 * @ipl: complete packet length
 * @jhl: next header length
 *
 * Check the expected next layer availability in the specified sk_buff.
 * Return the next layer pointer if pass, NULL otherwise.
 */
static void *tcf_csum_skb_nextlayer(struct sk_buff *skb,
				    unsigned int ihl, unsigned int ipl,
				    unsigned int jhl)
{
	int ntkoff = skb_network_offset(skb);
	int hl = ihl + jhl;

	if (!pskb_may_pull(skb, ipl + ntkoff) || (ipl < hl) ||
124
	    skb_try_make_writable(skb, hl + ntkoff))
125 126 127 128 129
		return NULL;
	else
		return (void *)(skb_network_header(skb) + ihl);
}

J
Jamal Hadi Salim 已提交
130 131
static int tcf_csum_ipv4_icmp(struct sk_buff *skb, unsigned int ihl,
			      unsigned int ipl)
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
{
	struct icmphdr *icmph;

	icmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmph));
	if (icmph == NULL)
		return 0;

	icmph->checksum = 0;
	skb->csum = csum_partial(icmph, ipl - ihl, 0);
	icmph->checksum = csum_fold(skb->csum);

	skb->ip_summed = CHECKSUM_NONE;

	return 1;
}

static int tcf_csum_ipv4_igmp(struct sk_buff *skb,
			      unsigned int ihl, unsigned int ipl)
{
	struct igmphdr *igmph;

	igmph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*igmph));
	if (igmph == NULL)
		return 0;

	igmph->csum = 0;
	skb->csum = csum_partial(igmph, ipl - ihl, 0);
	igmph->csum = csum_fold(skb->csum);

	skb->ip_summed = CHECKSUM_NONE;

	return 1;
}

J
Jamal Hadi Salim 已提交
166 167
static int tcf_csum_ipv6_icmp(struct sk_buff *skb, unsigned int ihl,
			      unsigned int ipl)
168 169
{
	struct icmp6hdr *icmp6h;
170
	const struct ipv6hdr *ip6h;
171 172 173 174 175

	icmp6h = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*icmp6h));
	if (icmp6h == NULL)
		return 0;

176
	ip6h = ipv6_hdr(skb);
177 178 179 180 181 182 183 184 185 186 187
	icmp6h->icmp6_cksum = 0;
	skb->csum = csum_partial(icmp6h, ipl - ihl, 0);
	icmp6h->icmp6_cksum = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
					      ipl - ihl, IPPROTO_ICMPV6,
					      skb->csum);

	skb->ip_summed = CHECKSUM_NONE;

	return 1;
}

J
Jamal Hadi Salim 已提交
188 189
static int tcf_csum_ipv4_tcp(struct sk_buff *skb, unsigned int ihl,
			     unsigned int ipl)
190 191
{
	struct tcphdr *tcph;
192
	const struct iphdr *iph;
193

194 195 196
	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV4)
		return 1;

197 198 199 200
	tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
	if (tcph == NULL)
		return 0;

201
	iph = ip_hdr(skb);
202 203 204 205 206 207 208 209 210 211
	tcph->check = 0;
	skb->csum = csum_partial(tcph, ipl - ihl, 0);
	tcph->check = tcp_v4_check(ipl - ihl,
				   iph->saddr, iph->daddr, skb->csum);

	skb->ip_summed = CHECKSUM_NONE;

	return 1;
}

J
Jamal Hadi Salim 已提交
212 213
static int tcf_csum_ipv6_tcp(struct sk_buff *skb, unsigned int ihl,
			     unsigned int ipl)
214 215
{
	struct tcphdr *tcph;
216
	const struct ipv6hdr *ip6h;
217

218 219 220
	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_TCPV6)
		return 1;

221 222 223 224
	tcph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*tcph));
	if (tcph == NULL)
		return 0;

225
	ip6h = ipv6_hdr(skb);
226 227 228 229 230 231 232 233 234 235 236
	tcph->check = 0;
	skb->csum = csum_partial(tcph, ipl - ihl, 0);
	tcph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr,
				      ipl - ihl, IPPROTO_TCP,
				      skb->csum);

	skb->ip_summed = CHECKSUM_NONE;

	return 1;
}

J
Jamal Hadi Salim 已提交
237 238
static int tcf_csum_ipv4_udp(struct sk_buff *skb, unsigned int ihl,
			     unsigned int ipl, int udplite)
239 240
{
	struct udphdr *udph;
241
	const struct iphdr *iph;
242 243
	u16 ul;

244 245 246
	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
		return 1;

247 248 249
	/*
	 * Support both UDP and UDPLITE checksum algorithms, Don't use
	 * udph->len to get the real length without any protocol check,
250 251 252 253 254 255 256 257
	 * UDPLITE uses udph->len for another thing,
	 * Use iph->tot_len, or just ipl.
	 */

	udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
	if (udph == NULL)
		return 0;

258
	iph = ip_hdr(skb);
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
	ul = ntohs(udph->len);

	if (udplite || udph->check) {

		udph->check = 0;

		if (udplite) {
			if (ul == 0)
				skb->csum = csum_partial(udph, ipl - ihl, 0);
			else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
				skb->csum = csum_partial(udph, ul, 0);
			else
				goto ignore_obscure_skb;
		} else {
			if (ul != ipl - ihl)
				goto ignore_obscure_skb;

			skb->csum = csum_partial(udph, ul, 0);
		}

		udph->check = csum_tcpudp_magic(iph->saddr, iph->daddr,
						ul, iph->protocol,
						skb->csum);

		if (!udph->check)
			udph->check = CSUM_MANGLED_0;
	}

	skb->ip_summed = CHECKSUM_NONE;

ignore_obscure_skb:
	return 1;
}

J
Jamal Hadi Salim 已提交
293 294
static int tcf_csum_ipv6_udp(struct sk_buff *skb, unsigned int ihl,
			     unsigned int ipl, int udplite)
295 296
{
	struct udphdr *udph;
297
	const struct ipv6hdr *ip6h;
298 299
	u16 ul;

300 301 302
	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_UDP)
		return 1;

303 304 305
	/*
	 * Support both UDP and UDPLITE checksum algorithms, Don't use
	 * udph->len to get the real length without any protocol check,
306 307 308 309 310 311 312 313
	 * UDPLITE uses udph->len for another thing,
	 * Use ip6h->payload_len + sizeof(*ip6h) ... , or just ipl.
	 */

	udph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*udph));
	if (udph == NULL)
		return 0;

314
	ip6h = ipv6_hdr(skb);
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
	ul = ntohs(udph->len);

	udph->check = 0;

	if (udplite) {
		if (ul == 0)
			skb->csum = csum_partial(udph, ipl - ihl, 0);

		else if ((ul >= sizeof(*udph)) && (ul <= ipl - ihl))
			skb->csum = csum_partial(udph, ul, 0);

		else
			goto ignore_obscure_skb;
	} else {
		if (ul != ipl - ihl)
			goto ignore_obscure_skb;

		skb->csum = csum_partial(udph, ul, 0);
	}

	udph->check = csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, ul,
				      udplite ? IPPROTO_UDPLITE : IPPROTO_UDP,
				      skb->csum);

	if (!udph->check)
		udph->check = CSUM_MANGLED_0;

	skb->ip_summed = CHECKSUM_NONE;

ignore_obscure_skb:
	return 1;
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
static int tcf_csum_sctp(struct sk_buff *skb, unsigned int ihl,
			 unsigned int ipl)
{
	struct sctphdr *sctph;

	if (skb_is_gso(skb) && skb_shinfo(skb)->gso_type & SKB_GSO_SCTP)
		return 1;

	sctph = tcf_csum_skb_nextlayer(skb, ihl, ipl, sizeof(*sctph));
	if (!sctph)
		return 0;

	sctph->checksum = sctp_compute_cksum(skb,
					     skb_network_offset(skb) + ihl);
	skb->ip_summed = CHECKSUM_NONE;
363
	skb->csum_not_inet = 0;
364 365 366 367

	return 1;
}

368 369
static int tcf_csum_ipv4(struct sk_buff *skb, u32 update_flags)
{
370
	const struct iphdr *iph;
371 372 373 374 375 376 377 378 379 380 381 382
	int ntkoff;

	ntkoff = skb_network_offset(skb);

	if (!pskb_may_pull(skb, sizeof(*iph) + ntkoff))
		goto fail;

	iph = ip_hdr(skb);

	switch (iph->frag_off & htons(IP_OFFSET) ? 0 : iph->protocol) {
	case IPPROTO_ICMP:
		if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
383 384
			if (!tcf_csum_ipv4_icmp(skb, iph->ihl * 4,
						ntohs(iph->tot_len)))
385 386 387 388
				goto fail;
		break;
	case IPPROTO_IGMP:
		if (update_flags & TCA_CSUM_UPDATE_FLAG_IGMP)
389 390
			if (!tcf_csum_ipv4_igmp(skb, iph->ihl * 4,
						ntohs(iph->tot_len)))
391 392 393 394
				goto fail;
		break;
	case IPPROTO_TCP:
		if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
395
			if (!tcf_csum_ipv4_tcp(skb, iph->ihl * 4,
396
					       ntohs(iph->tot_len)))
397 398 399 400
				goto fail;
		break;
	case IPPROTO_UDP:
		if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
401
			if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4,
402
					       ntohs(iph->tot_len), 0))
403 404 405 406
				goto fail;
		break;
	case IPPROTO_UDPLITE:
		if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
407
			if (!tcf_csum_ipv4_udp(skb, iph->ihl * 4,
408
					       ntohs(iph->tot_len), 1))
409 410
				goto fail;
		break;
411 412 413 414 415
	case IPPROTO_SCTP:
		if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) &&
		    !tcf_csum_sctp(skb, iph->ihl * 4, ntohs(iph->tot_len)))
			goto fail;
		break;
416 417 418
	}

	if (update_flags & TCA_CSUM_UPDATE_FLAG_IPV4HDR) {
419
		if (skb_try_make_writable(skb, sizeof(*iph) + ntkoff))
420 421
			goto fail;

422
		ip_send_check(ip_hdr(skb));
423 424 425 426 427 428 429 430
	}

	return 1;

fail:
	return 0;
}

J
Jamal Hadi Salim 已提交
431 432
static int tcf_csum_ipv6_hopopts(struct ipv6_opt_hdr *ip6xh, unsigned int ixhl,
				 unsigned int *pl)
433 434 435 436 437 438 439 440
{
	int off, len, optlen;
	unsigned char *xh = (void *)ip6xh;

	off = sizeof(*ip6xh);
	len = ixhl - off;

	while (len > 1) {
441
		switch (xh[off]) {
442
		case IPV6_TLV_PAD1:
443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500
			optlen = 1;
			break;
		case IPV6_TLV_JUMBO:
			optlen = xh[off + 1] + 2;
			if (optlen != 6 || len < 6 || (off & 3) != 2)
				/* wrong jumbo option length/alignment */
				return 0;
			*pl = ntohl(*(__be32 *)(xh + off + 2));
			goto done;
		default:
			optlen = xh[off + 1] + 2;
			if (optlen > len)
				/* ignore obscure options */
				goto done;
			break;
		}
		off += optlen;
		len -= optlen;
	}

done:
	return 1;
}

static int tcf_csum_ipv6(struct sk_buff *skb, u32 update_flags)
{
	struct ipv6hdr *ip6h;
	struct ipv6_opt_hdr *ip6xh;
	unsigned int hl, ixhl;
	unsigned int pl;
	int ntkoff;
	u8 nexthdr;

	ntkoff = skb_network_offset(skb);

	hl = sizeof(*ip6h);

	if (!pskb_may_pull(skb, hl + ntkoff))
		goto fail;

	ip6h = ipv6_hdr(skb);

	pl = ntohs(ip6h->payload_len);
	nexthdr = ip6h->nexthdr;

	do {
		switch (nexthdr) {
		case NEXTHDR_FRAGMENT:
			goto ignore_skb;
		case NEXTHDR_ROUTING:
		case NEXTHDR_HOP:
		case NEXTHDR_DEST:
			if (!pskb_may_pull(skb, hl + sizeof(*ip6xh) + ntkoff))
				goto fail;
			ip6xh = (void *)(skb_network_header(skb) + hl);
			ixhl = ipv6_optlen(ip6xh);
			if (!pskb_may_pull(skb, hl + ixhl + ntkoff))
				goto fail;
501
			ip6xh = (void *)(skb_network_header(skb) + hl);
502 503 504 505 506 507 508 509
			if ((nexthdr == NEXTHDR_HOP) &&
			    !(tcf_csum_ipv6_hopopts(ip6xh, ixhl, &pl)))
				goto fail;
			nexthdr = ip6xh->nexthdr;
			hl += ixhl;
			break;
		case IPPROTO_ICMPV6:
			if (update_flags & TCA_CSUM_UPDATE_FLAG_ICMP)
510
				if (!tcf_csum_ipv6_icmp(skb,
511 512 513 514 515
							hl, pl + sizeof(*ip6h)))
					goto fail;
			goto done;
		case IPPROTO_TCP:
			if (update_flags & TCA_CSUM_UPDATE_FLAG_TCP)
516
				if (!tcf_csum_ipv6_tcp(skb,
517 518 519 520 521
						       hl, pl + sizeof(*ip6h)))
					goto fail;
			goto done;
		case IPPROTO_UDP:
			if (update_flags & TCA_CSUM_UPDATE_FLAG_UDP)
522
				if (!tcf_csum_ipv6_udp(skb, hl,
523
						       pl + sizeof(*ip6h), 0))
524 525 526 527
					goto fail;
			goto done;
		case IPPROTO_UDPLITE:
			if (update_flags & TCA_CSUM_UPDATE_FLAG_UDPLITE)
528
				if (!tcf_csum_ipv6_udp(skb, hl,
529
						       pl + sizeof(*ip6h), 1))
530 531
					goto fail;
			goto done;
532 533 534 535 536
		case IPPROTO_SCTP:
			if ((update_flags & TCA_CSUM_UPDATE_FLAG_SCTP) &&
			    !tcf_csum_sctp(skb, hl, pl + sizeof(*ip6h)))
				goto fail;
			goto done;
537 538 539 540 541 542 543 544 545 546 547 548 549
		default:
			goto ignore_skb;
		}
	} while (pskb_may_pull(skb, hl + 1 + ntkoff));

done:
ignore_skb:
	return 1;

fail:
	return 0;
}

J
Jamal Hadi Salim 已提交
550 551
static int tcf_csum(struct sk_buff *skb, const struct tc_action *a,
		    struct tcf_result *res)
552
{
553
	struct tcf_csum *p = to_tcf_csum(a);
554
	struct tcf_csum_params *params;
555
	u32 update_flags;
556 557 558 559
	int action;

	rcu_read_lock();
	params = rcu_dereference(p->params);
560

561
	tcf_lastuse_update(&p->tcf_tm);
562
	bstats_cpu_update(this_cpu_ptr(p->common.cpu_bstats), skb);
563

564
	action = params->action;
565
	if (unlikely(action == TC_ACT_SHOT))
566
		goto drop_stats;
567

568
	update_flags = params->update_flags;
569
	switch (tc_skb_protocol(skb)) {
570 571 572 573 574 575 576 577 578 579
	case cpu_to_be16(ETH_P_IP):
		if (!tcf_csum_ipv4(skb, update_flags))
			goto drop;
		break;
	case cpu_to_be16(ETH_P_IPV6):
		if (!tcf_csum_ipv6(skb, update_flags))
			goto drop;
		break;
	}

580 581
unlock:
	rcu_read_unlock();
582 583 584
	return action;

drop:
585 586 587
	action = TC_ACT_SHOT;

drop_stats:
588
	qstats_drop_inc(this_cpu_ptr(p->common.cpu_qstats));
589
	goto unlock;
590 591
}

J
Jamal Hadi Salim 已提交
592 593
static int tcf_csum_dump(struct sk_buff *skb, struct tc_action *a, int bind,
			 int ref)
594 595
{
	unsigned char *b = skb_tail_pointer(skb);
596
	struct tcf_csum *p = to_tcf_csum(a);
597
	struct tcf_csum_params *params;
598 599 600 601 602 603 604
	struct tc_csum opt = {
		.index   = p->tcf_index,
		.refcnt  = p->tcf_refcnt - ref,
		.bindcnt = p->tcf_bindcnt - bind,
	};
	struct tcf_t t;

605 606 607 608
	params = rtnl_dereference(p->params);
	opt.action = params->action;
	opt.update_flags = params->update_flags;

609 610
	if (nla_put(skb, TCA_CSUM_PARMS, sizeof(opt), &opt))
		goto nla_put_failure;
611 612

	tcf_tm_dump(&t, &p->tcf_tm);
613
	if (nla_put_64bit(skb, TCA_CSUM_TM, sizeof(t), &t, TCA_CSUM_PAD))
614
		goto nla_put_failure;
615 616 617 618 619 620 621 622

	return skb->len;

nla_put_failure:
	nlmsg_trim(skb, b);
	return -1;
}

623 624 625 626 627 628 629 630 631
static void tcf_csum_cleanup(struct tc_action *a)
{
	struct tcf_csum *p = to_tcf_csum(a);
	struct tcf_csum_params *params;

	params = rcu_dereference_protected(p->params, 1);
	kfree_rcu(params, rcu);
}

632 633
static int tcf_csum_walker(struct net *net, struct sk_buff *skb,
			   struct netlink_callback *cb, int type,
634
			   const struct tc_action_ops *ops)
635 636 637
{
	struct tc_action_net *tn = net_generic(net, csum_net_id);

638
	return tcf_generic_walker(tn, skb, cb, type, ops);
639 640
}

641
static int tcf_csum_search(struct net *net, struct tc_action **a, u32 index)
642 643 644
{
	struct tc_action_net *tn = net_generic(net, csum_net_id);

645
	return tcf_idr_search(tn, a, index);
646 647
}

648
static struct tc_action_ops act_csum_ops = {
649 650 651 652 653 654
	.kind		= "csum",
	.type		= TCA_ACT_CSUM,
	.owner		= THIS_MODULE,
	.act		= tcf_csum,
	.dump		= tcf_csum_dump,
	.init		= tcf_csum_init,
655
	.cleanup	= tcf_csum_cleanup,
656 657
	.walk		= tcf_csum_walker,
	.lookup		= tcf_csum_search,
658
	.size		= sizeof(struct tcf_csum),
659 660 661 662 663 664
};

static __net_init int csum_init_net(struct net *net)
{
	struct tc_action_net *tn = net_generic(net, csum_net_id);

665
	return tc_action_net_init(tn, &act_csum_ops);
666 667
}

668
static void __net_exit csum_exit_net(struct list_head *net_list)
669
{
670
	tc_action_net_exit(net_list, csum_net_id);
671 672 673 674
}

static struct pernet_operations csum_net_ops = {
	.init = csum_init_net,
675
	.exit_batch = csum_exit_net,
676 677
	.id   = &csum_net_id,
	.size = sizeof(struct tc_action_net),
678 679 680 681 682 683 684
};

MODULE_DESCRIPTION("Checksum updating actions");
MODULE_LICENSE("GPL");

static int __init csum_init_module(void)
{
685
	return tcf_register_action(&act_csum_ops, &csum_net_ops);
686 687 688 689
}

static void __exit csum_cleanup_module(void)
{
690
	tcf_unregister_action(&act_csum_ops, &csum_net_ops);
691 692 693 694
}

module_init(csum_init_module);
module_exit(csum_cleanup_module);