act_pedit.c 11.6 KB
Newer Older
L
Linus Torvalds 已提交
1
/*
2
 * net/sched/act_pedit.c	Generic packet editor
L
Linus Torvalds 已提交
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 *		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.
 *
 * Authors:	Jamal Hadi Salim (2002-4)
 */

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/rtnetlink.h>
#include <linux/module.h>
#include <linux/init.h>
20
#include <linux/slab.h>
21
#include <net/netlink.h>
L
Linus Torvalds 已提交
22 23 24
#include <net/pkt_sched.h>
#include <linux/tc_act/tc_pedit.h>
#include <net/tc_act/tc_pedit.h>
25
#include <uapi/linux/tc_act/tc_pedit.h>
L
Linus Torvalds 已提交
26

27
static unsigned int pedit_net_id;
28
static struct tc_action_ops act_pedit_ops;
29

30
static const struct nla_policy pedit_policy[TCA_PEDIT_MAX + 1] = {
J
jamal 已提交
31
	[TCA_PEDIT_PARMS]	= { .len = sizeof(struct tc_pedit) },
32
	[TCA_PEDIT_KEYS_EX]   = { .type = NLA_NESTED },
33 34
};

35 36
static const struct nla_policy pedit_key_ex_policy[TCA_PEDIT_KEY_EX_MAX + 1] = {
	[TCA_PEDIT_KEY_EX_HTYPE]  = { .type = NLA_U16 },
37
	[TCA_PEDIT_KEY_EX_CMD]	  = { .type = NLA_U16 },
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
};

static struct tcf_pedit_key_ex *tcf_pedit_keys_ex_parse(struct nlattr *nla,
							u8 n)
{
	struct tcf_pedit_key_ex *keys_ex;
	struct tcf_pedit_key_ex *k;
	const struct nlattr *ka;
	int err = -EINVAL;
	int rem;

	if (!nla || !n)
		return NULL;

	keys_ex = kcalloc(n, sizeof(*k), GFP_KERNEL);
	if (!keys_ex)
		return ERR_PTR(-ENOMEM);

	k = keys_ex;

	nla_for_each_nested(ka, nla, rem) {
		struct nlattr *tb[TCA_PEDIT_KEY_EX_MAX + 1];

		if (!n) {
			err = -EINVAL;
			goto err_out;
		}
		n--;

		if (nla_type(ka) != TCA_PEDIT_KEY_EX) {
			err = -EINVAL;
			goto err_out;
		}

		err = nla_parse_nested(tb, TCA_PEDIT_KEY_EX_MAX, ka,
73
				       pedit_key_ex_policy, NULL);
74 75 76
		if (err)
			goto err_out;

77 78
		if (!tb[TCA_PEDIT_KEY_EX_HTYPE] ||
		    !tb[TCA_PEDIT_KEY_EX_CMD]) {
79 80 81 82 83
			err = -EINVAL;
			goto err_out;
		}

		k->htype = nla_get_u16(tb[TCA_PEDIT_KEY_EX_HTYPE]);
84
		k->cmd = nla_get_u16(tb[TCA_PEDIT_KEY_EX_CMD]);
85

86 87
		if (k->htype > TCA_PEDIT_HDR_TYPE_MAX ||
		    k->cmd > TCA_PEDIT_CMD_MAX) {
88 89 90 91 92 93 94
			err = -EINVAL;
			goto err_out;
		}

		k++;
	}

D
Dan Carpenter 已提交
95 96
	if (n) {
		err = -EINVAL;
97
		goto err_out;
D
Dan Carpenter 已提交
98
	}
99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

	return keys_ex;

err_out:
	kfree(keys_ex);
	return ERR_PTR(err);
}

static int tcf_pedit_key_ex_dump(struct sk_buff *skb,
				 struct tcf_pedit_key_ex *keys_ex, int n)
{
	struct nlattr *keys_start = nla_nest_start(skb, TCA_PEDIT_KEYS_EX);

