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