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