PF statistics into MRTG

Below is a short little script that i wrote with some help of some FreeBSD Perl developers (Thanks mat and siks).

The script should be runned with root permissions, sudo could be used for that:

For example with the follwing information in /usr/local/etc/sudoers (or where your configfile lives)

User_Alias MRTG = mrtg
MRTG NOPASSWD: /sbin/pfctl -si

The script will run /sbin/pfctl -si which will give you statistical output which can be used for processing by mrtg after running pf.pl. Do note that the current markup might not fit on the script. Please click on the Plain text button to see the original script in a new window, which will fit.

[perl]
#!/usr/bin/perl -w

# $Id: pf-to-mrtg.pl 831 2008-09-07 13:45:27Z remko $
#
# (c) 2005 Remko Lodder
# (c) 2008 Remko Lodder
# Based upon ipf.pl by Ronald Florence
# last modified remko@elvandar.org, 05 Sept 2009
# Made the script working again.

# Example output from pfctl -si
#Interface Stats for fxp0 IPv4 IPv6
# Bytes In 156041 0
# Bytes Out 1895602 0
# Packets In
# Passed 1504 0
# Blocked 256 0
# Packets Out
# Passed 1756 0
# Blocked 0 0
#
#State Table Total Rate
# current entries 33
# searches 8865 10.7/s
# inserts 204 0.2/s
# removals 171 0.2/s
#Counters
# match 460 0.6/s
# bad-offset 0 0.0/s
# fragment 0 0.0/s
# short 0 0.0/s
# normalize 0 0.0/s
# memory 0 0.0/s

use strict;
use Getopt::Std;

# prototype variables.
my($inp,$outp) = 0;

my ($out,$in,$uptime,$firewall);
my(@pfctl);
my(%option);

# define some variables upfront
my $scriptname = __FILE__;
my $version = “2.0″;

# Index our options
getopts(“bBfimMnpsS”, \%option);

my $pass_flag = 1 if $option{p};
my $block_flag = 1 if $option{b};
my $bytes_flag = 1 if $option{B};
my $state_flag = 1 if $option{s};
my $search_flag = 1 if $option{S};
my $inres_flag = 1 if $option{i};
my $match_flag = 1 if $option{m};
my $frag_flag = 1 if $option{f};
my $short_flag = 1 if $option{n};
my $memory_flag = 1 if $option{M};

@pfctl=`/sbin/pfctl -si`;

foreach my $packetfilter (@pfctl) {
if ($pass_flag || $block_flag) {
if ($packetfilter =~ /Packets In/) { $in = 1; $out = 0; }
if ($packetfilter =~ /Packets Out/) { $in = 0; $out = 1; }
if ($pass_flag) {
if ($packetfilter =~ /Passed\s+(\d+)/) { if ($in) { $inp += $1; } }
if ($packetfilter =~ /Passed\s+(\d+)/) { if ($out) { $outp += $1; } }
}
if ($block_flag) {
if ($packetfilter =~ /Blocked\s+(\d+)/) { if ($in) { $inp += $1; } }
if ($packetfilter =~ /Blocked\s+(\d+)/) { if ($out) { $outp += $1; } }
}
}

elsif ($bytes_flag) {
if ($packetfilter =~ /Bytes\sIn\s+(\d+)/) { $inp += $1; }
if ($packetfilter =~ /Bytes\sOut\s+(\d+)/) { $outp += $1; }
}
elsif ($state_flag) {
if ($packetfilter =~ /current entries\s+(\d+)/) { $inp += $1 }
$outp = 0;
}
elsif ($search_flag) {
if ($packetfilter =~ /searches\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /searches\s+\d+\s+(\d+)/) { $outp += $1 }
}
elsif ($inres_flag) {
if ($packetfilter =~ /inserts\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /removals\s+(\d+)/) { $outp += $1 }
}
elsif ($match_flag) {
if ($packetfilter =~ /match\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /match\s+\d+\s+(\d+)/) { $outp += $1 }
}
elsif ($frag_flag) {
if ($packetfilter =~ /bad-offset\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /fragment\s+(\d+)/) { $outp += $1 }
}
elsif ($short_flag) {
if ($packetfilter =~ /short\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /normalize\s+(\d+)/) { $outp += $1 }
}
elsif ($memory_flag) {
if ($packetfilter =~ /memory\s+(\d+)/) { $inp += $1 }
if ($packetfilter =~ /memory\s+\d+\s+(\d+)/) { $outp += $1 }
}
else {
print(“Usage:\t$scriptname [ -b ] [ -B ] [ -f ] [ -i ] [ -m ] [ -M ] [ -n ] [ -p ] [ -s ] [ -S ]
\t-b\tIndex blocked packets incoming and outgoing.
\t-B\tIndex bytes incoming and outgoing
\t-f\tIndex bad-offsets and fragments
\t-i\tIndex inserts and removals
\t-m\tIndex matched packets current values and usage rate
\t-M\tIndex memory usage current values and usage rate
\t-n\tIndex short packets and normalized packets
\t-p\tIndex passed packets incoming and outgoing.
\t-s\tIndex states other value will be zero (0)
\t-S\tIndex searches current searches and search rate
Version: $version
Originally written by Remko Lodder .\n”);
exit(1);
}
}

