Thursday, December 19, 2013

In Pursuit of Port Forwarding

On one of my latest projects I found the need for stable port forwarding using an AWS NAT instance as the front end.  At first I used the quickest tool in my arsenal, the SSH port forward:
ssh -fgL 80:192.168.1.100:8884 localhost sleep 3600

This creates a simple port forwarding rule which will close on it's own in an hour's time.  Depending on what you're doing this is also nice as it a secure tunnel to do the forwarding, which can be nice depending on the network you're connecting across. You can also not use the -f option, and follow the SSH connection, which would then keep the port forward rule working until you "exit" the ssh connection.  This is great as a quick tool, and for temporary access, but I just don't trust the connection to be stable enough to consider it permanent.

Enter iptables.  I've used it before, and it definitely has it's place in the Linux networking toolbox, but it can be a bit cumbersome to use, especially if you're not using it regularly.  Furthermore, when you go looking for help on the Internet, everyone has a different example of rules that worked for them.  In the end I ended up with this configuration working for me:
sudo /sbin/iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 80 -j DNAT --to 192.168.1.100:8884
sudo /sbin/iptables -t nat -A POSTROUTING -p tcp -j MASQUERADE

At first I had fooled myself by testing from the same instance I was doing the forwarding on, but once I realized that mistake, the above rules worked great for LAN and WAN traffic originating outside of the NAT instance. While I was willing to accept that as fair compromise, since what I really needed to work was working, I just didn't like the feeling of it all. Not to mention this still left me with the iptables-save / iptables-restore mechanism to setup to make sure the rules I put in place would survive a reboot. It was after a long time of researching and testing that I got the iptables configuration to work, in the end to decide I didn't really like it.

This is when xinetd occurred to me. A service made to accept connections and manage services, in this case I didn't need the service management, just some port forwarding, and there's a configuration option just for such a thing! You can place a config like this in your /etc/xinetd.d dir:
service port-forward-80
{
  type = UNLISTED
  socket_type = stream
  protocol = tcp
  wait = no
  user = root
  bind = 0.0.0.0
  port = 80
  only_from = 0.0.0.0
  redirect = 192.168.1.100 8884
}

This way once you
sudo /etc/init.d/xinetd restart (sudo service xinetd restart)
it will listen on the "port" and forward to the "redirect" IP address and port. The "type = UNLISTED" tells xinetd that there is no service that needs to be controlled by this config block. This is simple, easy to read, is managed by a service (which is likely running if you've got it installed), so it will easily survive reboots, and the configuration is simpler to store in version control and be automated with a tool like puppet. Additionally, this instantly worked from access via LAN, WAN, and traffic originating on instance, yet one more reason why this approach was better than iptables. For those interested in monitoring please note: when the xinetd process is listening a port check will return true even when the remote port is not available.

There's another project called redir, and while I've not actually tried it, the syntax looks quite simple:
redir --laddr=192.168.1.1 --lport=80 --caddr 192.168.1.100 --cport=8884

But again, after realizing the robust nature of xinetd and it's ability to do the job as a managed service, I decided to stick with it rather than one of these more temporary (or less then perfect) solutions.

References: