Combining ipfw/natd and SSH Tunnels
Mac OS X — 14 Mar 2005 11:11 — 1962 days ago

Recently I needed to do some web development on a set of hosts which are part of a test/staging platform. This test system is not reachable directly via the Internet, only via SSH tunneling. Simply setting up a tunnel did not work though, because there are several hosts and only one can use port 80, the others would have to be accessed using different ports. This in turn would not work because the system generates lots of URLs for links and HTTP redirect messages to all involved hosts, and these URLs don’t include the changed port number. All the while, the host names entered in the browser (and therefore in the “Host” HTTP header) must be what the systems expect, otherwise the Apache virtual host mapping and some of my own software breaks.

In the end I used a few /etc/hosts entries, multiple SSH tunnels, and the ipfw/natd firewalling/network address translation software included in Mac OS X to do it. I used this as an opportunity to learn about natd/ipfw, which were completely new to me. Since I still don’t really know what I’m doing in this area, you’re welcome to post improvements to this setup in the comments :-)

The setup works like this:

  • I need to access port 80 on three hosts, let’s call them hosta.example.com, hostb.example.com and hostc.example.com
  • Three different SSH tunnels are set up to these three hosts/ports, listening on the local ports 10082, 10083, 10084
  • Since there are no publicly visible DNS records for the hosts, their names are added to the /etc/hosts file. We assign the names to additional 127.0.0.x addresses, which we activate on the loopback interface.
  • Firewall rules are set up to divert traffic to the three new addresses to three separate natd processes. Each one rewrites packets for a particular host so they get sent to the corresponding SSH tunnel. When the response packets arrive out of the tunnel, the mapping is reversed.

The additions in the /etc/hosts file look like this:

127.0.0.2 hosta.example.com
127.0.0.3 hostb.example.com
127.0.0.4 hostc.example.com

The script I use to set up everything looks like this:

function cleanup {
    echo "cleaning up..."
    killall natd
    ipfw -f flush
    perl -p -i -e 's/^(127.0.0.(2|3|4).+)/#\1/g' /etc/hosts
    sysctl -w net.inet.ip.fw.verbose=0
    exit
}

trap cleanup INT

perl -p -i -e 's/^#(127.0.0.(2|3|4).+)/\1/g' /etc/hosts

sysctl -w net.inet.ip.fw.verbose=1
killall natd
ipfw -f flush


for i in 2 3 4; do

    ifconfig lo0 127.0.0.$i alias
    
    ipfw add divert 1000$i log tcp from me to 127.0.0.$i
    ipfw add divert 1000$i log tcp from 127.0.0.$i 1008$i to me
    natd -l -port 1000$i -interface lo0 \
    -proxy_only -proxy_rule port 80 server 127.0.0.$i:1008$i

done

ssh -g -a -x -N -T \
    -L10082:hosta.example.com:80 \
    -L10083:hostb.example.com:80 \
    -L10084:hostc.example.com:80 \
    mliyanage@gateway.example.com

When I start it, it stays in the foreground. As soon as I’m finished, I hit Ctrl-C and it cleans up everything.


Comments
Posted by Sascha Welter on 18 Mar 2005 08:43

Uah! Why don't you just:
ssh -D 4000 remote.server.tld
And then set one of your web browsers up to Socks proxy on 127.0.0.1:4000. Viola, problem solved, all web browsers in the "remote" subnet are normally reachable.

Posted by Marc on 18 Mar 2005 09:59

Hi Sascha :-)

Well this was really more of an exercise to learn about ipfw/natd. Accessing the servers was only an excuse to tinker with something new...

The SOCKS solution is cool and works really well in Firefox, but I cannot get it to work in Safari. Maybe a SOCKS v5/v4 issue? Firefox allows me to choose v4.

Posted by Marc on 18 Mar 2005 10:22

Never mind, the solution is here: http://www.dribin.org/dave/blog/archives/2004/11/22/ssh_socks/

Powered By blojsom