Content-Type: RST Oh, the headache! I have recently forayed into deeper waters of Linux administration. In this case, it was **iptables**, for the main purpose of port forwarding to give the Internet access to my local machines. I will document my experience with this here just because I bashed my head against it for a week before figuring it out, and I would not like others to go through the same experience. But let's back up a little. What am I trying to do here? Well, here is my network setup: .. figure :: /upload/network-diagram But since I'm not super-experienced in this stuff, all you get is a poor man's diagram that I will explain. To skip my pretty explanation, go to TLDR_. My first computer, the web server, responds to "opensourcenerd.com" (which is just a domain name that maps to its real IP address). With this simple connection to the internet, it can both start connections with other machines over the internet, and accept incoming connections. This means that my server can both (for example) get packages from an Ubuntu server to install, and also respond to requests for its information, such as you requesting to read this blog entry. In my diagram, I have represented this by the two-way arrows on its connection to the internet. .. /upload/two-way-fail My other computers rest on a private network that my college (`NYU-Poly`_) has set up for its students. For security issues, it (like all conscientious internet providers) has a firewall that blocks any incoming connections except for those to a few select computers, such as the school's webserver. Naturally, none of my machines qualify, so if I had some virus or backdoor application here, the evil hacker who wrote it couldn't give it commands unless it contacted him first - which my personal firewall should disallow in the first place. .. _`NYU-Poly`: http://www.poly.edu Another reason the hacker could not do this is because the IP address of my computers here is not static, like the one of my opensourcenerd.com server is. Therefore, depending on the time of the request made, which router the netbook is connected to, and the phase of the moon, the packets might have to go to 128.238.99.51, or to 128.238.68.123. However, one day I decide I want to host a Sauerbraten_ server for my friends and me. We're all over the country, though (bah, humbug, college) so it needs to be on an IP address accessible to all of us. The only machine we own that satisfies that requirement, though, is my web server. Problem: its RAM limit means I may have to shut down vital services (like my blog) to run a game. Not too good a solution. .. _`Sauerbraten`: http://www.sauerbraten.org .. figure :: /upload/sauerbraten-screenshot Wouldn't it be nicer if I could get one of my local computers to host it? Well, there is hope, since I have a VPN (Virtual Private Network) between all my computers. This means that they all have a "fake" network connection, that makes them act as if they were all connected to the same router with plain cables. Of course, the real underpinnings are a bit more complicated, but OpenVPN (the VPN I use) makes the communication fairly transparent -- even allowing my webserver to directly connect to my local computers, regardless of firewalls! Putting two and two together: why don't I "host" the game on my webserver, by forwarding all packets and connections straight through to my netbook, where the game is actually hosted? ------- .. _tldr : Well, I *can*! The objective here is to take all traffic from a certain port P (which is used by an incoming connection) and forward it to a local machine, and back. A utility featured on most Linux distributions is **iptables**. Part of the `netfilter project`_, its purpose is to provide a layer to networking in which "rules" can be applied to all traffic. These rules are organized into "tables", hence the name iptables. So, how did I get this to work? .. _`netfilter project`: http://www.netfilter.org After a bit of Googling, I came up with these two commands to run in order to forward traffic from port 8888 on my webserver to port 80 on my netbook: .. sourcecode :: sh $ iptables -t filter -A FORWARD -p tcp -i tap0 -d 172.16.0.2 --dport 80 -j ACCEPT $ iptables -t nat -A PREROUTING -p tcp -i eth0 -d xxx.xxx.xxx.xxx --dport 8888 -j DNAT --to 172.16.0.2:80 $ # Where xxx.xxx.xxx.xxx is my webserver's IP address The first line establishes that the network interface ``tap0`` (my VPN interface) should allow the forwarding of all TCP/IP packets toward 172.16.0.2 with the destination port 80. In my server's case, this was not strictly necessary, since my rules are set to fully allow communication in the first place. The second line is more vital. It specifies that previous to routing a packet destined for my server on port 8888, the NAT (network address translation) table should apply Dynamic NAT to it, sending it to 172.16.0.2 (my netbook), on port 80. That's pretty straightforward, but of course, it didn't work. The packet would reach my netbook, but no response would ever get through. After some time stumbling in the dark, I realized what the problem was. The connection was trying to be established through the Internet->VPS->VPN->netbook route, but my netbook was trying to respond not through the VPN, but through the regular internet connection. While it gets an A for effort... .. figure :: /upload/internet-fail-cat It failed. On a regular setup (read: without a VPN), where the netbook would connect to the internet through the server using a single interface, this would have worked. In my case, what I needed was for my server to pretend to be the computer making the request. In iptables' language, this is called "masquerading": .. sourcecode :: sh $ iptables -t nat -a POSTROUTING -o tap0 -j MASQUERADE Or, in English: take all outgoing traffic to the ``tap0`` interface, and pretend it's from you. And, with all that effort, I have accomplished port forwarding! Those contacting opensourcenerd:8888 will get my netbook's port 80 responding! .. figure :: /upload/hooray-cat But that's only one single port. Turns out Sauerbraten needs two ports forwarded, and most games need more than that. What now? Does a Python enthusiast stand by and do repetitive grunt work? NO! He writes a nifty script that does it for him! In my case, this neat little thing, that automatically generates my tables for me. Given this input: .. sourcecode :: sh # Ext-port Int-IP Int-port 13102 172.16.0.2 80 # Netbook Apache 13103 172.16.0.3 80 # D800 Apache 13110 172.16.0.2 13110 # Sauerbraten 13111 172.16.0.2 13111 # Sauerbraten Ping # 7777 172.16.0.4 7777 # UT2004 # 7778 172.16.0.4 7778 # UT2004 # 7779 172.16.0.4 80 # UT2004 Webcontrol It generates the tables based on the contents as described. The ``#`` marks stand for comments, and my code treats them as such. It even checks for port ranges and forwards them accordingly! By posting its mass of code, I hereby end this rant on port forwarding with Linux... with a VPN in the mix. .. sourcecode :: python #!/usr/bin/python from subprocess import Popen as popen from subprocess import PIPE from sys import exit def composeDNAT(extport, intip, intport, protocol='tcp'): cmd = [IPTABLES, '-t', 'nat', '-A', 'PREROUTING', '-p', protocol, '-d', EXTIP, '--dport', extport, '-m', 'state', '--state', 'NEW,ESTABLISHED,RELATED', '-j', 'DNAT', '--to', intip+':'+intport] if intport == 'RANGE': cmd[-1] = cmd[-1][:-6] return cmd # Check for root permissions, necessary for iptables work rootchecker = popen(['whoami'], stdout=PIPE) rootchecker.wait() if rootchecker.stdout.read().strip() != 'root': print "You must be root to run this." exit(0) # Flush old iptables rules popen(['iptables', '-F']) popen(['iptables', '-F', '-t', 'nat']) # Define constants needed later EXTIP = 'xxx.xxx.xxx.xxx' # Censored for un-rootly eyes EXTIF = 'eth0' INTIF = 'tap0' IPTABLES = '/sbin/iptables' # Get data and start iteration lines = open('ports.txt').read().split('\n') for line in lines: if '#' in line: line = line[:line.index('#')] print line if not line.strip(): continue extport, intip, intport = line.strip().split() # Set up the Dynamic NAT for forwarding cmd = composeDNAT(extport, intip, intport, protocol='tcp') popen(cmd) # Do it for UDP too, for good measure cmd = composeDNAT(extport, intip, intport, protocol='udp') popen(cmd) # Set up masquerading to the inside, # to make sure packets get returned here, not to default gateway cmd = [IPTABLES, '-t', 'nat', '-A', 'POSTROUTING', '-o', INTIF, '-j', 'MASQUERADE']