Puffy Logo Packet Filter was developed for OpenBSD, but has been ported to many other operating systems. The filtering syntax is similar to IPFilter, with some modifications to make it clearer. Network Address Translation (NAT) and Quality of Service (QoS) have been integrated into PF, QoS by importing the ALTQ queuing software and linking it with PF’s configuration. Features such as pfsync and CARP for failover and redundancy, authpf for session authentication, and ftp-proxy to ease firewalling the difficult FTP protocol, have also extended PF. Also PF supports SMP (Symmetric multiprocessing) & STO (Stateful Tracking Options).

One of the many innovative features of Packet Filter is it’s logging. PF’s logging is configurable per rule within the pf.conf and logs are provided from PF by a pseudo-network interface called pflog, which is the only way to lift data from kernel-level mode for user-level programs. Logs may be monitored using standard utilities such as tcpdump, which in OpenBSD has been extended especially for the purpose, or saved to disk in the tcpdump/pcap binary format using the pflogd daemon.

In this post, I will create a pf.conf that will control access for the VLANs that I have setup in the previous posts and also allow internet access via NAT translation. This will be a basic example, so for further reading, I highly recommend reading Peter N. M. Hansteen’s Firewalling with OpenBSD’s PF packet filter Manual.

Below is an explanation of a basic pf.conf file that can be easily expanded for your own useage.. I will explain the various sections to give a fuller understanding of the file.

General PF Housekeeping

  • I use a block-policy of drop. This means the packet is dropped silently. A more friendly way is to use the return option, this returns a RST packet for blocked TCP packets and an ICMP Unreachable packet for all other types of packets. You can read more about the block-policy option here.

  • To log I use the external interface. The logging data is stored as binary data and can be read with tcpdump. Again, more info is available here

  • I choose to skip all processing on the loopback interface where filtering, normalization, queueing, etc, are not required. More information on the skip option is available in the OpenBSD PF Faqs: Runtime Options page.

  • In my networks and when I venture out onto the internet, I have found setting the MTU packet size of 1440 works well. In fact, I have never had any problems with this MTU packet size so I match all packets and scrub these to set the default MTU packet size as 1440. More on MTU and scrubbing here.

  • More than likely, you have private IP address space behind your firewall, so you will need to NAT as your packets travel from your internal interfaces out to the world wide web. This is set with the nat-to directive. NAT is further explained in the A simple gateway, NAT if you need it - Setting Up section of the “Firewalling with OpenBSD’s PF Packet Filter” book.

  • Use antispoof to keep out the freaks, antispoof is a common special case of filtering and blocking. This mechanism protects against activity from spoofed or forged IP addresses, mainly by blocking packets appearing on interfaces and in directions which are logically not possible. More information is here… Straight from the horses mouth.

Note: This following section (which I have detailed above) is further down in the pf.conf file, however I felt it was more important to discuss first as it is more of general settings for PF.

set block-policy drop
set loginterface egress
set skip on lo0

match in all scrub (no-df random-id max-mss 1440)
match out on egress inet from !(egress:network) to any nat-to (egress:0)

antispoof quick for { egress $int_if }

PF Macros

I use PF macros to make the configuration file easier to understand. The macros below are self explanatory, however, if you wish, you can read more about macros here. Of note, PF includes an inbuilt macro named egress. This egress group macro is determined by the interface(s) that holds the default route(s).

#hosts
administrator = "192.168.1.111"
samba_server = "192.168.3.10"
brother_printer = "192.168.3.250"

#networks
public_network = "192.168.2.0/24"
servers_network = "192.168.3.0/24"
un-used_network = "192.168.4.0/24"

#interfaces
int_if = "re0"
ext_if = "re1"

Martians Table

The Martians Table is set with the purpose of denying traffic with non-routeable addresses out to the Internet. It is also useful for blocking non-routeable addresses from entering your network. You can read about the martians table in more detail here.

table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16     \
                   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3 \
                   192.168.0.0/16 198.18.0.0/15 198.51.100.0/24        \
                   203.0.113.0/24 }

Blocking Traffic