	for (; n > 0; n--) {
		struct nlattr *key_start;

		key_start = nla_nest_start(skb, TCA_PEDIT_KEY_EX);

117 118
		if (nla_put_u16(skb, TCA_PEDIT_KEY_EX_HTYPE, keys_ex->htype) ||
		    nla_put_u16(skb, TCA_PEDIT_KEY_EX_CMD, keys_ex->cmd)) {
119 120 121 122 123 124 125 126 127 128 129 130 131 132
			nlmsg_trim(skb, keys_start);
			return -EINVAL;
		}

		nla_nest_end(skb, key_start);

		keys_ex++;
	}

	nla_nest_end(skb, keys_start);

	return 0;
}

133
static int tcf_pedit_init(struct net *net, struct nlattr *nla,
134
			  struct nlattr *est, struct tc_action **a,
135 136
			  int ovr, int bind, bool rtnl_held,
			  struct netlink_ext_ack *extack)
L
Linus Torvalds 已提交
137
{
138
	struct tc_action_net *tn = net_generic(net, pedit_net_id);
139
	struct nlattr *tb[TCA_PEDIT_MAX + 1];
L
Linus Torvalds 已提交
140
	struct tc_pedit_key *keys = NULL;
141
	struct tcf_pedit_key_ex *keys_ex;
142 143 144 145
	struct tc_pedit *parm;
	struct nlattr *pattr;
	struct tcf_pedit *p;
	int ret = 0, err;
L
Linus Torvalds 已提交
146 147
	int ksize;

148 149
	if (!nla) {
		NL_SET_ERR_MSG_MOD(extack, "Pedit requires attributes to be passed");
L
Linus Torvalds 已提交
150
		return -EINVAL;
151
	}
L
Linus Torvalds 已提交
152

153
	err = nla_parse_nested(tb, TCA_PEDIT_MAX, nla, pedit_policy, NULL);
154 155 156
	if (err < 0)
		return err;

157 158 159
	pattr = tb[TCA_PEDIT_PARMS];
	if (!pattr)
		pattr = tb[TCA_PEDIT_PARMS_EX];
160 161
	if (!pattr) {
		NL_SET_ERR_MSG_MOD(extack, "Missing required TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute");
L
Linus Torvalds 已提交
162
		return -EINVAL;
163
	}
164 165

	parm = nla_data(pattr);
L
Linus Torvalds 已提交
166
	ksize = parm->nkeys * sizeof(struct tc_pedit_key);
167 168
	if (nla_len(pattr) < sizeof(*parm) + ksize) {
		NL_SET_ERR_MSG_ATTR(extack, pattr, "Length of TCA_PEDIT_PARMS or TCA_PEDIT_PARMS_EX pedit attribute is invalid");
L
Linus Torvalds 已提交
169
		return -EINVAL;
170
	}
L
Linus Torvalds 已提交
171

172 173 174 175
	keys_ex = tcf_pedit_keys_ex_parse(tb[TCA_PEDIT_KEYS_EX], parm->nkeys);
	if (IS_ERR(keys_ex))
		return PTR_ERR(keys_ex);

176 177
	err = tcf_idr_check_alloc(tn, &parm->index, a, bind);
	if (!err) {
178
		if (!parm->nkeys) {
179
			tcf_idr_cleanup(tn, parm->index);
180
			NL_SET_ERR_MSG_MOD(extack, "Pedit requires keys to be passed");
181 182
			ret = -EINVAL;
			goto out_free;
183
		}
184 185
		ret = tcf_idr_create(tn, parm->index, est, a,
				     &act_pedit_ops, bind, false);
186 187
		if (ret) {
			tcf_idr_cleanup(tn, parm->index);
188
			goto out_free;
189
		}
190
		p = to_pedit(*a);
L
Linus Torvalds 已提交
191
		keys = kmalloc(ksize, GFP_KERNEL);
192
		if (!keys) {
193
			tcf_idr_release(*a, bind);
194 195
			ret = -ENOMEM;
			goto out_free;
L
Linus Torvalds 已提交
196 197
		}
		ret = ACT_P_CREATED;
198
	} else if (err > 0) {
199
		if (bind)
200 201
			goto out_free;
		if (!ovr) {
202
			tcf_idr_release(*a, bind);
203 204 205
			ret = -EEXIST;
			goto out_free;
		}
206
		p = to_pedit(*a);
207
		if (p->tcfp_nkeys && p->tcfp_nkeys != parm->nkeys) {
L
Linus Torvalds 已提交
208
			keys = kmalloc(ksize, GFP_KERNEL);
209
			if (!keys) {
210 211
				ret = -ENOMEM;
				goto out_free;
212
			}
L
Linus Torvalds 已提交
213
		}
214 215
	} else {
		return err;
L
Linus Torvalds 已提交
216 217
	}

218 219 220
	spin_lock_bh(&p->tcf_lock);
	p->tcfp_flags = parm->flags;
	p->tcf_action = parm->action;
L
Linus Torvalds 已提交
221
	if (keys) {
222 223 224
		kfree(p->tcfp_keys);
		p->tcfp_keys = keys;
		p->tcfp_nkeys = parm->nkeys;
L
Linus Torvalds 已提交
225
	}
226
	memcpy(p->tcfp_keys, parm->keys, ksize);
227 228 229 230

	kfree(p->tcfp_keys_ex);
	p->tcfp_keys_ex = keys_ex;

231
	spin_unlock_bh(&p->tcf_lock);
L
Linus Torvalds 已提交
232
	if (ret == ACT_P_CREATED)
233
		tcf_idr_insert(tn, *a);
L
Linus Torvalds 已提交
234
	return ret;
235 236 237 238
out_free:
	kfree(keys_ex);
	return ret;

L
Linus Torvalds 已提交
239 240
}

