Linux NAT with a Web Interface

In 2006 I wrote a web interface for Linux NAT tables. It was not pretty, but it moved NAT from the core routers or firewalls onto a server that the PC Techs could manage. The goal was to push management of creating printer NAT translations from the Senior team to the team that adds and removes the physical printers.

As of last weekend that server is finally out of service and I have been able to write up a howto and offer the code. At it’s peak we had nearly 1,000 hosts and printers combined being NATed.

When I initially wrote it, it was on two physical servers.  During one upgrade we moved it to a single virtual machine where it has run for the past six years.  I upgraded it a couple of times, but I think it’s pretty a good project to have run all of the printing in a hospital for eleven years.  At one point there was a bug in the code that allowed PC techs to create duplicate NATs.  This only became a problem when the server was rebooted.  I found that bug and fixed it shortly after.

I used Ubuntu Linux, and this is the what the /etc/network/interfaces looked like. Notice that #translate line. This tells the scripts that there is no translate to be created, while a line #translate tells the scripts to translate the real address to In this manner, the /etc/network/inerfaces becomes the flat file that creates all of the NATs.

root@translate2:/var/www# cat /etc/network/interfaces
auto lo
iface lo inet loopback

auto eth0
iface eth0 inet static

auto eth1
iface eth1 inet static

auto eth0:4
iface eth0:4 inet static

In order to allow the PC Techs to make the changes, I modified suidcgi.c from Sverre Huseby. Below is what I added to the suidcgi.c comments.

 *  FILE            suidcgi.c
 *  MODULE OF       suidcgi - a set UID wrapper for CGI scripts.
 *  DESCRIPTION     This is a setuid wrapper, intended to run CGI scripts
 *                  accessed from the World Wide Web. The script will be
 *                  run as the user owning it, giving access to files
 *                  without making them readable and writable to all.
 *  WRITTEN BY      Sverre H. Huseby <>
 *  HACKED BY	    Jud Bishop <>
 *  			My hack has removed most of the sanity checking put
 *  			in by Sverre.  I need to manipulate iptables and
 *  			interfaces from the web and kept getting caught by
 *  			different sanity checks.  I'm sure for good reason 🙂
 *  			So the point of this paragraph is this, this script
 *  			is insecure on so many levels that you should not use
 *  			it!

In order to make the cgi set uid of root, you have to create a symlink from the .cgi to the program suidcgi.

root@translate2:/var/www/cgi-bin# ls -alh
total 96K
drwxr-xr-x 3 root root     4.0K 2015-04-30 13:57 .
drwxr-xr-x 5 root root     4.0K 2006-10-25 12:06 ..
-rwxr-xr-x 1 root root     3.5K 2006-10-30 05:54
lrwxrwxrwx 1 root root        7 2013-02-12 14:05 delete_files.cgi -> suidcgi
-rwxr-xr-x 1 root root     5.6K 2012-05-10 07:33
-rwxr-xr-x 1 root root     3.6K 2009-01-09 10:56
lrwxrwxrwx 1 root root        7 2013-02-12 14:05 ifconfig.cgi -> suidcgi
-rwxr-xr-x 1 root root      280 2006-10-25 12:07
lrwxrwxrwx 1 root root        7 2013-02-12 14:05 interface.cgi -> suidcgi
-rwxr-xr-x 1 root root     6.8K 2009-04-29 09:02
-rw-r--r-- 1 root www-data  35K 2017-01-03 08:18 iptables
lrwxrwxrwx 1 root root        7 2013-02-12 14:05 iptables.cgi -> suidcgi
-rwxr-xr-x 1 root root      322 2006-10-25 15:00
lrwxrwxrwx 1 root root        7 2013-02-12 14:05 list.cgi -> suidcgi
-rwxr-xr-x 1 root root     3.5K 2006-10-30 08:08
drwxr-xr-x 2 root root     4.0K 2013-11-21 11:01 old
-rwxr-xr-x 1 root root      288 2006-10-25 12:07 printenv
-rwsr-xr-x 1 root root     6.0K 2006-10-25 12:07 suidcgi

