Firewall Builder: Convert Linux Iptables Configuration to OpenBSD and PF

Posted on in Categories Iptables, Linux, OpenBSD, Security last updated March 25, 2009

Lets see how much effort it is going to take to convert this configuration to entirely different firewall platform – PF on OpenBSD. There are different ways to do this. I could make a copy of each member firewall (linux-test-1 and linux-test-2), set platform and host OS in the copy to PF and OpenBSD and then create new cluster object. This would be a sensible way because it preserves old objects which helps to roll back in case something does not work out. However, to make the explanation shorter, I am going to make the changes in place by modifying existing objects.

I start with member firewalls. Open each one in the editor and change its name, platform and host OS as shown in Figure 26 for the first member:

Figure 26. Converting member firewall to PF/OpenBSD

Figure 26. Converting member firewall to PF/OpenBSD
Figure 26. Converting member firewall to PF/OpenBSD

Set version of PF to match version of your OpenBSD machine. Do the same change to the second member firewall, then check failover group of interface “eth0” of the cluster object:

Figure 27. Failover group indicates that the cluster configuration does not match members

Figure 27. Failover group indicates that the cluster configuration does not match members
Figure 27. Failover group indicates that the cluster configuration does not match members

Failover group declares status of both members “Invalid”, this is because the platform and host OS of members do not match configuration of the cluster object anymore. They should match exactly, so we have to reconfigure the cluster object to platform “PF” and host OS “OpenBSD” as well. This should fix the status of both members in the failover group dialog.

To switch to OpenBSD from Linux we need to change failover protocol from heartbeat to CARP as well. The protocol is configured in the failover group object. List of available protocols depends on the firewall platform chosen in the parent cluster object. While cluster was set up as “iptables”, possible choices of failover protocols were “heartbeat”, “VRRP”, “OpenAIS” and “None”. “CARP” was not in the list because it is not available on Linux. After the cluster is switched to “PF”, the list consists only of “CARP” and “None” as shown in Figure 28:

Figure 28. Failover protocol choices for PF/OpenBSD

Figure 28. Failover protocol choices for PF/OpenBSD
Figure 28. Failover protocol choices for PF/OpenBSDÂ

Firewall Builder can configure CARP interfaces on BSD. For that, it needs some parameters of the CARP protocol. You can configure these if you click “Edit protocol parameters” button in the failover group object dialog. This brings another dialog where you can configure CARP password, vhid and some other parameters:

Figure 29. CARP parameters

Figure 29. CARP parameters
Figure 29. CARP parametersÂ

Last thing we have to change is the names of interfaces. On OpenBSD loopback is “lo0” and ethernet interface can be for example “pcn0”. To rename interfaces find them in the tree, open in the editor and change the name. This needs to be done with interface objects of both member firewalls and the cluster. Significant difference between CARP protocol and heartbeat on Linux is that CARP creates its own network interfaces named “carpNN”. In Firewall Builder terms this means we need to name cluster interface object “carp0” (remmber that in case of Linux cluster, cluster interface name was the same as names of corresponding member firewalls). After all interfaces have been renamed, my final configuration looks like shown in Figure 30:


I also changed ip addresses of interfaces pcn0 of both member firewalls to avoid conflict with still running linux firewalls.

Figure 30. Final configuration for PF cluster

Figure 30. Final configuration for PF cluster
Figure 30. Final configuration for PF cluster

Now we can recompile the cluster again. For PF fwbuilder generates two files for each member firewall. One file has extension .conf and contains PF configuration. The other file has extension .fw and is an activation script.

Looking inside the generated .conf file, we see PF implementation of the same policy rules (this is just a fragment with first few rules):

# Tables: (2)
table <tbl.r0.d> { , , , }
table <tbl.r0.s> { , , , }

# # Rule -2 CARP (automatic)
pass quick on pcn0 inet proto carp from any to any label "RULE -2 -- ACCEPT "
# Rule backup ssh access rule
# backup ssh access rule
pass in quick inet proto tcp from to <tbl.r0.d> port 22 \
    flags any label "RULE -1 -- ACCEPT "
