1 /* vi: set sw=4 ts=4: */
2 /*
3  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
4  *
5  * Authors: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
6  *
7  * Bernhard Reutner-Fischer adjusted for busybox
8  */
9 //config:config TC
10 //config:	bool "tc (8.3 kb)"
11 //config:	default y
12 //config:	help
13 //config:	Show / manipulate traffic control settings
14 //config:
15 //config:config FEATURE_TC_INGRESS
16 //config:	bool "Enable ingress"
17 //config:	default y
18 //config:	depends on TC
19 
20 //applet:IF_TC(APPLET(tc, BB_DIR_SBIN, BB_SUID_DROP))
21 
22 //kbuild:lib-$(CONFIG_TC) += tc.o
23 
24 //usage:#define tc_trivial_usage
25 /* //usage: "[OPTIONS] OBJECT CMD [dev STRING]" */
26 //usage:	"OBJECT CMD [dev STRING]"
27 //usage:#define tc_full_usage "\n\n"
28 //usage:	"OBJECT: qdisc|class|filter\n"
29 //usage:	"CMD: add|del|change|replace|show\n"
30 //usage:	"\n"
31 //usage:	"qdisc [handle QHANDLE] [root|"IF_FEATURE_TC_INGRESS("ingress|")"parent CLASSID]\n"
32 /* //usage: "[estimator INTERVAL TIME_CONSTANT]\n" */
33 //usage:	"	[[QDISC_KIND] [help|OPTIONS]]\n"
34 //usage:	"	QDISC_KIND := [p|b]fifo|tbf|prio|cbq|red|etc.\n"
35 //usage:	"qdisc show [dev STRING]"IF_FEATURE_TC_INGRESS(" [ingress]")"\n"
36 //usage:	"class [classid CLASSID] [root|parent CLASSID]\n"
37 //usage:	"	[[QDISC_KIND] [help|OPTIONS] ]\n"
38 //usage:	"class show [ dev STRING ] [root|parent CLASSID]\n"
39 //usage:	"filter [pref PRIO] [protocol PROTO]\n"
40 /* //usage: "\t[estimator INTERVAL TIME_CONSTANT]\n" */
41 //usage:	"	[root|classid CLASSID] [handle FILTERID]\n"
42 //usage:	"	[[FILTER_TYPE] [help|OPTIONS]]\n"
43 //usage:	"filter show [dev STRING] [root|parent CLASSID]"
44 
45 #include "libbb.h"
46 #include "common_bufsiz.h"
47 
48 #include "libiproute/utils.h"
49 #include "libiproute/ip_common.h"
50 #include "libiproute/rt_names.h"
51 #include <linux/pkt_sched.h> /* for the TC_H_* macros */
52 
53 /* This is the deprecated multiqueue interface */
54 #ifndef TCA_PRIO_MAX
55 enum
56 {
57 	TCA_PRIO_UNSPEC,
58 	TCA_PRIO_MQ,
59 	__TCA_PRIO_MAX
60 };
61 #define TCA_PRIO_MAX    (__TCA_PRIO_MAX - 1)
62 #endif
63 
64 #define parse_rtattr_nested(tb, max, rta) \
65 	(parse_rtattr((tb), (max), RTA_DATA(rta), RTA_PAYLOAD(rta)))
66 
67 /* nullifies tb on error */
68 #define __parse_rtattr_nested_compat(tb, max, rta, len) \
69 ({ \
70 	if ((RTA_PAYLOAD(rta) >= len) \
71 	 && (RTA_PAYLOAD(rta) >= RTA_ALIGN(len) + sizeof(struct rtattr)) \
72 	) { \
73 		rta = RTA_DATA(rta) + RTA_ALIGN(len); \
74 		parse_rtattr_nested(tb, max, rta); \
75 	} else \
76 		memset(tb, 0, sizeof(struct rtattr *) * (max + 1)); \
77 })
78 
79 #define parse_rtattr_nested_compat(tb, max, rta, data, len) \
80 ({ \
81 	data = RTA_PAYLOAD(rta) >= len ? RTA_DATA(rta) : NULL; \
82 	__parse_rtattr_nested_compat(tb, max, rta, len); \
83 })
84 
85 #define show_details (0) /* not implemented. Does anyone need it? */
86 #define use_iec (0) /* not currently documented in the upstream manpage */
87 
88 
89 struct globals {
90 	int filter_ifindex;
91 	uint32_t filter_qdisc;
92 	uint32_t filter_parent;
93 	uint32_t filter_prio;
94 	uint32_t filter_proto;
95 } FIX_ALIASING;
96 #define G (*(struct globals*)bb_common_bufsiz1)
97 #define filter_ifindex (G.filter_ifindex)
98 #define filter_qdisc (G.filter_qdisc)
99 #define filter_parent (G.filter_parent)
100 #define filter_prio (G.filter_prio)
101 #define filter_proto (G.filter_proto)
102 #define INIT_G() do { \
103 	setup_common_bufsiz(); \
104 	BUILD_BUG_ON(sizeof(G) > COMMON_BUFSIZE); \
105 } while (0)
106 
107 /* Allocates a buffer containing the name of a class id.
108  * The caller must free the returned memory.  */
print_tc_classid(uint32_t cid)109 static char* print_tc_classid(uint32_t cid)
110 {
111 #if 0 /* IMPOSSIBLE */
112 	if (cid == TC_H_ROOT)
113 		return xasprintf("root");
114 #endif
115 	if (cid == TC_H_UNSPEC)
116 		return xasprintf("none");
117 	if (TC_H_MAJ(cid) == 0)
118 		return xasprintf(":%x", TC_H_MIN(cid));
119 	if (TC_H_MIN(cid) == 0)
120 		return xasprintf("%x:", TC_H_MAJ(cid)>>16);
121 	return xasprintf("%x:%x", TC_H_MAJ(cid)>>16, TC_H_MIN(cid));
122 }
123 
124 /* Get a qdisc handle.  Return 0 on success, !0 otherwise.  */
get_qdisc_handle(uint32_t * h,const char * str)125 static int get_qdisc_handle(uint32_t *h, const char *str)
126 {
127 	uint32_t maj;
128 	char *p;
129 
130 	maj = TC_H_UNSPEC;
131 	if (strcmp(str, "none") == 0)
132 		goto ok;
133 	maj = strtoul(str, &p, 16);
134 	if (p == str)
135 		return 1;
136 	maj <<= 16;
137 	if (*p != ':' && *p != '\0')
138 		return 1;
139  ok:
140 	*h = maj;
141 	return 0;
142 }
143 
144 /* Get class ID.  Return 0 on success, !0 otherwise.  */
get_tc_classid(uint32_t * h,const char * str)145 static int get_tc_classid(uint32_t *h, const char *str)
146 {
147 	uint32_t maj, min;
148 	char *p;
149 
150 	maj = TC_H_ROOT;
151 	if (strcmp(str, "root") == 0)
152 		goto ok;
153 	maj = TC_H_UNSPEC;
154 	if (strcmp(str, "none") == 0)
155 		goto ok;
156 	maj = strtoul(str, &p, 16);
157 	if (p == str) {
158 		if (*p != ':')
159 			return 1;
160 		maj = 0;
161 	}
162 	if (*p == ':') {
163 		if (maj >= (1<<16))
164 			return 1;
165 		maj <<= 16;
166 		str = p + 1;
167 		min = strtoul(str, &p, 16);
168 //FIXME: check for "" too?
169 		if (*p != '\0' || min >= (1<<16))
170 			return 1;
171 		maj |= min;
172 	} else if (*p != 0)
173 		return 1;
174  ok:
175 	*h = maj;
176 	return 0;
177 }
178 
print_rate(char * buf,int len,uint32_t rate)179 static void print_rate(char *buf, int len, uint32_t rate)
180 {
181 	double tmp = (double)rate*8;
182 
183 	if (use_iec) {
184 		if (tmp >= 1000*1024*1024)
185 			snprintf(buf, len, "%.0fMibit", tmp/(1024*1024));
186 		else if (tmp >= 1000*1024)
187 			snprintf(buf, len, "%.0fKibit", tmp/1024);
188 		else
189 			snprintf(buf, len, "%.0fbit", tmp);
190 	} else {
191 		if (tmp >= 1000*1000000)
192 			snprintf(buf, len, "%.0fMbit", tmp/1000000);
193 		else if (tmp >= 1000*1000)
194 			snprintf(buf, len, "%.0fKbit", tmp/1000);
195 		else
196 			snprintf(buf, len, "%.0fbit",  tmp);
197 	}
198 }
199 
200 #if 0
201 /* This is "pfifo_fast".  */
202 static int prio_parse_opt(int argc, char **argv, struct nlmsghdr *n)
203 {
204 	return 0;
205 }
206 #endif
prio_print_opt(struct rtattr * opt)207 static int prio_print_opt(struct rtattr *opt)
208 {
209 	int i;
210 	struct tc_prio_qopt *qopt;
211 	struct rtattr *tb[TCA_PRIO_MAX+1];
212 
213 	if (opt == NULL)
214 		return 0;
215 	parse_rtattr_nested_compat(tb, TCA_PRIO_MAX, opt, qopt, sizeof(*qopt));
216 	printf("bands %u priomap ", qopt->bands);
217 	for (i=0; i<=TC_PRIO_MAX; i++)
218 		printf(" %d", qopt->priomap[i]);
219 
220 	if (tb[TCA_PRIO_MQ])
221 		printf(" multiqueue: o%s ",
222 		    *(unsigned char *)RTA_DATA(tb[TCA_PRIO_MQ]) ? "n" : "ff");
223 
224 	return 0;
225 }
226 
227 #if 0
228 /* Class Based Queue */
229 static int cbq_parse_opt(int argc, char **argv, struct nlmsghdr *n)
230 {
231 	return 0;
232 }
233 #endif
cbq_print_opt(struct rtattr * opt)234 static int cbq_print_opt(struct rtattr *opt)
235 {
236 	struct rtattr *tb[TCA_CBQ_MAX+1];
237 	struct tc_ratespec *r = NULL;
238 	struct tc_cbq_lssopt *lss = NULL;
239 	struct tc_cbq_wrropt *wrr = NULL;
240 	struct tc_cbq_fopt *fopt = NULL;
241 	struct tc_cbq_ovl *ovl = NULL;
242 	const char *const error = "CBQ: too short %s opt";
243 	char buf[64];
244 
245 	if (opt == NULL)
246 		goto done;
247 	parse_rtattr_nested(tb, TCA_CBQ_MAX, opt);
248 
249 	if (tb[TCA_CBQ_RATE]) {
250 		if (RTA_PAYLOAD(tb[TCA_CBQ_RATE]) < sizeof(*r))
251 			bb_error_msg(error, "rate");
252 		else
253 			r = RTA_DATA(tb[TCA_CBQ_RATE]);
254 	}
255 	if (tb[TCA_CBQ_LSSOPT]) {
256 		if (RTA_PAYLOAD(tb[TCA_CBQ_LSSOPT]) < sizeof(*lss))
257 			bb_error_msg(error, "lss");
258 		else
259 			lss = RTA_DATA(tb[TCA_CBQ_LSSOPT]);
260 	}
261 	if (tb[TCA_CBQ_WRROPT]) {
262 		if (RTA_PAYLOAD(tb[TCA_CBQ_WRROPT]) < sizeof(*wrr))
263 			bb_error_msg(error, "wrr");
264 		else
265 			wrr = RTA_DATA(tb[TCA_CBQ_WRROPT]);
266 	}
267 	if (tb[TCA_CBQ_FOPT]) {
268 		if (RTA_PAYLOAD(tb[TCA_CBQ_FOPT]) < sizeof(*fopt))
269 			bb_error_msg(error, "fopt");
270 		else
271 			fopt = RTA_DATA(tb[TCA_CBQ_FOPT]);
272 	}
273 	if (tb[TCA_CBQ_OVL_STRATEGY]) {
274 		if (RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]) < sizeof(*ovl))
275 			bb_error_msg("CBQ: too short overlimit strategy %u/%u",
276 				(unsigned) RTA_PAYLOAD(tb[TCA_CBQ_OVL_STRATEGY]),
277 				(unsigned) sizeof(*ovl));
278 		else
279 			ovl = RTA_DATA(tb[TCA_CBQ_OVL_STRATEGY]);
280 	}
281 
282 	if (r) {
283 		print_rate(buf, sizeof(buf), r->rate);
284 		printf("rate %s ", buf);
285 		if (show_details) {
286 			printf("cell %ub ", 1<<r->cell_log);
287 			if (r->mpu)
288 				printf("mpu %ub ", r->mpu);
289 			if (r->overhead)
290 				printf("overhead %ub ", r->overhead);
291 		}
292 	}
293 	if (lss && lss->flags) {
294 		bool comma = false;
295 		bb_putchar('(');
296 		if (lss->flags&TCF_CBQ_LSS_BOUNDED) {
297 			printf("bounded");
298 			comma = true;
299 		}
300 		if (lss->flags&TCF_CBQ_LSS_ISOLATED) {
301 			if (comma)
302 				bb_putchar(',');
303 			printf("isolated");
304 		}
305 		printf(") ");
306 	}
307 	if (wrr) {
308 		if (wrr->priority != TC_CBQ_MAXPRIO)
309 			printf("prio %u", wrr->priority);
310 		else
311 			printf("prio no-transmit");
312 		if (show_details) {
313 			printf("/%u ", wrr->cpriority);
314 			if (wrr->weight != 1) {
315 				print_rate(buf, sizeof(buf), wrr->weight);
316 				printf("weight %s ", buf);
317 			}
318 			if (wrr->allot)
319 				printf("allot %ub ", wrr->allot);
320 		}
321 	}
322  done:
323 	return 0;
324 }
325 
print_qdisc(const struct sockaddr_nl * who UNUSED_PARAM,struct nlmsghdr * hdr,void * arg UNUSED_PARAM)326 static FAST_FUNC int print_qdisc(
327 		const struct sockaddr_nl *who UNUSED_PARAM,
328 		struct nlmsghdr *hdr,
329 		void *arg UNUSED_PARAM)
330 {
331 	struct tcmsg *msg = NLMSG_DATA(hdr);
332 	int len = hdr->nlmsg_len;
333 	struct rtattr * tb[TCA_MAX+1];
334 	char *name;
335 
336 	if (hdr->nlmsg_type != RTM_NEWQDISC && hdr->nlmsg_type != RTM_DELQDISC) {
337 		/* bb_error_msg("not a qdisc"); */
338 		return 0; /* ??? mimic upstream; should perhaps return -1 */
339 	}
340 	len -= NLMSG_LENGTH(sizeof(*msg));
341 	if (len < 0) {
342 		/* bb_error_msg("wrong len %d", len); */
343 		return -1;
344 	}
345 	/* not the desired interface? */
346 	if (filter_ifindex && filter_ifindex != msg->tcm_ifindex)
347 		return 0;
348 	memset (tb, 0, sizeof(tb));
349 	parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
350 	if (tb[TCA_KIND] == NULL) {
351 		/* bb_error_msg("%s: NULL kind", "qdisc"); */
352 		return -1;
353 	}
354 	if (hdr->nlmsg_type == RTM_DELQDISC)
355 		printf("deleted ");
356 	name = (char*)RTA_DATA(tb[TCA_KIND]);
357 	printf("qdisc %s %x: ", name, msg->tcm_handle>>16);
358 	if (filter_ifindex == 0)
359 		printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
360 	if (msg->tcm_parent == TC_H_ROOT)
361 		printf("root ");
362 	else if (msg->tcm_parent) {
363 		char *classid = print_tc_classid(msg->tcm_parent);
364 		printf("parent %s ", classid);
365 		if (ENABLE_FEATURE_CLEAN_UP)
366 			free(classid);
367 	}
368 	if (msg->tcm_info != 1)
369 		printf("refcnt %d ", msg->tcm_info);
370 	if (tb[TCA_OPTIONS]) {
371 		static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
372 		int qqq = index_in_strings(_q_, name);
373 		if (qqq == 0) { /* pfifo_fast aka prio */
374 			prio_print_opt(tb[TCA_OPTIONS]);
375 		} else if (qqq == 1) { /* class based queuing */
376 			cbq_print_opt(tb[TCA_OPTIONS]);
377 		} else {
378 			/* don't know how to print options for this qdisc */
379 			printf("(options for %s)", name);
380 		}
381 	}
382 	bb_putchar('\n');
383 	return 0;
384 }
385 
print_class(const struct sockaddr_nl * who UNUSED_PARAM,struct nlmsghdr * hdr,void * arg UNUSED_PARAM)386 static FAST_FUNC int print_class(
387 		const struct sockaddr_nl *who UNUSED_PARAM,
388 		struct nlmsghdr *hdr,
389 		void *arg UNUSED_PARAM)
390 {
391 	struct tcmsg *msg = NLMSG_DATA(hdr);
392 	int len = hdr->nlmsg_len;
393 	struct rtattr * tb[TCA_MAX+1];
394 	char *name, *classid;
395 
396 	/*XXX Eventually factor out common code */
397 
398 	if (hdr->nlmsg_type != RTM_NEWTCLASS && hdr->nlmsg_type != RTM_DELTCLASS) {
399 		/* bb_error_msg("not a class"); */
400 		return 0; /* ??? mimic upstream; should perhaps return -1 */
401 	}
402 	len -= NLMSG_LENGTH(sizeof(*msg));
403 	if (len < 0) {
404 		/* bb_error_msg("wrong len %d", len); */
405 		return -1;
406 	}
407 	/* not the desired interface? */
408 	if (filter_qdisc && TC_H_MAJ(msg->tcm_handle ^ filter_qdisc))
409 		return 0;
410 	memset (tb, 0, sizeof(tb));
411 	parse_rtattr(tb, TCA_MAX, TCA_RTA(msg), len);
412 	if (tb[TCA_KIND] == NULL) {
413 		/* bb_error_msg("%s: NULL kind", "class"); */
414 		return -1;
415 	}
416 	if (hdr->nlmsg_type == RTM_DELTCLASS)
417 		printf("deleted ");
418 
419 	name = (char*)RTA_DATA(tb[TCA_KIND]);
420 	classid = !msg->tcm_handle ? NULL : print_tc_classid(
421 				filter_qdisc ? TC_H_MIN(msg->tcm_handle) : msg->tcm_handle);
422 	printf ("class %s %s ", name, classid);
423 	if (ENABLE_FEATURE_CLEAN_UP)
424 		free(classid);
425 
426 	if (filter_ifindex == 0)
427 		printf("dev %s ", ll_index_to_name(msg->tcm_ifindex));
428 	if (msg->tcm_parent == TC_H_ROOT)
429 		printf("root ");
430 	else if (msg->tcm_parent) {
431 		classid = print_tc_classid(filter_qdisc ?
432 				TC_H_MIN(msg->tcm_parent) : msg->tcm_parent);
433 		printf("parent %s ", classid);
434 		if (ENABLE_FEATURE_CLEAN_UP)
435 			free(classid);
436 	}
437 	if (msg->tcm_info)
438 		printf("leaf %x ", msg->tcm_info >> 16);
439 	/* Do that get_qdisc_kind(RTA_DATA(tb[TCA_KIND])).  */
440 	if (tb[TCA_OPTIONS]) {
441 		static const char _q_[] ALIGN1 = "pfifo_fast\0""cbq\0";
442 		int qqq = index_in_strings(_q_, name);
443 		if (qqq == 0) { /* pfifo_fast aka prio */
444 			/* nothing. */ /*prio_print_opt(tb[TCA_OPTIONS]);*/
445 		} else if (qqq == 1) { /* class based queuing */
446 			/* cbq_print_copt() is identical to cbq_print_opt(). */
447 			cbq_print_opt(tb[TCA_OPTIONS]);
448 		} else {
449 			/* don't know how to print options for this class */
450 			printf("(options for %s)", name);
451 		}
452 	}
453 	bb_putchar('\n');
454 
455 	return 0;
456 }
457 
print_filter(const struct sockaddr_nl * who UNUSED_PARAM,struct nlmsghdr * hdr UNUSED_PARAM,void * arg UNUSED_PARAM)458 static FAST_FUNC int print_filter(
459 		const struct sockaddr_nl *who UNUSED_PARAM,
460 		struct nlmsghdr *hdr UNUSED_PARAM,
461 		void *arg UNUSED_PARAM)
462 {
463 	return 0;
464 }
465 
466 int tc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
tc_main(int argc UNUSED_PARAM,char ** argv)467 int tc_main(int argc UNUSED_PARAM, char **argv)
468 {
469 	static const char objects[] ALIGN1 =
470 		"qdisc\0""class\0""filter\0"
471 		;
472 	enum { OBJ_qdisc = 0, OBJ_class, OBJ_filter };
473 	static const char commands[] ALIGN1 =
474 		"add\0""delete\0""change\0"
475 		"link\0" /* only qdisc */
476 		"replace\0"
477 		"show\0""list\0"
478 		;
479 	enum {
480 		CMD_add = 0, CMD_del, CMD_change,
481 		CMD_link,
482 		CMD_replace,
483 		CMD_show
484 	};
485 	static const char args[] ALIGN1 =
486 		"dev\0" /* qdisc, class, filter */
487 		"root\0" /* class, filter */
488 		"parent\0" /* class, filter */
489 		"qdisc\0" /* class */
490 		"handle\0" /* change: qdisc, class(classid) list: filter */
491 		"classid\0" /* change: for class use "handle" */
492 		"preference\0""priority\0""protocol\0" /* filter */
493 		;
494 	enum {
495 		ARG_dev = 0,
496 		ARG_root,
497 		ARG_parent,
498 		ARG_qdisc,
499 		ARG_handle,
500 		ARG_classid,
501 		ARG_pref, ARG_prio, ARG_proto
502 	};
503 	struct rtnl_handle rth;
504 	struct tcmsg msg;
505 	int ret, obj, cmd, arg;
506 	char *dev = NULL;
507 
508 	INIT_G();
509 
510 	if (!*++argv)
511 		bb_show_usage();
512 	xrtnl_open(&rth);
513 	ret = EXIT_SUCCESS;
514 
515 	obj = index_in_substrings(objects, *argv++);
516 	if (obj < 0)
517 		bb_show_usage();
518 
519 	cmd = CMD_show; /* list (aka show) is the default */
520 	if (*argv) {
521 		cmd = index_in_substrings(commands, *argv);
522 		if (cmd < 0)
523 			invarg_1_to_2(*argv, argv[-1]);
524 		argv++;
525 	}
526 
527 	memset(&msg, 0, sizeof(msg));
528 	if (AF_UNSPEC != 0)
529 		msg.tcm_family = AF_UNSPEC;
530 	ll_init_map(&rth);
531 
532 	while (*argv) {
533 		arg = index_in_substrings(args, *argv);
534 		if (arg == ARG_dev) {
535 			NEXT_ARG();
536 			if (dev)
537 				duparg2("dev", *argv);
538 			dev = *argv++;
539 			msg.tcm_ifindex = xll_name_to_index(dev);
540 			if (cmd >= CMD_show)
541 				filter_ifindex = msg.tcm_ifindex;
542 			continue;
543 		}
544 		if ((arg == ARG_qdisc && obj == OBJ_class && cmd >= CMD_show)    /* tc class show|list qdisc HANDLE */
545 		 || (arg == ARG_handle && obj == OBJ_qdisc && cmd == CMD_change) /* tc qdisc change handle HANDLE */
546 		) {
547 			NEXT_ARG();
548 			/* We don't care about duparg2("qdisc handle",*argv) for now */
549 			if (get_qdisc_handle(&filter_qdisc, *argv))
550 				invarg_1_to_2(*argv, "qdisc");
551 		} else
552 		if (obj != OBJ_qdisc /* tc class|filter root|parent | tc filter preference|priority|protocol */
553 		 && (arg == ARG_root
554 		    || arg == ARG_parent
555 		    || (obj == OBJ_filter && arg >= ARG_pref)
556 		    )
557 		) {
558 			/* nothing */
559 		} else {
560 			invarg_1_to_2(*argv, "command");
561 		}
562 		NEXT_ARG();
563 		if (arg == ARG_root) {
564 			if (msg.tcm_parent)
565 				duparg("parent", *argv);
566 			msg.tcm_parent = TC_H_ROOT;
567 			if (obj == OBJ_filter)
568 				filter_parent = TC_H_ROOT;
569 		} else
570 		if (arg == ARG_parent) {
571 			uint32_t handle;
572 			if (msg.tcm_parent)
573 				duparg(*argv, "parent");
574 			if (get_tc_classid(&handle, *argv))
575 				invarg_1_to_2(*argv, "parent");
576 			msg.tcm_parent = handle;
577 			if (obj == OBJ_filter)
578 				filter_parent = handle;
579 		} else
580 		if (arg == ARG_handle) { /* filter::list */
581 			if (msg.tcm_handle)
582 				duparg(*argv, "handle");
583 			/* reject LONG_MIN || LONG_MAX */
584 			/* TODO: for fw
585 			slash = strchr(handle, '/');
586 			if (slash != NULL)
587 				*slash = '\0';
588 			 */
589 			msg.tcm_handle = get_u32(*argv, "handle");
590 			/* if (slash) {if (get_u32(uint32_t &mask, slash+1, NULL)) inv mask; addattr32(n, MAX_MSG, TCA_FW_MASK, mask); */
591 		} else
592 		if (arg == ARG_classid
593 		 && obj == OBJ_class
594 		 && cmd == CMD_change
595 		) {
596 			/* TODO */
597 		} else
598 		if (arg == ARG_pref || arg == ARG_prio) { /* filter::list */
599 			if (filter_prio)
600 				duparg(*argv, "priority");
601 			filter_prio = get_u32(*argv, "priority");
602 		} else
603 		if (arg == ARG_proto) { /* filter::list */
604 			uint16_t tmp;
605 			if (filter_proto)
606 				duparg(*argv, "protocol");
607 			if (ll_proto_a2n(&tmp, *argv))
608 				invarg_1_to_2(*argv, "protocol");
609 			filter_proto = tmp;
610 		}
611 	}
612 
613 	if (cmd >= CMD_show) { /* show or list */
614 		if (obj == OBJ_filter)
615 			msg.tcm_info = TC_H_MAKE(filter_prio<<16, filter_proto);
616 		if (rtnl_dump_request(&rth, obj == OBJ_qdisc ? RTM_GETQDISC :
617 						obj == OBJ_class ? RTM_GETTCLASS : RTM_GETTFILTER,
618 						&msg, sizeof(msg)) < 0)
619 			bb_simple_perror_msg_and_die("can't send dump request");
620 
621 		xrtnl_dump_filter(&rth, obj == OBJ_qdisc ? print_qdisc :
622 						obj == OBJ_class ? print_class : print_filter,
623 						NULL);
624 	}
625 	if (ENABLE_FEATURE_CLEAN_UP) {
626 		rtnl_close(&rth);
627 	}
628 	return ret;
629 }
630