Skip to content

Setting up a new External to Internal IP mapping on OpenBSD

Fri Jul 27 2007

This is correct for version 3.9 of OpenBSD, but the rules are simple enough that the idea will upgrade if necessary later.

In order to complete this, you must have sudo privileges on the firewall at After you login, cd to /etc and sudo the su command to easily edit the files we need to touch:

$cd /etc
$ sudo su

Let's setup an address as an example. Currently, we have a dmz server called geoweb which is publicly addressed by the host

There is a DNS host record (A record) setup in our online services with cbeyond that associates the external IP

to this hostname, meaning when someone makes a request to, they will be pointed at the above IP address. What we need to do first is tell the external NIC card (xl0) to listen for this IP address along with any other public IP addresses we want to manage. This is done using ifconfig by invoking the alias command. To actually do this, we are going to type in the following command:

ifconfig xl0 alias netmask 0xffffffff

Here we are telling ifconfig to configure the interface named xl0 to listen for packets sent to this IP address. The netmask part is a hexidecimal representation of all full octets and is equivalent to This means that we will be broadcasting the exact same IP address as entered.

Now the running server knows that it is listening for this IP address. The firewall ruleset allows ping echorequests thru, so if we are at a remote location we can see this by pinging The problem is that we need to make sure the alias is set up even after a reboot. To do this we have to add an entry to the hostname.xl0 file. Using vi, we need to add an alias setting that will load when the os is booting.

#vi hostname.xl0
(to get to the end of the text, type shift-G. Open the next line for editing by typing o. So the commands will be:


Then add the following line to set it up:

alias netmask

Hit escape to stop editing and then :x to save your changes. Now we've associated the external IP address to the xl0 nic card. All traffic except ping should be blocked by default, so we have to make some changes to pf.conf to allow dmz traffic to flow to the internal IP when addressed by the external IP.

There are already rulesets for other IP addresses in pf.conf - just follow those as an example to allow dmz traffic. Start by adding a macro for both the internal and external IPs. We're going to name them according to the host, adding a prefix to distinguish between publicly addressable and private addresses. Add these under the other macro definitions:


Now we need to add a binat rule to link these addresses in the firewall. This only sets up the link, incoming packets will still have to pass the ruleset before being passed thru. Find the other binat rules and add one for the new host:

binat on $ext from $prv_geoweb to any -> $pa_geoweb

This tells the firewall that if it gets any packets that are going to or from, change their destination/source IP to the external one. The traffic still ends up at the internal IP, but not before passing the filter rules defined below the NAT rules. Pings will still work, but we need to add a filter rule to allow our desired dmz services thru. We need to add the following rule to the ruleset after the other rules that serve the same purpose for other IPs:

pass in on $ext inet proto tcp from any to $prv_geoweb port $dmz_services
flags S/SA synproxy state

Let's break this rule down. We're telling the firewall to pass or allow a packet to reach its destination (in this case $prv_geoweb or This is slightly confusing, since we really want traffic to go to the public  address, right? Well, in pf the address translation rules are run first, so by the time the packet reaches this rule, it really is heading to the internal address because of the binat rule we defined earlier. $ext is defined as a macro and represents the xl0 nic card. inet tells pf that we are only interested in IPv4 requests at this time. from any simply means that any address whatsoever can reach this IP (the whole point of a dmz). You would change this to a specific IP or IP range if you wanted to lock more people out of even dmz services, for example if we were hosting a client server to be managed by our firewall. to $prv_geoweb is as I stated earlier telling pf that the packet must be heading toward that IP. This allows us to customize the ports available if necessary. port $dmz_services says that the packet must be assigned to one of the ports defined in the $dmz_services macro defined at the top of pf.conf currently as ftp, www, https, and 8080. These abbreviations correspond to standard tcp ports where ftp=21, www=80, and https=443. Any packet that is not heading for one of these ports is dropped and forgotten. (power of the firewall, ya see?;) the slash is a line continuating character, so the next line is part of the same rule. flags S/SA restricts the types of tcp requests that are allowed to pass. They represent syn/syn+ack which does the basic tcp handshake required for network communication.

That's all that's required for the ruleset, so save the changes to the file. Now it's time to let pf know about its new config changes. We will do this using pfctl:

#pfctl -f pf.conf

This tells pf to load the ruleset contained in the pf.conf file. This is also what is called on startup, so we know we will have the same rules defined even after a reboot. If pfctl doesn't return anything then it worked. You can verify by calling

#pfctl -sa

which tells pf to show all of the info associated with it in the console. That's it! is now secured thru our OpenBSD packet filter firewall.

💾 May the source be with you. v3.2.419