1/*	$NetBSD: quip_client.c,v 1.8.2.1 2006/03/18 12:16:52 peter 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
104enum nametype { INTERFACE, CLASS, FILTER, CONDITIONER };
105
106static FILE *server = NULL;
107int quip_echo = 0;
108
109static char *extract_ifname(const char *);
110
111int
112quip_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		fprintf(stderr, "can't talk to altqd!\n"
126			"probably, altqd is not running\n");
127		return (-1);
128	}
129
130	if ((server = fdopen(fd, "r+")) == NULL) {
131		warn("fdopen: can't open stream to the quip server");
132		return (-1);
133	}
134	return (0);
135}
136
137int
138quip_closeserver(void)
139{
140	if (server != NULL)
141		return fclose(server);
142	return (0);
143}
144
145void
146quip_sendrequest(FILE *fp, const char *request)
147{
148	char buf[QUIPMSG_MAXSIZE], *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 */
177int
178quip_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
268void
269quip_rawmode(void)
270{
271	char line[MAXLINESIZE];
272	int result_code;
273
274	printf(">>>Entering the raw interactive mode to the server:\n\n");
275	if (server == NULL) {
276		printf("No server available!\n");
277		return;
278	}
279
280	while (1) {
281		printf("%% "); fflush(stdout);
282		/* read a line from stdin */
283		if (fgets(line, sizeof(line), stdin) == NULL)
284			break;
285
286		if (line[0] == '\n') {
287			/* if a blank line, echo locally */
288			fputs(line, stdout);
289			continue;
290		}
291		if (line[0] == 'q') {
292			printf("Exit\n");
293			break;
294		}
295
296		/* send the input line to the server */
297		quip_sendrequest(server, line);
298
299		/* get a response message from the server */
300		result_code = quip_recvresponse(server, NULL, NULL, NULL);
301	}
302}
303
304char *
305quip_selectinterface(char *ifname)
306{
307	char buf[BODY_MAXSIZE], *cp;
308	int result_code, len;
309	u_int if_index;
310	static char interface[64];
311
312	if (server == NULL)
313		return (ifname);
314
315	/* get an inferface list from the server */
316	quip_sendrequest(server, "GET list?*\n");
317
318	result_code = quip_recvresponse(server, NULL, buf, &len);
319	if (result_code != 200)
320		errx(1, "can't get interface list");
321
322	cp = buf;
323	while (1) {
324		if (sscanf(cp, "%x %63s", &if_index, interface) != 2)
325			break;
326		if (ifname == NULL) {
327			/* if name isn't specified, return the 1st entry */
328			return (interface);
329		}
330		if (strcmp(ifname, interface) == 0)
331			/* found the matching entry */
332			return (interface);
333		if ((cp = strchr(cp+1, '\n')) == NULL)
334			break;
335	}
336	errx(1, "can't get interface");
337	return (NULL);
338}
339
340char *
341quip_selectqdisc(char *ifname, char *qdisc_name)
342{
343	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE];
344	int result_code, len;
345	static char qdisc[64];
346
347	if (server == NULL) {
348		if (ifname == NULL || qdisc_name == NULL)
349			errx(1, "when disabling server communication,\n"
350			    "specify both interface (-i) and qdisc (-q)!");
351		return (qdisc_name);
352	}
353
354	/* get qdisc info from the server */
355	snprintf(req, sizeof(req), "GET qdisc?%s\n", ifname);
356	quip_sendrequest(server, req);
357
358	result_code = quip_recvresponse(server, NULL, buf, &len);
359	if (result_code != 200)
360		errx(1, "can't get qdisc info");
361
362	if (sscanf(buf, "%s", qdisc) != 1)
363		errx(1, "can't get qdisc name");
364
365	if (qdisc_name != NULL && strcmp(qdisc, qdisc_name) != 0)
366		errx(1, "qdisc %s on %s doesn't match specified qdisc %s",
367		    qdisc, ifname, qdisc_name);
368
369	return (qdisc);
370}
371
372void
373quip_chandle2name(const char *ifname, u_long handle, char *name, size_t size)
374{
375	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
376	int result_code, len;
377
378	name[0] = '\0';
379	if (server == NULL)
380		return;
381
382	/* get class name from the server */
383	snprintf(req, sizeof(req), "GET handle-to-name?%s:%#lx\n", ifname, handle);
384	quip_sendrequest(server, req);
385
386	result_code = quip_recvresponse(server, NULL, buf, &len);
387	if (result_code != 200)
388		errx(1, "can't get class name");
389
390	if ((cp = strchr(buf, '\n')) != NULL)
391		*cp = '\0';
392	if ((cp = strrchr(buf, '/')) != NULL)
393		strlcpy(name, cp+1, size);
394}
395
396void
397quip_printqdisc(const char *ifname)
398{
399	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
400	int result_code, len;
401
402	if (server == NULL) {
403		printf("No server available!\n");
404		return;
405	}
406
407	/* get qdisc info from the server */
408	snprintf(req, sizeof(req), "GET qdisc?%s\n", ifname);
409	quip_sendrequest(server, req);
410
411	result_code = quip_recvresponse(server, NULL, buf, &len);
412	if (result_code != 200)
413		errx(1, "can't get qdisc info");
414
415	/* replace newline by space */
416	cp = buf;
417	while ((cp = strchr(cp, '\n')) != NULL)
418		*cp = ' ';
419
420	printf("  qdisc:%s\n", buf);
421}
422
423void
424quip_printfilter(const char *ifname, const u_long handle)
425{
426	char buf[BODY_MAXSIZE], req[REQ_MAXSIZE], *cp;
427	int result_code, len;
428
429	if (server == NULL) {
430		printf("No server available!\n");
431		return;
432	}
433
434	/* get qdisc info from the server */
435	snprintf(req, sizeof(req), "GET filter?%s::%#lx\n", ifname, handle);
436	quip_sendrequest(server, req);
437
438	result_code = quip_recvresponse(server, NULL, buf, &len);
439	if (result_code != 200)
440		errx(1, "can't get filter info");
441
442	if ((cp = strchr(buf, '\n')) != NULL)
443		*cp = '\0';
444	printf("%s", buf);
445}
446
447static char *
448extract_ifname(const char *name)
449{
450	char *cp;
451	int len;
452	static char ifname[64];
453
454	if ((cp = strchr(name, ':')) != NULL)
455		len = cp - name;
456	else
457		len = strlen(name);
458	len = MIN(len, 63);
459	strncpy(ifname, name, len);
460	ifname[len] = '\0';
461	return (ifname);
462}
463
464void
465quip_printconfig(void)
466{
467	char buf[BODY_MAXSIZE], name[256], *cp, *p, *flname;
468	int result_code, len;
469	enum nametype type;
470	u_long handle;
471
472	if (server == NULL) {
473		printf("No server available!\n");
474		return;
475	}
476
477	/* get a total list from the server */
478	quip_sendrequest(server, "GET list\n");
479
480	result_code = quip_recvresponse(server, NULL, buf, &len);
481	if (result_code != 200)
482		errx(1, "can't get total list");
483
484	printf("------------ current configuration ------------");
485
486	cp = buf;
487	while (1) {
488		if (sscanf(cp, "%lx %255s", &handle, name) != 2)
489			break;
490
491		if ((p = strchr(name, ':')) == NULL)
492			type = INTERFACE;
493		else if (strchr(p+1, ':') == NULL)
494			type = CLASS;
495		else
496			type = FILTER;
497
498		switch (type) {
499		case INTERFACE:
500			printf("\ninterface: %s  (index:%lu)\n",
501			       name, handle);
502			quip_printqdisc(name);
503			break;
504		case CLASS:
505			printf("class: %s  (handle:%#lx)\n",
506			       name, handle);
507			break;
508		case FILTER:
509			flname = strrchr(name, ':') + 1;
510			printf("  filter: name:%s [", flname);
511			quip_printfilter(extract_ifname(name), handle);
512			printf("]  (handle:%#lx)\n", handle);
513			break;
514		case CONDITIONER:
515			break;
516		}
517
518		if ((cp = strchr(cp+1, '\n')) == NULL)
519			break;
520	}
521	printf("-----------------------------------------------\n\n");
522}
523
524