1-to-1 NAT + firewall with GNU/Linux

Greg Forte1) - UD LUG presentation - 4/29/2003

Overview of NAT

NAT stands for Network Address Translation, a feature of netfilter (and many other firewall or “network connection sharing” programs/products) that substitutes IP addresses and port numbers in packets flowing through the NAT server according to some prescribed formula(s). The most common use of NAT is to share one publically accessible address (say, the IP address of your DSL router or cable modem) amongst several computers, allowing multiple machines to have internet access while using only one public IP address. This is not only valuable in situations where only one public IP address is available (as in the home DSL or cable modem example), but also in preserving existing IPv4 address space by using a relatively small number of public IP addresses for a large number of machines.

The NAT configuration described above has a major drawback - since all communications between the NATted machines and the Internet appear to belong to the same IP address (the address of the NAT server as observed by any machine “outside” the NAT server), the NAT server must also translate port numbers in order to ensure that communications belonging to two different NATted machines don't get confused. In the most basic NAT setup, port numbers are assigned semi-randomly with no guarantees that the port number will not be changed, so the NATted machines in such a setup can only act as clients, they cannot offer any services to the outside world. Many NAT implementations have provisions to allow a particular port number to be mapped permanently to a static port number on a particular NATted machine, allowing you to run, e.g., a webserver on one of the NATted machines. This is great if you only want to run one webserver … but with only one public IP address it is not possible to run more than one of any given service on the NATted machines on that service's normal port (e.g. 80 for http). Of course, you can always run your services on a non-standard port, but that brings up a host of other problems, most of them human rather than technical. There are also other advanced mechanisms that can be used in some instances (e.g. virtual servers and request forwarding in apache) to handle this problem, but these are service and server dependent, and at any rate are beyond the scope of this mini-tutorial.

So what _is_ 1-to-1 NAT?

The problems with “traditional” NAT stem mainly from the one-to-many aspect: more than one private IP address contending for port space on a single public ip address. If you have the public IP addresses to “waste”, then a variety of translation schemes to bypass this problem become possible. One possibility is to use one (or more) public addresses for any number of machines that will offer services to the outside world (the minimum number of public addresses necessary would be equal to the maximum number of instances of the same service you wanted to run on different machines, in general), and (perhaps) a separate public ip address to serve all of the client-only machines. Taking this scheme to its logical extreme, you could simply use one public IP address for each internal machine. This is the scheme that I ended up implementing. This might seem particularly pointless at first glance - why bother NATting at all if you're just going to do a 1-to-1 substitution of public IP address for private IP address? Read on. I was motivated by a desire to setup a firewall similar in spirit to the bridging firewall that I spoke about last year:

  • I had several machines I wanted to firewall, each with its own public IP address and registered hostname already assigned. I wanted to keep the same IP addresses and hostnames, for convenience and also to avoid limiting my options for future reconfiguration/expansion of those machines (most of them are testing boxes, so the services they run tends to change fairly frequently).
  • In the case of the bridge, there was an implicit trust relationship among the machines “behind” the bridge. They chattered amongst themselves constantly, and I didn't want to monitor or limit that chatter, only communications between them and the outside world. Thus a “normal” one-to-many NAT setup would probably have been just as effective, especially since there weren't multiple instances of any services to worry about. In this case I had just the opposite scenario: I didn't want implicit trust relationships between any of the machines, and I didn't want to have to worry about services clashing. The “no implicit trust” issue is where the 1-to-1 NAT becomes useful - by placing each machine in its own address space (DMZ) so that they cannot communicate directly with each other (bearing in mind that they are all still plugged into the same physical switch), I force all communications between them to go via the NAT firewall so that there can only be explicit trust relationships (i.e. whatever rules I set up in the firewall).

why use GNU/Linux to build a 1-to-1 NAT firewall?

Silly question, this is a Linux users group, after all. ;-)