# Rule 0 (carp0)
block in log quick on pcn0 inet from <tbl.r0.s> to <tbl.r0.s> \
    no state label "RULE 0 -- DROP "
# Rule 1 (lo0)
pass quick on lo0 inet from any to any no state label "RULE 1 -- ACCEPT "

Figure 31. Example of a rule associated with a cluster interface

Figure 31. Example of a rule associated with a cluster interface
Figure 31. Example of a rule associated with a cluster interface

Look at the rule #0 in the screenshot Figure 19 (the anti-spoofing rule). The same rule is shown in Figure 31, except I removed label “outside” from the interface carp0 to make it clear which interface is placed in the “Interface” column of the rule.

This rule has interface object that belongs to the cluster in its “Interface” column. Firewall Builder GUI does not accept member firewall interface in this column. Only interfaces of the cluster are allowed in the “Interface” column of the rule set that belongs to the cluster. Interfaces of the Linux cluster have the same names as corresponding member firewall interfaces. In my example above member interfaces were “eth0” and cluster interface had the same name. This is because cluster interface object is an abstraction that serves several purposes: it is a place where failover protocol parameters are configured and also it represents member firewall interfaces in rules when the program compiles the policy and generates firewall script or configuration file. Cluster interface object will be replaced with interface of the member firewall for which the policy is being compiled. When fwbuilder compiles it for the member #1, it replaces cluster interface objects with interfaces of member #1. When it then compiles the same rules for member #2, it replaces cluster interfaces with interfaces of member #2.

This feels intuitive when we build Linux cluster because names of member interfaces and cluster interfaces are the same. When I use cluster interface “eth0” in the rule, it is essentially the same as using firewall’s interface with the same name (except it is not the same, internally) so it is the configuration I am used to when I start configuring clusters have spent some time working with regular firewalls in fwbuilder.

Interfaces of BSD cluster have names that directly correspond to the names of failover protocol interfaces carpNN which really exist on the firewall machine. The problem is that PF does not inspect packets on these interfaces and therefore PF rules should not be attached to these interfaces. Yet, fwbuilder uses BSD cluster interfaces carpNN in the same way as explained above. if you want to attach rules to particular interfaces using “on <intf>” clause, you need to use cluster interface object in the rules. In this case, just like when we were building Linux cluster, fwbuilder will replace carpNN with interfaces of member firewall that are configured in the failover group of the cluster interface.

I realize this can be counter-intuitive, especially to those who know all details of BSD cluster configuration by heart and are very used to working with CARP. We may be able to improve the model in future versions of fwbuilder if there is enough user demand.


In addition to rules for the failover protocol, Firewall Builder can automatically add rules to permit packets used by the state synchronization protocol. In case of PF this is pfsync. Protocol parameters are configured in the “State Sync Group” object that is located in the tree immediately under the cluster. Generated script can also configure pfsync interface and some parameters of the protocol.

The bottom part of the activation script is interesting. This is where CARP interface is configured and PF configuration is activated. Here is how this looks like:

