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.



Posted in Code, Linux | Leave a comment

Troubleshoot MPLS Connectivity

Step 1 — IGP for MPLS cloud
Verification – You are on your own. (YOYO)

Step 2 — LDP Adjacencies
Pro Tip – Add the CE network to advertisements so that you don’t have to ping from some specific loopback address to confirm connectivity.
sh mpls interfaces ← Key command, is LDP enabled
sh mpls ldp neighbor ← Is LDP transport working ← Is the RID advertised?
sh mpls ldp bindings ← LRIB similar to routing table
sh mpls forwarding-table ← LFIB similar to CEF table ← Key command
sh ip cef

Step 3 — MP-BGP between PEs
sh bgp vpnv4 unicast all summary ← Did the VPNv4 BGP establish?
debug bgp vpnv4 unicast updates ← Are extended communities being sent?
sh bgp vpnv4 unicast all neighbor x.x.x.x advertised-routes ← Are the VPNv4 routes being sent
clear ip bgp * [in|out]

Step 4 — Create VRFs, RDs and RTs
sh ip vrf detail ← Key command
sh ip vrf ← Was the vrf properly allocated
sh run vrf
sh ip route vrf
sh ip route vrf *
clear ip route vrf * ← Force re-import/export

Step 5 — Add VRFs to BGP
sh bgp vpnv4 unicast vrf BLUE

Step 6 — Customer to Provider IGP
Once again YOYO.
sh ip vrf int

Step 7 — Redistribute between MP-BGP and Customer
sh bgp vpnv4 unicast all
sh ip ospf database
sh ip eigrp topology
sh ip rip database

Step 8 — Verification
sh ip route
sh ip route vrf X

Random commands:
sh vrf ← Great Command
sh ip vrf
sh ip route vrf *
sh ip route vrf [vrf name]
ping vrf [vrf]
traceroute vrf [vrf]
sh ip bgp vpnv4 vrf RED
sh mpls ldp bindings 24
sh ip cef vrf BLUE
sh mpls forwarding-table
sh mpls ldp bindings local
sh ip bgp vpnv4 vrf RED
sh mpls ldp interfaces
sh mpls ldp neigh
sh bgp vpnv4 unicast all summary
debug vpnv4 unicast updates
sh ip cef vrf BLUE
sh bgp vpnv4 unicast all
sh ip bgp vpnv4 vrf EAMC

Posted in CCIE, Routing | Leave a comment

Enable Virtualization within a VM

So much of my lab is virtualized that I am continuously tweaking the networking and settings within VMware.  Most of the lab runs on VMware 5.5 so there are some manual tweaks that I have to do that are done in the GUI of 6.x.  This note is so that I don’t have to keep googling how to enable nested virtualization.

In order to expose hardware acceleration within a VMware host:

/vmfs/volumes/56f54bc4-e6a63540-8ac0-782bcb1f2794/WinGNS3 # vi WinGNS3.vmx

Add this line:

vhv.enable = "TRUE"
Posted in CCIE | Leave a comment

Finished 2010

I just finished adding the posts form 2010 with a smattering of other random posts. I am tired of the repetitive nature of adding posts and am going to get in some studying today.

Posted in Thoughts | Leave a comment

iTerm2 with VIRL

Yesterday my boss purchased VIRL for me. I run it on one of my ESXi servers and wanted to be able to maintain the environment I have gotten used to running, namely tabbed terminals in iTerm2. Easier said than done.

I used a couple of blogs to help get me going, but their scripts did not work for me, leading to this post.  I hope it will help others.

These are my settings under the Preferences pane of VM Maestro.screen-shot-2016-12-20-at-7-16-48-pm

Below is the script that finally worked for me. Please note, the delay 2 is one of the biggest changes I had to make, otherwise the script would put both telnet commands into the same tab.

-- 2016-12-20
-- Jud Bishop

on run argv
	-- last argument should be the window title
	set windowtitle to item (the count of argv) of argv as text
	-- all but last argument go into CLI parameters
	set cliargs to ""
	repeat with arg in items 1 thru -2 of argv
		set cliargs to cliargs & " " & arg as text
	end repeat
	delay 2
	tell application "iTerm"
		set the bounds of the first window to {1000, 500, 1900, 1200}
		if (count of windows) = 0 then
			set t to (create window with default profile)
			set t to current window
		end if
		tell t
			create tab with default profile
			set s to current session
			tell s
				write text cliargs
				set name to windowtitle
			end tell
		end tell
	end tell
end run

Posted in CCIE, Routing | Leave a comment

C360 or Cisco Expert-Level Training Script


I’ve been using the C360 labs for training and have gotten tired of fighting with window management. At first I figured it would be good practice for the lab, but now I’m just tired of messing with windows, so I wrote a script to help alleviate my annoyance.

The server and port range for the lab you work on changes every time you run a different lab, so the script had to take that into account.  If you pay attention the ports you connect to are sequential, so just check the port and server of R1 and you have everything to you need.

At the end of the script it puts the iTerm windows in the lower right-hand corner of my left screen so I don’t have to move it by hand. 😉

Here are the pop-ups from the script:




-- 2016-12-04 
-- Jud Bishop 

set SERVER to the text returned of (display dialog ¬
	"Enter the server IP:" default answer "")

set FIRSTPORT to the text returned of (display dialog ¬
	"Enter the first port:" default answer "11501")

tell application "iTerm"
	set netWindow to (create window with default profile)
	select first window
	set DEVICES to {"R1", "R2", "R3", "R4", "R5", "R6", "SW1", "SW2", "SW3", "SW4", "R7", "R8", "R9", "BB"}
	repeat with I from 1 to 14
		tell current window
			set newTab to (create tab with default profile)
			tell current session
				write text "telnet " & SERVER & " " & FIRSTPORT
				set name to item I of DEVICES
			end tell
		end tell
	end repeat
	set the bounds of the first window to {1000, 500, 1900, 1200}
end tell


Posted in CCIE, Code, Routing | Leave a comment

Finished 2009

I finished adding the posts from 2009, will start on 2010 soon.

Posted in Uncategorized | Leave a comment