Here is a listing of the default web server directory.

root@translate2:/var/www/html# ls /var/www/html/ -alh
total 20K
drwxr-xr-x 2 root root 4.0K 2009-01-12 12:42 .
drwxr-xr-x 5 root root 4.0K 2006-10-25 12:06 ..
-rw-r--r-- 1 www-data www-data  284 2006-11-06 13:38 index.html
-rwxr-xr-x 1 www-data www-data 1.5K 2006-10-25 12:07 interface.html

When the server starts, it reads the /etc/rc.local script, which calls the rc.local.firewall script. This script reads the /etc/network/interfaces file and builds the iptables rules.

cat rc.local.firewall

# 2006-11-06
# Jud Bishop

# flush the default filter rule
system "iptables --flush";

# flush the nat filter
system "iptables --table nat --flush";

# delete all of the other chains
system "iptables --delete-chain";
system "iptables --table nat --delete-chain";

# Read the file into an array
open (FILE,"</etc/network/interfaces") or die "Error: can't open file /etc/network/interfaces\n $!";
        # put the file into the array
        @array = <FILE>;
close FILE or die "Error: can't close $file\n $!";

# Take all of the new lines off of the array.
foreach $j ( 0 .. $#array )
        chomp @array[$j];

foreach $j ( 0 .. $#array )
        if ( @array[$j] =~ /^#translate/ )
                ($na, $translate) = split /\ /, @array[$j];

        } elsif (  @array[$j] =~ /^address/ ) {
                ($na, $cerner) = split /\ /, @array[$j];

                if ( $translate !~ /^0/ )
			system "iptables -t nat -A PREROUTING --destination $cerner -j DNAT --to $translate";

# make the source look like the translate box
system "iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to";

# enable ip forwarding
system "echo 1 > /proc/sys/net/ipv4/ip_forward";

#iptables -t nat -n -L >/var/www/cgi-bin/iptables

The process of creating or deleting a NAT works along these lines. A PC tech browses to the translate server and chooses an option:


The simple HTML behind the index page, just chooses where to send the PC tech next.


<a href=interface.html>Add Translation</a>

<a href=../cgi-bin/>Delete Translation</a> 

<a href=../cgi-bin/>Show Translations</a>

<a href=../cgi-bin/iptables.cgi>Show iptables</a>

<a href=../cgi-bin/ifconfig.cgi>Show interfaces</a>


Adding a translation walks the PC tech through the option to add a translation, confirm that it is correct and then creates it. Notice that I have pre-populated the form with some suggested IP address ranges to try and cut down on the number of errors.




Once the translation has been confirmed, it calls /var/www/cgi-bin/ that edits the /etc/network/interfaces file as well as add the NAT. Below is the comments section from the file. As I type this, I think about how I would have created this process differently today, however, it’s interesting to read my thoughts. This code has been in production over ten years, so one way or another it has stood the test of time.

# 2006-09-12
# Jud Bishop
#  I know I could be using suidperl but it has too much checking for me to be
#  able to manipulate system files.  SCARY.
#  I hate to think this is such a hack, but I check to make sure I get an IP
#  address coming in and I use full paths.
#  I also chose to turn off -T for taintedness although you see hacks left over
#  to deal with it.  When I get time I'll clean up the code.
#   This script reads in /etc/interfaces, finds the last interface, then adds
#   the new interface as the last one. It adds the interface to /etc/interfaces,
#   creates the interface on the fly and then adds a nat translation for it.
#   It never brings down nat, does everything on the fly.

Delete does something similar, it reads in the /etc/network/interfaces file as an array, finds the interface to delete, removes it. Then goes on toe down the interface and remove the NAT. There were no juicy comments in that file.



This entry was posted in Code, Linux. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s