241
static void tcf_pedit_cleanup(struct tc_action *a)
L
Linus Torvalds 已提交
242
{
243
	struct tcf_pedit *p = to_pedit(a);
W
WANG Cong 已提交
244
	struct tc_pedit_key *keys = p->tcfp_keys;
245

W
WANG Cong 已提交
246
	kfree(keys);
247
	kfree(p->tcfp_keys_ex);
L
Linus Torvalds 已提交
248 249
}

250 251 252 253 254 255 256 257 258 259 260
static bool offset_valid(struct sk_buff *skb, int offset)
{
	if (offset > 0 && offset > skb->len)
		return false;

	if  (offset < 0 && -offset > skb_headroom(skb))
		return false;

	return true;
}

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 293
static int pedit_skb_hdr_offset(struct sk_buff *skb,
				enum pedit_header_type htype, int *hoffset)
{
	int ret = -EINVAL;

	switch (htype) {
	case TCA_PEDIT_KEY_EX_HDR_TYPE_ETH:
		if (skb_mac_header_was_set(skb)) {
			*hoffset = skb_mac_offset(skb);
			ret = 0;
		}
		break;
	case TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK:
	case TCA_PEDIT_KEY_EX_HDR_TYPE_IP4:
	case TCA_PEDIT_KEY_EX_HDR_TYPE_IP6:
		*hoffset = skb_network_offset(skb);
		ret = 0;
		break;
	case TCA_PEDIT_KEY_EX_HDR_TYPE_TCP:
	case TCA_PEDIT_KEY_EX_HDR_TYPE_UDP:
		if (skb_transport_header_was_set(skb)) {
			*hoffset = skb_transport_offset(skb);
			ret = 0;
		}
		break;
	default:
		ret = -EINVAL;
		break;
	};

	return ret;
}

294
static int tcf_pedit(struct sk_buff *skb, const struct tc_action *a,
295
		     struct tcf_result *res)
