act_pedit.c 11.3 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
			  int ovr, int bind, struct netlink_ext_ack *extack)
L
Linus Torvalds 已提交
136
{
137
	struct tc_action_net *tn = net_generic(net, pedit_net_id);
138
	struct nlattr *tb[TCA_PEDIT_MAX + 1];
L
Linus Torvalds 已提交
139
	struct tc_pedit_key *keys = NULL;
140
	struct tcf_pedit_key_ex *keys_ex;
141 142 143 144
	struct tc_pedit *parm;
	struct nlattr *pattr;
	struct tcf_pedit *p;
	int ret = 0, err;
L
Linus Torvalds 已提交
145 146
	int ksize;

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

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

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

	parm = nla_data(pattr);
L
Linus Torvalds 已提交
165
	ksize = parm->nkeys * sizeof(struct tc_pedit_key);
166 167
	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 已提交
168
		return -EINVAL;
169
	}
L
Linus Torvalds 已提交
170

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

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

211 212 213
	spin_lock_bh(&p->tcf_lock);
	p->tcfp_flags = parm->flags;
	p->tcf_action = parm->action;
L
Linus Torvalds 已提交
214
	if (keys) {
215 216 217
		kfree(p->tcfp_keys);
		p->tcfp_keys = keys;
		p->tcfp_nkeys = parm->nkeys;
L
Linus Torvalds 已提交
218
	}
219
	memcpy(p->tcfp_keys, parm->keys, ksize);
220 221 222 223

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

224
	spin_unlock_bh(&p->tcf_lock);
L
Linus Torvalds 已提交
225
	if (ret == ACT_P_CREATED)
226
		tcf_idr_insert(tn, *a);
L
Linus Torvalds 已提交
227
	return ret;
228 229 230 231
out_free:
	kfree(keys_ex);
	return ret;

L
Linus Torvalds 已提交
232 233
}

234
static void tcf_pedit_cleanup(struct tc_action *a)
L
Linus Torvalds 已提交
235
{
236
	struct tcf_pedit *p = to_pedit(a);
W
WANG Cong 已提交
237
	struct tc_pedit_key *keys = p->tcfp_keys;
238

W
WANG Cong 已提交
239
	kfree(keys);
240
	kfree(p->tcfp_keys_ex);
L
Linus Torvalds 已提交
241 242
}

243 244 245 246 247 248 249 250 251 252 253
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;
}

254 255 256 257 258 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
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;
}

287
static int tcf_pedit(struct sk_buff *skb, const struct tc_action *a,
288
		     struct tcf_result *res)
L
Linus Torvalds 已提交
289
{
290
	struct tcf_pedit *p = to_pedit(a);
291
	int i;
L
Linus Torvalds 已提交
292

293
	if (skb_unclone(skb, GFP_ATOMIC))
E
Eric Dumazet 已提交
294
		return p->tcf_action;
L
Linus Torvalds 已提交
295

296
	spin_lock(&p->tcf_lock);
L
Linus Torvalds 已提交
297

298
	tcf_lastuse_update(&p->tcf_tm);
L
Linus Torvalds 已提交
299

300 301
	if (p->tcfp_nkeys > 0) {
		struct tc_pedit_key *tkey = p->tcfp_keys;
302
		struct tcf_pedit_key_ex *tkey_ex = p->tcfp_keys_ex;
303 304
		enum pedit_header_type htype =
			TCA_PEDIT_KEY_EX_HDR_TYPE_NETWORK;
305
		enum pedit_cmd cmd = TCA_PEDIT_KEY_EX_CMD_SET;
L
Linus Torvalds 已提交
306

307
		for (i = p->tcfp_nkeys; i > 0; i--, tkey++) {
308
			u32 *ptr, hdata;
L
Linus Torvalds 已提交
309
			int offset = tkey->off;
310
			int hoffset;
311
			u32 val;
312 313 314 315
			int rc;

			if (tkey_ex) {
				htype = tkey_ex->htype;
316 317
				cmd = tkey_ex->cmd;

318 319 320 321 322
				tkey_ex++;
			}

			rc = pedit_skb_hdr_offset(skb, htype, &hoffset);
			if (rc) {
323
				pr_info("tc action pedit bad header type specified (0x%x)\n",
324 325 326
					htype);
				goto bad;
			}
L
Linus Torvalds 已提交
327 328

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

331
				if (!offset_valid(skb, hoffset + tkey->at)) {
332
					pr_info("tc action pedit 'at' offset %d out of bounds\n",
333
						hoffset + tkey->at);
334 335
					goto bad;
				}
336
				d = skb_header_pointer(skb, hoffset + tkey->at,
337
						       sizeof(_d), &_d);
C
Changli Gao 已提交
338
				if (!d)
L
Linus Torvalds 已提交
339
					goto bad;
C
Changli Gao 已提交
340
				offset += (*d & tkey->offmask) >> tkey->shift;
L
Linus Torvalds 已提交
341 342 343
			}

			if (offset % 4) {
344
				pr_info("tc action pedit offset must be on 32 bit boundaries\n");
L
Linus Torvalds 已提交
345 346
				goto bad;
			}
347

348
			if (!offset_valid(skb, hoffset + offset)) {
349
				pr_info("tc action pedit offset %d out of bounds\n",
350
					hoffset + offset);
L
Linus Torvalds 已提交
351 352 353
				goto bad;
			}

354
			ptr = skb_header_pointer(skb, hoffset + offset,
355
						 sizeof(hdata), &hdata);
C
Changli Gao 已提交
356 357
			if (!ptr)
				goto bad;
L
Linus Torvalds 已提交
358
			/* just do it, baby */
359 360 361 362 363 364 365 366
			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:
367
				pr_info("tc action pedit bad command (%d)\n",
368 369 370 371 372
					cmd);
				goto bad;
			}

			*ptr = ((*ptr & tkey->mask) ^ val);
373
			if (ptr == &hdata)
374
				skb_store_bits(skb, hoffset + offset, ptr, 4);
L
Linus Torvalds 已提交
375
		}
