Loopback NAT with pf
A useful trick with hosting servers via ADSL connections at home is to provide something called NAT loopback. In this text, I will be giving an example setup using OpenBSD’s pf tool. Imagine the following:
You have a development server hosted via an ADSL line, behind a NAT router/firewall. You would like this development server to be visible to others via the Internet, and you have assigned a name in DNS which maps to one of the externally visible IP addressed assigned to you via your ADSL hosting company. You then set up a virtual host on the development webserver so that it responds to the DNS name you’ve assigned it.
A problem arises when you need to talk to the machine locally on your network, using the DNS name you’ve given it. Without any special rules, this will not work due to the following series of events:
- You generate a request to your webserver’s external IP address, which is bound to your NAT box’s external interface
- Your request hits the NAT box, and the destination IP is rewritten as the local address, and is forwarded on
- The request hits the webserver, but the source address is on the local LAN. The webserver replies directly to your machine
- Your computer receives the reply, but the source IP address is the local address, not the remote address, so the data is discarded
What is required within the pf rules is a redirect that takes traffic bound for the external IP address of the webserver, and rewrites the source address, so the reply gets sent back to the NAT box, and in turn gets rewritten and redirected back to the originating host.
# DEFINES ##########
# network interfaces
internal_if = "sip0"
external_if = "sip1"
# NAT box
gw = "192.0.2.1"
# webserver
www_int_ip = "10.0.0.2"
www_ext_ip = "192.0.2.2"
# ports to be redirected
www_ports_tcp = " {80} "
# RULES ##########
# define our general NAT
nat on $external_if inet from $internal_if:network to any -> $gw
# define our external sources to the webserver
rdr on $external_if inet proto tcp from any to $www_ext_ip
port $www_ports_tcp -> $www_int_ip
# for local requests, rewrite the destination as the local
# IP, rather than the remote one
rdr on $int_if inet proto tcp from $int_if:network to $www_ext_ip
port $www_ports_tcp -> $www_int_ip
# don't NAT other traffic
no nat on $int_if proto tcp from $int_if
to $int_if:network
# keep state on traffic going to the webserver's internal IP address
nat on $int_if proto tcp from $int_if:network to $www_int_ip
port $www_ports_tcp -> $int_if
More information can be found in the pf FAQ. Thanks go to Jasper Wallace for originally writing the pf rules for the setup in our flat.