So, how do I do this?

  1. Find hardware to run it on. In the bridging firewall talk I said “any old hardware (almost)” would do, and mentioned that I was able to achieve full 100MBps throughput on a 300MHz Pentium-II with a little CPU to spare. In this case I used a 333 Mhz PII, but I haven't done much in the way of load testing, mostly because none of the machines involved here provide any critical services or generally use a lot of bandwidth. NAT rules do tend to use more CPU, so something slightly heavier duty might be desirable (say, a 450 or 500 Mhz Pentium III). I still use the two-nic configuration. This is not strictly necessary but makes it somewhat less confusing to write the firewall rules.
  2. Install your favorite distro and reconfigure your kernel (if necessary). If you are using a distribution that already provides you with a late model 2.4.x kernel, then odds are you won't need to mess with the kernel at all. If you do need to compile a kernel, you'll just want to turn on “Network packet filtering” (under “Networking options”) and enable most (all?) options under “IP: Netfilter Configuration”, probably as modules. At the very least you'll want “Full NAT”.
  3. Set up your firewall rules. Wasn't that easy? One of the reasons I like this setup is because it involves fewer “pieces” than the bridging firewall. The firewall rules tend to be a little more complex than they were on the bridge, however.
    • Here is a sample script taken from a box configured as described above (2 interfaces, one “external” and one “internal”).
    • This is not a tutorial on netfilter, and the script is fairly well commented. A few salient points:
    • The first thing the script does is configure the DMZ addresses and routes. This could just as easily be done elsewhere, but since the firewall is acting as the gateway for each DMZ network, it makes sense to control it from here. Note that I've used a “throwaway” address on the internal interface (192.168.0.1) which isn't used for anything, but it lets the network scripts bring up the internal interface normally, so that the firewall script doesn't have to worry about it. This makes the internal and external alias setup symmetric. Note also that the firewall's public interface is now answering for all of the public IP addresses “belonging” to the other machines, i.e. in arpwatch all of those IP addresses will show up as belonging to the the MAC address of the firewall's public interface, even though they effectively still refer to the NATted boxes. This is the biggest functional difference between this setup and the bridge/firewall. Of course, that would be arpwatch from a machine outside the firewall (or the firewall itself) - on the NATted hosts all you'll see in arpwatch is the firewall's internal interface and NATted hosts.
    • Make sure any necessary kernel modules are loaded, and hit settings in /proc (at a minimum, /proc/sys/net/ipv4/ip_forward has to be set to “1”, otherwise your firewall won't forward any packets).
    • Clear all chains and set the default policies very strict (drop everything) before setting up any rules.
    • Most of the rest is pretty self-explanatory.
  4. Reconfigure the to-be-NATted hosts network settings. Generally all this requires is changing the /etc/hosts file and the interface configuration file(s). This is one of the downsides of this approach, but the changes are minimal and easy to make.
  5. One last item you might want to address: hostname resolution works normally with no changes to any of the systems - except between the firewall machine and the NATted machines. There are several workarounds. One solution for the firewall-to-NATted-host direction is to use the OUTPUT chain of the NAT table to set up DNAT rules that parallel the ones in the nat PREROUTING chain. Alternately, a relatively simple DNS server using a “fake” domain (e.g. “dmz.net”) can handle this direction, and could probably be used for the other direction as well (using a different “fake” domain for each DMZ, and inserting that domain first in the NATted hosts search order). A simpler solution for the NATted-host-to-firewall direction is to add an entry in /etc/hosts on each NATted machine for the firewall machine (using the appropriate DMZ address). This works in the reverse direction, as well, so it's mostly a case of personal preference. Of course, the internal IP addresses can always be used directly, but this is probably not the best solution ;-)

Other resources:

  • Oskar Andreasson's iptables tutorial (this thing seems to jump around a lot, try Googling “iptables-tutorial” if the server doesn't respond).
  • That's it, really.
1) Greg Forte - University of Delaware - 2003
gforte _at_ udel _dot_ edu
tux