xref: /netbsd-src/usr.sbin/altq/altqstat/quip_client.c (revision 2fa9c3e76d86542794d36109b01568f22b2611c0)
1 /*	$NetBSD: quip_client.c,v 1.16 2024/10/04 15:37:00 rillig Exp $	*/
2 /*	$KAME: quip_client.c,v 1.9 2003/05/17 05:59:00 itojun Exp $	*/
3 /*
4  * Copyright (C) 1999-2000
5  *	Sony Computer Science Laboratories, Inc.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY SONY CSL AND CONTRIBUTORS ``AS IS'' AND
17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL SONY CSL OR CONTRIBUTORS BE LIABLE
20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26  * SUCH DAMAGE.
27  */
28 
29 #include <sys/param.h>
30 #include <sys/socket.h>
31 #include <sys/time.h>
32 #include <sys/un.h>
33 
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <unistd.h>
37 #include <string.h>
38 #include <errno.h>
39 #include <err.h>
40 
41 #include "quip_client.h"
42 #include "altqstat.h"
43 
44 /*
45  * quip (queue information protocol) is a http-like protocol
46  * in order to retrieve information from the server.
47  * a unix domain TCP socket "/var/run/altq_quip" is used for
48  * client-server style communication.
49  *
50  * there are 2 quip message types: request and response.
51  * request format: (only single-line request message is used at this moment)
52  *	request-line
53  *
54  *      request-line = <method> <operation>[?<query>] <quip-version>
55  *	<method> = GET (only GET is defined at this moment)
56  *	<operation> = list | handle-to-name | qdisc | filter
57  *	query format is operation dependent but most query takes
58  *	<interface> or <class> or <filter>.
59  *	<interface> = <if_name>
60  *	<class>     = <if_name>:<class_path>/<class_name>
61  *	<filter>    = <if_name>:<class_path>/<class_name>:<filter_name>
62  *	"list" operation accepts "*" as a wildcard.
63  *
64  * response format:
65  *	status-line
66  * 	response-headers (0 or more)
67  *	<blank line>
68  *	body
69  *
70  *	status-line = <quip-version> <status-code> <reason phrase>
71  *	response-header = Content-Length:<value>
72  *
73  *	"Content-Length" specifies the length of the message body.
74  *
75  * example:
76  *	to retrieve a list of classes (handle and name) on interface "fxp0":
77  *	a request message looks like,
78  *		GET list?fxp0:* QUIP/1.0<cr>
79  *	a response message looks like,
80  *		QUIP/1.0 200 OK<cr>
81  *		Content-Length:86<cr>
82  *		<cr>
83  *		0000000000	fxp0:/root<cr>
84  *		0xc0d1be00	fxp0:/root/parent<cr>
85  *		0xc0d1ba00	fxp0:/root/parent/child<cr>
86  *
87  *	other examples:
88  *	list all interfaces, classes, and filters:
89  *		GET list QUIP/1.0<cr>
90  *	list all interfaces:
91  *		GET list?* QUIP/1.0<cr>
92  *	list all classes:
93  *		GET list?*:* QUIP/1.0<cr>
94  *	list all filters:
95  *		GET list?*:*:* QUIP/1.0<cr>
96  *	convert class handle to class name:
97  *		GET handle-to-name?fxp0:0xc0d1be00 QUIP/1.0<cr>
98  *	convert filter handle to filter name:
99  *		GET handle-to-name?fxp0::0x1000000a QUIP/1.0<cr>
100  */
101 
102 #define	MAXLINESIZE	1024
103 
104 enum nametype { INTERFACE, CLASS, FILTER, CONDITIONER };
105 
106 static FILE *server = NULL;
107 int quip_echo = 0;
108 
109 static char *extract_ifname(const char *);
110 
111 int
112 quip_openserver(void)
113 {
114 	struct sockaddr_un addr;
115 	int fd;
116 
117 	if ((fd = socket(AF_LOCAL, SOCK_STREAM, 0)) < 0)
118 		err(1, "can't open socket");
119 
120 	bzero(&addr, sizeof(addr));
121 	addr.sun_family = AF_LOCAL;
122 	strlcpy(addr.sun_path, QUIP_PATH,sizeof(addr.sun_path));
123 
124 	if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
125 		warnx("can't talk to altqd;probably, altqd is not running");
126 		return (-1);
127 	}
128 
129 	if ((server = fdopen(fd, "r+")) == NULL) {
130 		warn("fdopen: can't open stream to the quip server");
131 		return (-1);
132 	}
133 	return (0);
134 }
135 
136 int
137 quip_closeserver(void)
138 {
139 	if (server != NULL)
140 		return fclose(server);
141 	return (0);
142 }
143 
144 void
145 quip_sendrequest(FILE *fp, const char *request)
146 {
147 	char buf[QUIPMSG_MAXSIZE];
148 	const char *cp;
149 	int n;
150 
151 	if ((cp = strstr(request, "QUIP")) == NULL) {
152 		cp = strchr(request, '\n');
153 		n = cp - request;
154 		if (cp == NULL || n > REQ_MAXSIZE - 10)
155 			return;
156 		strncpy(buf, request, n);
157 		snprintf(buf + n, REQ_MAXSIZE - n, " QUIP/1.0");
158 		strlcat(buf, cp, REQ_MAXSIZE);
159 	}
160 	else
161 		strlcpy(buf, request, REQ_MAXSIZE);
162 
163 	if (fputs(buf, fp) != 0)
164 		err(1, "fputs");
165 	if (fflush(fp) != 0)
166 		err(1, "fflush");
167 	if (quip_echo) {
168 		fputs("<< ", stdout);
169 		fputs(buf, stdout);
170 	}
171 }
172 
173 /*
174  * recv_response receives a response message from the server
175  * and returns status_code.
176  */
177 int
178 quip_recvresponse(FILE *fp, char *header, char *body, int *blen)
179 {
180 	char buf[MAXLINESIZE], version[MAXLINESIZE];
181 	int code, resid, len, buflen;
182 	int end_of_header = 0;
183 
184 	if (blen != NULL)
185 		*blen = 0;
186 	code = 0;
187 	resid = 0;
188 	buflen = RES_MAXSIZE;
189 	while (fgets(buf, sizeof(buf), fp) != 0) {
190 		if (quip_echo) {
191 			fputs(">  ", stdout);
192 			fputs(buf, stdout);
193 		}
194 
195 		if (!end_of_header) {
196 			/* process message header */
197 			if (header != NULL) {
198 				len = strlcpy(header, buf, buflen);
199 				if (len >= buflen) {
200 					/* header too long */
201 					fpurge(fp);
202 					return (-1);
203 				}
204 				header += len;
205 				buflen -= len;
206 			}
207 
208 			if (code == 0) {
209 				/* status line expected */
210 				if (buf[0] == '\n') {
211 					/* ignore blank lines */
212 				}
213 				/* XXX sizeof(version) == 1024 */
214 				else if (sscanf(buf, "%1023s %d",
215 						version, &code) != 2) {
216 					/* can't get result code */
217 					fpurge(fp);
218 					return (-1);
219 				}
220 			}
221 			else {
222 				/* entity header expected */
223 				char *field, *cp;
224 
225 				if (buf[0] == '\n') {
226 					/* end of header */
227 					end_of_header = 1;
228 					buflen = BODY_MAXSIZE;
229 					if (resid == 0)
230 						/* no message body */
231 						return (code);
232 				}
233 
234 				cp = buf;
235 				field = strsep(&cp, ":");
236 				if (strcmp(field, "Content-Length") == 0) {
237 					if (sscanf(cp, "%d", &resid) != 1) {
238 						fpurge(fp);
239 						return (-1);
240 					}
241 					if (blen != NULL)
242 						*blen = resid;
243 				}
244 			}
245 		}
246 		else {
247 			/* process message body */
248 			if (body != NULL) {
249 				len = strlcpy(body, buf, buflen);
250 				if (len >= buflen) {
251 					/* body too long */
252 					fpurge(fp);
253 					return (-1);
254 				}
255 				body += len;
256 				buflen -= len;
257 			}
258 			else
259 				len = strlen(buf);
260 			resid -= len;
261 			if (resid <= 0)
262 				return (code);
263 		}
264 	}
265 	return (-1);
266 }
267 
268 void
269 quip_rawmode(void)
270 {
271 	char line[MAXLINESIZE];
272 
273 	printf(">>>Entering the raw interactive mode to the server:\n\n");
274 	if (server == NULL) {
275 		printf("No server available!\n");
276 		return;
277 	}
278 
279 	while (1) {
280 		printf("%% "); fflush(stdout);
281 		/* read a line from stdin */
282 		if (fgets(line, sizeof(line), stdin) == NULL)
283 			break;
284 
285 		if (line[0] == '\n') {
286 			/* if a blank line, echo locally */
287 			fputs(line, stdout);
288 			continue;
289 		}
290 		if (line[0] == 'q') {
291 			printf("Exit\n");
292 			break;
293 		}
294 
295 		/* send the input line to the server */
296 		quip_sendrequest(server, line);
297 
298 		/* get a response message from the server */
299 		(void)quip_recvresponse(server, NULL, NULL, NULL);
300 	}
301 }
302 
303 char *
304 quip_selectinterface(char *ifname)
305 {
306 	char buf[BODY_MAXSIZE], *cp;
307 	int result_code, len;
308 	u_int if_index;
309 	static char interface[64];
310 
311 	if (server == NULL)
312 		return (ifname);
313 
314 	/* get an interface list from the server */
315 	quip_sendrequest(server, "GET list?*\n");
316 
317 	result_code = quip_recvresponse(server, NULL, buf, &len);
318 	if (result_code != 200)
319 		errx(1, "can't get interface list");
320 
321 	cp = buf;
322 	while (1) {
323 		if (sscanf(cp, "%x %63s", &if_index, interface) != 2)
324 			break;
325 		if (ifname == NULL) {
326 			/* if name isn't specified, return the 1st entry */
327 			return (interface);
328 		}
329 		if (strcmp(ifname, interface) == 0)
330 			/* found the matching entry */
331 			return (interface);
332 		if ((cp = strchr(cp+1, '\n')) == NULL)
333 			break;
334 	}
335 	errx(1, "can't get interface");
336 	return (NULL);
337 }
338 
339 char *
340 quip_selectqdisc(char *ifname, char *qdisc_name)
341 {
342 	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE];
343 	int result_code, len;
344 	static char qdisc[64];
345 
346 	if (server == NULL) {
347 		if (ifname == NULL || qdisc_name == NULL)
348 			errx(1, "when disabling server communication,\n"
349 			    "specify both interface (-i) and qdisc (-q)!");
350 		return (qdisc_name);
351 	}
352 
353 	/* get qdisc info from the server */
354 	snprintf(req, sizeof(req), "GET qdisc?%s\n", ifname);
355 	quip_sendrequest(server, req);
356 
357 	result_code = quip_recvresponse(server, NULL, buf, &len);
358 	if (result_code != 200)
359 		errx(1, "can't get qdisc info");
360 
361 	if (sscanf(buf, "%63s", qdisc) != 1)
362 		errx(1, "can't get qdisc name");
363 
364 	if (qdisc_name != NULL && strcmp(qdisc, qdisc_name) != 0)
365 		errx(1, "qdisc %s on %s doesn't match specified qdisc %s",
366 		    qdisc, ifname, qdisc_name);
367 
368 	return (qdisc);
369 }
370 
371 void
372 quip_chandle2name(const char *ifname, u_long handle, char *name, size_t size)
373 {
374 	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
375 	int result_code, len;
376 
377 	name[0] = '\0';
378 	if (server == NULL)
379 		return;
380 
381 	/* get class name from the server */
382 	snprintf(req, sizeof(req), "GET handle-to-name?%s:%#lx\n", ifname, handle);
383 	quip_sendrequest(server, req);
384 
385 	result_code = quip_recvresponse(server, NULL, buf, &len);
386 	if (result_code != 200)
387 		errx(1, "can't get class name");
388 
389 	if ((cp = strchr(buf, '\n')) != NULL)
390 		*cp = '\0';
391 	if ((cp = strrchr(buf, '/')) != NULL)
392 		strlcpy(name, cp+1, size);
393 }
394 
395 void
396 quip_printqdisc(const char *ifname)
397 {
398 	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
399 	int result_code, len;
400 
401 	if (server == NULL) {
402 		printf("No server available!\n");
403 		return;
404 	}
405 
406 	/* get qdisc info from the server */
407 	snprintf(req, sizeof(req), "GET qdisc?%s\n", ifname);
408 	quip_sendrequest(server, req);
409 
410 	result_code = quip_recvresponse(server, NULL, buf, &len);
411 	if (result_code != 200)
412 		errx(1, "can't get qdisc info");
413 
414 	/* replace newline by space */
415 	cp = buf;
416 	while ((cp = strchr(cp, '\n')) != NULL)
417 		*cp = ' ';
418 
419 	printf("  qdisc:%s\n", buf);
420 }
421 
422 void
423 quip_printfilter(const char *ifname, const u_long handle)
424 {
425 	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
426 	int result_code, len;
427 
428 	if (server == NULL) {
429 		printf("No server available!\n");
430 		return;
431 	}
432 
433 	/* get qdisc info from the server */
434 	snprintf(req, sizeof(req), "GET filter?%s::%#lx\n", ifname, handle);
435 	quip_sendrequest(server, req);
436 
437 	result_code = quip_recvresponse(server, NULL, buf, &len);
438 	if (result_code != 200)
439 		errx(1, "can't get filter info");
440 
441 	if ((cp = strchr(buf, '\n')) != NULL)
442 		*cp = '\0';
443 	printf("%s", buf);
444 }
445 
446 static char *
447 extract_ifname(const char *name)
448 {
449 	const char *cp;
450 	int len;
451 	static char ifname[64];
452 
453 	if ((cp = strchr(name, ':')) != NULL)
454 		len = cp - name;
455 	else
456 		len = strlen(name);
457 	len = MIN(len, 63);
458 	strncpy(ifname, name, len);
459 	ifname[len] = '\0';
460 	return (ifname);
461 }
462 
463 void
464 quip_printconfig(void)
465 {
466 	char buf[BODY_MAXSIZE], name[256], *cp, *p, *flname;
467 	int result_code, len;
468 	enum nametype type;
469 	u_long handle;
470 
471 	if (server == NULL) {
472 		printf("No server available!\n");
473 		return;
474 	}
475 
476 	/* get a total list from the server */
477 	quip_sendrequest(server, "GET list\n");
478 
479 	result_code = quip_recvresponse(server, NULL, buf, &len);
480 	if (result_code != 200)
481 		errx(1, "can't get total list");
482 
483 	printf("------------ current configuration ------------");
484 
485 	cp = buf;
486 	while (1) {
487 		if (sscanf(cp, "%lx %255s", &handle, name) != 2)
488 			break;
489 
490 		if ((p = strchr(name, ':')) == NULL)
491 			type = INTERFACE;
492 		else if (strchr(p+1, ':') == NULL)
493 			type = CLASS;
494 		else
495 			type = FILTER;
496 
497 		switch (type) {
498 		case INTERFACE:
499 			printf("\ninterface: %s  (index:%lu)\n",
500 			       name, handle);
501 			quip_printqdisc(name);
502 			break;
503 		case CLASS:
504 			printf("class: %s  (handle:%#lx)\n",
505 			       name, handle);
506 			break;
507 		case FILTER:
508 			flname = strrchr(name, ':') + 1;
509 			printf("  filter: name:%s [", flname);
510 			quip_printfilter(extract_ifname(name), handle);
511 			printf("]  (handle:%#lx)\n", handle);
512 			break;
513 		case CONDITIONER:
514 			break;
515 		}
516 
517 		if ((cp = strchr(cp+1, '\n')) == NULL)
518 			break;
519 	}
520 	printf("-----------------------------------------------\n\n");
521 }
522 
523