xref: /openbsd/sbin/pfctl/pfctl_queue.c (revision 67686547)
1 /*	$OpenBSD: pfctl_queue.c,v 1.8 2024/05/19 10:39:40 jsg 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 
44 struct 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 
56 struct pfctl_queue_node {
57 	TAILQ_ENTRY(pfctl_queue_node)	entries;
58 	struct pf_queuespec		qs;
59 	struct queue_stats		qstats;
60 };
61 TAILQ_HEAD(qnodes, pfctl_queue_node) qnodes = TAILQ_HEAD_INITIALIZER(qnodes);
62 
63 int			 pfctl_update_qstats(int);
64 void			 pfctl_insert_queue_node(const struct pf_queuespec,
65 			    const struct queue_stats);
66 struct pfctl_queue_node	*pfctl_find_queue_node(const char *, const char *);
67 void			 pfctl_print_queue_node(int, struct pfctl_queue_node *,
68 			    int);
69 void			 pfctl_print_queue_nodestat(int,
70 			    const struct pfctl_queue_node *);
71 void			 update_avg(struct queue_stats *);
72 char			*rate2str(double);
73 
74 int
pfctl_show_queues(int dev,const char * iface,int opts,int verbose2)75 pfctl_show_queues(int dev, const char *iface, int opts, int verbose2)
76 {
77 	struct pfctl_queue_node	*node;
78 	int			 nodes, dotitle = (opts & PF_OPT_SHOWALL);
79 
80 
81 	if ((nodes = pfctl_update_qstats(dev)) <= 0)
82 		return (nodes);
83 
84 	TAILQ_FOREACH(node, &qnodes, entries) {
85 		if (iface != NULL && strcmp(node->qs.ifname, iface))
86 			continue;
87 		if (dotitle) {
88 			pfctl_print_title("QUEUES:");
89 			dotitle = 0;
90 		}
91 		pfctl_print_queue_node(dev, node, opts);
92 	}
93 
94 	while (verbose2 && nodes > 0) {
95 		printf("\n");
96 		fflush(stdout);
97 		sleep(STAT_INTERVAL);
98 		if ((nodes = pfctl_update_qstats(dev)) == -1)
99 			return (-1);
100 		TAILQ_FOREACH(node, &qnodes, entries) {
101 			if (iface != NULL && strcmp(node->qs.ifname, iface))
102 				continue;
103 			pfctl_print_queue_node(dev, node, opts);
104 		}
105 	}
106 	while ((node = TAILQ_FIRST(&qnodes)) != NULL)
107 		TAILQ_REMOVE(&qnodes, node, entries);
108 	return (0);
109 }
110 
111 int
pfctl_update_qstats(int dev)112 pfctl_update_qstats(int dev)
113 {
114 	struct pfctl_queue_node	*node;
115 	struct pfioc_queue	 pq;
116 	struct pfioc_qstats	 pqs;
117 	u_int32_t		 mnr, nr;
118 	struct queue_stats	 qstats;
119 	static u_int32_t	 last_ticket;
120 
121 	memset(&pq, 0, sizeof(pq));
122 	memset(&pqs, 0, sizeof(pqs));
123 	memset(&qstats, 0, sizeof(qstats));
124 	if (ioctl(dev, DIOCGETQUEUES, &pq) == -1) {
125 		warn("DIOCGETQUEUES");
126 		return (-1);
127 	}
128 
129 	/* if a new set is found, start over */
130 	if (pq.ticket != last_ticket)
131 		while ((node = TAILQ_FIRST(&qnodes)) != NULL)
132 			TAILQ_REMOVE(&qnodes, node, entries);
133 	last_ticket = pq.ticket;
134 
135 	mnr = pq.nr;
136 	for (nr = 0; nr < mnr; ++nr) {
137 		pqs.nr = nr;
138 		pqs.ticket = pq.ticket;
139 		pqs.buf = &qstats.data;
140 		pqs.nbytes = sizeof(qstats.data);
141 		if (ioctl(dev, DIOCGETQSTATS, &pqs) == -1) {
142 			warn("DIOCGETQSTATS");
143 			return (-1);
144 		}
145 		if ((node = pfctl_find_queue_node(pqs.queue.qname,
146 		    pqs.queue.ifname)) != NULL) {
147 			memcpy(&node->qstats.data, &qstats.data,
148 			    sizeof(qstats.data));
149 			update_avg(&node->qstats);
150 		} else {
151 			pfctl_insert_queue_node(pqs.queue, qstats);
152 		}
153 	}
154 	return (mnr);
155 }
156 
157 void
pfctl_insert_queue_node(const struct pf_queuespec qs,const struct queue_stats qstats)158 pfctl_insert_queue_node(const struct pf_queuespec qs,
159     const struct queue_stats qstats)
160 {
161 	struct pfctl_queue_node	*node;
162 
163 	node = calloc(1, sizeof(struct pfctl_queue_node));
164 	if (node == NULL)
165 		err(1, "pfctl_insert_queue_node: calloc");
166 	memcpy(&node->qs, &qs, sizeof(qs));
167 	memcpy(&node->qstats, &qstats, sizeof(qstats));
168 	TAILQ_INSERT_TAIL(&qnodes, node, entries);
169 	update_avg(&node->qstats);
170 }
171 
172 struct pfctl_queue_node *
pfctl_find_queue_node(const char * qname,const char * ifname)173 pfctl_find_queue_node(const char *qname, const char *ifname)
174 {
175 	struct pfctl_queue_node	*node;
176 
177 	TAILQ_FOREACH(node, &qnodes, entries)
178 		if (!strcmp(node->qs.qname, qname)
179 		    && !(strcmp(node->qs.ifname, ifname)))
180 			return (node);
181 	return (NULL);
182 }
183 
184 void
pfctl_print_queue_node(int dev,struct pfctl_queue_node * node,int opts)185 pfctl_print_queue_node(int dev, struct pfctl_queue_node *node, int opts)
186 {
187 	if (node == NULL)
188 		return;
189 
190 	print_queuespec(&node->qs);
191 	if (opts & PF_OPT_VERBOSE)
192 		pfctl_print_queue_nodestat(dev, node);
193 
194 	if (opts & PF_OPT_DEBUG)
195 		printf("  [ qid=%u parent_qid=%u ifname=%s]\n",
196 		    node->qs.qid, node->qs.parent_qid, node->qs.ifname);
197 }
198 
199 void
pfctl_print_queue_nodestat(int dev,const struct pfctl_queue_node * node)200 pfctl_print_queue_nodestat(int dev, const struct pfctl_queue_node *node)
201 {
202 	struct hfsc_class_stats *stats =
203 	    (struct hfsc_class_stats *)&node->qstats.data.hfsc;
204 	struct fqcodel_stats *fqstats =
205 	    (struct fqcodel_stats *)&node->qstats.data.fqc;
206 
207 	printf("  [ pkts: %10llu  bytes: %10llu  "
208 	    "dropped pkts: %6llu bytes: %6llu ]\n",
209 	    (unsigned long long)stats->xmit_cnt.packets,
210 	    (unsigned long long)stats->xmit_cnt.bytes,
211 	    (unsigned long long)stats->drop_cnt.packets,
212 	    (unsigned long long)stats->drop_cnt.bytes);
213 	if (node->qs.parent_qid == 0 && (node->qs.flags & PFQS_FLOWQUEUE) &&
214 	    !(node->qs.flags & PFQS_ROOTCLASS)) {
215 		double avg = 0, dev = 0;
216 
217 		if (fqstats->flows > 0) {
218 			avg = (double)fqstats->delaysum /
219 			    (double)fqstats->flows;
220 			dev = sqrt(fmax(0, (double)fqstats->delaysumsq /
221 			    (double)fqstats->flows - avg * avg));
222 		}
223 
224 		printf("  [ qlength: %3d/%3d  avg delay: %.3fms std-dev: %.3fms"
225 		    "  flows: %3d ]\n", stats->qlength, stats->qlimit,
226 		    avg / 1000, dev / 1000, fqstats->flows);
227 	} else
228 		printf("  [ qlength: %3d/%3d ]\n", stats->qlength,
229 		    stats->qlimit);
230 
231 	if (node->qstats.avgn < 2)
232 		return;
233 
234 	printf("  [ measured: %7.1f packets/s, %s/s ]\n",
235 	    node->qstats.avg_packets / STAT_INTERVAL,
236 	    rate2str((8 * node->qstats.avg_bytes) / STAT_INTERVAL));
237 }
238 
239 void
update_avg(struct queue_stats * s)240 update_avg(struct queue_stats *s)
241 {
242 	struct hfsc_class_stats *stats =
243 	    (struct hfsc_class_stats *)&s->data;
244 
245 	if (s->avgn > 0) {
246 		if (stats->xmit_cnt.bytes >= s->prev_bytes)
247 			s->avg_bytes = ((s->avg_bytes * (s->avgn - 1)) +
248 			    (stats->xmit_cnt.bytes - s->prev_bytes)) /
249 			    s->avgn;
250 		if (stats->xmit_cnt.packets >= s->prev_packets)
251 			s->avg_packets = ((s->avg_packets * (s->avgn - 1)) +
252 			    (stats->xmit_cnt.packets - s->prev_packets)) /
253 			    s->avgn;
254 	}
255 
256 	s->prev_bytes = stats->xmit_cnt.bytes;
257 	s->prev_packets = stats->xmit_cnt.packets;
258 	if (s->avgn < AVGN_MAX)
259 		s->avgn++;
260 }
261 
262 #define	R2S_BUFS	8
263 #define	RATESTR_MAX	16
264 
265 char *
rate2str(double rate)266 rate2str(double rate)
267 {
268 	char		*buf;
269 	static char	 r2sbuf[R2S_BUFS][RATESTR_MAX];  /* ring bufer */
270 	static int	 idx = 0;
271 	int		 i;
272 	static const char unit[] = " KMG";
273 
274 	buf = r2sbuf[idx++];
275 	if (idx == R2S_BUFS)
276 		idx = 0;
277 
278 	for (i = 0; rate >= 1000 && i <= 3; i++)
279 		rate /= 1000;
280 
281 	if ((int)(rate * 100) % 100)
282 		snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
283 	else
284 		snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
285 
286 	return (buf);
287 }
288