376

L
Linus Torvalds 已提交
377
		goto done;
378
	} else {
379
		WARN(1, "pedit BUG: index %d\n", p->tcf_index);
380
	}
L
Linus Torvalds 已提交
381 382

bad:
383
	p->tcf_qstats.overlimits++;
L
Linus Torvalds 已提交
384
done:
385
	bstats_update(&p->tcf_bstats, skb);
386 387
	spin_unlock(&p->tcf_lock);
	return p->tcf_action;
L
Linus Torvalds 已提交
388 389
}

390 391
static int tcf_pedit_dump(struct sk_buff *skb, struct tc_action *a,
			  int bind, int ref)
L
Linus Torvalds 已提交
392
{
393
	unsigned char *b = skb_tail_pointer(skb);
394
	struct tcf_pedit *p = to_pedit(a);
L
Linus Torvalds 已提交
395 396
	struct tc_pedit *opt;
	struct tcf_t t;
397 398
	int s;

399
	s = sizeof(*opt) + p->tcfp_nkeys * sizeof(struct tc_pedit_key);
L
Linus Torvalds 已提交
400 401

	/* netlink spinlocks held above us - must use ATOMIC */
402
	opt = kzalloc(s, GFP_ATOMIC);
403
	if (unlikely(!opt))
L
Linus Torvalds 已提交
404 405
		return -ENOBUFS;

406 407 408 409 410 411 412 413
	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;
	opt->refcnt = p->tcf_refcnt - ref;
	opt->bindcnt = p->tcf_bindcnt - bind;
L
Linus Torvalds 已提交
414

415 416 417 418 419 420 421 422 423
	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;
	}
424 425

	tcf_tm_dump(&t, &p->tcf_tm);
426
	if (nla_put_64bit(skb, TCA_PEDIT_TM, sizeof(t), &t, TCA_PEDIT_PAD))
427
		goto nla_put_failure;
428

429
	kfree(opt);
L
Linus Torvalds 已提交
430 431
	return skb->len;

432
nla_put_failure:
433
	nlmsg_trim(skb, b);
434
	kfree(opt);
L
Linus Torvalds 已提交
435 436 437
	return -1;
}

438 439
static int tcf_pedit_walker(struct net *net, struct sk_buff *skb,
			    struct netlink_callback *cb, int type,
440 441
			    const struct tc_action_ops *ops,
			    struct netlink_ext_ack *extack)
442 443 444
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

445
	return tcf_generic_walker(tn, skb, cb, type, ops, extack);
446 447
}

448 449
static int tcf_pedit_search(struct net *net, struct tc_action **a, u32 index,
			    struct netlink_ext_ack *extack)
450 451 452
{
	struct tc_action_net *tn = net_generic(net, pedit_net_id);

453
	return tcf_idr_search(tn, a, index);
454 455
}

456
static struct tc_action_ops act_pedit_ops = {
L
Linus Torvalds 已提交
457 458 459 460 461 462 463
	.kind		=	"pedit",
	.type		=	TCA_ACT_PEDIT,
	.owner		=	THIS_MODULE,
	.act		=	tcf_pedit,
	.dump		=	tcf_pedit_dump,
	.cleanup	=	tcf_pedit_cleanup,
	.init		=	tcf_pedit_init,
464 465
	.walk		=	tcf_pedit_walker,
	.lookup		=	tcf_pedit_search,
466
	.size		=	sizeof(struct tcf_pedit),
467 468 469 470 471 472
};

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

473
	return tc_action_net_init(tn, &act_pedit_ops);
474 475
}

476
static void __net_exit pedit_exit_net(struct list_head *net_list)
477
{
478
	tc_action_net_exit(net_list, pedit_net_id);
479 480 481 482
}

static struct pernet_operations pedit_net_ops = {
	.init = pedit_init_net,
483
	.exit_batch = pedit_exit_net,
484 485
	.id   = &pedit_net_id,
	.size = sizeof(struct tc_action_net),
L
Linus Torvalds 已提交
486 487 488 489 490 491
};

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

492
static int __init pedit_init_module(void)
L
Linus Torvalds 已提交
493
{
494
	return tcf_register_action(&act_pedit_ops, &pedit_net_ops);
L
Linus Torvalds 已提交
495 496
}

497
static void __exit pedit_cleanup_module(void)
L
Linus Torvalds 已提交
498
{
499
	tcf_unregister_action(&act_pedit_ops, &pedit_net_ops);
L
Linus Torvalds 已提交
500 501 502 503 504
}

module_init(pedit_init_module);
module_exit(pedit_cleanup_module);