print “$inp\n”,
“$outp\n”;

$_ = `/usr/bin/uptime`;
($uptime) = /up\s+(.*),\s+.+user/;
$uptime =~ s/(^1\s+day)\s/1/;

chop($firewall = `uname -nm`);
print “$uptime\n”,

__END__

=head1 NAME

pf-to-mrtg.pl – generates statistics for use with MRTG

=head1 SYNOPSIS

Two example configuration options for MRTG:

Target[connections]: `/path/to/pf-to-mrtg.pl -p`
Options[connections]: growright, perhour
MaxBytes[connections]: 50000
Title[connections]: Firewall Connections
PageTop[connections]:

Firewall Connections

YLegend[connections]: packets/hr
ShortLegend[connections]: pkts/h
Legend1[connections]: Incoming Connections
Legend2[connections]: Outgoing Connections

Target[state]: `/path/to/pf-to-mrtg.pl -s`
Options[state]: growright, gauge, integer
MaxBytes[state]: 2048
Title[state]: State Table
PageTop[state]:

NAT & IP state tables

YLegend[state]: states
ShortLegend[state]: states
Legend1[state]: NAT states
Legend2[state]: IP states
LegendI[state]: nat:
LegendO[state]: ip:

Other available targets are `pf-to-mrtg.pl -b`
`pf-to-mrtg.pl -B`
`pf-to-mrtg.pl -f`
`pf-to-mrtg.pl -i`
`pf-to-mrtg.pl -m`
`pf-to-mrtg.pl -M`
`pf-to-mrtg.pl -n`
`pf-to-mrtg.pl -p`
`pf-to-mrtg.pl -s`
`pf-to-mrtg.pl -S`

See the instructions in the help to see what they represent.

=head1 DESCRIPTION

The purpose of this script is to be able to create statistics
from various PF counters. By using this script you will be
able to see what your firewall does, how it behaves, and see
trends. Note: Due to MRTG’s nature you will get spikes after
rebooting the machine.

=head1 LICENSE

This module is licensed under the BSD license.

=head1 AUTHOR

Remko Lodder with additional help from
Mattieu Arnold and Pasi Hirvonen.

Originally based on work from Ron James (before this was entirely rewritten).

=cut
[/perl]

7 thoughts on “PF statistics into MRTG”

  1. Hi!

    I have a problem with results your script returns. For each argument i try, i get empty result. Do you know what the problem could be? I’m running OpenBSD 4.3

    Thanx!

  2. Hello Ales,

    So, can you submit me what the output of pfctl -si looks like? Because it only parses that and returns values based on the option you get.

    Cheers,
    remko

  3. That’s a neat idea I didn’t think of before.

    The regex expressions have been stripped of their escape character (backslash) so they won’t actually match any entries. I guess it has been filtered as it was posted.

    Thanks for posting :)

  4. Grant: Yes that seems right. I will look whether I can find the original script, and place it under code comments (so that it will look a bit weird on the page, but when you click on the plain text version you will get the right version shown).

    Thanks for the info’s!

  5. I fixed the script again, made it strict compliant, added the -w flag so that warnings are printed where needed (one can remove that), and used my standard markup tool to view it nicely on screen, or click on the plaintext button to see the original version.

    It could still use some work, but at least it is working again. Enjoy it!

  6. Version 2.0 had been released, which is a rewrite of the previous version, a bit less hackish, and a lot nicer to read imo. Enjoy!

  7. Looks very neat! Hopefully I can check it out this week. It’s about time I setup some stat gathering on my server and you’ve inspired me to do it :)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>