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
24#include "utils.h"
25#include "tc_util.h"
26#include "tc_common.h"
27
28static int usage(void);
29
30static int usage(void)
31{
32	fprintf(stderr, "Usage: tc qdisc [ add | del | replace | change | get ] dev STRING\n");
33	fprintf(stderr, "       [ handle QHANDLE ] [ root | ingress | parent CLASSID ]\n");
34	fprintf(stderr, "       [ estimator INTERVAL TIME_CONSTANT ]\n");
35	fprintf(stderr, "       [ [ QDISC_KIND ] [ help | OPTIONS ] ]\n");
36	fprintf(stderr, "\n");
37	fprintf(stderr, "       tc qdisc show [ dev STRING ] [ingress]\n");
38	fprintf(stderr, "Where:\n");
39	fprintf(stderr, "QDISC_KIND := { [p|b]fifo | tbf | prio | cbq | red | etc. }\n");
40	fprintf(stderr, "OPTIONS := ... try tc qdisc add <desired QDISC_KIND> help\n");
41	return -1;
42}
43
44int tc_qdisc_modify(int cmd, unsigned flags, int argc, char **argv)
45{
46	struct qdisc_util *q = NULL;
47	struct tc_estimator est;
48	char  d[16];
49	char  k[16];
50	struct {
51		struct nlmsghdr 	n;
52		struct tcmsg 		t;
53		char   			buf[TCA_BUF_MAX];
54	} req;
55
56	memset(&req, 0, sizeof(req));
57	memset(&est, 0, sizeof(est));
58	memset(&d, 0, sizeof(d));
59	memset(&k, 0, sizeof(k));
60
61	req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
62	req.n.nlmsg_flags = NLM_F_REQUEST|flags;
63	req.n.nlmsg_type = cmd;
64	req.t.tcm_family = AF_UNSPEC;
65
66	while (argc > 0) {
67		if (strcmp(*argv, "dev") == 0) {
68			NEXT_ARG();
69			if (d[0])
70				duparg("dev", *argv);
71			strncpy(d, *argv, sizeof(d)-1);
72		} else if (strcmp(*argv, "handle") == 0) {
73			__u32 handle;
74			if (req.t.tcm_handle)
75				duparg("handle", *argv);
76			NEXT_ARG();
77			if (get_qdisc_handle(&handle, *argv))
78				invarg(*argv, "invalid qdisc ID");
79			req.t.tcm_handle = handle;
80		} else if (strcmp(*argv, "root") == 0) {
81			if (req.t.tcm_parent) {
82				fprintf(stderr, "Error: \"root\" is duplicate parent ID\n");
83				return -1;
84			}
85			req.t.tcm_parent = TC_H_ROOT;
86#ifdef TC_H_INGRESS
87		} else if (strcmp(*argv, "ingress") == 0) {
88			if (req.t.tcm_parent) {
89				fprintf(stderr, "Error: \"ingress\" is a duplicate parent ID\n");
90				return -1;
91			}
92			req.t.tcm_parent = TC_H_INGRESS;
93			strncpy(k, "ingress", sizeof(k)-1);
94			q = get_qdisc_kind(k);
95			req.t.tcm_handle = 0xffff0000;
96
97			argc--; argv++;
98			break;
99#endif
100		} else if (strcmp(*argv, "parent") == 0) {
101			__u32 handle;
102			NEXT_ARG();
103			if (req.t.tcm_parent)
104				duparg("parent", *argv);
105			if (get_tc_classid(&handle, *argv))
106				invarg(*argv, "invalid parent ID");
107			req.t.tcm_parent = handle;
108		} else if (matches(*argv, "estimator") == 0) {
109			if (parse_estimator(&argc, &argv, &est))
110				return -1;
111		} else if (matches(*argv, "help") == 0) {
112			usage();
113		} else {
114			strncpy(k, *argv, sizeof(k)-1);
115
116			q = get_qdisc_kind(k);
117			argc--; argv++;
118			break;
119		}
120		argc--; argv++;
121	}
122
123	if (k[0])
124		addattr_l(&req.n, sizeof(req), TCA_KIND, k, strlen(k)+1);
125	if (est.ewma_log)
126		addattr_l(&req.n, sizeof(req), TCA_RATE, &est, sizeof(est));
127
128	if (q) {
129		if (!q->parse_qopt) {
130			fprintf(stderr, "qdisc '%s' does not support option parsing\n", k);
131			return -1;
132		}
133		if (q->parse_qopt(q, argc, argv, &req.n))
134			return 1;
135	} else {
136		if (argc) {
137			if (matches(*argv, "help") == 0)
138				usage();
139
140			fprintf(stderr, "Garbage instead of arguments \"%s ...\". Try \"tc qdisc help\".\n", *argv);
141			return -1;
142		}
143	}
144
145	if (d[0])  {
146		int idx;
147
148 		ll_init_map(&rth);
149
150		if ((idx = ll_name_to_index(d)) == 0) {
151			fprintf(stderr, "Cannot find device \"%s\"\n", d);
152			return 1;
153		}
154		req.t.tcm_ifindex = idx;
155	}
156
157 	if (rtnl_talk(&rth, &req.n, 0, 0, NULL, NULL, NULL) < 0)
158		return 2;
159
160	return 0;
161}
162
163static int filter_ifindex;
164
165static int print_qdisc(const struct sockaddr_nl *who,
166		       struct nlmsghdr *n,
167		       void *arg)
168{
169	FILE *fp = (FILE*)arg;
170	struct tcmsg *t = NLMSG_DATA(n);
171	int len = n->nlmsg_len;
172	struct rtattr * tb[TCA_MAX+1];
173	struct qdisc_util *q;
174	char abuf[256];
175
176	if (n->nlmsg_type != RTM_NEWQDISC && n->nlmsg_type != RTM_DELQDISC) {
177		fprintf(stderr, "Not a qdisc\n");
178		return 0;
179	}
180	len -= NLMSG_LENGTH(sizeof(*t));
181	if (len < 0) {
182		fprintf(stderr, "Wrong len %d\n", len);
183		return -1;
184	}
185
186	if (filter_ifindex && filter_ifindex != t->tcm_ifindex)
187		return 0;
188
189	memset(tb, 0, sizeof(tb));
190	parse_rtattr(tb, TCA_MAX, TCA_RTA(t), len);
191
192	if (tb[TCA_KIND] == NULL) {
193		fprintf(stderr, "print_qdisc: NULL kind\n");
194		return -1;
195	}
196
197	if (n->nlmsg_type == RTM_DELQDISC)
198		fprintf(fp, "deleted ");
199
200	fprintf(fp, "qdisc %s %x: ", (char*)RTA_DATA(tb[TCA_KIND]), t->tcm_handle>>16);
201	if (filter_ifindex == 0)
202		fprintf(fp, "dev %s ", ll_index_to_name(t->tcm_ifindex));
203	if (t->tcm_parent == TC_H_ROOT)
204		fprintf(fp, "root ");
205	else if (t->tcm_parent) {
206		print_tc_classid(abuf, sizeof(abuf), t->tcm_parent);
207		fprintf(fp, "parent %s ", abuf);
208	}
209	if (t->tcm_info != 1) {
210		fprintf(fp, "refcnt %d ", t->tcm_info);
211	}
212	/* pfifo_fast is generic enough to warrant the hardcoding --JHS */
213
214	if (0 == strcmp("pfifo_fast", RTA_DATA(tb[TCA_KIND])))
215		q = get_qdisc_kind("prio");
216	else
217		q = get_qdisc_kind(RTA_DATA(tb[TCA_KIND]));
218
219	if (tb[TCA_OPTIONS]) {
220		if (q)
221			q->print_qopt(q, fp, tb[TCA_OPTIONS]);
222		else
223			fprintf(fp, "[cannot parse qdisc parameters]");
224	}
225	fprintf(fp, "\n");
226	if (show_stats) {
227		struct rtattr *xstats = NULL;
228
229		if (tb[TCA_STATS] || tb[TCA_STATS2] || tb[TCA_XSTATS]) {
230			print_tcstats_attr(fp, tb, " ", &xstats);
231			fprintf(fp, "\n");
232		}
233
234		if (q && xstats && q->print_xstats) {
235			q->print_xstats(q, fp, xstats);
236			fprintf(fp, "\n");
237		}
238	}
239	fflush(fp);
240	return 0;
241}
242
243
244int tc_qdisc_list(int argc, char **argv)
245{
246	struct tcmsg t;
247	char d[16];
248
249	memset(&t, 0, sizeof(t));
250	t.tcm_family = AF_UNSPEC;
251	memset(&d, 0, sizeof(d));
252
253	while (argc > 0) {
254		if (strcmp(*argv, "dev") == 0) {
255			NEXT_ARG();
256			strncpy(d, *argv, sizeof(d)-1);
257#ifdef TC_H_INGRESS
258                } else if (strcmp(*argv, "ingress") == 0) {
259                             if (t.tcm_parent) {
260                                     fprintf(stderr, "Duplicate parent ID\n");
261                                     usage();
262                             }
263                             t.tcm_parent = TC_H_INGRESS;
264#endif
265		} else if (matches(*argv, "help") == 0) {
266			usage();
267		} else {
268			fprintf(stderr, "What is \"%s\"? Try \"tc qdisc help\".\n", *argv);
269			return -1;
270		}
271
272		argc--; argv++;
273	}
274
275 	ll_init_map(&rth);
276
277	if (d[0]) {
278		if ((t.tcm_ifindex = ll_name_to_index(d)) == 0) {
279			fprintf(stderr, "Cannot find device \"%s\"\n", d);
280			return 1;
281		}
282		filter_ifindex = t.tcm_ifindex;
283	}
284
285 	if (rtnl_dump_request(&rth, RTM_GETQDISC, &t, sizeof(t)) < 0) {
286		perror("Cannot send dump request");
287		return 1;
288	}
289
290 	if (rtnl_dump_filter(&rth, print_qdisc, stdout, NULL, NULL) < 0) {
291		fprintf(stderr, "Dump terminated\n");
292		return 1;
293	}
294
295	return 0;
296}
297
298int do_qdisc(int argc, char **argv)
299{
300	if (argc < 1)
301		return tc_qdisc_list(0, NULL);
302	if (matches(*argv, "add") == 0)
303		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
304	if (matches(*argv, "change") == 0)
305		return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1);
306	if (matches(*argv, "replace") == 0)
307		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
308	if (matches(*argv, "link") == 0)
309		return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1);
310	if (matches(*argv, "delete") == 0)
311		return tc_qdisc_modify(RTM_DELQDISC, 0,  argc-1, argv+1);
312#if 0
313	if (matches(*argv, "get") == 0)
314		return tc_qdisc_get(RTM_GETQDISC, 0,  argc-1, argv+1);
315#endif
316	if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
317	    || matches(*argv, "lst") == 0)
318		return tc_qdisc_list(argc-1, argv+1);
319	if (matches(*argv, "help") == 0)
320		usage();
321	fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv);
322	return -1;
323}
324