L
Linus Torvalds 已提交
296
{
297
	struct tcf_pedit *p = to_pedit(a);
298
	int i;
L
Linus Torvalds 已提交
299

300
	if (skb_unclone(skb, GFP_ATOMIC))
E
Eric Dumazet 已提交
301
		return p->tcf_action;
L
Linus Torvalds 已提交
302

303
	spin_lock(&p->tcf_lock);
L
Linus Torvalds 已提交
304

305
	tcf_lastuse_update(&p->tcf_tm);
L
Linus Torvalds 已提交
306

307 308
	if (p->tcfp_nkeys > 0) {
		struct tc_pedit_key *tkey = p->tcfp_keys;
309
		struct tcf_pedit_key_ex *tkey_ex = p->tcfp_keys_ex;
310 311
		enum pedit_header_type htype =
			TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
312
		enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
L
Linus Torvalds 已提交
313

314
		for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
315
			u32 *ptr, hdata;
L
Linus Torvalds 已提交
316
			int offset = tkey->off;
317
			int hoffset;
318
			u32 val;
319 320 321 322
			int rc;

			if (tkey_ex) {
				htype = tkey_ex->htype;
323 324
				cmd = tkey_ex->cmd;

325 326 327 328 329
				tkey_ex++;
			}

			rc = pedit_skb_hdr_offset(skb, htype, &hoffset);
			if (rc) {
330
				pr_info("tc action pedit bad header type specified (0x%x)\n",
331 332 333
					htype);
				goto bad;
			}
L
Linus Torvalds 已提交
334 335

			if (tkey->offmask) {
336
				u8 *d, _d;
C
Changli Gao 已提交
337

338
				if (!offset_valid(skb, hoffset + tkey->at)) {
339
					pr_info("tc action pedit 'at' offset %d out of bounds\n",
340
						hoffset + tkey->at);
341 342
					goto bad;
				}
343
				d = skb_header_pointer(skb, hoffset + tkey->at,
344
						       sizeof(_d), &_d);
C
Changli Gao 已提交
345
				if (!d)
L
Linus Torvalds 已提交
346
					goto bad;
C
Changli Gao 已提交
347
				offset += (*d & tkey->offmask) >> tkey->shift;
L
Linus Torvalds 已提交
348 349 350
			}

			if (offset % 4) {
351
				pr_info("tc action pedit offset must be on 32 bit boundaries\n");
L
Linus Torvalds 已提交
352 353
				goto bad;
			}
354

355
			if (!offset_valid(skb, hoffset + offset)) {
356
				pr_info("tc action pedit offset %d out of bounds\n",
357
					hoffset + offset);
L
Linus Torvalds 已提交
358 359 360
				goto bad;
			}

361
			ptr = skb_header_pointer(skb, hoffset + offset,
362
						 sizeof(hdata), &hdata);
C
Changli Gao 已提交
363 364
			if (!ptr)
				goto bad;
L
Linus Torvalds 已提交
365
			/* just do it, baby */
366 367 368 369 370 371 372 373
			switch (cmd) {
			case TCA_PEDIT_KEY_EX_CMD_SET:
				val = tkey->val;
				break;
			case TCA_PEDIT_KEY_EX_CMD_ADD:
				val = (*ptr + tkey->val) & ~tkey->mask;
				break;
			default:
374
				pr_info("tc action pedit bad command (%d)\n",
375 376 377 378 379
					cmd);
				goto bad;
			}

			*ptr = ((*ptr & tkey->mask) ^ val);
380
			if (ptr == &hdata)
381
				skb_store_bits(skb, hoffset + offset, ptr, 4);
L
Linus Torvalds 已提交
382
		}
383

L
Linus Torvalds 已提交
384
		goto done;
385
	} else {
386
		WARN(1, "pedit BUG: index %d\n", p->tcf_index);
387
	}
L
Linus Torvalds 已提交
388 389

bad:
390
	p->tcf_qstats.overlimits++;
L
Linus Torvalds 已提交
391
done:
392
	bstats_update(&p->tcf_bstats, skb);
393 394
	spin_unlock(&p->tcf_lock);
	return p->tcf_action;
L
Linus Torvalds 已提交
395 396
}

397 398
static int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a,
			  int bind, int ref)
