NAME
Net::Firewall::BlockerHelper::backends::pf - pf backend for Net::Firewall::BlockerHelper.
VERSION
Version 0.1.0
SYNOPSIS
The rules and table this backend creates all live under the anchor <prefix>/<name>, which is what allows them to be managed dynamically without ever touching the main ruleset. pf will only evaluate the anchor if pf.conf contains an anchor rule for it though, so a bit of one time setup is required. With the default prefix of kur that means adding the like of the following and reloading pf.conf.
anchor "kur/*"
The rules loaded into the anchor are block drop quick rules, so they will win out over any non-quick pass rules regardless of placement, but the anchor line does need to come before any quick pass rules that would otherwise match the traffic in question.
use Net::Firewall::BlockerHelper::backends::pf;
my $backend1;
my $backend2;
eval {
$backend1 = Net::Firewall::BlockerHelper::backends::pf->new(
backend => 'pf',
name => 'all',
options=>{ kill=>1 },
);
$backend2 = Net::Firewall::BlockerHelper::backends::pf->new(
backend => 'pf',
ports => ['143'],
protocols => ['tcp'],
name => 'imap',
options=>{ kill=>0 },
);
};
if ($@) {
print 'Error: '
. $Error::Helper::error
. "\nError String: "
. $Error::Helper::errorString
. "\nError Flag: "
. $Error::Helper::errorFlag . "\n";
}
print `pfctl -sr`
$backend1->init;
$backend2->init;
print `pfctl -sr`
$backend1->ban(ban=>'1.2.3.4');
$backend1->ban(ban=>'4.3.2.1');
$backend2->ban(ban=>'4.3.2.1');
use Data::Dumper;
print Dumper($backend1->list);
print Dumper($backend2->list);
$backend1->unban(ban=>'4.3.2.1');
use Data::Dumper;
print Dumper($backend1->list);
print Dumper($backend2->list);
$backend1->teardown;
print `pfctl -sr`
$backend2->teardown;
print `pfctl -sr`
METHODS
new
Initiates the the object.
- options :: Backend specific options that will be passed to the backend unchecked
outside of making sure it is a hash ref if defined. See below for furhter info.
- Default :: {}
- ports :: A array of ports to block. Checked to make sure they are positive ints or a valid
service name via getservbyname. All ports will be blocked if non are specified. If
duplicates are removed.
- Default :: []
- protocols :: A array of protocols to block. This is checked against
/etc/protocols via the function getprotobyname. Duplicates will be
discarded. If no protocols are given, tcp, udp, icmp, and icmp6 are
blocked, unless ports are given, in which case it defaults to tcp
and udp. Ports are only attached to port-capable protocols
(tcp/udp/sctp); other protocols are blocked without a port.
- Default :: ['tcp','udp','icmp','icmp6'], or ['tcp','udp'] when ports are given
- prefix :: Prefix to use. Must match the regex /^[a-zA-Z0-9]+$/
- default :: kur
- name :: Name of this specific instance. This must be specified.
- default :: undef
The options hash accepts the following.
- kill :: If it should kill states for the banned IP or not. Handles
both IPv4 and IPv6 and is scoped to what is being blocked. With
protocols and/or ports configured the state table is searched and
matching states killed by ID, filtered to the blocked protocols
and ports (pf keeps state for UDP as well, so blocking only udp
kills only udp states and leaves tcp alone); the state matching
follows the family of the banned IP as pf prints IPv4 states as
addr:port and IPv6 ones as addr[port]. With nothing configured
everything is being blocked and pfctl -k is used, killing all
states for the IP.
- Default :: 0
All errors are considered fatal, meaning if new fails it will die.
my $backend;
eval {
$backend = Net::Firewall::BlockerHelper::backends::pf->new(
backend => 'pf',
ports => ['22'],
protocols => ['tcp'],
name => 'ssh',
);
};
if ($@) {
print 'Error: '
. $Error::Helper::error
. "\nError String: "
. $Error::Helper::errorString
. "\nError Flag: "
. $Error::Helper::errorFlag . "\n";
}
init
Initiates the backend. Best effort cleanup commands are run first, flushing and killing the table and flushing the anchor rules, so remnants of a previous run do not linger. Then the table <prefix>_<name> is created (persist, with counters) and block drop quick rules for the configured protocols and ports are loaded into the anchor <prefix>/<name> via pfctl -f-.
Note that pf.conf must contain a matching anchor rule for any of this to have an effect. See "SYNOPSIS".
No arguments are taken.
May called a second time, it will error.
$backend->init;
ban
Bans an IP. The value of ban is validated as being a IPv4 or IPv6 address and lowercased, then added to the pf table via pfctl -a <prefix>/<name> -t <prefix>_<name> -T add. If the option kill is true, states for the IP are killed as well, scoped to the configured protocols and ports. Banning an already banned IP is a noop.
$backend->ban(ban => $ip);
unban
Unbans an IP. The value of ban is validated as being a IPv4 or IPv6 address and lowercased, then removed from the pf table via pfctl -a <prefix>/<name> -t <prefix>_<name> -T delete. Unbanning an IP that is not banned is a noop.
$backend->unban(ban => $ip);
ban_cidr
Bans a CIDR range by adding it to the pf table via pfctl -T add. pf tables accept a network prefix in the same manner as a single address. The value of ban is validated as being a IPv4 or IPv6 CIDR range and lowercased. Banning an already banned range is a noop.
$backend->ban_cidr(ban => '1.2.3.0/24');
unban_cidr
Unbans a CIDR range by deleting it from the pf table via pfctl -T delete. The value of ban is validated as being a IPv4 or IPv6 CIDR range and lowercased. Unbanning a range that is not banned is a noop.
$backend->unban_cidr(ban => '1.2.3.0/24');
list_cidr
List banned CIDR ranges. Returns an array of the currently banned CIDR ranges. Single IPs are not included; for those see "list".
my @banned_cidrs = $backend->list_cidr;
list
List banned IPs. Returns an array of the currently banned single IPs. CIDR ranges are not included; for those see "list_cidr".
my @banned = $backend->list;
re_init
Tells the backend to re-init it's self.
This will call teardown and init again. After that it will re-added all previously added bans.
Will error if already inited.
$backend->re_init;
teardown
Tears down the setup for the backend.
This flushes and kills the pf table and flushes the rules from the anchor, removing both the table and the block rules.
If called prior to calling init, this will error. It won't check if it has been inited or not.
$backend->teardown;
stop
Alias for "teardown", provided for parity with the fail2ban actionstop concept.
$backend->stop;
check
Verifies that the pf table is still present under the anchor and that the anchor still contains the block rules. Returns a true value if the setup is intact and a false value if any part of it appears to have been removed. This is the equivalent of fail2ban's actioncheck.
if ( !$backend->check ) {
$backend->re_init;
}
flush
Removes all currently banned IPs at once by flushing the pf table, leaving the table and rules in place. This is the equivalent of fail2ban's actionflush.
$backend->flush;
ERROR CODES / FLAGS
Error handling is provided by Error::Helper. All errors are considered fatal.
1, notInited
The backend has not been inited yet.
2, invalidPortSpecified
Port is either not an int within the range 1 to 65535 or a name that can be resolved by getservbyname.
3, portsNotArray
The data passed to new for ports is not an array.
4, protocolsNotArray
The data passed to new for protocols is not an array.
5, invalidPortSpecified
Port is either not an int within the range 1 to 65535 or a name that can be resolved by getservbyname.
6, invalidPrefixSpecified
The specified prefix did not match /^[a-zA-Z0-9]+$/.
7, invalidName
The name is either undef or does not match /^[a-zA-Z0-9\-]+$/.
8, optionsNotHash
The item passed to new for options is not a hash.
9, noBanItem
No IP or CIDR range specified to ban or unban.
10, banItemNotIP
The item to ban is not an IP. Either wrong ref type or regexp test using Regexp::IPv4 and Regexp::IPv6 failed.
11, invalidBackend
The specified backend failed to pass a basic sanity check of making sure it matches the regexp /^[a-zA-Z0-9\_]+$/.
12, backendInitError
Failed to init the backend.
13, banFailed
Failed to ban the item.
14, unbanFailed
Failed to unban the item.
15, listFailed
Failed to get a list of bans.
16, reInitFailed
Failed to re_init the backend.
17, teardownFailed
Failed to teardown the backend.
18, alreadyInited
init called, but the backend has already been inited.
19, nameTooLong
The combined prefix and name is longer than 31 characters, the max pf table name length.
23, initFailed
One of the required commands for init failed.
24, checkFailed
The backend check raised an error.
25, flushFailed
Failed to flush the bans.
26, banCidrFailed
Failed to ban the CIDR range.
27, unbanCidrFailed
Failed to unban the CIDR range.
28, cidrItemNotCidr
The item to ban is not a CIDR range. Either wrong ref type or it is not an IPv4 or IPv6 address followed by a prefix length valid for its family.
29, cidrNotSupported
The backend does not support CIDR bans.
30, listCidrFailed
Failed to get a list of CIDR bans.
AUTHOR
Zane C. Bowers-Hadley, <vvelox at vvelox.ent>
BUGS
Please report any bugs or feature requests to bug-net-firewall-blockerhelper at rt.cpan.org, or through the web interface at https://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-Firewall-BlockerHelper. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
SUPPORT
You can find documentation for this module with the perldoc command.
perldoc Net::Firewall::BlockerHelper
You can also look for information at:
RT: CPAN's request tracker (report bugs here)
https://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-Firewall-BlockerHelper
Search CPAN
ACKNOWLEDGEMENTS
LICENSE AND COPYRIGHT
This software is Copyright (c) 2023 by Zane C. Bowers-Hadley.
This is free software, licensed under:
The GNU Lesser General Public License, Version 2.1, February 1999