1/*
2 * tc_qdisc.c		"tc qdisc".
3 *
4 *		This program is free software; you can redistribute it and/or
5 *		modify it under the terms of the GNU General Public License
6 *		as published by the Free Software Foundation; either version
7 *		2 of the License, or (at your option) any later version.
8 *
9 * Authors:	Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru>
10 *		J Hadi Salim: Extension to ingress
11 */
12
13#include <stdio.h>
14#include <stdlib.h>
15#include <unistd.h>
16#include <syslog.h>
17#include <fcntl.h>
18#include <sys/socket.h>
19#include <netinet/in.h>
20#include <arpa/inet.h>
21#include <string.h>
22#include <math.h>
23#include <malloc.h>
24
25#include "utils.h"
26#include "tc_util.h"
27#include "tc_common.h"
28
29static int usage(void);
30
31static int usage(void)
32{
33	fprintf(stderr, "Usage: tc qdisc [ add | del | replace | change | show ] dev STRING\n");
34	fprintf(stderr, "       [ handle QHANDLE ] [ root | ingress | parent CLASSID ]\n");
35	fprintf(stderr, "       [ estimator INTERVAL TIME_CONSTANT ]\n");
36	fprintf(stderr, "       [ stab [ help | STAB_OPTIONS] ]\n");
37	fprintf(stderr, "       [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n");
38	fprintf(stderr, "\n");
39	fprintf(stderr, "       tc qdisc show [ dev STRING ] [ingress]\n");
40	fprintf(stderr, "Where:\n");
41	fprintf(stderr, "QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n");
42	fprintf(stderr, "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n");
43	fprintf(stderr, "STAB_OPTIONS := ... try tc qdisc add stab help\n");
44	return -1;
45}
46
47int tc_qdisc_modify(int cmd, unsigned flags, int argc, char **argv)
48{
49	struct qdisc_util *q = NULL;
50	struct tc_estimator est;
51	struct {
52		struct tc_sizespec	szopts;
53		__u16			*data;
54	} stab;
55	char  d[16];
56	char  k[16];
57	struct {
58		struct nlmsghdr 	n;
59		struct tcmsg 		t;
60		char   			buf[TCA_BUF_MAX];
61	} req;
62
63	memset(&req, 0, sizeof(req));
64	memset(&stab, 0, sizeof(stab));
65	memset(&est, 0, sizeof(est));
66	memset(&d, 0, sizeof(d));
67	memset(&k, 0, sizeof(k));
68
69	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
70	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
71	req.n.nlmsg_type = cmd;
72	req.t.tcm_family = AF_UNSPEC;
73
74	while (argc > 0) {
75		if (strcmp(*argv, "dev") == 0) {
76			NEXT_ARG();
77			if (d[0])
78				duparg("dev", *argv);
79			strncpy(d, *argv, sizeof(d)-1);
80		} else if (strcmp(*argv, "handle") == 0) {
81			__u32 handle;
82			if (req.t.tcm_handle)
83				duparg("handle", *argv);
84			NEXT_ARG();
85			if (get_qdisc_handle(&handle, *argv))
86				invarg(*argv, "invalid qdisc ID");
87			req.t.tcm_handle = handle;
88		} else if (strcmp(*argv, "root") == 0) {
89			if (req.t.tcm_parent) {
90				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
91				return -1;
92			}
93			req.t.tcm_parent = TC_H_ROOT;
94#ifdef TC_H_INGRESS
95		} else if (strcmp(*argv, "ingress") == 0) {
96			if (req.t.tcm_parent) {
97				fprintf(stderr, "Error: \"ingress\" is a duplicate parent ID\n");
98				return -1;
99			}
100			req.t.tcm_parent = TC_H_INGRESS;
101			strncpy(k, "ingress", sizeof(k)-1);
102			q = get_qdisc_kind(k);
103			req.t.tcm_handle = 0xffff0000;
104
105			argc--; argv++;
106			break;
107#endif
108		} else if (strcmp(*argv, "parent") == 0) {
109			__u32 handle;
110			NEXT_ARG();
111			if (req.t.tcm_parent)
112				duparg("parent", *argv);
113			if (get_tc_classid(&handle, *argv))
114				invarg(*argv, "invalid parent ID");
115			req.t.tcm_parent = handle;
116		} else if (matches(*argv, "estimator") == 0) {
117			if (parse_estimator(&argc, &argv, &est))
118				return -1;
119		} else if (matches(*argv, "stab") == 0) {
120			if (parse_size_table(&argc, &argv, &stab.szopts) < 0)
121				return -1;
122			continue;
123		} else if (matches(*argv, "help") == 0) {
124			usage();
125		} else {
126			strncpy(k, *argv, sizeof(k)-1);
127
128			q = get_qdisc_kind(k);
129			argc--; argv++;
130			break;
131		}
132		argc--; argv++;
133	}
134
135	if (k[0])
136		addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
137	if (est.ewma_log)
138		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
139
140	if (q) {
141		if (!q->parse_qopt) {
142			fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
143			return -1;
144		}
145		if (q->parse_qopt(q, argc, argv, &req.n))
146			return 1;
147	} else {
148		if (argc) {
149			if (matches(*argv, "help") == 0)
150				usage();
151
152			fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc qdisc help\".\n", *argv);
153			return -1;
154		}
155	}
156
157	if (check_size_table_opts(&stab.szopts)) {
158		struct rtattr *tail;
159
160		if (tc_calc_size_table(&stab.szopts, &stab.data) < 0) {
161			fprintf(stderr, "failed to calculate size table.\n");
162			return -1;
163		}
164
165		tail = NLMSG_TAIL(&req.n);
166		addattr_l(&req.n, sizeof(req), TCA_STAB, NULL, 0);
167		addattr_l(&req.n, sizeof(req), TCA_STAB_BASE, &stab.szopts,
168			  sizeof(stab.szopts));
169		if (stab.data)
170			addattr_l(&req.n, sizeof(req), TCA_STAB_DATA, stab.data,
171				  stab.szopts.tsize * sizeof(__u16));
172		tail->rta_len = (void *)NLMSG_TAIL(&req.n) - (void *)tail;
173		if (stab.data)
174			free(stab.data);
175	}
176
177	if (d[0])  {
178		int idx;
179
180 		ll_init_map(&rth);
181
182		if ((idx = ll_name_to_index(d)) == 0) {
183			fprintf(stderr, "Cannot find device \"%s\"\n", d);
184			return 1;
185		}
186		req.t.tcm_ifindex = idx;
187	}
188
189	if (rtnl_talk(&rth, &req.n, 0, 0, NULL) < 0)
190		return 2;
191
192	return 0;
193}
194
195static int filter_ifindex;
196
197int print_qdisc(const struct sockaddr_nl *who,
198		       struct nlmsghdr *n,
199		       void *arg)
200{
201	FILE *fp = (FILE*)arg;
202	struct tcmsg *t = NLMSG_DATA(n);
203	int len = n->nlmsg_len;
204	struct rtattr * tb[TCA_MAX+1];
205	struct qdisc_util *q;
206	char abuf[256];
207
208	if (n->nlmsg_type != RTM_NEWQDISC && n->nlmsg_type != RTM_DELQDISC) {
209		fprintf(stderr, "Not a qdisc\n");
210		return 0;
211	}
212	len -= NLMSG_LENGTH(sizeof(*t));
213	if (len < 0) {
214		fprintf(stderr, "Wrong len %d\n", len);
215		return -1;
216	}
217
218	if (filter_ifindex && filter_ifindex != t->tcm_ifindex)
219		return 0;
220
221	memset(tb, 0, sizeof(tb));
222	parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
223
224	if (tb[TCA_KIND] == NULL) {
225		fprintf(stderr, "print_qdisc: NULL kind\n");
226		return -1;
227	}
228
229	if (n->nlmsg_type == RTM_DELQDISC)
230		fprintf(fp, "deleted ");
231
232	fprintf(fp, "qdisc %s %x: ", rta_getattr_str(tb[TCA_KIND]), t->tcm_handle>>16);
233	if (filter_ifindex == 0)
234		fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
235	if (t->tcm_parent == TC_H_ROOT)
236		fprintf(fp, "root ");
237	else if (t->tcm_parent) {
238		print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
239		fprintf(fp, "parent %s ", abuf);
240	}
241	if (t->tcm_info != 1) {
242		fprintf(fp, "refcnt %d ", t->tcm_info);
243	}
244	/* pfifo_fast is generic enough to warrant the hardcoding --JHS */
245
246	if (0 == strcmp("pfifo_fast", RTA_DATA(tb[TCA_KIND])))
247		q = get_qdisc_kind("prio");
248	else
249		q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
250
251	if (tb[TCA_OPTIONS]) {
252		if (q)
253			q->print_qopt(q, fp, tb[TCA_OPTIONS]);
254		else
255			fprintf(fp, "[cannot parse qdisc parameters]");
256	}
257	fprintf(fp, "\n");
258	if (show_details && tb[TCA_STAB]) {
259		print_size_table(fp, " ", tb[TCA_STAB]);
260		fprintf(fp, "\n");
261	}
262	if (show_stats) {
263		struct rtattr *xstats = NULL;
264
265		if (tb[TCA_STATS] || tb[TCA_STATS2] || tb[TCA_XSTATS]) {
266			print_tcstats_attr(fp, tb, " ", &xstats);
267			fprintf(fp, "\n");
268		}
269
270		if (q && xstats && q->print_xstats) {
271			q->print_xstats(q, fp, xstats);
272			fprintf(fp, "\n");
273		}
274	}
275	fflush(fp);
276	return 0;
277}
278
279
280int tc_qdisc_list(int argc, char **argv)
281{
282	struct tcmsg t;
283	char d[16];
284
285	memset(&t, 0, sizeof(t));
286	t.tcm_family = AF_UNSPEC;
287	memset(&d, 0, sizeof(d));
288
289	while (argc > 0) {
290		if (strcmp(*argv, "dev") == 0) {
291			NEXT_ARG();
292			strncpy(d, *argv, sizeof(d)-1);
293#ifdef TC_H_INGRESS
294                } else if (strcmp(*argv, "ingress") == 0) {
295                             if (t.tcm_parent) {
296                                     fprintf(stderr, "Duplicate parent ID\n");
297                                     usage();
298                             }
299                             t.tcm_parent = TC_H_INGRESS;
300#endif
301		} else if (matches(*argv, "help") == 0) {
302			usage();
303		} else {
304			fprintf(stderr, "What is \"%s\"? Try \"tc qdisc help\".\n", *argv);
305			return -1;
306		}
307
308		argc--; argv++;
309	}
310
311 	ll_init_map(&rth);
312
313	if (d[0]) {
314		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
315			fprintf(stderr, "Cannot find device \"%s\"\n", d);
316			return 1;
317		}
318		filter_ifindex = t.tcm_ifindex;
319	}
320
321 	if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
322		perror("Cannot send dump request");
323		return 1;
324	}
325
326 	if (rtnl_dump_filter(&rth, print_qdisc, stdout) < 0) {
327		fprintf(stderr, "Dump terminated\n");
328		return 1;
329	}
330
331	return 0;
332}
333
334int do_qdisc(int argc, char **argv)
335{
336	if (argc < 1)
337		return tc_qdisc_list(0, NULL);
338	if (matches(*argv, "add") == 0)
339		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
340	if (matches(*argv, "change") == 0)
341		return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1);
342	if (matches(*argv, "replace") == 0)
343		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
344	if (matches(*argv, "link") == 0)
345		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1);
346	if (matches(*argv, "delete") == 0)
347		return tc_qdisc_modify(RTM_DELQDISC, 0,  argc-1, argv+1);
348#if 0
349	if (matches(*argv, "get") == 0)
350		return tc_qdisc_get(RTM_GETQDISC, 0,  argc-1, argv+1);
351#endif
352	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
353	    || matches(*argv, "lst") == 0)
354		return tc_qdisc_list(argc-1, argv+1);
355	if (matches(*argv, "help") == 0) {
356		usage();
357		return 0;
358        }
359	fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv);
360	return -1;
361}
362