L
Linus Torvalds 已提交
399
{
400
	unsigned char *b = skb_tail_pointer(skb);
401
	struct tcf_pedit *p = to_pedit(a);
L
Linus Torvalds 已提交
402 403
	struct tc_pedit *opt;
	struct tcf_t t;
404 405
	int s;

406
	s = sizeof(*opt) + p->tcfp_nkeys * sizeof(struct tc_pedit_key);
L
Linus Torvalds 已提交
407 408

	/* netlink spinlocks held above us - must use ATOMIC */
409
	opt = kzalloc(s, GFP_ATOMIC);
410
	if (unlikely(!opt))
L
Linus Torvalds 已提交
411 412
		return -ENOBUFS;

413 414 415 416 417 418
	memcpy(opt->keys, p->tcfp_keys,
	       p->tcfp_nkeys * sizeof(struct tc_pedit_key));
	opt->index = p->tcf_index;
	opt->nkeys = p->tcfp_nkeys;
	opt->flags = p->tcfp_flags;
	opt->action = p->tcf_action;
419 420
	opt->refcnt = refcount_read(&p->tcf_refcnt) - ref;
	opt->bindcnt = atomic_read(&p->tcf_bindcnt) - bind;
L
Linus Torvalds 已提交
421

422 423 424 425 426 427 428 429 430
	if (p->tcfp_keys_ex) {
		tcf_pedit_key_ex_dump(skb, p->tcfp_keys_ex, p->tcfp_nkeys);

		if (nla_put(skb, TCA_PEDIT_PARMS_EX, s, opt))
			goto nla_put_failure;
	} else {
		if (nla_put(skb, TCA_PEDIT_PARMS, s, opt))
			goto nla_put_failure;
	}
431 432

	tcf_tm_dump(&t, &p->tcf_tm);
433
	if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD))
434
		goto nla_put_failure;
435

436
	kfree(opt);
L
Linus Torvalds 已提交
437 438
	return skb->len;

439
nla_put_failure:
440
	nlmsg_trim(skb, b);
441
	kfree(opt);
L
Linus Torvalds 已提交
442 443 444
	return -1;
}

445 446
static int tcf_pedit_walker(struct net *net, struct sk_buff *skb,
			    struct netlink_callback *cb, int type,
447 448
			    const struct tc_action_ops *ops,
			    struct netlink_ext_ack *extack)
449 450 451
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

452
	return tcf_generic_walker(tn, skb, cb, type, ops, extack);
453 454
}

455 456
static int tcf_pedit_search(struct net *net, struct tc_action **a, u32 index,
			    struct netlink_ext_ack *extack)
457 458 459
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

460
	return tcf_idr_search(tn, a, index);
461 462
}

463 464 465 466 467 468 469
static int tcf_pedit_delete(struct net *net, u32 index)
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

	return tcf_idr_delete_index(tn, index);
}

470
static struct tc_action_ops act_pedit_ops = {
L
Linus Torvalds 已提交
471 472 473 474 475 476 477
	.kind		=	"pedit",
	.type		=	TCA_ACT_PEDIT,
	.owner		=	THIS_MODULE,
	.act		=	tcf_pedit,
	.dump		=	tcf_pedit_dump,
	.cleanup	=	tcf_pedit_cleanup,
	.init		=	tcf_pedit_init,
478 479
	.walk		=	tcf_pedit_walker,
	.lookup		=	tcf_pedit_search,
480
	.delete		=	tcf_pedit_delete,
481
	.size		=	sizeof(struct tcf_pedit),
482 483 484 485 486 487
};

static __net_init int pedit_init_net(struct net *net)
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

488
	return tc_action_net_init(tn, &act_pedit_ops);
489 490
}

491
static void __net_exit pedit_exit_net(struct list_head *net_list)
492
{
493
	tc_action_net_exit(net_list, pedit_net_id);
494 495 496 497
}

static struct pernet_operations pedit_net_ops = {
	.init = pedit_init_net,
498
	.exit_batch = pedit_exit_net,
499 500
	.id   = &pedit_net_id,
	.size = sizeof(struct tc_action_net),
L
Linus Torvalds 已提交
501 502 503 504 505 506
};

MODULE_AUTHOR("Jamal Hadi Salim(2002-4)");
MODULE_DESCRIPTION("Generic Packet Editor actions");
MODULE_LICENSE("GPL");

507
static int __init pedit_init_module(void)
L
Linus Torvalds 已提交
508
{
509
	return tcf_register_action(&act_pedit_ops, &pedit_net_ops);
L
Linus Torvalds 已提交
510 511
}

512
static void __exit pedit_cleanup_module(void)
L
Linus Torvalds 已提交
513
{
514
	tcf_unregister_action(&act_pedit_ops, &pedit_net_ops);
L
Linus Torvalds 已提交
515 516 517 518 519
}

module_init(pedit_init_module);
module_exit(pedit_cleanup_module);