Return to:
Recently, I've been trying to setup IP Masquerading for a small network at home. Looking over the information available, I noticed that there really isn't a simple pre-built solution to setting up an IP Masquerading server. So I decided to write a little script to help ease setup. Once the script is finished, it will hopefully be a usefull utility that anyone can use to set up a basic ip-masq server.
This script will examine your routing table and build a basic set of rules that are suitable for ip masquerading.
If you don't want to cut the script out of this page, you can get it as a gziped file.
Warning: This script should be considered a beta release. If you notice any problems with it, please tell me.
If you want to take a look at an example of how the script will configure the IP masquerading, take a look at a trial run
Note: The trial run is from a previous version of the ip-masq program.
#!/usr/bin/perl -w # Perl script for setting up an ip masquerading box. This script relys on # the /proc filesystem, ipfwadm, and ifconfig. You must have a kernel # with ip masquerading support. # This script was written by Jeffrey Wong. If you notice # any problems with it please send me a message describing your problem and # if possible, how you fixed it. use strict; ################ # locataion of ipfwadm, change this if needed. ################ my($IPFWADM)='/sbin/ipfwadm'; ################ # locataion of ifconfig, change this if needed. ################ my($IFCONFIG)='/sbin/ifconfig'; ################# # If set to Yes, instead of setting up ip masquerading, it will print out # the commands it will use to set up ip masq. ################# #my($DEBUG_ON) = "yes"; my($DEBUG_ON) = "no"; ################## # If set to Yes, dhcp packets will be allowed through the masq'd interface. # If you use a cable modem you will have to set this to Yes. # This will open up the 2 dhcp ports as well as allowing the dhcp limited # broadcast address. ################## my($ALLOW_DHCP) = "yes"; #my($ALLOW_DHCP) = "no"; my(@route); # list that will consist of the following values: # ( default route interface, # [ network interface, interfaces IP mask (aaa.bbb.ccc.ddd/ee) ] # ) # example: (ppp0, eth0, 192.168.1.0/24, eth1, 192.168.2.0/24, # ppp0,123.123.123.123/32) my ($internet_gateway,$current_interface,$internet_ip,$num); my (@interfaces,@netmasks,%interface_mask); # test to see if user is root if ( $< == 0 || die "Sorry, you're not root.\n") {} # test to see if we have access to ipfwadm if ( -x $IPFWADM || die "Unable to find ipfwadm.\n" ) {} # test to see if we have ifconfig if ( -x $IFCONFIG || die "Unable to find ifconfig.\n" ) {} # test to see if we have a /proc if ( -d '/proc' || die "/proc filesystem not mounted.\n" ) {} # test to see if ipchains are implemented if ( -e '/proc/net/ip_fwchains') { die "Warning: This version of ip-masq can not be used on a computer supporting\n ipchaining.\n"; } # get routing info @route = getrouteinfo(); # sort routing info into more useful structures $internet_gateway = shift(@route); if ($internet_gateway eq "None") { die "Unable to find an internet connection.\n"; } $internet_ip = get_ip($internet_gateway); if ($internet_ip eq "Unknown") { die "Unable to determine IP for $internet_gateway.\n"; } while ( $#route >= 0 ) { $interface_mask{$route[0]} = $route[1]; push(@interfaces, shift(@route)); push(@netmasks, shift(@route)); } # now we start actually doing the actual masquerade setup. Instead of # directly calling ipfwadm, I'm using a wrapper function that will # take its parameters. This wrapper is used for debugging. # start by flushing all the old rules and setting default to deny call_ipfwadm ("-F -f"); call_ipfwadm ("-F -p deny"); call_ipfwadm ("-I -f"); call_ipfwadm ("-I -p deny"); call_ipfwadm ("-O -f"); call_ipfwadm ("-O -p reject"); # allow loopbacks call_ipfwadm ("-I -a accept -V 127.0.0.1 -S 0.0.0.0/0 -D 0.0.0.0/0"); call_ipfwadm ("-O -a accept -V 127.0.0.1 -S 0.0.0.0/0 -D 0.0.0.0/0"); # now for each interface, we establish rules for it while ($#interfaces >= 0 ) { $current_interface = $interfaces[0]; if ($current_interface eq $internet_gateway) { # its an interface to the internet # loop through the other internal networks for ($num = 0; $num <= $#netmasks; $num++) { if ($netmasks[$num] eq $interface_mask{$current_interface} ) { # masked machine trying to get out, allow call_ipfwadm (" -O -a accept -W $current_interface -S $internet_ip/32 -D 0.0.0.0/0"); #outside machine trying to talk to masq/firewall machine, allow call_ipfwadm (" -I -a accept -W $current_interface -S 0.0.0.0/0 -D $internet_ip/32"); } else { # outside machine claiming to be an internal machine, spoofed call_ipfwadm (" -I -i deny -W $current_interface -S $netmasks[$num] -D 0.0.0.0/0 -o"); # internal machine trying to get out, should have been masqed, # but it wasn't call_ipfwadm (" -O -i deny -W $current_interface -S $netmasks[$num] -D 0.0.0.0/0 -o"); # outside machine trying to talk to an internal machine # directly, don't let it call_ipfwadm (" -I -i deny -W $current_interface -S 0.0.0.0/0 -D $netmasks[$num] -o"); # internal machine looking for external machine with internal # ip number, don't let it call_ipfwadm (" -O -i deny -W $current_interface -S 0.0.0.0/0 -D $netmasks[$num] -o"); # masq anything from an internal ip going out. call_ipfwadm (" -F -a m -W $current_interface -S $netmasks[$num] -D 0.0.0.0/0"); if ($ALLOW_DHCP =~ /yes/i) { # special exception: allow any incoming port 67 or 68 for # dhcp renewals call_ipfwadm (" -I -i accept -P udp -W $current_interface -S 0.0.0.0/0 -D 0.0.0.0/0 67:68"); call_ipfwadm (" -I -i accept -P tcp -W $current_interface -S 0.0.0.0/0 -D 0.0.0.0/0 67:68"); call_ipfwadm (" -O -i accept -P udp -W $current_interface -S 0.0.0.0/0 -D 0.0.0.0/0 67:68"); call_ipfwadm (" -O -i accept -P tcp -W $current_interface -S 0.0.0.0/0 -D 0.0.0.0/0 67:68"); # allow dhcp limited broadcast attempts call_ipfwadm (" -I -i accept -W $current_interface -S 0.0.0.0/0 -D 255.255.255.255/32"); call_ipfwadm (" -O -i accept -W $current_interface -S 255.255.255.255/0 -D 0.0.0.0/0"); } } } } else { # its an interface to the internal network. # its coming from a machine on this interface, its okay call_ipfwadm (" -I -a accept -W $current_interface -S $interface_mask{$current_interface} -D 0.0.0.0/0"); # Unwanted packets to this interface should have been flushed out # by -I rules, so if it hasn't been caught yet, it should be okay call_ipfwadm (" -O -a accept -W $current_interface -S 0.0.0.0/0 -D $interface_mask{$current_interface}"); } shift (@interfaces); } # and we should be done now exit (0); sub getrouteinfo { # This sets up the array of routing info. my (@route); # routing info as described eariler my ($default_interface) = "None"; my ($mask,$iface); open (ROUTE, "/proc/net/route" || die "Unable to find /proc/net/route.\n"); # first line is a header ; while ( ) { chomp; # pull out interface name, destination, and mask /(\w+)\s*(\w+)\s*\w+\s*\w+\s*\w+\s*\w+\s*\w+\s*(\w+)\s*\w+\s*\w+\s*\w+/; $iface = $1; $mask = get_mask( $2, $3 ); if ( $mask =~ /^127./ ) { # we can skip loopback devices. All loopback devices match the # network of 127.0.0.0/8 } elsif ( $mask =~ /^0./ ) { # default routes always match the network 0.0.0.0/8 $default_interface = $iface; } else { # this should be a host of a network @route = (@route, $iface, $mask); } } unshift(@route,$default_interface); return (@route); } sub get_mask { # This takes 2 args, the first is an ip in hex format and the second # is a netmask in hex format. It returns the ip in dotted decimal # notation followed by a '/' and the number of bits. my ($Destination, $Mask) = @_; my ($dotted_number,$mask_bits); $dotted_number = dehex($Destination); $mask_bits = count_bits ($Mask); return ("$dotted_number/$mask_bits"); } sub dehex { # this returns a dotted decimal ip from a hex string. It assumes # that the hex string is a 8 hex-digit, little-endian byte ordered, # big-endian bit ordered string. my ($hex_string) = @_; my ($decimal_string); $hex_string =~ /([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})/i; $decimal_string = (hex $4) . "." . (hex $3) . "." . (hex $2) . "." . (hex $1); return ($decimal_string) } sub count_bits { my ($hex_string) = @_; my ($bit_count) = 0; my ($num,$next_char); for ($num = 0; $num < 8; $num++) { $next_char = substr($hex_string,$num,1); if ($next_char eq '0') { } elsif ($next_char eq '8') { $bit_count += 1; } elsif ($next_char eq 'C') { $bit_count += 2; } elsif ($next_char eq 'E') { $bit_count += 3; } elsif ($next_char eq 'F') { $bit_count += 4; } else { # anything else is an invalid subnet mask die "Illegal subnet mask ($hex_string) found.\n" } } return ($bit_count); } sub call_ipfwadm { my ($arg) = @_; unless ( $DEBUG_ON =~ /yes/i ) { print `$IPFWADM $arg`; } else { print "$IPFWADM $arg \n"; } } sub get_ip { # given an interface, return the ip associated with it or "Unknown" my ($iface) = @_; my ($ip) = "Unknown"; open (CONFIG, "$IFCONFIG|" || die "Unable to use ifconfig.\n"); while ( ) { if ( /^$iface/ ) { $_ = ; if ( /inet\saddr:\s*(\S+)/ ) { $ip = $1; } } } return ($ip); }