With PF, the last matching rule in the rule set is the one which is applied. This means we should block everything and then choose what we want to allow later on. With this in mind, we first start blocking everything in the martians table. We also set the quick keyword on the martians table which means that these packets will be dropped immediately and will not be considered for clearing later down in the configuration file. Next we make a blanket block on all packets, which we will later allow access to those that we wish to pass.

block in quick on { egress } from <martians> to any
block return out quick on { egress }  from any to <martians>

block all

Allowing Traffic

Here we choose which traffic we allow to pass.

  • First we want to allow traffic out to the internet.

  • Secondly, we allow the administrators workstation (as defined in the host macros), access to all networks.

  • Finally, we allow users of the public network access to the samba server and printer in the servers network.

All other packets will continue to be blocked.

# the below allows traffic to pass out of PF via specific interfaces
# here is where to limit what is allowed "out" into VLAN networks

    # pass out to internet
    pass out on { egress } inet

    # allow administrator workstation access to all VLANs
    pass out on vlan2 inet from $administrator to any
    pass out on vlan3 inet from $administrator to any
    pass out on vlan4 inet from $administrator to any

    # allow public network access to samba server
    pass out on vlan3 inet from $public_network to $samba_server

    # allow public network access to printer
    pass out on vlan3 inet from $public_network to $brother_printer

Allow All ICMP Traffic to Troubleshoot Networks

    # allow all ping traffic
    pass quick inet proto icmp all   

Allow Traffic Into PF from Internal Networks

Above we have allowed what traffic is allowed out of the PF firewall. We also need to allow traffic into the PF firewall before it can be traversed over the network. The following allows all traffic from the internal networks into the PF firewall.

# the below allows traffic from attached network into PF via listed interface

    #allow traffic to flow from VLANs into PF
    pass in on $int_if inet
    pass in on vlan2 inet
    pass in on vlan3 inet
    pass in on vlan4 inet

Before testiong, don’t forget to remove the PF=NO directive from the /etc/rc.conf.local file.

Working PF.conf File

Following is the complete pf.conf file that can be loaded with pfctl.

# file location and name: /etc/pf.conf

#hosts
administrator = "192.168.1.111"
samba_server = "192.168.3.10"
brother_printer = "192.168.3.250"

#networks
public_network = "192.168.2.0/24"
servers_network = "192.168.3.0/24"
un-used_network = "192.168.4.0/24"

#interfaces
int_if = "re0"
ext_if = "re1"

table <martians> { 0.0.0.0/8 10.0.0.0/8 127.0.0.0/8 169.254.0.0/16     \
                   172.16.0.0/12 192.0.0.0/24 192.0.2.0/24 224.0.0.0/3 \
                   192.168.0.0/16 198.18.0.0/15 198.51.100.0/24        \
                   203.0.113.0/24 }

set block-policy drop
set loginterface egress
set skip on lo0

# The egress group is determined by the interface(s) that holds the 
# default route(s).

match in all scrub (no-df random-id max-mss 1440)
match out on egress inet from !(egress:network) to any nat-to (egress:0)
antispoof quick for { egress $int_if }

block in quick on { egress } from <martians> to any
block return out quick on { egress }  from any to <martians>

block all

# the below allows traffic to pass out of PF via specific interfaces
# here is where to limit what is allowed "out" into VLAN networks

    # pass out to internet
    pass out on { egress } inet

    # allow administrator workstation access to all VLANs
    pass out on vlan2 inet from $administrator to any
    pass out on vlan3 inet from $administrator to any
    pass out on vlan4 inet from $administrator to any

    # allow public network access to samba server
    pass out on vlan3 inet from $public_network to $samba_server

    # allow public network access to printer
    pass out on vlan3 inet from $public_network to $brother_printer

    # allow all ping traffic
    pass quick inet proto icmp all   

# the below allows traffic from attached network into PF via listed interface

    #allow traffic to flow from VLANs into PF
    pass in on $int_if inet
    pass in on vlan2 inet
    pass in on vlan3 inet
    pass in on vlan4 inet

Wrapping up

That is all that we need to test our firewall with our HP Switch. I hope this demonstrates where rules need to be added to expand the configuration for your own purposes.