1/*	$OpenBSD: pfctl_queue.c,v 1.7 2019/06/28 13:32:45 deraadt Exp $ */
2
3/*
4 * Copyright (c) 2003 - 2013 Henning Brauer <henning@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19#include <sys/types.h>
20#include <sys/ioctl.h>
21#include <sys/socket.h>
22
23#include <net/if.h>
24#include <netinet/in.h>
25#include <net/pfvar.h>
26#include <arpa/inet.h>
27
28#include <err.h>
29#include <math.h>
30#include <stdio.h>
31#include <stdlib.h>
32#include <string.h>
33#include <unistd.h>
34
35#include <net/hfsc.h>
36#include <net/fq_codel.h>
37
38#include "pfctl.h"
39#include "pfctl_parser.h"
40
41#define AVGN_MAX	8
42#define STAT_INTERVAL	5
43
44struct queue_stats {
45	union {
46		struct hfsc_class_stats	hfsc;
47		struct fqcodel_stats	fqc;
48	}			 data;
49	int			 avgn;
50	double			 avg_bytes;
51	double			 avg_packets;
52	u_int64_t		 prev_bytes;
53	u_int64_t		 prev_packets;
54};
55
56struct pfctl_queue_node {
57	TAILQ_ENTRY(pfctl_queue_node)	entries;
58	struct pf_queuespec		qs;
59	struct queue_stats		qstats;
60};
61TAILQ_HEAD(qnodes, pfctl_queue_node) qnodes = TAILQ_HEAD_INITIALIZER(qnodes);
62
63int			 pfctl_update_qstats(int);
64void			 pfctl_insert_queue_node(const struct pf_queuespec,
65			    const struct queue_stats);
66struct pfctl_queue_node	*pfctl_find_queue_node(const char *, const char *);
67void			 pfctl_print_queue_node(int, struct pfctl_queue_node *,
68			    int);
69void			 print_qstats(struct queue_stats);
70void			 pfctl_free_queue_node(struct pfctl_queue_node *);
71void			 pfctl_print_queue_nodestat(int,
72			    const struct pfctl_queue_node *);
73void			 update_avg(struct queue_stats *);
74char			*rate2str(double);
75
76int
77pfctl_show_queues(int dev, const char *iface, int opts, int verbose2)
78{
79	struct pfctl_queue_node	*node;
80	int			 nodes, dotitle = (opts & PF_OPT_SHOWALL);
81
82
83	if ((nodes = pfctl_update_qstats(dev)) <= 0)
84		return (nodes);
85
86	TAILQ_FOREACH(node, &qnodes, entries) {
87		if (iface != NULL && strcmp(node->qs.ifname, iface))
88			continue;
89		if (dotitle) {
90			pfctl_print_title("QUEUES:");
91			dotitle = 0;
92		}
93		pfctl_print_queue_node(dev, node, opts);
94	}
95
96	while (verbose2 && nodes > 0) {
97		printf("\n");
98		fflush(stdout);
99		sleep(STAT_INTERVAL);
100		if ((nodes = pfctl_update_qstats(dev)) == -1)
101			return (-1);
102		TAILQ_FOREACH(node, &qnodes, entries) {
103			if (iface != NULL && strcmp(node->qs.ifname, iface))
104				continue;
105			pfctl_print_queue_node(dev, node, opts);
106		}
107	}
108	while ((node = TAILQ_FIRST(&qnodes)) != NULL)
109		TAILQ_REMOVE(&qnodes, node, entries);
110	return (0);
111}
112
113int
114pfctl_update_qstats(int dev)
115{
116	struct pfctl_queue_node	*node;
117	struct pfioc_queue	 pq;
118	struct pfioc_qstats	 pqs;
119	u_int32_t		 mnr, nr;
120	struct queue_stats	 qstats;
121	static u_int32_t	 last_ticket;
122
123	memset(&pq, 0, sizeof(pq));
124	memset(&pqs, 0, sizeof(pqs));
125	memset(&qstats, 0, sizeof(qstats));
126	if (ioctl(dev, DIOCGETQUEUES, &pq) == -1) {
127		warn("DIOCGETQUEUES");
128		return (-1);
129	}
130
131	/* if a new set is found, start over */
132	if (pq.ticket != last_ticket)
133		while ((node = TAILQ_FIRST(&qnodes)) != NULL)
134			TAILQ_REMOVE(&qnodes, node, entries);
135	last_ticket = pq.ticket;
136
137	mnr = pq.nr;
138	for (nr = 0; nr < mnr; ++nr) {
139		pqs.nr = nr;
140		pqs.ticket = pq.ticket;
141		pqs.buf = &qstats.data;
142		pqs.nbytes = sizeof(qstats.data);
143		if (ioctl(dev, DIOCGETQSTATS, &pqs) == -1) {
144			warn("DIOCGETQSTATS");
145			return (-1);
146		}
147		if ((node = pfctl_find_queue_node(pqs.queue.qname,
148		    pqs.queue.ifname)) != NULL) {
149			memcpy(&node->qstats.data, &qstats.data,
150			    sizeof(qstats.data));
151			update_avg(&node->qstats);
152		} else {
153			pfctl_insert_queue_node(pqs.queue, qstats);
154		}
155	}
156	return (mnr);
157}
158
159void
160pfctl_insert_queue_node(const struct pf_queuespec qs,
161    const struct queue_stats qstats)
162{
163	struct pfctl_queue_node	*node;
164
165	node = calloc(1, sizeof(struct pfctl_queue_node));
166	if (node == NULL)
167		err(1, "pfctl_insert_queue_node: calloc");
168	memcpy(&node->qs, &qs, sizeof(qs));
169	memcpy(&node->qstats, &qstats, sizeof(qstats));
170	TAILQ_INSERT_TAIL(&qnodes, node, entries);
171	update_avg(&node->qstats);
172}
173
174struct pfctl_queue_node *
175pfctl_find_queue_node(const char *qname, const char *ifname)
176{
177	struct pfctl_queue_node	*node;
178
179	TAILQ_FOREACH(node, &qnodes, entries)
180		if (!strcmp(node->qs.qname, qname)
181		    && !(strcmp(node->qs.ifname, ifname)))
182			return (node);
183	return (NULL);
184}
185
186void
187pfctl_print_queue_node(int dev, struct pfctl_queue_node *node, int opts)
188{
189	if (node == NULL)
190		return;
191
192	print_queuespec(&node->qs);
193	if (opts & PF_OPT_VERBOSE)
194		pfctl_print_queue_nodestat(dev, node);
195
196	if (opts & PF_OPT_DEBUG)
197		printf("  [ qid=%u parent_qid=%u ifname=%s]\n",
198		    node->qs.qid, node->qs.parent_qid, node->qs.ifname);
199}
200
201void
202pfctl_print_queue_nodestat(int dev, const struct pfctl_queue_node *node)
203{
204	struct hfsc_class_stats *stats =
205	    (struct hfsc_class_stats *)&node->qstats.data.hfsc;
206	struct fqcodel_stats *fqstats =
207	    (struct fqcodel_stats *)&node->qstats.data.fqc;
208
209	printf("  [ pkts: %10llu  bytes: %10llu  "
210	    "dropped pkts: %6llu bytes: %6llu ]\n",
211	    (unsigned long long)stats->xmit_cnt.packets,
212	    (unsigned long long)stats->xmit_cnt.bytes,
213	    (unsigned long long)stats->drop_cnt.packets,
214	    (unsigned long long)stats->drop_cnt.bytes);
215	if (node->qs.parent_qid == 0 && (node->qs.flags & PFQS_FLOWQUEUE) &&
216	    !(node->qs.flags & PFQS_ROOTCLASS)) {
217		double avg = 0, dev = 0;
218
219		if (fqstats->flows > 0) {
220			avg = (double)fqstats->delaysum /
221			    (double)fqstats->flows;
222			dev = sqrt(fmax(0, (double)fqstats->delaysumsq /
223			    (double)fqstats->flows - avg * avg));
224		}
225
226		printf("  [ qlength: %3d/%3d  avg delay: %.3fms std-dev: %.3fms"
227		    "  flows: %3d ]\n", stats->qlength, stats->qlimit,
228		    avg / 1000, dev / 1000, fqstats->flows);
229	} else
230		printf("  [ qlength: %3d/%3d ]\n", stats->qlength,
231		    stats->qlimit);
232
233	if (node->qstats.avgn < 2)
234		return;
235
236	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
237	    node->qstats.avg_packets / STAT_INTERVAL,
238	    rate2str((8 * node->qstats.avg_bytes) / STAT_INTERVAL));
239}
240
241void
242update_avg(struct queue_stats *s)
243{
244	struct hfsc_class_stats *stats =
245	    (struct hfsc_class_stats *)&s->data;
246
247	if (s->avgn > 0) {
248		if (stats->xmit_cnt.bytes >= s->prev_bytes)
249			s->avg_bytes = ((s->avg_bytes * (s->avgn - 1)) +
250			    (stats->xmit_cnt.bytes - s->prev_bytes)) /
251			    s->avgn;
252		if (stats->xmit_cnt.packets >= s->prev_packets)
253			s->avg_packets = ((s->avg_packets * (s->avgn - 1)) +
254			    (stats->xmit_cnt.packets - s->prev_packets)) /
255			    s->avgn;
256	}
257
258	s->prev_bytes = stats->xmit_cnt.bytes;
259	s->prev_packets = stats->xmit_cnt.packets;
260	if (s->avgn < AVGN_MAX)
261		s->avgn++;
262}
263
264#define	R2S_BUFS	8
265#define	RATESTR_MAX	16
266
267char *
268rate2str(double rate)
269{
270	char		*buf;
271	static char	 r2sbuf[R2S_BUFS][RATESTR_MAX];  /* ring bufer */
272	static int	 idx = 0;
273	int		 i;
274	static const char unit[] = " KMG";
275
276	buf = r2sbuf[idx++];
277	if (idx == R2S_BUFS)
278		idx = 0;
279
280	for (i = 0; rate >= 1000 && i <= 3; i++)
281		rate /= 1000;
282
283	if ((int)(rate * 100) % 100)
284		snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
285	else
286		snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
287
288	return (buf);
289}
290