Introduction
One of the most common things to do with FreeBSD is to use it as a gateway to the internet.
Most internet connections have 1 IP assigned to them (via dialup, dhcp, or PPPoE) and have to serve that connection
to the whole network. That means that an entire network must share 1 public IP. How is this possible? Well,
a concept called NAT or
Network Address Translation was invented to do this very thing.
NAT was originally designed to address the lack of IP address space on the internet and also to relieve IP
routing tables.
About NAT
So how does this work? Let's take a look at our network (it's animated so be patient):
A machine on the Local Ethernet wants to go to the internet, it must
traverse the FreeBSD machine (via their default gateway). It is the job of the FreeBSD machine to route them
to the right place. However, you can see that the IP address of Workstation-1 is 192.168.0.10. The BSD machine
must change that address before it sends the packets out to the internet. In fact, he must change the source
address to an address that is routeable across the internet. Once the FreeBSD machine changes the address, the packets
get sent out on the internet. When the packet returns, the same thing happens, but instead of changing the source address
in the pakcet headers, it changes the destination address back to 192.168.0.10 and the packet gets sent back to your local
workstation-1.
This process is called NAT or sometimes referred to as IP Maquerading. The point is that the client workstation does not have
to know that this process is happening.
The program that does this change is called natd. natd is a userland daemon that runs seperate from the kernel on your FreeBSD machine.
So How does natd change the packets? Well, that is where ipfw comes in. Through the ipfw `divert` command packets are sent to natd first, natd changes the packet header information, then the packets get reinjected back into the "IP packet processing system" and away they go. (See figure 1.1 above).
Installation
natd comes default with system so you do not need to install it. However, the default kernel does not have support
for "diverting" packets. You have to rebuild the kernel with this support. To do this :
# cd /sys/i386/conf # cp GENERIC LOCAL # vi LOCAL Add the following line into the file: options IPDIVERT Save the file. Then type: # config LOCAL # cd ../../compile/LOCAL # make depend && make && make install You will see a bunch of garbage going across your screen. When it is done: # vi /etc/rc.conf Add the following line into this file: firewall_enable="YES" firewall_type="OPEN" gateway_enable="YES" natd_enable="YES" natd_interface="xl0" Now you will need to reboot your system: # shutdown -r now
Your system will reboot and should come up with natd running. To verify lets see if everythings setup:
First let's check that the firewall has the proper ruleset running: # ipfw -a l 00050 1566423 901667271 divert 8668 ip from any to any via xl0 00100 116714 10731910 allow ip from any to any via lo0 00200 0 0 deny ip from any to 127.0.0.0/8 65000 3342945 1813053300 allow ip from any to any 65535 0 0 deny ip from any to any
OK, looks as if the firewall is working. Let's look at what rule 50 above (first line) is doing. It basically says, "Send any packet incoming or outgoing on interface xl0 to port 8668 on the local machine".
Luckily natd is running on port 8668, so natd will get the packets from this line. After natd is done with the packets, they get reinjected at the next rule...in this case, it happens to be rule 100.
Now let's see if natd is running: # ps -auxw |grep nat root 182 0.0 1.4 528 180 ?? Rs 20Apr01 28:23.03 /sbin/natd -n xl0
WOW! it looks as if its running! The "-n" option tells natd to use the IP assigned to xl0 as the address to alias packets to. So when packets leave the local network their source address will be the address assigned to interface xl0. You added this option when you specified "natd_interface=xl0" in /etc/rc.conf above.
Let's test it out to see if we can get to the internet:# ping ftp.freebsd.org PING ftp.freebsd.org (209.180.6.225): 56 data bytes 64 bytes from 209.180.6.225: icmp_seq=0 ttl=240 time=81.597 ms 64 bytes from 209.180.6.225: icmp_seq=1 ttl=240 time=115.910 ms 64 bytes from 209.180.6.225: icmp_seq=2 ttl=240 time=50.444 ms ^C --- ftp.freebsd.org ping statistics --- 3 packets transmitted, 3 packets received, 0% packet loss round-trip min/avg/max/stddev = 50.444/82.650/115.910/26.737 ms #
OMFG! It works...
natd redirection
OK, so natd is up and running, but what if we have a web server or dns server on the inside network. How can we extend service through the firewall to an internal machine on incoming requests from the internet? The answer is to supply options to natd to "point" to the right machine and service". For example, another diagram (hehe):
Since web server traffic runs on port 80 tcp we need to add the following option to nat:
natd -n xl0 -redirect_port tcp 192.168.0.10:80 80
The redirect_port option given to natd says "send any tcp traffic destined for port 80 to 192.168.0.10 on port 80".
Another option is to send all traffic destined for your outside IP (from the internet) to an internal machine.
This option is called redirect_address. So the following line:
natd -n xl0 -redirect_address 192.168.0.10 20.30.40.50
The redirect_address option given to natd says "send ALL traffic destined for 20.30.40.50 (my outside IP)
to 192.168.0.10". This option is sometimes called static nat, whereas normal nat operation is sometimes called
PAT (Port Address Translation or Overloaded NAT).
Another point of interest for static nat is that internal machines that have a redirect_address option assigned to them will keep their public IP out on the internet. ie, They will appear to be coming from the public IP assigned to them by redirect_address when that machine is out on the internet. It's symmetrical!
These options can be added to /etc/rc.conf so they will stay in effect even after a reboot:
natd_flags="-redirect_address 192.168.0.10 20.30.40.50"
Customizing natd
There are several options that can be given to natd to make it do really cool stuff. See the natd man page for more info.
If you look at these options they can be quite long. So I like to put them into a seperate config file...so let's do
it:
Create a file: # vi /etc/natd.conf In the file let's put our options: port 8668 interface xl0 redirect_port tcp 192.168.0.10:80 80 redirect_port tcp 192.168.0.10:53 53 redirect_port udp 192.168.0.10:53 53
Save the file. The above config file says that we want to send web and dns traffic to machine 192.168.0.10 on the local network (Yippie!!). It also has the interface option which was described earlier and the "port" option which tells natd to listen on port 8668 (the default). You don't have to include it but I like to anyway as a reference.
Now let's start natd with the newly created configuartion file: # natd -f /etc/natd.conf Add this option to /etc/rc.conf so it starts up correctly on a reboot: natd_flags="-f /etc/natd.conf"
Adding Firewalling with ipfw in conjunction with nat
Before reading this section, please review the firewalling section I wrote.
There are several ways to add firewalling rules to your nat network. However, there is 1 key point to remember:
FreeBSD reinjections diverted packets at the next rule in the firewall. This is an important concept
to remember. Let's look at the base firewall again:
# ipfw -a l 00050 1566423 901667271 divert 8668 ip from any to any via xl0 00100 116714 10731910 allow ip from any to any via lo0 00200 0 0 deny ip from any to 127.0.0.0/8 65000 3342945 1813053300 allow ip from any to any 65535 0 0 deny ip from any to any
With this ruleset, packets are sent to natd via rule #50. After natd changes the packet header source/destination, the packet re-enters the firewall at rule #100 "allow ip from any to any via lo0" and continues through the ruleset until a match is found.
The order in which you add the rules doesn't really matter...before or after the divert rule. If you add them before the divert rule, you must firewall based on your public address. If you firewall after the divert rule then you are dealing with your internal addresses.
Adding an ipfw rule before the divert rule where 20.30.40.50 is your public IP: # ipfw add 40 deny tcp from any to 20.30.40.50 in via xl0 OR Adding an ipfw rule after the divert rule where 192.168.0.0/24 is your private net: # ipfw add 60 deny tcp from any to 192.168.0.0/24 80 in via xl0
Either of the above commands will block tcp port 80 traffic (web traffic) inbound from the internet to your machine. I would recommend the first command over the first. The reason? There are 2. First, The second rule may not catch web traffic to your BSD machine itself. Second, to reduce the load on the natd process.
Why not add rules before the divert rule? Well, it's a good question and here's the answer. Once a packet hits a match in the firewall ruleset it does not reenter until it leaves another interface. Since your divert rule is running on the outside interface, which is the last hop before it enters the internet, your packets will never get diverted!! And yes, they will leave your machine with the real inside IP address...ACK!
A couple of good rules to follow:
# ipfw add 40 allow tcp from any to 20.30.40.51 80 in via xl0 # ipfw add 50 divert natd ip from any to any via xl0 # ipfw add 60 deny tcp from 20.30.40.51 53 to any out via xl0 # ipfw add 70 deny tcp from any to 192.168.0.10 53 in via xl0
Note that rule #60 above is not necessarily needed and should never get hit (unless you are doing zone transfers outside your network) but it was put in there to prove a point. That our can do it.
conclusion
Well there you have it. The combination of natd and ipfw can prove to be a powerful additive to the FreeBSD OS. You may also want to check out the ipfw and natd man page for more advanced needs. Enjoy!