src/sbin/pfctl/pfctl_queue.c

290 lines
7.5 KiB
C

/* $OpenBSD: pfctl_queue.c,v 1.7 2019/06/28 13:32:45 deraadt Exp $ */
/*
* Copyright (c) 2003 - 2013 Henning Brauer <henning@openbsd.org>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <netinet/in.h>
#include <net/pfvar.h>
#include <arpa/inet.h>
#include <err.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/hfsc.h>
#include <net/fq_codel.h>
#include "pfctl.h"
#include "pfctl_parser.h"
#define AVGN_MAX 8
#define STAT_INTERVAL 5
struct queue_stats {
union {
struct hfsc_class_stats hfsc;
struct fqcodel_stats fqc;
} data;
int avgn;
double avg_bytes;
double avg_packets;
u_int64_t prev_bytes;
u_int64_t prev_packets;
};
struct pfctl_queue_node {
TAILQ_ENTRY(pfctl_queue_node) entries;
struct pf_queuespec qs;
struct queue_stats qstats;
};
TAILQ_HEAD(qnodes, pfctl_queue_node) qnodes = TAILQ_HEAD_INITIALIZER(qnodes);
int pfctl_update_qstats(int);
void pfctl_insert_queue_node(const struct pf_queuespec,
const struct queue_stats);
struct pfctl_queue_node *pfctl_find_queue_node(const char *, const char *);
void pfctl_print_queue_node(int, struct pfctl_queue_node *,
int);
void print_qstats(struct queue_stats);
void pfctl_free_queue_node(struct pfctl_queue_node *);
void pfctl_print_queue_nodestat(int,
const struct pfctl_queue_node *);
void update_avg(struct queue_stats *);
char *rate2str(double);
int
pfctl_show_queues(int dev, const char *iface, int opts, int verbose2)
{
struct pfctl_queue_node *node;
int nodes, dotitle = (opts & PF_OPT_SHOWALL);
if ((nodes = pfctl_update_qstats(dev)) <= 0)
return (nodes);
TAILQ_FOREACH(node, &qnodes, entries) {
if (iface != NULL && strcmp(node->qs.ifname, iface))
continue;
if (dotitle) {
pfctl_print_title("QUEUES:");
dotitle = 0;
}
pfctl_print_queue_node(dev, node, opts);
}
while (verbose2 && nodes > 0) {
printf("\n");
fflush(stdout);
sleep(STAT_INTERVAL);
if ((nodes = pfctl_update_qstats(dev)) == -1)
return (-1);
TAILQ_FOREACH(node, &qnodes, entries) {
if (iface != NULL && strcmp(node->qs.ifname, iface))
continue;
pfctl_print_queue_node(dev, node, opts);
}
}
while ((node = TAILQ_FIRST(&qnodes)) != NULL)
TAILQ_REMOVE(&qnodes, node, entries);
return (0);
}
int
pfctl_update_qstats(int dev)
{
struct pfctl_queue_node *node;
struct pfioc_queue pq;
struct pfioc_qstats pqs;
u_int32_t mnr, nr;
struct queue_stats qstats;
static u_int32_t last_ticket;
memset(&pq, 0, sizeof(pq));
memset(&pqs, 0, sizeof(pqs));
memset(&qstats, 0, sizeof(qstats));
if (ioctl(dev, DIOCGETQUEUES, &pq) == -1) {
warn("DIOCGETQUEUES");
return (-1);
}
/* if a new set is found, start over */
if (pq.ticket != last_ticket)
while ((node = TAILQ_FIRST(&qnodes)) != NULL)
TAILQ_REMOVE(&qnodes, node, entries);
last_ticket = pq.ticket;
mnr = pq.nr;
for (nr = 0; nr < mnr; ++nr) {
pqs.nr = nr;
pqs.ticket = pq.ticket;
pqs.buf = &qstats.data;
pqs.nbytes = sizeof(qstats.data);
if (ioctl(dev, DIOCGETQSTATS, &pqs) == -1) {
warn("DIOCGETQSTATS");
return (-1);
}
if ((node = pfctl_find_queue_node(pqs.queue.qname,
pqs.queue.ifname)) != NULL) {
memcpy(&node->qstats.data, &qstats.data,
sizeof(qstats.data));
update_avg(&node->qstats);
} else {
pfctl_insert_queue_node(pqs.queue, qstats);
}
}
return (mnr);
}
void
pfctl_insert_queue_node(const struct pf_queuespec qs,
const struct queue_stats qstats)
{
struct pfctl_queue_node *node;
node = calloc(1, sizeof(struct pfctl_queue_node));
if (node == NULL)
err(1, "pfctl_insert_queue_node: calloc");
memcpy(&node->qs, &qs, sizeof(qs));
memcpy(&node->qstats, &qstats, sizeof(qstats));
TAILQ_INSERT_TAIL(&qnodes, node, entries);
update_avg(&node->qstats);
}
struct pfctl_queue_node *
pfctl_find_queue_node(const char *qname, const char *ifname)
{
struct pfctl_queue_node *node;
TAILQ_FOREACH(node, &qnodes, entries)
if (!strcmp(node->qs.qname, qname)
&& !(strcmp(node->qs.ifname, ifname)))
return (node);
return (NULL);
}
void
pfctl_print_queue_node(int dev, struct pfctl_queue_node *node, int opts)
{
if (node == NULL)
return;
print_queuespec(&node->qs);
if (opts & PF_OPT_VERBOSE)
pfctl_print_queue_nodestat(dev, node);
if (opts & PF_OPT_DEBUG)
printf(" [ qid=%u parent_qid=%u ifname=%s]\n",
node->qs.qid, node->qs.parent_qid, node->qs.ifname);
}
void
pfctl_print_queue_nodestat(int dev, const struct pfctl_queue_node *node)
{
struct hfsc_class_stats *stats =
(struct hfsc_class_stats *)&node->qstats.data.hfsc;
struct fqcodel_stats *fqstats =
(struct fqcodel_stats *)&node->qstats.data.fqc;
printf(" [ pkts: %10llu bytes: %10llu "
"dropped pkts: %6llu bytes: %6llu ]\n",
(unsigned long long)stats->xmit_cnt.packets,
(unsigned long long)stats->xmit_cnt.bytes,
(unsigned long long)stats->drop_cnt.packets,
(unsigned long long)stats->drop_cnt.bytes);
if (node->qs.parent_qid == 0 && (node->qs.flags & PFQS_FLOWQUEUE) &&
!(node->qs.flags & PFQS_ROOTCLASS)) {
double avg = 0, dev = 0;
if (fqstats->flows > 0) {
avg = (double)fqstats->delaysum /
(double)fqstats->flows;
dev = sqrt(fmax(0, (double)fqstats->delaysumsq /
(double)fqstats->flows - avg * avg));
}
printf(" [ qlength: %3d/%3d avg delay: %.3fms std-dev: %.3fms"
" flows: %3d ]\n", stats->qlength, stats->qlimit,
avg / 1000, dev / 1000, fqstats->flows);
} else
printf(" [ qlength: %3d/%3d ]\n", stats->qlength,
stats->qlimit);
if (node->qstats.avgn < 2)
return;
printf(" [ measured: %7.1f packets/s, %s/s ]\n",
node->qstats.avg_packets / STAT_INTERVAL,
rate2str((8 * node->qstats.avg_bytes) / STAT_INTERVAL));
}
void
update_avg(struct queue_stats *s)
{
struct hfsc_class_stats *stats =
(struct hfsc_class_stats *)&s->data;
if (s->avgn > 0) {
if (stats->xmit_cnt.bytes >= s->prev_bytes)
s->avg_bytes = ((s->avg_bytes * (s->avgn - 1)) +
(stats->xmit_cnt.bytes - s->prev_bytes)) /
s->avgn;
if (stats->xmit_cnt.packets >= s->prev_packets)
s->avg_packets = ((s->avg_packets * (s->avgn - 1)) +
(stats->xmit_cnt.packets - s->prev_packets)) /
s->avgn;
}
s->prev_bytes = stats->xmit_cnt.bytes;
s->prev_packets = stats->xmit_cnt.packets;
if (s->avgn < AVGN_MAX)
s->avgn++;
}
#define R2S_BUFS 8
#define RATESTR_MAX 16
char *
rate2str(double rate)
{
char *buf;
static char r2sbuf[R2S_BUFS][RATESTR_MAX]; /* ring bufer */
static int idx = 0;
int i;
static const char unit[] = " KMG";
buf = r2sbuf[idx++];
if (idx == R2S_BUFS)
idx = 0;
for (i = 0; rate >= 1000 && i <= 3; i++)
rate /= 1000;
if ((int)(rate * 100) % 100)
snprintf(buf, RATESTR_MAX, "%.2f%cb", rate, unit[i]);
else
snprintf(buf, RATESTR_MAX, "%d%cb", (int)rate, unit[i]);
return (buf);
}