Note

This guide assumes you're running FreeBSD 4.x with the ports collection installed, and that you have root access on the machine. All commands should be executed as root and all files that you'll need to edit are usually only editable by root. A # sign before a command indicates it must be run as root.

Introduction

NAT (Network Address Translation) is solution for software routing of IPv4 and ICMPv4 packets, funneling all the hosts on a local network using reserved internal IPs into a single globally routable IP address. In simpler terms, this means providing an internet connection to a local network through your FreeBSD box.

Why might you want to do this? You may be on a broadband internet connection which provides you with only one globally routable IP address (i.e., connecting a second machine is futile because you have no IP address and/or cannot obtain one from the provider). Even if your internet provider does allow you multiple IP addresses, you may want to have your internal machines (which could be running Windows or other OSes less secure than FreeBSD) unreachable to the internet at large for security reasons. Your provider may give you IP addresses on different subnets, making FTP or SSH between your machines inefficient on account of going through the provider's router instead of over your local network.

The second question is, why would you use FreeBSD for this when many fairly cheap hardware solutions exist, such as those from Linksys or Netgear? Using an embedded router and DHCP server like those prevents you from doing anything useful with the external IP address. If you wanted to run a web or email server on this IP address, you're out of luck. The good news is that running a NAT router on a FreeBSD box doesn't prevent it from doing other useful tasks at all.

So now that we have that out of the way, here's a fairly easy step by step guide to setting up NAT on FreeBSD. This guide assumes some familiarity with FreeBSD, but you needn't be a wizard at Unix or networking to get it working.

What you will need

First of all, you'll need a machine running FreeBSD with two network cards in it (ethernet). Unfortunately, this is pretty much non-negotiable. I recommend 3Com Etherlink XL ("xl" driver) or Intel Etherexpress Pro ("fxp" driver) PCI cards. The latter are especially good if you really need high transfer rates under 100BaseTX.

Secondly, you're going to need to have your network physically connected like this:


IntHost1 \
          \
IntHost2 -- Hub/Switch/WirelessAP --- (If1)-FreeBSD-(If0) --- Internet(CableModem/DSL/etc.) 
          /
IntHost3 /

Note that this is a very abstract diagram. Beyond just being connected to one of the network interfaces on the FreeBSD box, it doesn't matter how the internal network is set up. You could have a number of hubs or switches connected. You could even connect a wireless access point (not a router - the FreeBSD box is the router!) to it and have all your internal machines be wi-fi equipped laptops. It's up to you! Just make sure the FreeBSD box is connected to a normal port on the hub or switch, not the uplink. The second network interface on the FreeBSD box must be connected to your ISP - your cable modem, DSL modem, ethernet wall jack, wherever you get outside internet connectivity from.

In the diagram above, we have If0 and If1. In the system configuration directions I will assume the two cards are Etherlink XLs, so FreeBSD will call them xl0 and xl1. For the purposes of example, we'll assume xl0 is connected to the outside and xl1 is connected to the inside network, as in the diagram above.

Step 1 - Compiling the Kernel

The kernel needs certain options enabled to do NAT. If you're a veteran at compiling kernels in FreeBSD, this'll be very easy for you. All we need to do is add the following lines to the kernel config file:


options		IPFIREWALL
options		IPDIVERT

In most cases it'll be okay to add this, as well:

options		IPFIREWALL_DEFAULT_TO_ACCEPT

This will save you a step later. If you're not uptight about security with ipfw, you should be fine to enable default to accept. If you've never used ipfw before, you don't need to worry about security repercussions of enabling it. Usually you would start by allowing all packets as the lowest priority rule anyway.

If you know where your kernel config file is and how to compile the kernel, go for it. If not, read on.

If you've never recompiled the kernel since installing, you'll need to start by getting the source code. If you have a FreeBSD CD handy, it might be easier to copy the source code from it, or install it using sysinstall. If you don't have one handy, try this. Install cvsup:


# cd /usr/ports/net/cvsup; make install

Create a supfile to tell cvsup which sources to download by putting the following into a file which you name whatever you like and place wherever you like, let's say /home/you/releng4.sup:

*default  host=cvsup3.FreeBSD.org
*default  base=/usr
*default  prefix=/usr
*default  release=cvs delete use-rel-suffix compress prune
*default  tag=RELENG_4
src-all
src-crypto
src-secure

Then run cvsup:

# cvsup /home/you/releng4.sup

It will take a while, but when it's done you'll have your very own copy of the FreeBSD source code. Now, do the following:

# cd /usr/src/sys/i386/conf
# cp GENERIC YOURMACHINE

You should change YOURMACHINE to whatever the name of your machine is, as this is the custom for naming kernel configurations. Make the config alternations shown above, then proceed to compile the new kernel:

# config YOURMACHINE
# cd ../../compile/YOURMACHINE
# make clean
# make depend
# make
# make install

Reboot your machine. If all went well, you should see the name of your kernel in the initial dmesg messages.

Step 2 - Configuring the External Interface

I can't tell you precisely how to do this, since how you access your provider may vary depending on the provider. If you can already access the internet through your external interface, you can skip the section. For the vast majority of cable and DSL applications these days, as well as university or corporate networks, any machine that can speak DHCP can get an IP. To use DHCP to obtain the IP we'd just do this:


# dhclient xl0

You should see dhclient do its job and obtain an IP address. You can add the following to to make it obtain an IP address on bootup using DHCP:

ifconfig_xl0="dhcp"

