Packet Filter
pf is included in the basic FreeBSD install for versions newer than 5.3 as a separate run time loadable module. Apart from pf there are a variety of firewalls available for FreeBSD. IPFW is a rather crude firewall, probably easiest to get to grips with for users migrating to FreeBSD from linux, as it does resemble iptables etc. IPFILTER was, until the port of pf, probably the best in terms of stateful packet filtering, but development of this has lagged of late. There is no doubt that pf is the slickest firewall available on any OS and has a wide array of configuration options and tricks available.
Simply add
pf_enable="YES"
to /etc/rc.conf, reboot, and it's there. Easy as it is to lock yourself out while testing your configuration, add
*/10 * * * * /sbin/pfctl -d
to root's crontab, to clear any rules every 10 minutes. You can delete this later when you're done. Until you've updated your /etc/rc.conf as above and rebooted, this will continually barf about /dev/pf not being found, but this is not a problem.
Most example configurations are for a firewall on one server, protecting a server (or servers) behind it. In this case we'll have one standalone server. Getting down to details, edit /etc/rc.conf and make sure it has these six lines:
pf_enable="YES"
pf_rules="/etc/pf.rules"
pf_program="/sbin/pfctl"
pflog_enable="YES"
pflog_logfile="/var/log/pflog"
pflog_program="/sbin/pflogd"
As always, when creating or installing anything which write to a new log file, take care to consider rotating this, otherwise someday you'll run out of disk space! More on this later.
On this server the external interface is fxp0, and the loopback lo0, which gives the two lines (we'll add all these to /etc/pf.rules later):
IF="fxp0"
LOCAL_IF="lo0"
I will allow traffic on the following ports:
tcp_ports="{ 25, 53, 80, 110, 113, 143, 443, 465 }"
udp_ports="{ 53, 113, 143 }"
I'll deal with ssh (port 22) and connection to DirectAdmin (port 2222) separately.
Now to define some IP ranges. My ISP's range will be the trusted ones for the trusted ports above. I only want to allow in traffic destined for my own IPs, and also I'll block traffic for any reserved or private IP space. Using dummy values for all these, I get the three macros:
TRUSTED="{ 1.2.3.4/16, 2.3.4.5/16 }"
SERVER="{ 10.11.12.13/27 }"
NO_ROUTE="{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"
Note that I have 32 usable IPs on this server, hence the /27 in the SERVER macro above. Actually there are two more, one used by NAC and one reserved for DirectAdmin's licence, but I've ignored these in the macro. The NO_ROUTE IP values are the real ones I use, not dummy ones. If there are any IPs you wish to block further, you can define them by adding the line:
table <badguys> persist file "/etc/pf.blocklist.txt"
and you'll need to create the file /etc/pf.blocklist.txt and put your list of banned IPs in it. We follow all this by our filtering options, and then our actual rules.
Our /etc/pf.rules should then look like:
#----
# Macros
IF="fxp0"
LOCAL_IF="lo0"
TRUSTED="{ 1.2.3.4/16, 2.3.4.5/16 }"
SERVER="{ 10.11.12.13/27 }"
NO_ROUTE="{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }"
tcp_ports="{ 25, 53, 80, 110, 113, 143, 443, 465 }"
udp_ports="{ 53, 113, 143 }"
# Tables
table <badguys> persist file "/etc/pf.blocklist.txt"
# Options
set optimization normal
set block-policy drop
set require-order yes
# Traffic Normalization
scrub in all
# Packet Filtering
block in log all
block out log all
# Drop our 'badguys' 'quick' with no reply or logging.
block in quick on $IF from <badguys> to any
antispoof log quick for $LOCAL_IF inet
pass in on $LOCAL_IF inet all keep state
pass out on $LOCAL_IF inet all keep state
antispoof log quick for $IF inet
block in log quick on $IF from $NO_ROUTE to $IF
block return-rst in log quick on $IF inet proto tcp from any to $SERVER port 113
# trusted-only traffic
pass in on $IF inet proto tcp from $TRUSTED to $SERVER port ssh flags S/SA modulate state
pass in on $IF inet proto tcp from $TRUSTED to $SERVER port 2222 flags S/SA modulate state
# allowed udp,icmp traffic
pass in on $IF inet proto udp from any to $SERVER port $udp_ports keep state
pass in on $IF inet proto icmp from any to $SERVER icmp-type 8 code 0 keep state
# allowed tcp traffic
pass in on $IF inet proto tcp from any to $SERVER port $tcp_ports flags S/SA modulate state
# Pass FTP
pass in on $IF proto tcp from $TRUSTED to any port 21 flags S/SA keep state
pass in on $IF proto tcp from $TRUSTED to any port 20 flags S/SA keep state
pass in on $IF proto tcp from any to any port > 49151 keep state
pass out on $IF proto tcp from $SERVER to any port 21 flags S/SA keep state
# Pass out rule allowing all with modulate state
pass out on $IF proto tcp all modulate state flags S/SA
# Pass out rules for UDP, ICMP
pass out on $IF proto { udp, icmp } all keep state
block out log quick on $IF from $IF to $NO_ROUTE
pass out on $IF inet from $IF to any keep state
#----
Save this, reboot, and you're done. If you wish to allow ssh, ftp or port 2222 access from any IP address, simple change $TRUSTED to any, in the rules above. You may need this if you don't know your ISP's full allocation of IP addresses anyway.
If you get any warnings about ALTQ not being enabled (we're not using ALTQ here) then simply edit the kernel config - note: never edit the GENERIC kernel, always make a copy and work on that, i.e.
cp /usr/src/sys/i386/conf/GENERIC /usr/src/sys/i386/conf/MYKERNEL
and then edit MYKERNEL, adding
options ALTQ
at the start of the options list already there, and rebuild, reinstall the kernel. All you require to do this is:
cd /usr/src
make buildkernel kernconf=MYKERNEL
make installkernel kernconf=MYKERNEL
reboot
Read the docs to get more info on pf and ALTQ
man pf.conf
man pfctl

