xref: /linux/net/sched/act_pedit.c (revision 6a87e0f0ce1ae8d70566935215430e718ea776ff)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * net/sched/act_pedit.c	Generic packet editor
4  *
5  * Authors:	Jamal Hadi Salim (2002-4)
6  */
7 
8 #include <linux/types.h>
9 #include <linux/kernel.h>
10 #include <linux/string.h>
11 #include <linux/errno.h>
12 #include <linux/skbuff.h>
13 #include <linux/rtnetlink.h>
14 #include <linux/module.h>
15 #include <linux/init.h>
16 #include <linux/ip.h>
17 #include <linux/ipv6.h>
18 #include <linux/slab.h>
19 #include <net/ipv6.h>
20 #include <net/netlink.h>
21 #include <net/pkt_sched.h>
22 #include <linux/tc_act/tc_pedit.h>
23 #include <net/tc_act/tc_pedit.h>
24 #include <uapi/linux/tc_act/tc_pedit.h>
25 #include <net/pkt_cls.h>
26 #include <net/tc_wrapper.h>
27 
28 static struct tc_action_ops act_pedit_ops;
29 
30 static const struct nla_policy pedit_policy[TCA_PEDIT_MAX + 1] = {
31 	[TCA_PEDIT_PARMS]	= { .len = sizeof(struct tc_pedit) },
32 	[TCA_PEDIT_PARMS_EX]	= { .len = sizeof(struct tc_pedit) },
33 	[TCA_PEDIT_KEYS_EX]   = { .type = NLA_NESTED },
34 };
35 
36 static const struct nla_policy pedit_key_ex_policy[TCA_PEDIT_KEY_EX_MAX + 1] = {
37 	[TCA_PEDIT_KEY_EX_HTYPE] =
38 		NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_HDR_TYPE_MAX),
39 	[TCA_PEDIT_KEY_EX_CMD] = NLA_POLICY_MAX(NLA_U16, TCA_PEDIT_CMD_MAX),
40 };
41 
42 static struct tcf_pedit_key_ex *tcf_pedit_keys_ex_parse(struct nlattr *nla,
43 							u8 n, struct netlink_ext_ack *extack)
44 {
45 	struct tcf_pedit_key_ex *keys_ex;
46 	struct tcf_pedit_key_ex *k;
47 	const struct nlattr *ka;
48 	int err = -EINVAL;
49 	int rem;
50 
51 	if (!nla)
52 		return NULL;
53 
54 	keys_ex = kcalloc(n, sizeof(*k), GFP_KERNEL);
55 	if (!keys_ex)
56 		return ERR_PTR(-ENOMEM);
57 
58 	k = keys_ex;
59 
60 	nla_for_each_nested(ka, nla, rem) {
61 		struct nlattr *tb[TCA_PEDIT_KEY_EX_MAX + 1];
62 
63 		if (!n) {
64 			NL_SET_ERR_MSG_MOD(extack, "Can't parse more extended keys than requested");
65 			err = -EINVAL;
66 			goto err_out;
67 		}
68 		n--;
69 
70 		if (nla_type(ka) != TCA_PEDIT_KEY_EX) {
71 			NL_SET_ERR_MSG_ATTR(extack, ka, "Unknown attribute, expected extended key");
72 			err = -EINVAL;
73 			goto err_out;
74 		}
75 
76 		err = nla_parse_nested_deprecated(tb, TCA_PEDIT_KEY_EX_MAX,
77 						  ka, pedit_key_ex_policy,
78 						  NULL);
79 		if (err)
80 			goto err_out;
81 
82 		if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_HTYPE)) {
83 			NL_SET_ERR_MSG(extack, "Missing required attribute");
84 			err = -EINVAL;
85 			goto err_out;
86 		}
87 
88 		if (NL_REQ_ATTR_CHECK(extack, nla, tb, TCA_PEDIT_KEY_EX_CMD)) {
89 			NL_SET_ERR_MSG(extack, "Missing required attribute");
90 			err = -EINVAL;
91 			goto err_out;
92 		}
93 
94 		k->htype = nla_get_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]);
95 		k->cmd = nla_get_u16(tb[TCA_PEDIT_KEY_EX_CMD]);
96 
97 		k++;
98 	}
99 
100 	if (n) {
101 		NL_SET_ERR_MSG_MOD(extack, "Not enough extended keys to parse");
102 		err = -EINVAL;
103 		goto err_out;
104 	}
105 
106 	return keys_ex;
107 
108 err_out:
109 	kfree(keys_ex);
110 	return ERR_PTR(err);
111 }
112 
113 static int tcf_pedit_key_ex_dump(struct sk_buff *skb,
114 				 struct tcf_pedit_key_ex *keys_ex, int n)
115 {
116 	struct nlattr *keys_start = nla_nest_start_noflag(skb,
117 							  TCA_PEDIT_KEYS_EX);
118 
119 	if (!keys_start)
120 		goto nla_failure;
121 	for (; n > 0; n--) {
122 		struct nlattr *key_start;
123 
124 		key_start = nla_nest_start_noflag(skb, TCA_PEDIT_KEY_EX);
125 		if (!key_start)
126 			goto nla_failure;
127 
128 		if (nla_put_u16(skb, TCA_PEDIT_KEY_EX_HTYPE, keys_ex->htype) ||
129 		    nla_put_u16(skb, TCA_PEDIT_KEY_EX_CMD, keys_ex->cmd))
130 			goto nla_failure;
131 
132 		nla_nest_end(skb, key_start);
133 
134 		keys_ex++;
135 	}
136 
137 	nla_nest_end(skb, keys_start);
138 
139 	return 0;
140 nla_failure:
141 	nla_nest_cancel(skb, keys_start);
142 	return -EINVAL;
143 }
144 
145 static void tcf_pedit_cleanup_rcu(struct rcu_head *head)
146 {
147 	struct tcf_pedit_parms *parms =
148 		container_of(head, struct tcf_pedit_parms, rcu);
149 
150 	kfree(parms->tcfp_keys_ex);
151 	kfree(parms->tcfp_keys);
152 
153 	kfree(parms);
154 }
155 
156 static int tcf_pedit_init(struct net *net, struct nlattr *nla,
157 			  struct nlattr *est, struct tc_action **a,
158 			  struct tcf_proto *tp, u32 flags,
159 			  struct netlink_ext_ack *extack)
160 {
161 	struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id);
162 	bool bind = flags & TCA_ACT_FLAGS_BIND;
163 	struct tcf_chain *goto_ch = NULL;
164 	struct tcf_pedit_parms *oparms, *nparms;
165 	struct nlattr *tb[TCA_PEDIT_MAX + 1];
166 	struct tc_pedit *parm;
167 	struct nlattr *pattr;
168 	struct tcf_pedit *p;
169 	int ret = 0, err;
170 	int i, ksize;
171 	u32 index;
172 
173 	if (!nla) {
174 		NL_SET_ERR_MSG_MOD(extack, "Pedit requires attributes to be passed");
175 		return -EINVAL;
176 	}
177 
178 	err = nla_parse_nested_deprecated(tb, TCA_PEDIT_MAX, nla,
179 					  pedit_policy, NULL);
180 	if (err < 0)
181 		return err;
182 
183 	pattr = tb[TCA_PEDIT_PARMS];
184 	if (!pattr)
185 		pattr = tb[TCA_PEDIT_PARMS_EX];
186 	if (!pattr) {
187 		NL_SET_ERR_MSG_MOD(extack, "Missing required TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute");
188 		return -EINVAL;
189 	}
190 
191 	parm = nla_data(pattr);
192 
193 	index = parm->index;
194 	err = tcf_idr_check_alloc(tn, &index, a, bind);
195 	if (!err) {
196 		ret = tcf_idr_create_from_flags(tn, index, est, a,
197 						&act_pedit_ops, bind, flags);
198 		if (ret) {
199 			tcf_idr_cleanup(tn, index);
200 			return ret;
201 		}
202 		ret = ACT_P_CREATED;
203 	} else if (err > 0) {
204 		if (bind)
205 			return 0;
206 		if (!(flags & TCA_ACT_FLAGS_REPLACE)) {
207 			ret = -EEXIST;
208 			goto out_release;
209 		}
210 	} else {
211 		return err;
212 	}
213 
214 	if (!parm->nkeys) {
215 		NL_SET_ERR_MSG_MOD(extack, "Pedit requires keys to be passed");
216 		ret = -EINVAL;
217 		goto out_release;
218 	}
219 	ksize = parm->nkeys * sizeof(struct tc_pedit_key);
220 	if (nla_len(pattr) < sizeof(*parm) + ksize) {
221 		NL_SET_ERR_MSG_ATTR(extack, pattr, "Length of TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute is invalid");
222 		ret = -EINVAL;
223 		goto out_release;
224 	}
225 
226 	nparms = kzalloc(sizeof(*nparms), GFP_KERNEL);
227 	if (!nparms) {
228 		ret = -ENOMEM;
229 		goto out_release;
230 	}
231 
232 	nparms->tcfp_keys_ex =
233 		tcf_pedit_keys_ex_parse(tb[TCA_PEDIT_KEYS_EX], parm->nkeys, extack);
234 	if (IS_ERR(nparms->tcfp_keys_ex)) {
235 		ret = PTR_ERR(nparms->tcfp_keys_ex);
236 		goto out_free;
237 	}
238 
239 	err = tcf_action_check_ctrlact(parm->action, tp, &goto_ch, extack);
240 	if (err < 0) {
241 		ret = err;
242 		goto out_free_ex;
243 	}
244 
245 	nparms->tcfp_off_max_hint = 0;
246 	nparms->tcfp_flags = parm->flags;
247 	nparms->tcfp_nkeys = parm->nkeys;
248 
249 	nparms->tcfp_keys = kmemdup(parm->keys, ksize, GFP_KERNEL);
250 	if (!nparms->tcfp_keys) {
251 		ret = -ENOMEM;
252 		goto put_chain;
253 	}
254 
255 	for (i = 0; i < nparms->tcfp_nkeys; ++i) {
256 		u32 offmask = nparms->tcfp_keys[i].offmask;
257 		u32 cur = nparms->tcfp_keys[i].off;
258 
259 		/* The AT option can be added to static offsets in the datapath */
260 		if (!offmask && cur % 4) {
261 			NL_SET_ERR_MSG_MOD(extack, "Offsets must be on 32bit boundaries");
262 			ret = -EINVAL;
263 			goto out_free_keys;
264 		}
265 
266 		/* sanitize the shift value for any later use */
267 		nparms->tcfp_keys[i].shift = min_t(size_t,
268 						   BITS_PER_TYPE(int) - 1,
269 						   nparms->tcfp_keys[i].shift);
270 
271 		/* The AT option can read a single byte, we can bound the actual
272 		 * value with uchar max.
273 		 */
274 		cur += (0xff & offmask) >> nparms->tcfp_keys[i].shift;
275 
276 		/* Each key touches 4 bytes starting from the computed offset */
277 		nparms->tcfp_off_max_hint =
278 			max(nparms->tcfp_off_max_hint, cur + 4);
279 	}
280 
281 	p = to_pedit(*a);
282 
283 	spin_lock_bh(&p->tcf_lock);
284 	goto_ch = tcf_action_set_ctrlact(*a, parm->action, goto_ch);
285 	oparms = rcu_replace_pointer(p->parms, nparms, 1);
286 	spin_unlock_bh(&p->tcf_lock);
287 
288 	if (oparms)
289 		call_rcu(&oparms->rcu, tcf_pedit_cleanup_rcu);
290 
291 	if (goto_ch)
292 		tcf_chain_put_by_act(goto_ch);
293 
294 	return ret;
295 
296 out_free_keys:
297 	kfree(nparms->tcfp_keys);
298 put_chain:
299 	if (goto_ch)
300 		tcf_chain_put_by_act(goto_ch);
301 out_free_ex:
302 	kfree(nparms->tcfp_keys_ex);
303 out_free:
304 	kfree(nparms);
305 out_release:
306 	tcf_idr_release(*a, bind);
307 	return ret;
308 }
309 
310 static void tcf_pedit_cleanup(struct tc_action *a)
311 {
312 	struct tcf_pedit *p = to_pedit(a);
313 	struct tcf_pedit_parms *parms;
314 
315 	parms = rcu_dereference_protected(p->parms, 1);
316 
317 	if (parms)
318 		call_rcu(&parms->rcu, tcf_pedit_cleanup_rcu);
319 }
320 
321 static bool offset_valid(struct sk_buff *skb, int offset)
322 {
323 	if (offset > 0 && offset > skb->len)
324 		return false;
325 
326 	if  (offset < 0 && -offset > skb_headroom(skb))
327 		return false;
328 
329 	return true;
330 }
331 
332 static int pedit_l4_skb_offset(struct sk_buff *skb, int *hoffset, const int header_type)
333 {
334 	const int noff = skb_network_offset(skb);
335 	int ret = -EINVAL;
336 	struct iphdr _iph;
337 
338 	switch (skb->protocol) {
339 	case htons(ETH_P_IP): {
340 		const struct iphdr *iph = skb_header_pointer(skb, noff, sizeof(_iph), &_iph);
341 
342 		if (!iph)
343 			goto out;
344 		*hoffset = noff + iph->ihl * 4;
345 		ret = 0;
346 		break;
347 	}
348 	case htons(ETH_P_IPV6):
349 		ret = ipv6_find_hdr(skb, hoffset, header_type, NULL, NULL) == header_type ? 0 : -EINVAL;
350 		break;
351 	}
352 out:
353 	return ret;
354 }
355 
356 static int pedit_skb_hdr_offset(struct sk_buff *skb,
357 				 enum pedit_header_type htype, int *hoffset)
358 {
359 	int ret = -EINVAL;
360 	/* 'htype' is validated in the netlink parsing */
361 	switch (htype) {
362 	case TCA_PEDIT_KEY_EX_HDR_TYPE_ETH:
363 		if (skb_mac_header_was_set(skb)) {
364 			*hoffset = skb_mac_offset(skb);
365 			ret = 0;
366 		}
367 		break;
368 	case TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK:
369 	case TCA_PEDIT_KEY_EX_HDR_TYPE_IP4:
370 	case TCA_PEDIT_KEY_EX_HDR_TYPE_IP6:
371 		*hoffset = skb_network_offset(skb);
372 		ret = 0;
373 		break;
374 	case TCA_PEDIT_KEY_EX_HDR_TYPE_TCP:
375 		ret = pedit_l4_skb_offset(skb, hoffset, IPPROTO_TCP);
376 		break;
377 	case TCA_PEDIT_KEY_EX_HDR_TYPE_UDP:
378 		ret = pedit_l4_skb_offset(skb, hoffset, IPPROTO_UDP);
379 		break;
380 	default:
381 		break;
382 	}
383 	return ret;
384 }
385 
386 TC_INDIRECT_SCOPE int tcf_pedit_act(struct sk_buff *skb,
387 				    const struct tc_action *a,
388 				    struct tcf_result *res)
389 {
390 	enum pedit_header_type htype = TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
391 	enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
392 	struct tcf_pedit *p = to_pedit(a);
393 	struct tcf_pedit_key_ex *tkey_ex;
394 	struct tcf_pedit_parms *parms;
395 	struct tc_pedit_key *tkey;
396 	u32 max_offset;
397 	int i;
398 
399 	parms = rcu_dereference_bh(p->parms);
400 
401 	max_offset = (skb_transport_header_was_set(skb) ?
402 		      skb_transport_offset(skb) :
403 		      skb_network_offset(skb)) +
404 		     parms->tcfp_off_max_hint;
405 	if (skb_ensure_writable(skb, min(skb->len, max_offset)))
406 		goto done;
407 
408 	tcf_lastuse_update(&p->tcf_tm);
409 	tcf_action_update_bstats(&p->common, skb);
410 
411 	tkey = parms->tcfp_keys;
412 	tkey_ex = parms->tcfp_keys_ex;
413 
414 	for (i = parms->tcfp_nkeys; i > 0; i--, tkey++) {
415 		int offset = tkey->off;
416 		int hoffset = 0;
417 		u32 *ptr, hdata;
418 		u32 val;
419 		int rc;
420 
421 		if (tkey_ex) {
422 			htype = tkey_ex->htype;
423 			cmd = tkey_ex->cmd;
424 
425 			tkey_ex++;
426 		}
427 
428 		rc = pedit_skb_hdr_offset(skb, htype, &hoffset);
429 		if (rc) {
430 			pr_info_ratelimited("tc action pedit unable to extract header offset for header type (0x%x)\n", htype);
431 			goto bad;
432 		}
433 
434 		if (tkey->offmask) {
435 			u8 *d, _d;
436 
437 			if (!offset_valid(skb, hoffset + tkey->at)) {
438 				pr_info_ratelimited("tc action pedit 'at' offset %d out of bounds\n",
439 						    hoffset + tkey->at);
440 				goto bad;
441 			}
442 			d = skb_header_pointer(skb, hoffset + tkey->at,
443 					       sizeof(_d), &_d);
444 			if (!d)
445 				goto bad;
446 
447 			offset += (*d & tkey->offmask) >> tkey->shift;
448 			if (offset % 4) {
449 				pr_info_ratelimited("tc action pedit offset must be on 32 bit boundaries\n");
450 				goto bad;
451 			}
452 		}
453 
454 		if (!offset_valid(skb, hoffset + offset)) {
455 			pr_info_ratelimited("tc action pedit offset %d out of bounds\n", hoffset + offset);
456 			goto bad;
457 		}
458 
459 		ptr = skb_header_pointer(skb, hoffset + offset,
460 					 sizeof(hdata), &hdata);
461 		if (!ptr)
462 			goto bad;
463 		/* just do it, baby */
464 		switch (cmd) {
465 		case TCA_PEDIT_KEY_EX_CMD_SET:
466 			val = tkey->val;
467 			break;
468 		case TCA_PEDIT_KEY_EX_CMD_ADD:
469 			val = (*ptr + tkey->val) & ~tkey->mask;
470 			break;
471 		default:
472 			pr_info_ratelimited("tc action pedit bad command (%d)\n", cmd);
473 			goto bad;
474 		}
475 
476 		*ptr = ((*ptr & tkey->mask) ^ val);
477 		if (ptr == &hdata)
478 			skb_store_bits(skb, hoffset + offset, ptr, 4);
479 	}
480 
481 	goto done;
482 
483 bad:
484 	tcf_action_inc_overlimit_qstats(&p->common);
485 done:
486 	return p->tcf_action;
487 }
488 
489 static void tcf_pedit_stats_update(struct tc_action *a, u64 bytes, u64 packets,
490 				   u64 drops, u64 lastuse, bool hw)
491 {
492 	struct tcf_pedit *d = to_pedit(a);
493 	struct tcf_t *tm = &d->tcf_tm;
494 
495 	tcf_action_update_stats(a, bytes, packets, drops, hw);
496 	tm->lastuse = max_t(u64, tm->lastuse, lastuse);
497 }
498 
499 static int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a,
500 			  int bind, int ref)
501 {
502 	unsigned char *b = skb_tail_pointer(skb);
503 	struct tcf_pedit *p = to_pedit(a);
504 	struct tcf_pedit_parms *parms;
505 	struct tc_pedit *opt;
506 	struct tcf_t t;
507 	int s;
508 
509 	spin_lock_bh(&p->tcf_lock);
510 	parms = rcu_dereference_protected(p->parms, 1);
511 	s = struct_size(opt, keys, parms->tcfp_nkeys);
512 
513 	opt = kzalloc(s, GFP_ATOMIC);
514 	if (unlikely(!opt)) {
515 		spin_unlock_bh(&p->tcf_lock);
516 		return -ENOBUFS;
517 	}
518 
519 	memcpy(opt->keys, parms->tcfp_keys,
520 	       flex_array_size(opt, keys, parms->tcfp_nkeys));
521 	opt->index = p->tcf_index;
522 	opt->nkeys = parms->tcfp_nkeys;
523 	opt->flags = parms->tcfp_flags;
524 	opt->action = p->tcf_action;
525 	opt->refcnt = refcount_read(&p->tcf_refcnt) - ref;
526 	opt->bindcnt = atomic_read(&p->tcf_bindcnt) - bind;
527 
528 	if (parms->tcfp_keys_ex) {
529 		if (tcf_pedit_key_ex_dump(skb, parms->tcfp_keys_ex,
530 					  parms->tcfp_nkeys))
531 			goto nla_put_failure;
532 
533 		if (nla_put(skb, TCA_PEDIT_PARMS_EX, s, opt))
534 			goto nla_put_failure;
535 	} else {
536 		if (nla_put(skb, TCA_PEDIT_PARMS, s, opt))
537 			goto nla_put_failure;
538 	}
539 
540 	tcf_tm_dump(&t, &p->tcf_tm);
541 	if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD))
542 		goto nla_put_failure;
543 	spin_unlock_bh(&p->tcf_lock);
544 
545 	kfree(opt);
546 	return skb->len;
547 
548 nla_put_failure:
549 	spin_unlock_bh(&p->tcf_lock);
550 	nlmsg_trim(skb, b);
551 	kfree(opt);
552 	return -1;
553 }
554 
555 static int tcf_pedit_offload_act_setup(struct tc_action *act, void *entry_data,
556 				       u32 *index_inc, bool bind,
557 				       struct netlink_ext_ack *extack)
558 {
559 	if (bind) {
560 		struct flow_action_entry *entry = entry_data;
561 		int k;
562 
563 		for (k = 0; k < tcf_pedit_nkeys(act); k++) {
564 			switch (tcf_pedit_cmd(act, k)) {
565 			case TCA_PEDIT_KEY_EX_CMD_SET:
566 				entry->id = FLOW_ACTION_MANGLE;
567 				break;
568 			case TCA_PEDIT_KEY_EX_CMD_ADD:
569 				entry->id = FLOW_ACTION_ADD;
570 				break;
571 			default:
572 				NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload");
573 				return -EOPNOTSUPP;
574 			}
575 			entry->mangle.htype = tcf_pedit_htype(act, k);
576 			entry->mangle.mask = tcf_pedit_mask(act, k);
577 			entry->mangle.val = tcf_pedit_val(act, k);
578 			entry->mangle.offset = tcf_pedit_offset(act, k);
579 			entry->hw_stats = tc_act_hw_stats(act->hw_stats);
580 			entry++;
581 		}
582 		*index_inc = k;
583 	} else {
584 		struct flow_offload_action *fl_action = entry_data;
585 		u32 cmd = tcf_pedit_cmd(act, 0);
586 		int k;
587 
588 		switch (cmd) {
589 		case TCA_PEDIT_KEY_EX_CMD_SET:
590 			fl_action->id = FLOW_ACTION_MANGLE;
591 			break;
592 		case TCA_PEDIT_KEY_EX_CMD_ADD:
593 			fl_action->id = FLOW_ACTION_ADD;
594 			break;
595 		default:
596 			NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload");
597 			return -EOPNOTSUPP;
598 		}
599 
600 		for (k = 1; k < tcf_pedit_nkeys(act); k++) {
601 			if (cmd != tcf_pedit_cmd(act, k)) {
602 				NL_SET_ERR_MSG_MOD(extack, "Unsupported pedit command offload");
603 				return -EOPNOTSUPP;
604 			}
605 		}
606 	}
607 
608 	return 0;
609 }
610 
611 static struct tc_action_ops act_pedit_ops = {
612 	.kind		=	"pedit",
613 	.id		=	TCA_ID_PEDIT,
614 	.owner		=	THIS_MODULE,
615 	.act		=	tcf_pedit_act,
616 	.stats_update	=	tcf_pedit_stats_update,
617 	.dump		=	tcf_pedit_dump,
618 	.cleanup	=	tcf_pedit_cleanup,
619 	.init		=	tcf_pedit_init,
620 	.offload_act_setup =	tcf_pedit_offload_act_setup,
621 	.size		=	sizeof(struct tcf_pedit),
622 };
623 
624 static __net_init int pedit_init_net(struct net *net)
625 {
626 	struct tc_action_net *tn = net_generic(net, act_pedit_ops.net_id);
627 
628 	return tc_action_net_init(net, tn, &act_pedit_ops);
629 }
630 
631 static void __net_exit pedit_exit_net(struct list_head *net_list)
632 {
633 	tc_action_net_exit(net_list, act_pedit_ops.net_id);
634 }
635 
636 static struct pernet_operations pedit_net_ops = {
637 	.init = pedit_init_net,
638 	.exit_batch = pedit_exit_net,
639 	.id   = &act_pedit_ops.net_id,
640 	.size = sizeof(struct tc_action_net),
641 };
642 
643 MODULE_AUTHOR("Jamal Hadi Salim(2002-4)");
644 MODULE_DESCRIPTION("Generic Packet Editor actions");
645 MODULE_LICENSE("GPL");
646 
647 static int __init pedit_init_module(void)
648 {
649 	return tcf_register_action(&act_pedit_ops, &pedit_net_ops);
650 }
651 
652 static void __exit pedit_cleanup_module(void)
653 {
654 	tcf_unregister_action(&act_pedit_ops, &pedit_net_ops);
655 }
656 
657 module_init(pedit_init_module);
658 module_exit(pedit_cleanup_module);
659