Port forwarding. It's a hard lesson everyone who has a NAT router has learned at one point or another. From transferring images via AIM or hosting a game of Battlefield 2, it's a very handy ability. While it's pretty easy with your average Linksys router or even a firewall based Linux distribution like IPCop, that's not how I roll. The Gentoo Hardened project lets me combine crazy technologies together to stop all sorts of attacks that haven't been discovered yet, so that's what I use. The problem is it takes more knowledge to get port forwarding working from the command line than it does from some check boxes. So, here's how I do it.
First off is the mechanism. I use Bastille, an all purpose networking configuration and hardening script to handle all of my firewall setup, including NAT and port forwarding. Obviously there are other ways, but this is how I prefer to handle it. I'll certainly be mentioning Bastille in the future and going more into that, but today I'm just giving a brief overview of port forwarding via it.
Note that the included example script that comes with the latest stable version of Bastille in Portage, v2.1.1-r3, won't work with modern systems. You'll get errors from IPTables claiming invalid commands. So, go ahead and grab the basic template here and move it to /etc/Bastille/firewall.d/pre-chain-split.d/, making dirs if needed. (Note that you'll have to remove the .txt at the end of the file name. .sh files are blocked by my server for security reasons, so I added the extra extension to allow it through.)
Now there's a lot in there, mainly comments and some code at the bottom that makes it work, but most of that you'll never have to touch. If you want to learn more about manually entering IPTables commands, go crazy, otherwise just focus on the line:
IP_FORWARDS=""
For all of the examples below we have two computers. The routing computer has two interfaces, eth0 and eth1. Eth1 is the Internet interface with an IP of 68.58.3.45 and eth0 is the internal interface. The server computer's IP is 192.168.0.10 and is hosting an SSH server.
The syntax can seem intimidating to those not well versed in networking, but it's more simple than you think. First off figure out what service you want open to the Internet. In this example we'll be working with SSH, an encrypted remote management protocol. After planning ahead slightly for how you want it setup, head here and look up your protocol. That'll list virtually all common services, games being the big exclusion. If you're looking to run a server for something less widely used like a game, TeamSpeak, etc. you'll have to look at that programs documentation for what port and protocol (TCP or UDP) it requires.
As listed in the example template, the syntax we're worried about looks like:
eth0-0.0.0.0-80-tcp-172.19.1.2-8000
The break down is:
|incoming interface|-|IP of incoming interface|-|incoming port|-|TCP or UDP|-|IP of the server computer|-|port on server computer|
The way this works, as with all port forwarding, is pretty simple. Here's the problem:
|Internet| <-> |NAT Router (i.e. external 68.58.3.45, internal 192.168.0.1)| <-> |Server Computer (i.e. internal 192.168.0.10 only)|
The problem comes with how NAT works. Very simply NAT hides the computers behind it from the Internet. The way it does this is by giving all of the computers behind itself internal IP addresses. One of the most common mistakes when people are first setting up servers for simple things like games is that they go and tell everyone that their IP is 192.168.0.100 for example. The problem is only the router and the other computers inside of your network understand that address. Any one looking at the network from outside, such as from the Internet, only see the router. Go here to view your external IP. That's what the rest of the world sees your network as. Starting to make sense? Your router has the external IP. The router's main job is to act as a middle man, taking requests and responses from inside and outside of the network and converting the addressess from internal to external and back.
The problem is how NAT operates with unrequested data, like when someone tries connecting to your server. Simply put NAT keeps an eye on outgoing data, and allows in responses. When something comes that doesn't match a previous outgoing message it just drops it. That's why the data never reaches the server. The router gets a request but simply has no idea what to do with it. How does it know what internal computer is running the server? That's where port forwarding comes in. You're going to allow external requests to reach an internal computer, but it takes a few pieces of information.
|incoming interface| = eth1
The first piece of information is obtained simply by running the command ifconfig on the routing computer. Results will include entries similar to the output below (truncated to save space):
eth0 Link encap:Ethernet HWaddr 00:0E:0C:A9:B8:20
inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0
eth1 Link encap:Ethernet HWaddr 00:0B:6A:6E:2C:80
inet addr:68.58.3.45 Bcast:255.255.255.255 Mask:255.255.252.0
So for our example, eth1 is the interface facing the Internet. This is visible simply by looking at the IP addresses each interface has been given. This is important because the router has to know what interface to expect incoming requests that break the normal NAT rules.
|IP of incoming interface| = 68.58.3.45
This is important. The script must accuratly have the IP address that has been assigned to the incoming interface. As stated above, use sites such as this to view your external IP. Keep this up to date! If your IP address changes without the script being updated your port forwarding will cease to function.
|incoming port|-|TCP or UDP| = 22-tcp
The port and protocol used are easy to determine by the documentation for your server program, including the page linked to above. Be careful since sometimes things like games may use several ports, for example one to get the game organized and for text chatting and another for the actual game data.
|IP of the server computer|-|port on server computer| = 192.168.0.10-22
The IP you need here is the address the router has assigned your server computer. ifconfig on the server computer will give you that information. For the sake of ease it'd be a good idea to assign your server a static IP (one which doesn't change), otherwise you'll have to update this script each time the server is assigned a new IP. Assuming you're not doing some fancy stuff with the port numbers, the port of the server computer will be the same as the port the router is listening to.
(Nothing forces you to use matching port numbers. You could just as easily tell everyone to use port 23 to SSH into the server, have the router listen on 23, and have it forward to the server using 22. This would appear as eth1-68.58.3.45-23-tcp-192.168.0.10-22.)
So now we take all the bits and toss them together inside of the IP_FORWARDS="" line.
IP_FORWARDS="eth1-68.58.3.45-22-tcp-192.168.0.10-22"
And that's it. The above line is actually from my router box. Simply put it'll take traffic incoming on interface eth1, IP 68.58.3.45, port 22/tcp, and forward it blindly to the internal computer 192.168.0.10 port 22. To expand it you just toss in a space and repeat all steps above. For example on my router box I forward port 22/tcp (SSH) and 443/tcp (HTTPS, secure web content) so the actual line is:
IP_FORWARDS="eth1-68.58.3.45-22-tcp-192.168.0.10-22 eth1-68.58.3.45-443-tcp-192.168.0.10-443"
Well, that's it. When you're first sitting there with a blank Linux computer it can seem intimidating doing all of this from a command line, so hopefully this cleared up the process some. Using Gentoo Linux and Bastille you can do basically anything. In the future I'll explain how to setup other features, such as NAT. Until then, hack well.
2006-02-23 EDIT: Somehow I managed to have put this up without mentioning the directory to put the script into so Bastille will load it up. It's fixed now.