When running pf firewalls and loadbalancers with relayd, some metrics are critical. One thing that might not be obvious when looking at pf is that the maximum number of sessions a firewall can handle is fixed, as evidenced from looking at the output of pfctl -si
Status: Enabled for 10 days 00:39:47 Debug: err State Table Total Rate current entries 6 searches 7970148 9.2/s inserts 60227 0.1/s removals 60221 0.1/s Counters match 67984 0.1/s bad-offset 0 0.0/s fragment 1 0.0/s short 167 0.0/s normalize 0 0.0/s memory 0 0.0/s bad-timestamp 0 0.0/s congestion 0 0.0/s ip-option 0 0.0/s proto-cksum 0 0.0/s state-mismatch 34 0.0/s state-insert 0 0.0/s state-limit 0 0.0/s src-limit 0 0.0/s synproxy 0 0.0/s
Now this can be compared to the limit values you have set which can be queried through pfctl -sm
states hard limit 200000 src-nodes hard limit 10000 frags hard limit 5000 tables hard limit 1000 table-entries hard limit 200000
In this particular example, the limit won't be reached for a while!
By default pf has a very low state limit to cope with machines with very small amounts of RAM, OpenBSD still runs on VAX where 32m of RAM is a lot. On a default install only 10k states are allowed, for a production firewall this is likely to be too small really soon. To raise this limit use the set limit { states }
. My advice would be to start at 10000 and to raise appropriately if need be.
Beware that once the state limit is reached, no new states will be allowed to keep old ones functioning leading to a difficult debugging situation.
As a bonus, here is a simple github gist which implements statistics collection for collectd, allowing you to graph state consumption and alert when the limit is nearly reached.
/*
* Copyright (c) 2010 Pierre-Yves Ritschard <pyr@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 <net/pfvar.h>
#include <limits.h>
#include <fcntl.h>
#include <paths.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#ifndef TEST
#include "collectd.h"
#include "plugin.h"
#else
typedef u_int64_t counter_t;
#endif
#define PF_SOCKET "/dev/pf"
struct pfdata {
int pd_dev;
};
static struct pfdata pd;
static int pf_init(void);
static int pf_read(void);
static void submit_counter(const char *, const char *, counter_t);
void
submit_counter(const char *type, const char *inst, counter_t val)
{
#ifndef TEST
value_t values[1];
value_list_t vl = VALUE_LIST_INIT;
values[0].counter = val;
vl.values = values;
vl.values_len = 1;
strlcpy(vl.host, hostname_g, sizeof(vl.host));
strlcpy(vl.plugin, "pf", sizeof(vl.plugin));
strlcpy(vl.type, type, sizeof(vl.type));
strlcpy(vl.type_instance, inst, sizeof(vl.type_instance));
plugin_dispatch_values(&vl);
#else
printf("%s.%s: %lld\n", type, inst, val);
#endif
}
int
pf_init(void)
{
struct pf_status status;
memset(&pd, '\0', sizeof(pd));
if ((pd.pd_dev = open(PF_SOCKET, O_RDWR)) == -1) {
return (-1);
}
if (ioctl(pd.pd_dev, DIOCGETSTATUS, &status) == -1) {
return (-1);
}
close(pd.pd_dev);
if (!status.running)
return (-1);
return (0);
}
int
pf_read(void)
{
int i;
struct pf_status status;
char *cnames[] = PFRES_NAMES;
char *lnames[] = LCNT_NAMES;
char *names[] = { "searches", "inserts", "removals" };
if ((pd.pd_dev = open(PF_SOCKET, O_RDWR)) == -1) {
return (-1);
}
if (ioctl(pd.pd_dev, DIOCGETSTATUS, &status) == -1) {
return (-1);
}
close(pd.pd_dev);
for (i = 0; i < PFRES_MAX; i++)
submit_counter("pf_counters", cnames[i], status.counters[i]);
for (i = 0; i < LCNT_MAX; i++)
submit_counter("pf_limits", lnames[i], status.lcounters[i]);
for (i = 0; i < FCNT_MAX; i++)
submit_counter("pf_state", names[i], status.fcounters[i]);
for (i = 0; i < SCNT_MAX; i++)
submit_counter("pf_source", names[i], status.scounters[i]);
return (0);
}
#ifdef TEST
int
main(int argc, char *argv[])
{
if (pf_init())
err(1, "pf_init");
if (pf_read())
err(1, "pf_read");
return (0);
}
#else
void module_register(void) {
plugin_register_init("pf", pf_init);
plugin_register_read("pf", pf_read);
}
#endif