If you need to assign your external IP address manually, follow the procedure described in the next step, changing the interface name and IP address as necessary.

Step 3 - Configuring the Internal Interface

You need to assign the internal interface a unique IP address in one of the reserved ranges. The ranges are:


10.0.0.0 - 10.255.255.255
172.16.0.0 - 172.31.255.255
192.168.0.0 - 192.168.255.255

It doesn't really matter which you choose. For example's sake, I'm going to use 192.168.0.1 for the internal IP address of the FreeBSD machine. To set this, issue the following command:

# ifconfig xl1 192.168.0.1 netmask 0xffffff00

I'm just going to assume you won't need more than 253 IP addresses for the internal network. If you do have this many machines, feel free to use a larger subnet. But this should be fine for any home application. To make this configuration permanent, you will need to add the following line to /etc/rc.conf:

ifconfig_xl1="192.168.0.1 netmask 0xffffff00"

Replace any existing ifconfig_xl1 line, of course.

Step 4 - Setting up natd and ipfw

When we recompiled the kernel, we enabled the IP packet filter ipfw. If you didn't enable default to accept in the kernel config file, you'll need to do something like this to allow any packets to pass through your FreeBSD box:

# ipfw add 30000 allow ip from any to any
	
In all cases, you'll need to add the following firewall rule to divert IP packets to the natd port:

# ipfw add 20000 divert natd from any to any via xl0

This will tell your machine to act as a router and discriminate between packets intended for it and packets intended for the internal machines. With this rule enabled, your machine will not receive any packets normally without running natd, so do yourself a favor and don't enable before natd it if you're connected remotely to the machine via SSH or something like that.

Setting ipfw rules on startup is a little tricky. You'll most likely need to edit /etc/rc.local, since the built-in firewall configuration is a little hairy. Edit (or create) /etc/rc.local and add:


if [ -x /sbin/ipfw ]; then
	/sbin/ipfw add 30000 allow ip from any to any; #(If necessary)
        /sbin/ipfw add 20000 divert natd all from any to any via xl0;
fi

Now you'll need to run natd, which physically does the packet routing. This is quite simple, just do:


# natd -interface xl0 -use_sockets -same_ports

The interface is whatever your external interface is. The "use_sockets" and "same_ports" options are nice in that will enable some things, like IRC DCC, to work (sometimes). If your machine is extremely slow (200 MHz or so) you might want to omit these, but for anything faster than that the performance hit will be negligable. In this day and age, with our fast machines, it's unlikely you'll notice any slowdown at all.

To enable natd starting up on boot, put this in /etc/rc.conf:


natd_enable="YES"
natd_interface="xl0"
natd_flags="-use_sockets -same_ports"

Optional Step 5 - Setting up dhcpd

Technically this isn't necessary. However, I think it's the most elegant solution in this scenario to run a DHCP server on the internal interface to give out IP addresses to the internal machines. If you like, you can configure them manually by whatever means their operating system has for TCP/IP configuration. Choose a unique IP address for each machine (in this example it must be in the range 192.168.0.2 - 192.168.0.254), set 192.168.0.1 as the default gateway, and 255.255.255.0 for the subnet mask.

For the nameservers on the internal machines, you can either run named and then set their nameserver as 192.168.0.1. If you don't know how to do this or don't want to do, the other solution is to set their nameservers to whatever your ISP provides to your FreeBSD box. You can read these in /etc/resolv.conf. If you obtain your external IP via DHCP, they will be placed here automatically. Otherwise you'll have had to have set them yourself while configuring your IP address.

If you'd like to set up a DHCP server to do all this for you, here's how you would go about it. First, install dhcpd, the DHCP server daemon, from the ports collection:


# cd /usr/ports/net/isc-dhcp3; make install

The configuration file for dhcpd will be placed in /usr/local/etc/dhcpd.conf (since all ports are rooted in /usr/local). You can delete the existing file, since a duplicate of it exists as dhcpd.conf.sample in the same directory. The only thing this file needs to contain is a very simple subnet definition:

subnet 192.168.0.0 netmask 255.255.255.0 {
	range 192.168.0.2 192.168.0.254;
	option routers 192.168.0.1;
	option broadcast-address 192.168.0.255;
	option domain-name "internal.mydomain.com";
	option domain-name-servers 192.168.0.1, 123.45.67.89, 98.76.54.232;
	default-lease-time 86400;
	max-lease-time 86400;
}

Replace the nameserver IP addresses besides 192.168.0.1 with whatever your ISP provides, and omit 192.168.0.1 if you're not running named. Also, if you're not running named you should set the domain-name to whatever the ISP provides. If you want to run your own sandbox domain with named, it can work nicely. However, that's beyond the scope of this document.

Note that we can set a very long lease time (one day, in the example) since this is a private network and there will be almost no machine turnover. This will prevent your machines from waking up from sleep mode every few minutes to renew their lease.

Now add this to /etc/rc.local (since this is not part of the base system and has no /etc/rc.conf structure) to make dhcpd start at boot time:


if [ -x /usr/local/sbin/dhcpd ]; then
        /usr/local/sbin/dhcpd;
fi

Now you can just run /usr/local/sbin/dhcpd manually right now to start dhcpd. Go to one of the machines connected to the internal network, set it to use DHCP configuration, and you should verify it got a 192.168.0.x IP address from the FreeBSD box. Since they are all on the same subnet as the FreeBSD machine, they will all discover it automatically with ARP - nothing required on your part. Go try to use the internet. Everything should work as well as if you were directly connected to the internet. Congratulations on a job well done!