configure_interfaces() {
    sync_carp_interfaces carp0
    $IFCONFIG carp0 vhid 100 pass secret    carpdev pcn0

    update_addresses_of_interface \
  "carp0" ""
    update_addresses_of_interface "lo0 ::1/128" ""
    update_addresses_of_interface "pcn0" ""

log "Activating firewall script generated Thu Mar 18 20:19:42 2010 by vadim"


$PFCTL   \
     -f \
    ${FWDIR}/bsd-test-1.conf || exit 1

Shell function “sync_carp_interfaces” is defined at the beginning of the same script, it compares list of carp interfaces defined in Firewall Builder with carp interfaces that really exist on the firewall machine. Interfaces that are missing are created and those that exist but are not defined in fwbuilder are deleted. If the set of carp interfaces matches those defined in fwbuilder, this function does nothing. Next, the script configured interface carp0 using parameters entered in the failover protocol dialog Figure 29 shown above. Calls to shell function “update_addresses_of_interface” update ip addresses of interfaces, including carp0. This function also does it incrementally by comparing required list of addresses with those that really are configured on the interface. If lists match, the function does not do anything, otherwise it adds or deletes addresses as appropriate.

Basically, you can start with OpenBSD or FreeBSD machine configured with one IP address on the interface that you can use to communicate with it. Script generated by fwbuilder will set up other addresses and failover protocol.

As you can see, conversion required few changes but not that much. I had to change firewall platform and host OS in member firewalls and cluster object, rename interfaces, possibly change IP addresses, change the name of the failover protocol and its parameters. Relationships between the cluster and member firewalls remained the same and so I did not have to add or remove firewalls to cluster failover group objects. Most importantly, I did not have to touch rules at all. Granted, this was very simple example and in more complicated cases some rules may need to be adjusted. Most often this is the case when original iptables policy used some modules and features unique to iptables. Most typical rules can be translated automatically with no change in the GUI.

About the author: This article seires is contributed by Vadim Kurland {vadim at fwbuilder DOT org}, the main author of Firewall Builder.

Posted by: Vivek Gite

The author is the creator of nixCraft and a seasoned sysadmin and a trainer for the Linux operating system/Unix shell scripting. He has worked with global clients and in various industries, including IT, education, defense and space research, and the nonprofit sector. Follow him on Twitter, Facebook, Google+.

2 comment

  1. Hallo,

    it is not so easy to migrate between PF@*bsd and iptables@linux. In your example, you have no firewall rules defined.

    The problem is the difference, how the system (say openbsd and linux) process the packets.

    In Linux (iptables) the packet must be accepted only in one of three chains: INPUT for packets comming to host; OUTPUT for packets going out from host and FORWARD for packets going through the host (from anything else to anything else).

    In OpenBSD (PF) the packet muss match on input (Inbound) for incoming packets and the packet muss match on output (outbound) for outgoing packets too. It means, packet going to host must match in inbound (pass in), packet going from host must match outbound (pass out) and packet going through must match both inbound and outbound (pass in and pass out or simply pass).

    For simple firewalls with two interfaces LAN and WAN and without too much control, you can write rule

    Src: A Dest: B Service: C Inbound/Outbound Accept – this rule need no change after conversion pf->iptables or iptables->pf, it looks nearly same in both systems.

    But if you want to define firewall rules on bigger FW (e.g. with 4 interfaces) with many rules and with better control, you need to define interfaces and then you need to define two rules:

    Src: A Dst: B Service: C Iface: D Inbound Accept
    Src: A Dst: B Service: C Iface: E Outbound Accept

    Using iptables one could write this rule simply iptables -A FORWARD -s A -d A –dport C -i D -o E -j ACCEPT, but fwbuilder support only one interface in rule. If I change the firewall type from pf@openbsd to iptables@linux, fwbuilder generates two rules:

    iptables -A FORWARD -s A -d B –dport C -i D -j ACCEPT
    iptables -A FORWARD -s A -d B –dport C -o E -j ACCEPT

    and the second rule is totaly useless, becase the packet is accepted in the first rule.

    Other example is from my habit. In the main table I create only “split” rules. I want to have order in my rules and I want separate rules for LAN incoming packets from DMZ incoming packets and WAN incoming packets. Therefore in iptables I create rules:

    Src: any Dst: any Service: Any Iface: LAN Inbound branch to PolicyInLAN
    Src: any Dst: any Service: Any Iface: WAN Inbound branch to PolicyInWAN
    Src: any Dst: any Service: Any Iface: DMZ Inbound branch to PolicyInDMZ

    then in every PolicyInXXX I can define other “split” rules depending on outgoing interface – I create following in PolicyInLAN:

    Src: any Dst: any Service: Any Iface: WAN Outbound branch to PolicyOutWAN
    Src: any Dst: any Service: Any Iface: DMZ Outbound branch to PolicyOutDMZ
    Src: any Dst: any Service: Any Iface: any Outbound branch to PolicyOutAll

    And then I can separate all rules. Because in iptables the Inbound and Outbound only means either which options of “-i” or “-o” will be used in FORWARD chain, or if the rule goes to INPUT or OUTPUT chain and the packet must be only once accepted, changing the iptables@linux firewall type to pf@openbsd also requires to remake rules totally, because the incoming packet never pass on “pass out” rule, which is created using Outbound direction.


    Robert Wolf

Leave a Comment