xref: /openbsd-src/sbin/pfctl/pfctl_queue.c (revision 6768654732933083e3c6dc6a789708ec2ef7c45c)
1*67686547Sjsg /*	$OpenBSD: pfctl_queue.c,v 1.8 2024/05/19 10:39:40 jsg Exp $ */
2f8d11d7cShenning 
3f8d11d7cShenning /*
4f8d11d7cShenning  * Copyright (c) 2003 - 2013 Henning Brauer <henning@openbsd.org>
5f8d11d7cShenning  *
6f8d11d7cShenning  * Permission to use, copy, modify, and distribute this software for any
7f8d11d7cShenning  * purpose with or without fee is hereby granted, provided that the above
8f8d11d7cShenning  * copyright notice and this permission notice appear in all copies.
9f8d11d7cShenning  *
10f8d11d7cShenning  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11f8d11d7cShenning  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12f8d11d7cShenning  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13f8d11d7cShenning  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14f8d11d7cShenning  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15f8d11d7cShenning  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16f8d11d7cShenning  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17f8d11d7cShenning  */
18f8d11d7cShenning 
19f8d11d7cShenning #include <sys/types.h>
20f8d11d7cShenning #include <sys/ioctl.h>
21f8d11d7cShenning #include <sys/socket.h>
22f8d11d7cShenning 
23f8d11d7cShenning #include <net/if.h>
24f8d11d7cShenning #include <netinet/in.h>
25f8d11d7cShenning #include <net/pfvar.h>
26f8d11d7cShenning #include <arpa/inet.h>
27f8d11d7cShenning 
28f8d11d7cShenning #include <err.h>
2970b55ec8Smikeb #include <math.h>
30f8d11d7cShenning #include <stdio.h>
31f8d11d7cShenning #include <stdlib.h>
32f8d11d7cShenning #include <string.h>
33f8d11d7cShenning #include <unistd.h>
34f8d11d7cShenning 
35f8d11d7cShenning #include <net/hfsc.h>
3670b55ec8Smikeb #include <net/fq_codel.h>
37f8d11d7cShenning 
38f8d11d7cShenning #include "pfctl.h"
39f8d11d7cShenning #include "pfctl_parser.h"
40f8d11d7cShenning 
41f8d11d7cShenning #define AVGN_MAX	8
42f8d11d7cShenning #define STAT_INTERVAL	5
43f8d11d7cShenning 
44f8d11d7cShenning struct queue_stats {
4570b55ec8Smikeb 	union {
4670b55ec8Smikeb 		struct hfsc_class_stats	hfsc;
4770b55ec8Smikeb 		struct fqcodel_stats	fqc;
4870b55ec8Smikeb 	}			 data;
49f8d11d7cShenning 	int			 avgn;
50f8d11d7cShenning 	double			 avg_bytes;
51f8d11d7cShenning 	double			 avg_packets;
52f8d11d7cShenning 	u_int64_t		 prev_bytes;
53f8d11d7cShenning 	u_int64_t		 prev_packets;
54f8d11d7cShenning };
55f8d11d7cShenning 
56f8d11d7cShenning struct pfctl_queue_node {
57f8d11d7cShenning 	TAILQ_ENTRY(pfctl_queue_node)	entries;
58f8d11d7cShenning 	struct pf_queuespec		qs;
59f8d11d7cShenning 	struct queue_stats		qstats;
60f8d11d7cShenning };
61f8d11d7cShenning TAILQ_HEAD(qnodes, pfctl_queue_node) qnodes = TAILQ_HEAD_INITIALIZER(qnodes);
62f8d11d7cShenning 
63f8d11d7cShenning int			 pfctl_update_qstats(int);
64f8d11d7cShenning void			 pfctl_insert_queue_node(const struct pf_queuespec,
65f8d11d7cShenning 			    const struct queue_stats);
66f8d11d7cShenning struct pfctl_queue_node	*pfctl_find_queue_node(const char *, const char *);
67f8d11d7cShenning void			 pfctl_print_queue_node(int, struct pfctl_queue_node *,
68f8d11d7cShenning 			    int);
69f8d11d7cShenning void			 pfctl_print_queue_nodestat(int,
70f8d11d7cShenning 			    const struct pfctl_queue_node *);
71f8d11d7cShenning void			 update_avg(struct queue_stats *);
7212aebd24Shenning char			*rate2str(double);
73f8d11d7cShenning 
74f8d11d7cShenning int
pfctl_show_queues(int dev,const char * iface,int opts,int verbose2)75f8d11d7cShenning pfctl_show_queues(int dev, const char *iface, int opts, int verbose2)
76f8d11d7cShenning {
77f8d11d7cShenning 	struct pfctl_queue_node	*node;
78f8d11d7cShenning 	int			 nodes, dotitle = (opts & PF_OPT_SHOWALL);
79f8d11d7cShenning 
80f8d11d7cShenning 
81f8d11d7cShenning 	if ((nodes = pfctl_update_qstats(dev)) <= 0)
82f8d11d7cShenning 		return (nodes);
83f8d11d7cShenning 
84f8d11d7cShenning 	TAILQ_FOREACH(node, &qnodes, entries) {
85f8d11d7cShenning 		if (iface != NULL && strcmp(node->qs.ifname, iface))
86f8d11d7cShenning 			continue;
87f8d11d7cShenning 		if (dotitle) {
88f8d11d7cShenning 			pfctl_print_title("QUEUES:");
89f8d11d7cShenning 			dotitle = 0;
90f8d11d7cShenning 		}
91f8d11d7cShenning 		pfctl_print_queue_node(dev, node, opts);
92f8d11d7cShenning 	}
93f8d11d7cShenning 
94f8d11d7cShenning 	while (verbose2 && nodes > 0) {
95f8d11d7cShenning 		printf("\n");
96f8d11d7cShenning 		fflush(stdout);
97f8d11d7cShenning 		sleep(STAT_INTERVAL);
98f8d11d7cShenning 		if ((nodes = pfctl_update_qstats(dev)) == -1)
99f8d11d7cShenning 			return (-1);
100f8d11d7cShenning 		TAILQ_FOREACH(node, &qnodes, entries) {
101f8d11d7cShenning 			if (iface != NULL && strcmp(node->qs.ifname, iface))
102f8d11d7cShenning 				continue;
103f8d11d7cShenning 			pfctl_print_queue_node(dev, node, opts);
104f8d11d7cShenning 		}
105f8d11d7cShenning 	}
106f8d11d7cShenning 	while ((node = TAILQ_FIRST(&qnodes)) != NULL)
107f8d11d7cShenning 		TAILQ_REMOVE(&qnodes, node, entries);
108f8d11d7cShenning 	return (0);
109f8d11d7cShenning }
110f8d11d7cShenning 
111f8d11d7cShenning int
pfctl_update_qstats(int dev)112f8d11d7cShenning pfctl_update_qstats(int dev)
113f8d11d7cShenning {
114f8d11d7cShenning 	struct pfctl_queue_node	*node;
115f8d11d7cShenning 	struct pfioc_queue	 pq;
116f8d11d7cShenning 	struct pfioc_qstats	 pqs;
117f8d11d7cShenning 	u_int32_t		 mnr, nr;
118f8d11d7cShenning 	struct queue_stats	 qstats;
119f8d11d7cShenning 	static u_int32_t	 last_ticket;
120f8d11d7cShenning 
121f8d11d7cShenning 	memset(&pq, 0, sizeof(pq));
122f8d11d7cShenning 	memset(&pqs, 0, sizeof(pqs));
123f8d11d7cShenning 	memset(&qstats, 0, sizeof(qstats));
124df69c215Sderaadt 	if (ioctl(dev, DIOCGETQUEUES, &pq) == -1) {
125f8d11d7cShenning 		warn("DIOCGETQUEUES");
126f8d11d7cShenning 		return (-1);
127f8d11d7cShenning 	}
128f8d11d7cShenning 
129f8d11d7cShenning 	/* if a new set is found, start over */
130f8d11d7cShenning 	if (pq.ticket != last_ticket)
131f8d11d7cShenning 		while ((node = TAILQ_FIRST(&qnodes)) != NULL)
132f8d11d7cShenning 			TAILQ_REMOVE(&qnodes, node, entries);
133f8d11d7cShenning 	last_ticket = pq.ticket;
134f8d11d7cShenning 
135f8d11d7cShenning 	mnr = pq.nr;
136f8d11d7cShenning 	for (nr = 0; nr < mnr; ++nr) {
137f8d11d7cShenning 		pqs.nr = nr;
138f8d11d7cShenning 		pqs.ticket = pq.ticket;
139f8d11d7cShenning 		pqs.buf = &qstats.data;
140f8d11d7cShenning 		pqs.nbytes = sizeof(qstats.data);
141df69c215Sderaadt 		if (ioctl(dev, DIOCGETQSTATS, &pqs) == -1) {
142f8d11d7cShenning 			warn("DIOCGETQSTATS");
143f8d11d7cShenning 			return (-1);
144f8d11d7cShenning 		}
145f8d11d7cShenning 		if ((node = pfctl_find_queue_node(pqs.queue.qname,
146f8d11d7cShenning 		    pqs.queue.ifname)) != NULL) {
147f8d11d7cShenning 			memcpy(&node->qstats.data, &qstats.data,
148f8d11d7cShenning 			    sizeof(qstats.data));
149f8d11d7cShenning 			update_avg(&node->qstats);
150f8d11d7cShenning 		} else {
151f8d11d7cShenning 			pfctl_insert_queue_node(pqs.queue, qstats);
152f8d11d7cShenning 		}
153f8d11d7cShenning 	}
154f8d11d7cShenning 	return (mnr);
155f8d11d7cShenning }
156f8d11d7cShenning 
157f8d11d7cShenning void
pfctl_insert_queue_node(const struct pf_queuespec qs,const struct queue_stats qstats)158f8d11d7cShenning pfctl_insert_queue_node(const struct pf_queuespec qs,
159f8d11d7cShenning     const struct queue_stats qstats)
160f8d11d7cShenning {
161f8d11d7cShenning 	struct pfctl_queue_node	*node;
162f8d11d7cShenning 
163f8d11d7cShenning 	node = calloc(1, sizeof(struct pfctl_queue_node));
164f8d11d7cShenning 	if (node == NULL)
165f8d11d7cShenning 		err(1, "pfctl_insert_queue_node: calloc");
166f8d11d7cShenning 	memcpy(&node->qs, &qs, sizeof(qs));
167f8d11d7cShenning 	memcpy(&node->qstats, &qstats, sizeof(qstats));
168f8d11d7cShenning 	TAILQ_INSERT_TAIL(&qnodes, node, entries);
169f8d11d7cShenning 	update_avg(&node->qstats);
170f8d11d7cShenning }
171f8d11d7cShenning 
172f8d11d7cShenning struct pfctl_queue_node *
pfctl_find_queue_node(const char * qname,const char * ifname)173f8d11d7cShenning pfctl_find_queue_node(const char *qname, const char *ifname)
174f8d11d7cShenning {
175f8d11d7cShenning 	struct pfctl_queue_node	*node;
176f8d11d7cShenning 
177f8d11d7cShenning 	TAILQ_FOREACH(node, &qnodes, entries)
178f8d11d7cShenning 		if (!strcmp(node->qs.qname, qname)
179f8d11d7cShenning 		    && !(strcmp(node->qs.ifname, ifname)))
180f8d11d7cShenning 			return (node);
181f8d11d7cShenning 	return (NULL);
182f8d11d7cShenning }
183f8d11d7cShenning 
184f8d11d7cShenning void
pfctl_print_queue_node(int dev,struct pfctl_queue_node * node,int opts)185f8d11d7cShenning pfctl_print_queue_node(int dev, struct pfctl_queue_node *node, int opts)
186f8d11d7cShenning {
187f8d11d7cShenning 	if (node == NULL)
188f8d11d7cShenning 		return;
189f8d11d7cShenning 
190f8d11d7cShenning 	print_queuespec(&node->qs);
191f8d11d7cShenning 	if (opts & PF_OPT_VERBOSE)
192f8d11d7cShenning 		pfctl_print_queue_nodestat(dev, node);
193f8d11d7cShenning 
194f8d11d7cShenning 	if (opts & PF_OPT_DEBUG)
195f8d11d7cShenning 		printf("  [ qid=%u parent_qid=%u ifname=%s]\n",
196f8d11d7cShenning 		    node->qs.qid, node->qs.parent_qid, node->qs.ifname);
197f8d11d7cShenning }
198f8d11d7cShenning 
199f8d11d7cShenning void
pfctl_print_queue_nodestat(int dev,const struct pfctl_queue_node * node)200f8d11d7cShenning pfctl_print_queue_nodestat(int dev, const struct pfctl_queue_node *node)
201f8d11d7cShenning {
20270b55ec8Smikeb 	struct hfsc_class_stats *stats =
20370b55ec8Smikeb 	    (struct hfsc_class_stats *)&node->qstats.data.hfsc;
20470b55ec8Smikeb 	struct fqcodel_stats *fqstats =
20570b55ec8Smikeb 	    (struct fqcodel_stats *)&node->qstats.data.fqc;
20670b55ec8Smikeb 
207f8d11d7cShenning 	printf("  [ pkts: %10llu  bytes: %10llu  "
208f8d11d7cShenning 	    "dropped pkts: %6llu bytes: %6llu ]\n",
20970b55ec8Smikeb 	    (unsigned long long)stats->xmit_cnt.packets,
21070b55ec8Smikeb 	    (unsigned long long)stats->xmit_cnt.bytes,
21170b55ec8Smikeb 	    (unsigned long long)stats->drop_cnt.packets,
21270b55ec8Smikeb 	    (unsigned long long)stats->drop_cnt.bytes);
213b28abc64Smikeb 	if (node->qs.parent_qid == 0 && (node->qs.flags & PFQS_FLOWQUEUE) &&
214b28abc64Smikeb 	    !(node->qs.flags & PFQS_ROOTCLASS)) {
21570b55ec8Smikeb 		double avg = 0, dev = 0;
21670b55ec8Smikeb 
21770b55ec8Smikeb 		if (fqstats->flows > 0) {
21870b55ec8Smikeb 			avg = (double)fqstats->delaysum /
21970b55ec8Smikeb 			    (double)fqstats->flows;
22070b55ec8Smikeb 			dev = sqrt(fmax(0, (double)fqstats->delaysumsq /
221aec39f4bSmikeb 			    (double)fqstats->flows - avg * avg));
22270b55ec8Smikeb 		}
22370b55ec8Smikeb 
22470b55ec8Smikeb 		printf("  [ qlength: %3d/%3d  avg delay: %.3fms std-dev: %.3fms"
22570b55ec8Smikeb 		    "  flows: %3d ]\n", stats->qlength, stats->qlimit,
22670b55ec8Smikeb 		    avg / 1000, dev / 1000, fqstats->flows);
22770b55ec8Smikeb 	} else
22870b55ec8Smikeb 		printf("  [ qlength: %3d/%3d ]\n", stats->qlength,
22970b55ec8Smikeb 		    stats->qlimit);
230f8d11d7cShenning 
231f8d11d7cShenning 	if (node->qstats.avgn < 2)
232f8d11d7cShenning 		return;
233f8d11d7cShenning 
234f8d11d7cShenning 	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
235f8d11d7cShenning 	    node->qstats.avg_packets / STAT_INTERVAL,
236f8d11d7cShenning 	    rate2str((8 * node->qstats.avg_bytes) / STAT_INTERVAL));
237f8d11d7cShenning }
238f8d11d7cShenning 
239f8d11d7cShenning void
update_avg(struct queue_stats * s)240f8d11d7cShenning update_avg(struct queue_stats *s)
241f8d11d7cShenning {
24270b55ec8Smikeb 	struct hfsc_class_stats *stats =
24370b55ec8Smikeb 	    (struct hfsc_class_stats *)&s->data;
24470b55ec8Smikeb 
245f8d11d7cShenning 	if (s->avgn > 0) {
24670b55ec8Smikeb 		if (stats->xmit_cnt.bytes >= s->prev_bytes)
247f8d11d7cShenning 			s->avg_bytes = ((s->avg_bytes * (s->avgn - 1)) +
24870b55ec8Smikeb 			    (stats->xmit_cnt.bytes - s->prev_bytes)) /
249f8d11d7cShenning 			    s->avgn;
25070b55ec8Smikeb 		if (stats->xmit_cnt.packets >= s->prev_packets)
251f8d11d7cShenning 			s->avg_packets = ((s->avg_packets * (s->avgn - 1)) +
25270b55ec8Smikeb 			    (stats->xmit_cnt.packets - s->prev_packets)) /
253f8d11d7cShenning 			    s->avgn;
254f8d11d7cShenning 	}
255f8d11d7cShenning 
25670b55ec8Smikeb 	s->prev_bytes = stats->xmit_cnt.bytes;
25770b55ec8Smikeb 	s->prev_packets = stats->xmit_cnt.packets;
258f8d11d7cShenning 	if (s->avgn < AVGN_MAX)
259f8d11d7cShenning 		s->avgn++;
260f8d11d7cShenning }
26112aebd24Shenning 
26212aebd24Shenning #define	R2S_BUFS	8
26312aebd24Shenning #define	RATESTR_MAX	16
26412aebd24Shenning 
26512aebd24Shenning char *
rate2str(double rate)26612aebd24Shenning rate2str(double rate)
26712aebd24Shenning {
26812aebd24Shenning 	char		*buf;
26912aebd24Shenning 	static char	 r2sbuf[R2S_BUFS][RATESTR_MAX];  /* ring bufer */
27012aebd24Shenning 	static int	 idx = 0;
27112aebd24Shenning 	int		 i;
27212aebd24Shenning 	static const char unit[] = " KMG";
27312aebd24Shenning 
27412aebd24Shenning 	buf = r2sbuf[idx++];
27512aebd24Shenning 	if (idx == R2S_BUFS)
27612aebd24Shenning 		idx = 0;
27712aebd24Shenning 
27812aebd24Shenning 	for (i = 0; rate >= 1000 && i <= 3; i++)
27912aebd24Shenning 		rate /= 1000;
28012aebd24Shenning 
28112aebd24Shenning 	if ((int)(rate * 100) % 100)
28212aebd24Shenning 		snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
28312aebd24Shenning 	else
28412aebd24Shenning 		snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
28512aebd24Shenning 
28612aebd24Shenning 	return (buf);
28712aebd24Shenning }
288