#!/usr/bin/perl
#
# PPP GateKeeper
# Copyright (C) 2008-2014, Linux Based Systems Design
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

use strict;
use warnings;

use Config::IniFiles;
use IO::Pty;
use IO::Pipe;
use IO::Handle;
use IO::Select;
use Getopt::Long;
use Fcntl qw(F_SETFD);

use POSIX qw(WNOHANG setsid);

use Data::Dumper;


my $VERSION = "0.1.0-201406111015";


# DO NOT CHANGE THESE
use constant {
	TABLE_BASE_ID => 100,

	# Starting value
	FWMARK_BASE_ID   => 0b0000000000001000,
	# Increment by this
	FWMARK_INCREMENT => 0b0000000000001000,
	# Mask all values we can have
	FWMARK_MASK_ALL  => 0b1111111111111000,
	# Mask for INTERNAL traffic
	FWMARK_MASK_INT  => 0b0000000000000001,
	# Mask for EXTERNAL traffic
	FWMARK_MASK_EXT  => 0b0000000000000010,

	ROUTING_TABLE => 240,
	ROUTING_TABLE_RULE_PRIORITY => 25000,
	ROUTING_TABLE_EXCL_DEFAULT_PRIORITY => 25,

	ROUTING_TABLE_DEFAULT_PRIORITY => 50,
	ROUTING_TABLE_DEFAULT_WEIGHT => 50,

	DEFAULT_ROUTE_DEFAULT_WEIGHT => 50,

	TRAFFIC_HISTORY_SIZE => 300,

	ROUTE_CACHE_PERIOD => 3600,
};

# Constants
my $configFile = "/etc/ppp/ppp-gatekeeper.conf";
my $pidFile = "/var/run/ppp-gatekeeper/ppp-gatekeeper.pid";
my $fwStateFile = "/var/run/ppp-gatekeeper/ppp-gatekeeper.ipt";


# Grab options
my %optctl = ();
GetOptions(\%optctl,
	'fg',
	'config:s',

	'help') or exit 1;


print(STDERR "PPP GateKeeper v$VERSION, Copyright (c) 2008-2014 Linux Based Systems Design\n")
	if (defined($optctl{'fg'}) || defined($optctl{'help'}));


# Check if user wants usage
if (defined($optctl{'help'})) {
	displayUsage();
}

if (defined($optctl{'config'}) && $optctl{'config'} ne "") {
	$configFile = $optctl{'config'};
}

# Check config file exists
if (! -f $configFile) {
	print(STDERR "ERROR: No configuration file '".$configFile."' found!\n");
	exit 1;
}

# Use config file, ignore case
tie my %inifile, 'Config::IniFiles', (
	-file => $configFile,
	-nocase => 1
) or die "Failed to open config file '".$configFile."': ".join("\n",@Config::IniFiles::errors);

# Loop with sections
my %config;
# special globals
my @localNets; my @defaultRoutePolicies;
foreach my $section (keys %inifile) {
	if ($section eq "global") {
		# Pull in globals we treat specially
		if (defined($inifile{'global'}->{'localnets'})) {
			@localNets = split(/[\s,]+/,$inifile{'global'}->{'localnets'});
		}
		if (defined($inifile{'global'}->{'default_route_policy'})) {
			@defaultRoutePolicies = split(/[\s,]+/,$inifile{'global'}->{'default_route_policy'});
		}
	} else {
		$config{$section} = $inifile{$section};
	}
}

# Become daemon if we have to
if (!defined($optctl{'fg'})) {
	daemonize();
}

logMsg('CONTROLLER',undef,'Initializing');

# Write PID file
open(PIDFILE,"> $pidFile") or die "Can't create '$pidFile': $!\n";
print PIDFILE $$;
close(PIDFILE);

# Make stderr autoflushed
STDERR->autoflush(1);

# Setup signal handler
$SIG{CHLD} = \&REAPER;
$SIG{INT} = \&INTERRUPT;
$SIG{TERM} = \&INTERRUPT;
$SIG{HUP} = \&RELOAD;
$SIG{USR1} = \&SIGUSR1;
$SIG{USR2} = \&SIGUSR2;

# Blank
my $globals;
$globals->{'default_route'} = {};
$globals->{'dns'} = {};
$globals->{'connections'} = {};
$globals->{'fd_list'} = {};
$globals->{'pid_list'} = {};
$globals->{'routes'} = {};
$globals->{'firewall'} = {};

# Time right now
my $now = time();

# Work out interface info
my $numIfaces = 0;
foreach my $connName (keys %config) {

	my $id = $numIfaces++;
	my $connConfig = $config{$connName};

	$connConfig->{'id'} = $id;
	$connConfig->{'name'} = $connName;

	$globals->{'connections'}->{$connName}->{'status'} = 'down';
	$globals->{'connections'}->{$connName}->{'last_state_change'} = $now;

	$connConfig->{'_internal'} = {};
	my $connConfigInternal = $connConfig->{'_internal'};
	$connConfigInternal->{'table_id'} = TABLE_BASE_ID + $id;
	$connConfigInternal->{'fwmark_id'} = FWMARK_BASE_ID + ($id * FWMARK_INCREMENT);

	# Check some options
	if (!defined($connConfig->{'type'})) {
		$connConfig->{'type'} = 'pppoe';
	}
}

# Pull in default route policies
foreach my $drp (@defaultRoutePolicies) {
	my ($tprio,$policy) = split('/:/',$drp);
	my $prio = sprintf('%02u',$tprio);
	# Check to make sure the value is valid
	if ($policy ne "rr" && $policy ne "random" && $policy ne "wrandom") {
		logMsg('CONTROLLER',undef,"Invalid default route policy '$policy' for '$tprio'\n");
		next;
	}
	# Set policy
	$globals->{'default_route'}->{'policies'}->{$prio} = $policy;
}


# Load localnets
runCommand('/usr/sbin/ipset','-N','pppgk-localnets','nethash');
runCommand('/usr/sbin/ipset','-F','pppgk-localnets');
foreach my $net (@localNets) {
	runCommand('/usr/sbin/ipset','-A','pppgk-localnets',$net);
}
# Excludes from tracking
runCommand('/usr/sbin/ipset','-N','pppgk-trackexcl','nethash');
# If for an odd reason a local route goes outbound, don't track it
foreach my $net (@localNets) {
	runCommand('/usr/sbin/ipset','-A','pppgk-trackexcl',$net);
}
runCommand('/usr/sbin/ipset','-F','pppgk-trackexcl');


# Add our own routing rule
runIPRuleAdd('lookup',ROUTING_TABLE,'prio',ROUTING_TABLE_RULE_PRIORITY);
runIPRouteFlushTable(ROUTING_TABLE);

# Restore the MARK value for ESTABLISHED connections from internal
runIPTablesAddToNew('main',
		'mangle','pppgk-preroute-e',
		'-j','CONNMARK','--restore-mark'
);
runIPTablesAddJump('main',
		'mangle','PREROUTING','pppgk-preroute-e',
		'-m','set','--match-set','pppgk-localnets','src',
		'-m','state','--state','ESTABLISHED,RELATED',
);
# This is the main rule which restores the RECENT route MARK value
runIPTablesNewChain('main','mangle','pppgk-preroute-int-r');
runIPTablesAddJump('main',
		'mangle','PREROUTING','pppgk-preroute-int-r',
		'-m','state','--state','NEW',
		'-m','mark','--mark',0,
		'-m','set','--match-set','pppgk-localnets','src'
);

# Restore mark for LOCAL traffic outbound not going to localnets
runIPTablesAddToNew('main',
		'mangle','pppgk-output-e',
		'-m','state','--state','ESTABLISHED,RELATED',
		'-j','CONNMARK','--restore-mark'
);
runIPTablesInsertJump('main',
		'mangle','OUTPUT',1,'pppgk-output-e',
		'-m','set','!','--match-set','pppgk-localnets','dst',
		'-m','state','--state','ESTABLISHED,RELATED',
);
# This is the main rule which marks RECENT route MARK values for LOCAL traffic going externally
runIPTablesNewChain('main','mangle','pppgk-output-int-nr');
runIPTablesInsertJump('main',
		'mangle','OUTPUT',2,'pppgk-output-int-nr',
		'-m','state','--state','NEW',
		'-m','mark','--mark',0,
		'-m','set','!','--match-set','pppgk-localnets','dst'
);

# Mark LOCAL traffic outbound and which interface its leaving on
runIPTablesNewChain('main','filter','pppgk-output-r');
runIPTablesInsertJump('main',
		'filter','OUTPUT',1,'pppgk-output-r',
		'-m','mark','!','--mark',0,
);


# CLAMP all forwarded traffic to max segment size
runIPTablesAddToNew('main','mangle','pppgk-forward-clamp',
		'-p','tcp','--tcp-flags','SYN,RST','SYN',
		'-j','TCPMSS','--clamp-mss-to-pmtu'
);
runIPTablesAddJump('main','mangle','FORWARD','pppgk-forward-clamp');

# Main rule for NEW forwarded traffic from our localnets outbound
runIPTablesNewChain('main','mangle','pppgk-forward-int-n');
runIPTablesAddJump('main','mangle',
		'FORWARD','pppgk-forward-int-n',
		'-m','state','--state','NEW',
		'-m','mark','--mark',0,
		'-m','set','--match-set','pppgk-localnets','src'
);

# Main rule for NEW forwarded traffic from the ppp side inbound
runIPTablesNewChain('main','mangle','pppgk-forward-ext-n');
runIPTablesAddJump('main','mangle',
		'FORWARD','pppgk-forward-ext-n',
		'-m','state','--state','NEW',
		'-m','mark','--mark',0,
		'-m','set','--match-set','pppgk-localnets','dst'
);


# This is the main rule for NEW traffic coming from EXTERNAL inbound
runIPTablesNewChain('main','mangle','pppgk-input-ext-n');
runIPTablesAddJump('main',
		'mangle','INPUT','pppgk-input-ext-n',
		'-m','state','--state','NEW',
		'-m','mark','--mark',0,
		'-m','set','!','--match-set','pppgk-localnets','src',
);

# Save the MARK value to CONNMARK for inbound traffic
runIPTablesAddToNew('main',
		'mangle','pppgk-input',
		'-m','mark','!','--mark',0,
		'-j','CONNMARK','--save-mark'
);
runIPTablesAddJump('main','mangle','INPUT','pppgk-input');


# Very last, CONNMARK all traffic with a MARK value
runIPTablesAddToNew('main',
		'nat','pppgk-postroute',
		'-m','mark','!','--mark',0,
		'-j','CONNMARK','--save-mark'
);
runIPTablesInsertJump('main','nat','POSTROUTING',1,'pppgk-postroute');


# Watch when we can do something
my $select = IO::Select->new();

logMsg('CONTROLLER',undef,"Running, startup may take up to 30s...\n");

# Main loop
my $mainExit = 0;
while ($mainExit < 5) {
	my $sleep = 1;

#	logMsg('CONTROLLER',undef,"About to run select()");
	# Can we read data yet?
	if (my @readyFDs = $select->can_read(1)) {
#		logMsg('CONTROLLER',undef,"select() can read");

		# Loop with ready fd's
		foreach my $fd (@readyFDs) {
			my $connName = $globals->{'fd_list'}->{$fd->fileno};
			my $connConfig = $config{$connName};
			my $connInternal = $globals->{'connections'}->{$connName}->{'_internal'};

#			logMsg('MONITOR',$connName,"select() can read fd ".$fd->fileno);

			# Inititalize buffer
			$connInternal->{'fd_buffer'} = "" if (!defined($connInternal->{'fd_buffer'}));

			# Loop while there is data being read
			my $nread;
			while ($nread = sysread($fd,$connInternal->{'fd_buffer'},1024,length($connInternal->{'fd_buffer'}))) {
				last if ($nread < 1024);
			}

#			logMsg('CONTROLLER',undef,"sysread() done");
			# pipe closed?
			last if (!defined($nread));
#			logMsg('CONTROLLER',undef,"sysread() last done: nread = $nread");

			# Split off lines
			my @lines = split(/\r?\n/,$connInternal->{'fd_buffer'});
			# If last bytes are the end of the line, then we have a solid list of
			# lines, else just save that in buffe for next time
			if ($connInternal->{'fd_buffer'} =~ /\r?\n$/) {
				$connInternal->{'fd_buffer'} = '';
			} else {
				$connInternal->{'fd_buffer'} = pop(@lines);
			}

			# Output logs
			if (defined($connConfig->{'logfile'})) {
				my ($sec,$min,$hour,$day,$mon,$year) = localtime;
				my $timestamp = sprintf("%04d/%02d/%02d-%02d:%02d:%02d",$year+1900, $mon+1, $day, $hour, $min, $sec);

				# Open logfile and loop with lines
				open(LOGFILE,'>>',$connConfig->{'logfile'});
				foreach my $line (@lines) {
					print(LOGFILE "$timestamp: $line\n");
				}
				close(LOGFILE);
			}

			# Loop through lines we got
			foreach my $line (@lines) {
#				logMsg('MONITOR',$connName,"LINE [$line]");

				# Got interface name
				if ($line =~ /^Using interface (\S+)/) {
					my $iface = $1;

					# Setup local IP
					$connInternal->{'interface'} = $iface;

					logMsg('MONITOR',$connName,'Interface = %s',$iface);

					# Do we have PID yet?
					setPPPDPID($connName);


				# Got local IP
				} elsif ($line =~ /^local\s+IP\s+address\s+(\S+)/) {
					my $local_ip = $1;

					# Setup local IP
					$connInternal->{'local_ip'} = $local_ip;

					logMsg('MONITOR',$connName,'Local IP = %s',$local_ip);


				# Remote IP
				} elsif ($line =~ /^remote\s+IP\s+address\s+(\S+)/) {
					my $remote_ip = $1;

					# Setup remote IP
					$connInternal->{'remote_ip'} = $remote_ip;

					logMsg('MONITOR',$connName,'Remote IP = %s',$remote_ip);

				# Got DNS
				} elsif ($line =~ /^(\S+)\s+DNS\s+address\s+(\S+)/) {
					my ($which,$dns) = ($1,$2);

					# Setup local IP
					$connInternal->{"dns_$which"} = $dns;

					logMsg('MONITOR',$connName,'DNS/%s = %s',lc($which),$dns);

				# Connection is up
				} elsif ($line =~ /^Script \/etc\/ppp\/ip-up finished/) {
					connStatus($connName,'up');


				# Master exit plan
				} elsif ($line eq '%%END%%') {
					logMsg('MONITOR',$connName,"!!!!MASTER EXIT!!!!");
					connStatus($connName,'down');
				}
			}
		}

		$sleep = 0;
	}

	# Sleep if something didn't happen
	sleep($sleep) if ($sleep);

	# Check if these processes are still alive?
	foreach my $pid (keys %{$globals->{'pid_list'}}) {
		# Clever, lets check if the pid dir exists
		if ( ! -d "/proc/$pid" ) {
			my $connName = $globals->{'pid_list'}->{$pid};

			logMsg('MONITOR',$connName,'PID %s no longer exists, marking down',$pid);
			connStatus($connName,'down');
		}
	}


	# Set now
	$now = time();

	# Look for state changes
	my %stats; # Connection status stats
	my @connNames = sort keys %{$globals->{'connections'}};  # Lets loop with the links in alphabetical order
	foreach my $connName (@connNames) {
		# Setup some vars we like
		my $connConfig = $config{$connName};
		# The connection
		my $thisConn = $globals->{'connections'}->{$connName};
		my $connInternal = $thisConn->{'_internal'};

		# Stats
		if (defined($stats{$thisConn->{'status'}})) {
			$stats{$thisConn->{'status'}}++;
		} else {
			$stats{$thisConn->{'status'}} = 1;
		}

		# We have something to do
		if (defined($thisConn->{'state_change'})) {
			# Check what state change it was
			if ($thisConn->{'state_change'} eq 'up') {
				logMsg('CONTROLLER',$connName,'Changed state to UP => %s[%s], %s => %s',$connConfig->{'interface'},
						$thisConn->{'_internal'}->{'interface'},$thisConn->{'_internal'}->{'local_ip'}, 
						$thisConn->{'_internal'}->{'remote_ip'});
				linkUp($connName);

			} elsif ($thisConn->{'state_change'} eq 'down') {
				logMsg('CONTROLLER',$connName,'Changed state to DOWN => uptime %0.2fhr',($now - $thisConn->{'_internal'}->{'timestamp'}) / 3600);
				$select->remove($connInternal->{'fd'});
				linkDown($connName);

			} elsif ($thisConn->{'state_change'} eq 'failed') {
				logMsg('CONTROLLER',$connName,'Changed state to FAILED');
				$select->remove($connInternal->{'fd'});
				linkFailed($connName);

			}

			# Lock & reset
			delete($thisConn->{'state_change'});

		# If no status updates, print out current state
		} else {
#			if ($thisConn->{'status'} eq "up") {
#				logMsg('CONTROLLER',$connName,'State is %s (%s[%s], %s => %s, %0.2fhr)',uc($thisConn->{'status'}),$connConfig->{'interface'},
#						$thisConn->{'_internal'}->{'interface'}, $thisConn->{'_internal'}->{'local_ip'}, $thisConn->{'_internal'}->{'remote_ip'},
#						($now - $thisConn->{'_internal'}->{'timestamp'}) / 3600);
#			} else {
#				logMsg('CONTROLLER',$connName,'State is %s',uc($thisConn->{'status'}));
#			}
		}

		# We should be connecting ...
		if ($mainExit == 0 && $thisConn->{'status'} eq 'down' && ($now - $thisConn->{'last_state_change'}) > 5) {
			my $pty = forkConnection($connName);
			$select->add($pty);
		}
	}

	# Update counters
	if (!$mainExit) {
		open(DEVSTATS,"< /proc/net/dev");
		while (my $line = <DEVSTATS>) {
			# Split off counters
			my (undef,$dev,$bytesIn,$packetsIn,undef,undef,undef,undef,undef,undef,$bytesOut,$packetsOut) = split(/[ \t:]+/,$line);

			# Find the interface and update
			foreach my $connName (@connNames) {
				# Setup some vars we like
				my $connConfig = $config{$connName};
				# The connection
				my $thisConn = $globals->{'connections'}->{$connName};
				my $connInternal = $thisConn->{'_internal'};

				# Setup counters
				if ($thisConn->{'status'} eq "up" && $connInternal->{'interface'} eq $dev) {
					# Setup counters
					unshift(@{$connInternal->{'counters'}->{'bytes_in'}},$bytesIn);
					pop(@{$connInternal->{'counters'}->{'bytes_in'}}) if (@{$connInternal->{'counters'}->{'bytes_in'}} > TRAFFIC_HISTORY_SIZE);

					unshift(@{$connInternal->{'counters'}->{'bytes_out'}},$bytesIn);
					pop(@{$connInternal->{'counters'}->{'bytes_out'}}) if (@{$connInternal->{'counters'}->{'bytes_out'}} > TRAFFIC_HISTORY_SIZE);

					unshift(@{$connInternal->{'counters'}->{'bytes_total'}},$bytesIn+$bytesOut);
					pop(@{$connInternal->{'counters'}->{'bytes_total'}}) if (@{$connInternal->{'counters'}->{'bytes_total'}} > TRAFFIC_HISTORY_SIZE);

					unshift(@{$connInternal->{'counters'}->{'packets_in'}},$packetsIn);
					pop(@{$connInternal->{'counters'}->{'packets_in'}}) if (@{$connInternal->{'counters'}->{'packets_in'}} > TRAFFIC_HISTORY_SIZE);

					unshift(@{$connInternal->{'counters'}->{'packets_out'}},$packetsIn);
					pop(@{$connInternal->{'counters'}->{'packets_out'}}) if (@{$connInternal->{'counters'}->{'packets_out'}} > TRAFFIC_HISTORY_SIZE);

					unshift(@{$connInternal->{'counters'}->{'packets_total'}},$packetsIn+$packetsOut);
					pop(@{$connInternal->{'counters'}->{'packets_total'}}) if (@{$connInternal->{'counters'}->{'packets_total'}} > TRAFFIC_HISTORY_SIZE);
				}
			}
		}
		close(DEVSTATS);
	}

	# Output some stats
	my $totalLinks = 0; my $linksNotDown = 0; my $statsLine = "";
	foreach my $s (sort keys %stats) { $statsLine .= " $s = ".$stats{$s}; $totalLinks += $stats{$s}; $linksNotDown++ if ($s ne "down"); }
	$statsLine .= " total = $totalLinks";
#	logMsg('CONTROLLER',undef,"STATUS => %s\n",$statsLine);

	# Exit if we set $mainExit and we have all links down
	$mainExit++ if ($linksNotDown == 0 && $mainExit);

#	print(STDERR Dumper($globals));
}

logMsg('CONTROLLER',undef,'Waiting for children');
wait();

logMsg('CONTROLLER',undef,'Shutting down');

# Remove iptables rules
clearInstanceIPTables('main');
# Remove our main process rules
runIPRuleDel('lookup',ROUTING_TABLE,'prio',ROUTING_TABLE_RULE_PRIORITY);
runIPRouteFlushTable(ROUTING_TABLE);
# Remove ipsets
runCommand('/usr/sbin/ipset','-X','pppgk-localnets');
runCommand('/usr/sbin/ipset','-X','pppgk-trackexcl');

# Remove state file
unlink($fwStateFile);
# Remove PID file
unlink($pidFile);

logMsg('CONTROLLER',undef,'Exiting');







# Fork connection off to multilink
sub forkConnection
{
	my $connName = shift;
	my $thisConn = $globals->{'connections'}->{$connName};
	my $connConfig = $config{$connName};

	connStatus($connName,'connecting');

	# Grab PTY
	my $pty = new IO::Pty;
	# Make happy tty
	my $tty = $pty->ttyname();

	# Fork
	my $pid = fork();
	defined($pid) || die("CONTROLLER($$/$connName): Can't fork: $!");

	# This is the parent
	if ($pid) {
		# Close slave
		$pty->close_slave();
		$pty->set_raw();

		setPID($connName,$pid);
		setFD($connName,$pty->fileno);

		return $pty;

	# This is the child
	} else {
		# Setup password pipe
		pipe(PSLAVE, PMASTER)
			or die("CHILD($$/$connName): $!");
		PMASTER->autoflush(1);

		sleep(1);

		print("%%START%%\n");

		my $ppid = fork();
		defined($ppid) || die("CHILD($$/$connName): Can't fork PASSFD: $!");

		# This is the parent
		if ($ppid) {
			close(PSLAVE);

			print(PMASTER $config{$connName}->{'password'});
			# pppd wants the other side closed
			close(PMASTER);

			# Wait for child
			waitpid($ppid,0);
			exit 0;

		} else {
			$pty->make_slave_controlling_terminal() || die("PPPD($$/$connName): Failed to make slave controlling terminal: $!");
			my $slave = $pty->slave();
			close($pty);
			$slave->set_raw();

			# Remap stdout & stdin
			my $ptyfd = $slave->fileno;
			close(STDIN);
			open(STDIN, "<&$ptyfd") || die("PPPD($$/$connName): Failed to remap STDIN on parent to PTY: $!");
			close(STDOUT);
			open(STDOUT, ">&$ptyfd") || die("PPPD($$/$connName): Failed to remap STDOUT on parent to PTY: $!");
			close(STDERR);
			open(STDERR, ">&$ptyfd") || die("PPPD($$/$connName): Failed to remap STDERR on parent to PTY: $!");
			# Close slave
			close($slave);

			# Password fd pipe, close master
			close(PMASTER);

			# If we have a init= option in the config, execute it to bring up the interface before we run ppp
			if (defined($connConfig->{'init'})) {
				if (-x $connConfig->{'init'}) {
					system($connConfig->{'init'});
				} else {
					die("PPPD($$/$connName): init= file is not executable '".$connConfig->{'init'}."'");
				}
			}
			# Extra args to pass to pppd
			my @preExtraArgs = ();
			my @extraArgs = ();

			# Check the type, pppoe first
			if ($connConfig->{'type'} eq "pppoe") {
				push(@preExtraArgs,'local');
				push(@preExtraArgs,'plugin','rp-pppoe.so');
			# Modem next
			} elsif ($connConfig->{'type'} eq "modem") {
				push(@preExtraArgs,'modem');
			} else {
				die("PPPD($$/$connName): Invalid connection type '".$connConfig->{'type'}."'");
			}

			# Check for ppp unit number
			if (defined($connConfig->{'ppp_unit'})) {
				push(@extraArgs,'unit',$connConfig->{'ppp_unit'});
			}
			# Check for ppp init script
			if (defined($connConfig->{'ppp_init'})) {
				push(@extraArgs,'init', $connConfig->{'ppp_init'});
			}
			# Check for ppp connect script
			if (defined($connConfig->{'ppp_connect'})) {
				push(@extraArgs,'connect', $connConfig->{'ppp_connect'});
			}
			# Check for ppp disconnect script
			if (defined($connConfig->{'ppp_disconnect'})) {
				push(@extraArgs,'disconnect', $connConfig->{'ppp_disconnect'});
			}

			# Set process name
			$0 = "pppd - [".$connConfig->{'name'}."] via ".$connConfig->{'interface'};


			# DO NOT close this FD on exec
			fcntl(PSLAVE,F_SETFD,0);
			system('/usr/sbin/pppd',
					@preExtraArgs,
					$connConfig->{'interface'},
					'plugin', 'passwordfd.so', 'passwordfd', PSLAVE->fileno,
					'nodetach',
					'lcp-echo-failure', 240,
					'lcp-echo-interval', 1,
					'noipdefault',
					'usepeerdns',
					'noauth',
					'user', $connConfig->{'username'},
					'linkname', $connConfig->{'name'},
					'logfd', 2,
					@extraArgs,
					'debug'
			) || print("PPPD($$/$connName): Failed to start pppd: $!%%\n");
			print("%%END%%\n");
			exit 0;
		}
	}

}


# Debug info
sub logMsg {
	my ($what,$connName,$message,@params) = @_;

	my ($sec,$min,$hour,$day,$mon,$year) = localtime;
	my $timestamp = sprintf("%04d/%02d/%02d-%02d:%02d:%02d",$year+1900, $mon+1, $day, $hour, $min, $sec);

	printf(STDERR "[$timestamp] $what($$".($connName ? "/$connName" : "")."): $message\n",@params);
}


# Change connection status
sub connStatus {
	my ($connName,$status) = @_;

	# Initialize if not already done
	my $thisConn = $globals->{'connections'}->{$connName};

	# Save previous status
	my $prevStatus = $thisConn->{'status'};

	# Update status
	$thisConn->{'status'} = $status;

	# Blew up while connecting
	if ($prevStatus eq 'connecting' && $status eq 'down') {
		$thisConn->{'state_change'} = 'failed';

	# If connection just came up, awesome
	} elsif ($prevStatus eq 'connecting' && $status eq 'up') {
		$thisConn->{'state_change'} = $status;

	# If connection was up and is now down, we need to down it
	} elsif ($prevStatus eq 'up' && $status eq 'down') {
		$thisConn->{'state_change'} = $status;
	}

	$thisConn->{'last_state_change'} = time();

}


# Link UP function
sub linkUp {
	my $connName = shift;


	# Internal data
	my $thisConn = $globals->{'connections'}->{$connName};
	my $connInternal = $thisConn->{'_internal'};
	my $connConfig = $config{$connName};
	my $connConfigInternal = $connConfig->{'_internal'};

	# Connection counters
	$connInternal->{'counters'}->{'bytes_in'} = [ 0 ];
	$connInternal->{'counters'}->{'bytes_out'} = [ 0 ];
	$connInternal->{'counters'}->{'bytes_total'} = [ 0 ];
	$connInternal->{'counters'}->{'packets_in'} = [ 0 ];
	$connInternal->{'counters'}->{'packets_out'} = [ 0 ];
	$connInternal->{'counters'}->{'packets_total'} = [ 0 ];

	# Set timestamp of connection
	$connInternal->{'timestamp'} = time();


	#
	# BASIC ROUTING
	#

	# Setup default route in the table
	runIPRouteAdd('default','via',$connInternal->{'remote_ip'},'dev',$connInternal->{'interface'},'table',
			$connConfigInternal->{'table_id'});

	# Link local nets to the default route
	runIPRuleAdd('fwmark',$connConfigInternal->{'fwmark_id'}."/".FWMARK_MASK_ALL,'lookup',$connConfigInternal->{'table_id'},
			'prio',$connConfigInternal->{'fwmark_id'});

	# Mark inbound traffic on local interface so we can re-route over it later
	runIPTablesAddToNew($connName,'mangle','pppgk-input-ext-n-'.$connInternal->{'interface'},
			'-j','MARK','--set-mark',$connConfigInternal->{'fwmark_id'} | FWMARK_MASK_EXT
	);
	runIPTablesAddJump($connName,'mangle','pppgk-input-ext-n','pppgk-input-ext-n-'.$connInternal->{'interface'},
			'--in-interface',$connInternal->{'interface'}
	);

	# Internal traffic outbound, check recent routing table
	runIPTablesAddToNew($connName,'mangle','pppgk-preroute-int-r-'.$connInternal->{'interface'},
			'-j','MARK','--set-mark',$connConfigInternal->{'fwmark_id'} | FWMARK_MASK_INT
	);
	runIPTablesAddJump($connName,'mangle','pppgk-preroute-int-r','pppgk-preroute-int-r-'.$connInternal->{'interface'},
			'-m','recent','--name','pppgk-'.$connInternal->{'interface'},'--rcheck','--rdest','--seconds',ROUTE_CACHE_PERIOD,
	);

	# Internal traffic outbound, set the MARK value we going out on
	runIPTablesAddToNew($connName,'mangle','pppgk-forward-int-n-'.$connInternal->{'interface'},
			'-j','MARK','--set-mark',$connConfigInternal->{'fwmark_id'} | FWMARK_MASK_INT
	);
	runIPTablesAddJump($connName,'mangle','pppgk-forward-int-n','pppgk-forward-int-n-'.$connInternal->{'interface'},,'--out-interface',$connInternal->{'interface'});

	# External traffic inbound, mark so we know what interface we came in on
	runIPTablesAddToNew($connName,'mangle','pppgk-forward-ext-n-'.$connInternal->{'interface'},
			'-j','MARK','--set-mark',$connConfigInternal->{'fwmark_id'} | FWMARK_MASK_EXT
	);
	runIPTablesAddJump($connName,'mangle','pppgk-forward-ext-n','pppgk-forward-ext-n-'.$connInternal->{'interface'},'--in-interface',$connInternal->{'interface'});

	# New traffic outbound, check and MARK value based on recent route list
	runIPTablesAddToNew($connName,'mangle','pppgk-output-nr-'.$connInternal->{'interface'},
			'-j','MARK','--set-mark',$connConfigInternal->{'fwmark_id'} | FWMARK_MASK_INT
	);
	runIPTablesInsertJump($connName,'mangle','pppgk-output-int-nr',1,'pppgk-output-nr-'.$connInternal->{'interface'},
			'-m','recent','--name','pppgk-'.$connInternal->{'interface'},'--rcheck','--rdest','--seconds',ROUTE_CACHE_PERIOD);
	runIPTablesAddJump($connName,'mangle','pppgk-output-int-nr','pppgk-output-nr-'.$connInternal->{'interface'},
			'--out-interface',$connInternal->{'interface'},
			'-m','mark','--mark',0
	);


	# Source NAT based on MARK value
	runIPTablesAddToNew($connName,'nat','pppgk-postroute-'.$connInternal->{'interface'},
			'-j','SNAT','--to',$connInternal->{'local_ip'}
	);
	runIPTablesAddJump($connName,'nat','POSTROUTING','pppgk-postroute-'.$connInternal->{'interface'},
			'-m','mark','--mark',$connConfigInternal->{'fwmark_id'}."/".FWMARK_MASK_ALL
	);

	# Update recent route table based on recently seen forwarded traffic
	runIPTablesAddToNew($connName,'filter','pppgk-forward-'.$connInternal->{'interface'},
			'-m','recent','--name','pppgk-'.$connInternal->{'interface'},'--set','--rdest',  # Last packet
	);
	runIPTablesInsertJump($connName,'filter','FORWARD',1,'pppgk-forward-'.$connInternal->{'interface'},
			'-m','mark','--mark',$connConfigInternal->{'fwmark_id'}."/".FWMARK_MASK_ALL,
			'-m','mark','--mark',FWMARK_MASK_INT."/".FWMARK_MASK_INT,
			'-m','set','!','--match-set','pppgk-trackexcl','dst'
	);

	# Update recent route table based on recently seen LOCAL traffic outbound
	runIPTablesAddToNew($connName,'filter','pppgk-output-r-'.$connInternal->{'interface'},
			'-m','recent','--name','pppgk-'.$connInternal->{'interface'},'--set','--rdest',  # Last packet seent
	);
	runIPTablesAddJump($connName,'filter','pppgk-output-r','pppgk-output-r-'.$connInternal->{'interface'},
			'-m','mark','--mark',$connConfigInternal->{'fwmark_id'}."/".FWMARK_MASK_ALL,
			'-m','mark','--mark',FWMARK_MASK_INT."/".FWMARK_MASK_INT,
			'-m','set','!','--match-set','pppgk-trackexcl','dst'
	);

	#
	# TC
	#
	if (defined($connConfig->{'use_shaping'}) && !$mainExit) {
		# If we have a link speed, use it below
		my $rateICMP = 64;
		my $rateDNS = 64;
		if ($connConfig->{'use_shaping'} =~ /^[0-9]+$/ && (my $linkSpeed = int($connConfig->{'use_shaping'})) > 0) {
			$rateICMP = $linkSpeed * 0.05; # 5%
			$rateDNS = $linkSpeed * 0.05; # 5%
		}

		# Setup prio with 3 bands of fair queuing
		runCommand('/sbin/tc','qdisc','add','dev',$connInternal->{'interface'},'root','handle','1:','prio');
		runCommand('/sbin/tc','qdisc','add','dev',$connInternal->{'interface'},'parent','1:1','handle','10:','sfq');
		runCommand('/sbin/tc','qdisc','add','dev',$connInternal->{'interface'},'parent','1:2','handle','20:','sfq');
		runCommand('/sbin/tc','qdisc','add','dev',$connInternal->{'interface'},'parent','1:3','handle','30:','sfq');

		# Prioritize ICMP
		runCommand('/sbin/tc','filter','add','dev',$connInternal->{'interface'},'parent','1:','protocol','ip','prio',1,'u32',
				'match','ip','protocol',1,'0xff',
				'police','rate',$rateICMP.'kbit','buffer',($rateICMP * 5).'k','continue',
				'flowid','1:1'
		);
		# Prioritize ACK
		runCommand('/sbin/tc','filter','add','dev',$connInternal->{'interface'},'parent','1:','protocol','ip','prio',1,'u32',
				'match','ip','protocol','0x6','0xff',
				'match','u8','0x05','0x0f','at',0,
				'match','u8','0x10','0x0f','at',33,
				'match','u16','0x0000','0xffc0','at',2,
				'flowid','1:1'
		);
		# Prioritize SYN-ACK
		runCommand('/sbin/tc','filter','add','dev',$connInternal->{'interface'},'parent','1:','protocol','ip','prio',1,'u32',
				'match','ip','protocol','0x6','0xff',
				'match','u8','0x05','0x0f','at',0,
				'match','u8','0x12','0x0f','at',33,
				'match','u16','0x0000','0xffc0','at',2,
				'flowid','1:1'
		);
		# Prioritize DNS
		runCommand('/sbin/tc','filter','add','dev',$connInternal->{'interface'},'parent','1:','protocol','ip','prio',1,'u32',
				'match','ip','protocol','0x11','0xff',
				'match','ip','dport','53','0xffff',
				'police','rate',$rateICMP.'kbit','buffer',($rateICMP * 5).'k','continue',
				'flowid','1:1'
		);
	}

	#
	# DNS
	#

	# If we using this dns, set it up
	if ($connConfig->{'use_dns'} && !$mainExit) {

		# Check if we have a primary and secondary
		if (defined($connInternal->{'dns_primary'}) && defined($connInternal->{'dns_secondary'})) {
			my $dnsName = sprintf('%02u-%s',$connConfig->{'use_dns'},$connName);
			my $dns = $globals->{'dns'};
			my $previousDNS = $dns->{'current'};

			# Setup list
			$dns->{'list'}->{$dnsName}->{'dns_primary'} = $connInternal->{'dns_primary'};
			$dns->{'list'}->{$dnsName}->{'dns_secondary'} = $connInternal->{'dns_secondary'};

			# Get highest priority
			my $highestPrio = (sort keys %{$dns->{'list'}})[-1];
			# Are we the highest priority?
			if ($highestPrio eq $dnsName) {
				# Do we have a current DNS
				if (defined($previousDNS)) {
					# Pull in the current DNS connection info
					my $dconni = $dns->{'conni_map'}->{$previousDNS};

					logMsg('CONTROLLER',$connName,"Activating DNS (previous '%s')",$previousDNS);
					# If there is already a DNS server, remove its routes
					runCommand('/sbin/ip','route','del',$dconni->{'dns_primary'},'via',$dconni->{'remote_ip'},'dev',
							$dconni->{'interface'},'table',ROUTING_TABLE);
					runCommand('/sbin/ip','route','del',$dconni->{'dns_secondary'},'via',$dconni->{'remote_ip'},'dev',
							$dconni->{'interface'},'table',ROUTING_TABLE);
				# Just output some text, we don't need to remove anything if we are the highest priority
				} else {
					logMsg('CONTROLLER',$connName,"Activating DNS");
				}
				# Add to exclusion list
				runCommand('/usr/sbin/ipset','-A','pppgk-trackexcl',$connInternal->{'dns_primary'}."/31");
				runCommand('/usr/sbin/ipset','-A','pppgk-trackexcl',$connInternal->{'dns_secondary'}."/31");
				# Add DNS routes
				runIPRouteAdd($connInternal->{'dns_primary'},'via',$connInternal->{'remote_ip'},'dev',
						$connInternal->{'interface'},'table',ROUTING_TABLE);
				runIPRouteAdd($connInternal->{'dns_secondary'},'via',$connInternal->{'remote_ip'},'dev',
						$connInternal->{'interface'},'table',ROUTING_TABLE);
				# Set ourselves up as the current DNS
				$dns->{'current'} = $dnsName;

				# Write out DNS's
				open(RESOLV,'> /etc/ppp/resolv-pppgk.conf');
				printf(RESOLV "nameserver %s\n",$connInternal->{'dns_primary'});
				printf(RESOLV "nameserver %s\n",$connInternal->{'dns_secondary'});
				close(RESOLV);
				# If we have resolvconf use it
				if ( -x "/sbin/resolvconf" ) {
					logMsg('CONTROLLER',$connName,"Adding DNS to resolvconf");
					runCommandForked("/bin/sh","-c","'/sbin/resolvconf -a ".$connInternal->{'interface'}." < /etc/ppp/resolv-pppgk.conf'");
				}
				# If we had a previous DNS, we must blow some stuff away
				if (defined($previousDNS)) {
					# Pull in the previous DNS connection info
					my $dconni = $dns->{'conni_map'}->{$previousDNS};

					# Blow the DNS IP's out of the tracking tables & remove DNS cache for them
					open(XTR,"> /proc/net/xt_recent/pppgk-".$dconni->{'interface'});
					runCommand('/sbin/ip','route','flush','cache',$dconni->{'dns_primary'});
					printf(XTR "-".$dconni->{'dns_primary'});
					runCommand('/sbin/ip','route','flush','cache',$dconni->{'dns_secondary'});
					printf(XTR "-".$dconni->{'dns_secondary'});
					close(XTR);
				}
			}
			# Add us to the map
			$dns->{'conni_map'}->{$dnsName} = $connInternal;


		# Do not have primary AND secondary
		} else {
			logMsg('CONTROLLER',$connName,"Cannot use for DNS - MISSING EITHER PRIMARY OR SECONDARY NAMESERVER");
		}
	}

	#
	# DEFAULT ROUTE
	#

	# If we using this as a default route, set it up
	if ($connConfig->{'use_default_route'} && !$mainExit) {
		my ($thisDefaultTPrio,$thisDefaultWeight) = split(':',$connConfig->{'use_default_route'});
		my $thisDefaultPrio = sprintf('%02u',$thisDefaultTPrio);
		my $defaultRoute = $globals->{'default_route'};


		# If we don't have a default route weight, just assign it 10
		if (!defined($thisDefaultWeight)) {
			$thisDefaultWeight = DEFAULT_ROUTE_DEFAULT_WEIGHT;
		}

		# Setup list
		$defaultRoute->{'list'}->{$thisDefaultPrio}->{$connName}->{'interface'} = $connInternal->{'interface'};
		$defaultRoute->{'list'}->{$thisDefaultPrio}->{$connName}->{'remote_ip'} = $connInternal->{'remote_ip'};

		# Get highest priority
		my $highestPrio = (sort keys %{$defaultRoute->{'list'}})[-1];
		# Are we the highest priority?
		if ($highestPrio eq $thisDefaultPrio) {
			my $metric = 100 - int($highestPrio);
			my $defaultRouteStr = join(',',keys %{$defaultRoute->{'list'}->{$thisDefaultPrio}});


			# Do we have a current default route
			if (defined($defaultRoute->{'current'})) {

				# Is this infact our current route
				if ($defaultRoute->{'current'} == $thisDefaultPrio) {
					logMsg('CONTROLLER',$connName,"Activating additional routes for default route priority '%s' => %s",
							$thisDefaultPrio,$defaultRouteStr);
				} else {
					# Work out previous metric
					my $prevMetric = 100 - int($defaultRoute->{'current'});

					logMsg('CONTROLLER',$connName,"Activating default route priority '%s' (previous priority '%s') => %s",
							$thisDefaultPrio,$defaultRoute->{'current'},$defaultRouteStr);

					# Remove previous default route
					runCommand('/sbin/ip','route','del','default','metric',$prevMetric);
				}

			# Just output some text, we don't need to remove anything if we are the highest priority
			} else {
				logMsg('CONTROLLER',$connName,"Activating new default route priority '%s' => %s",$thisDefaultPrio,$defaultRouteStr);
			}

			# Build route list
			my @rList;
			# If we have a policy use it
			if (defined($defaultRoute->{'policies'}) && defined(my $policy = $defaultRoute->{'policies'}->{$thisDefaultPrio})) {
				push(@rList,'mpath',$policy);
			}
			foreach my $routeConnName (keys %{$defaultRoute->{'list'}->{$thisDefaultPrio}}) {
				my $dconni = $defaultRoute->{'list'}->{$thisDefaultPrio}->{$routeConnName};
				push(@rList,'nexthop','via',$dconni->{'remote_ip'},'dev',$dconni->{'interface'},'weight',$thisDefaultWeight);
			}

			runCommand('/sbin/ip','route','replace','default','metric',$metric,@rList);

			# Set ourselves up as the current default route
			$defaultRoute->{'current'} = $thisDefaultPrio;
		}
	}


	#
	# ADDITIONAL ROUTES
	#

	# If we using this as a default route, set it up
	if ($connConfig->{'routing_table'} && !$mainExit) {
		# Global routing table
		my $routes = $globals->{'routes'};
		# List of routing table changes
		my $routesChanged;


		if (open(RTABLE,"< ".$connConfig->{'routing_table'})) {
			# Loop with routes
			while (my $line = <RTABLE>) {
				chomp($line);
				next if ($line =~ /^\s*$/);
				next if ($line =~ /^\s*#/);

				my ($route,$prio,$weight) = split(/\s+/,$line);

				# Default prio to 0 if its not specified
				if (!defined($prio) || $prio eq "") {
					$prio = ROUTING_TABLE_DEFAULT_PRIORITY;
				}
				if (!defined($weight) || $weight eq "") {
					$weight = ROUTING_TABLE_DEFAULT_WEIGHT;
				}

				my $thisRoutePrio = sprintf('%02u',$prio);

				$routes->{'list'}->{$route}->{$thisRoutePrio}->{$connName}->{'interface'} = $connInternal->{'interface'};
				$routes->{'list'}->{$route}->{$thisRoutePrio}->{$connName}->{'remote_ip'} = $connInternal->{'remote_ip'};
				$routes->{'list'}->{$route}->{$thisRoutePrio}->{$connName}->{'weight'} = $weight;

				$routesChanged->{$route} = $thisRoutePrio;
			}
			close(RTABLE);
		} else {
			logMsg('CONTROLLER',$connName,"Failed to open routing table '%s': %s",$connConfig->{'routing_table'},$!);
		}

		# Loop with route changes
		foreach my $route (keys %{$routesChanged}) {
			# Grab prio
			my $prio = $routesChanged->{$route};
			# Create our metric
			my $metric = 100 - int($prio);


			# Get highest priority in routing table
			my $highestPrio = (sort keys %{$routes->{'list'}->{$route}})[-1];

			# Does this link change/override the current route?
			if (!defined($routes->{'current'}->{$route}) || $highestPrio <= $prio) {
				my @rList;


				# Process routes
				foreach my $rConnName (keys %{$routes->{'list'}->{$route}->{$prio}}) {
					my $rConni = $routes->{'list'}->{$route}->{$prio}->{$rConnName};


					push(@rList,'nexthop','via',$rConni->{'remote_ip'},'dev',$rConni->{'interface'},'weight',$rConni->{'weight'});
				}

				# Set current route
				$routes->{'current'}->{$route} = $prio;

				# Add route
				runCommand('/sbin/ip','route','replace','table',ROUTING_TABLE,$route,'metric',$metric,@rList);
			}
		}
	}

	# Exclusions
	# nk: should these be above in the if () ?  - maybe not, we want to exclude no matter what
	if ($connConfig->{'routing_table_exclusions'} && !$mainExit) {
		if (open(RTABLE,"< ".$connConfig->{'routing_table_exclusions'})) {
			# Loop with routes
			while (my ($route,$prio) = split(/\s+/,<RTABLE>)) {
				chomp($route);
				# Skip blanks and comments
				next if ($route =~ /^\s*$/);
				next if ($route =~ /^\s*#/);
				# Set default if not set
				$prio = (defined($prio) && $prio ne "") ? ( 100 - int($prio) ) : ROUTING_TABLE_EXCL_DEFAULT_PRIORITY;
				# Add default route
				runIPRouteAdd('throw',$route,'metric',$prio,'table',ROUTING_TABLE);
			}
			close(RTABLE);
		} else {
			logMsg('CONTROLLER',$connName,"Failed to open routing table exclusions '%s': %s",$connConfig->{'routing_table'},$!);
		}
	}

	# If we firing up an external command, do it now
	if ($connConfig->{'ifup'} && !$mainExit) {
		logMsg('CONTROLLER',$connName,"Running ifup script '".$connConfig->{'ifup'}."'");
		runCommandForked($connConfig->{'ifup'},$connName,$connInternal->{'interface'},$connInternal->{'remote_ip'},$connInternal->{'local_ip'},
				defined($connInternal->{'primary_dns'}) ? $connInternal->{'primary_dns'} : "",
				defined($connInternal->{'secondary_dns'}) ? $connInternal->{'primary_dns'} : ""
		);
	}

}


# Link DOWN function
sub linkDown {
	my $connName = shift;


	# Internal data
	my $thisConn = $globals->{'connections'}->{$connName};
	my $connInternal = $thisConn->{'_internal'};
	my $connConfig = $config{$connName};
	my $connConfigInternal = $connConfig->{'_internal'};

	logMsg('CONTROLLER',$connName,"Link DOWN");

	#
	# BASIC ROUTING
	#

	# Remove routes from table
	runCommand('/sbin/ip','route','flush','table',$connConfigInternal->{'table_id'});
	# Remove routes for local nets
	runCommand('/sbin/ip','rule','del',
			'fwmark',$connConfigInternal->{'fwmark_id'}."/".FWMARK_MASK_ALL,
			'lookup',$connConfigInternal->{'table_id'},
			'prio',$connConfigInternal->{'fwmark_id'});
	# Remove iptables jumps to us
	clearInstanceIPTables($connName);


	#
	# CONNECTION TRACKING
	#

	# Flush everything
	if (-x '/usr/sbin/conntrack') {
		runCommandSilent('/usr/sbin/conntrack','-D','--reply-dst',$connInternal->{'local_ip'});
	}

	#
	# DNS
	#

	# Time to process DNS...
	if ($connConfig->{'use_dns'} && !$mainExit) {
		my $dnsName = sprintf('%02u-%s',$connConfig->{'use_dns'},$connName);
		my $dns = $globals->{'dns'};

		# Nuke ourselves from the DNS list
		delete($dns->{'list'}->{$dnsName});
		# Check if current DNS is provided by us
		if (defined($dns->{'current'}) && $dns->{'current'} eq $dnsName) {
			my $pconni = $dns->{'conni_map'}->{$dnsName};
			# Remove old DNS entries from excludes table
			runCommand('/usr/sbin/ipset','-D','pppgk-trackexcl',$pconni->{'dns_primary'});
			runCommand('/usr/sbin/ipset','-D','pppgk-trackexcl',$pconni->{'dns_secondary'});

			# Get next highest priority
			my $highestPrio = (sort keys %{$dns->{'list'}})[-1];
			# Is there a next highest priority?
			if (defined($highestPrio)) {
				# Pull in highest DNS priorities connection info
				my $dconni = $dns->{'conni_map'}->{$highestPrio};

				logMsg("CONTROLLER",$connName,"De-activating DNS (falling back to '%s')",$highestPrio);
				# Add new entries to exclusions table
				runCommand('/usr/sbin/ipset','-A','pppgk-trackexcl',$dconni->{'dns_primary'}."/32");
				runCommand('/usr/sbin/ipset','-A','pppgk-trackexcl',$dconni->{'dns_secondary'}."/32");
				# Add DNS routes to new DNS
				runIPRouteAdd($dconni->{'dns_primary'},'via',$dconni->{'remote_ip'},'dev',
						$dconni->{'interface'},'table',ROUTING_TABLE);
				runIPRouteAdd($dconni->{'dns_secondary'},'via',$dconni->{'remote_ip'},'dev',
						$dconni->{'interface'},'table',ROUTING_TABLE);
				# Set current DNS
				$dns->{'current'} = $highestPrio;

				# Write out DNS's
				open(RESOLV,'> /etc/ppp/resolv-pppgk.conf');
				printf(RESOLV "nameserver %s\n",$dconni->{'dns_primary'});
				printf(RESOLV "nameserver %s\n",$dconni->{'dns_secondary'});
				close(RESOLV);
				# If we have resolvconf use it
				if ( -x "/sbin/resolvconf" ) {
					logMsg('CONTROLLER',$connName,"Updating DNS with resolvconf");
					runCommandForked("/bin/sh","-c","'/sbin/resolvconf -d ".$connInternal->{'interface'}."'");
					runCommandForked("/bin/sh","-c","'/sbin/resolvconf -a ".$dconni->{'interface'}." < /etc/ppp/resolv-pppgk.conf'");
				}

			# If not OH NOES!
			} else {
				logMsg("CONTROLLER",$connName,"De-activating DNS (NO FALLBACK DNS AVAILABLE)");
				delete($dns->{'current'});
				# Nuke DNS's ?
				open(RESOLV,'> /etc/ppp/resolv-pppgk.conf');
				close(RESOLV);
			}
		}
		# Nuke us from the conni map
		delete($dns->{'conni_map'}->{$dnsName});
	}


	#
	# DEFAULT ROUTE
	# 

	# If we using this as a default route, set it up
	if ($connConfig->{'use_default_route'} && !$mainExit) {
		my ($thisDefaultTPrio,$thisDefaultWeight) = split(':',$connConfig->{'use_default_route'});
		my $thisDefaultPrio = sprintf('%02u',$thisDefaultTPrio);
		my $defaultRoute = $globals->{'default_route'};


		# If we don't have a default route weight, just assign it 10
		if (!defined($thisDefaultWeight)) {
			$thisDefaultWeight = ROUTING_TABLE_DEFAULT_PRIORITY;
		}

		# Nuke ourselves from the default route list
		delete($defaultRoute->{'list'}->{$thisDefaultPrio}->{$connName});
		if ((scalar keys %{$defaultRoute->{'list'}->{$thisDefaultPrio}}) == 0) {
			delete($defaultRoute->{'list'}->{$thisDefaultPrio});
		}

		# Get highest priority
		my $highestPrio = (sort keys %{$defaultRoute->{'list'}})[-1];
		if (defined($highestPrio)) {
			my $metric = 100 - int($highestPrio);
			my $defaultRouteStr = join(',',keys %{$defaultRoute->{'list'}->{$thisDefaultPrio}});


			# Is this infact our current route
			if ($defaultRoute->{'current'} eq $highestPrio) {
				logMsg('CONTROLLER',$connName,"Deactivating failed routes for default route priority '%s' => %s",
						$highestPrio,$defaultRouteStr);
			} else {
				logMsg('CONTROLLER',$connName,"Activating next default route priority '%s' (previous priority '%s') => %s",
						$highestPrio,$defaultRoute->{'current'},$defaultRouteStr);
			}

			# Build route list
			my @routes;
			# If we have a policy use it
			if (defined($defaultRoute->{'policies'}) && defined(my $policy = $defaultRoute->{'policies'}->{$thisDefaultPrio})) {
				push(@routes,'mpath',$policy);
			}
			foreach my $routeConnName (keys %{$defaultRoute->{'list'}->{$highestPrio}}) {
				my $dconni = $defaultRoute->{'list'}->{$highestPrio}->{$routeConnName};
				push(@routes,'nexthop','via',$dconni->{'remote_ip'},'dev',$dconni->{'interface'},'weight',$thisDefaultWeight);
			}

			# Old route should still be here, so remove it
			runCommand('/sbin/ip','route','replace','default','metric',$metric,@routes);

			# Set ourselves up as the current default route
			$defaultRoute->{'current'} = $highestPrio;

		# If not OH NOES!, if we had a current route, report we don't anymore
		} elsif (defined($defaultRoute->{'current'})) {
			my $metric = 100 - int($defaultRoute->{'current'});

			logMsg("CONTROLLER",$connName,"De-activating default route '%s' (NO FALLBACK DEFAULT ROUTE AVAILABLE)",$metric);

			delete($defaultRoute->{'current'});
		}
	}

	#
	# ADDITIONAL ROUTES
	#

	# If we using this as a default route, set it up
	if ($connConfig->{'routing_table'} && !$mainExit) {
		# Global routing table
		my $routes = $globals->{'routes'};
		# List of routing table changes
		my $routesChanged;


		# Loop with routes to see if we were in the routing table
		foreach my $route (keys %{$routes->{'list'}}) {
			# Loop with priorities
			foreach my $prio (keys %{$routes->{'list'}->{$route}}) {
				# Check if this connection is linked to route/prio we're looping with
				if (defined($routes->{'list'}->{$route}->{$prio}->{$connName})) {
					# If we're in the routing table...
					if ($routes->{'current'}->{$route} == $prio) {
						$routesChanged->{$route} = $prio;
						delete($routes->{'current'}->{$route});
					}
					# Remove us from the routing table
					delete($routes->{'list'}->{$route}->{$prio}->{$connName});
					if ((keys %{$routes->{'list'}->{$route}->{$prio}}) == 0) {
						delete($routes->{'list'}->{$route}->{$prio});
					}
				}
			}
		}

		# Loop with route changes
		foreach my $route (keys %{$routesChanged}) {
			# Grab prio
			my $prio = $routesChanged->{$route};
			# Create our metric
			my $metric = 100 - int($prio);


			# Get highest priority in routing table
			if ((my $highestPrio = (sort keys %{$routes->{'list'}->{$route}})[-1])) {
				my @rList;


				# Process routes
				foreach my $rConnName (keys %{$routes->{'list'}->{$route}->{$highestPrio}}) {
					my $rConni = $routes->{'list'}->{$route}->{$highestPrio}->{$rConnName};

					push(@rList,'nexthop','via',$rConni->{'remote_ip'},'dev',$rConni->{'interface'},'weight',$rConni->{'weight'});
				}
				# Set current route
				$routes->{'current'}->{$route} = $prio;

				# Add route
				runCommand('/sbin/ip','route','replace','table',ROUTING_TABLE,$route,'metric',$metric,@rList);
			}
		}
	}

	# If we firing up an external command, do it now
	if ($connConfig->{'ifdown'}) {
		runCommand($connConfig->{'ifdown'},$connName,$connInternal->{'interface'},$connInternal->{'remote_ip'},$connInternal->{'local_ip'},
				defined($connInternal->{'primary_dns'}) ? $connInternal->{'primary_dns'} : "",
				defined($connInternal->{'secondary_dns'}) ? $connInternal->{'primary_dns'} : ""
		);
	}

	# Clear intername info
	clearPIDFD($connName);
}


# Link FAILED function
sub linkFailed {
	my $connName = shift;


	logMsg('CONTROLLER',$connName,"Link FAILED");
	clearPIDFD($connName);
}


# Clear PID and FD info
sub clearPIDFD {
	my $connName = shift;


	# Internal data
	my $thisConn = $globals->{'connections'}->{$connName};
	my $pidList = $globals->{'pid_list'};
	my $fdList = $globals->{'fd_list'};
	my $connInternal = $thisConn->{'_internal'};

	# Not in use now
	delete($pidList->{$connInternal->{'pid'}}) if ($connInternal->{'pid'});
	delete($fdList->{$connInternal->{'fd'}}) if ($connInternal->{'fd'});
	delete($thisConn->{'_internal'});
}


# Set pppd pid
sub setPPPDPID {
	my $connName = shift;


	# Open PIDFILE
	if (!open(PIDFILE,"< /var/run/ppp-".$connName.".pid")) {
		logMsg('CONTROLLER',$connName,"Failed to open PIDFILE: $!");
		return;
	}
	# Pull in PID
	my $pppdPID = <PIDFILE>;
	close(PIDFILE);
	# Chomp off anything on the end
	chomp($pppdPID);

	# And set the PID for this connection
	setPID($connName,$pppdPID);

	logMsg('CONTROLLER',$connName,"Setting pppd pid to $pppdPID");
}


# Set PID
sub setPID {
	my ($connName,$pid) = @_;
	my $thisConn = $globals->{'connections'}->{$connName};

	# Remove old pid & fd
	if (defined($thisConn->{'_internal'}->{'pid'})) {
		delete($globals->{'pid_list'}->{$thisConn->{'_internal'}->{'pid'}});
	}
	# Setup new values
	$thisConn->{'_internal'}->{'pid'} = $pid;
	$globals->{'pid_list'}->{$pid} = $connName;
}


# Set FD
sub setFD {
	my ($connName,$fd) = @_;
	my $thisConn = $globals->{'connections'}->{$connName};

	# Remove old fd & fd
	if (defined($thisConn->{'_internal'}->{'fd'})) {
		delete($globals->{'fd_list'}->{$thisConn->{'_internal'}->{'fd'}});
	}
	# Setup new values
	$thisConn->{'_internal'}->{'fd'} = $fd;
	$globals->{'fd_list'}->{$fd} = $connName;
}


# Child reaper
sub REAPER {
	my $child;
	# If a second child dies while in the signal handler caused by the
	# first death, we won't get another signal. So must loop here else
	# we will leave the unreaped child as a zombie. And the next time
	# two children die we get another zombie. And so on.
	while (($child = waitpid(-1,&WNOHANG)) > 0) {

		# If we are a known child, set status to down
		if (defined($globals->{'pid_list'}->{$child})) {
			my $connName = $globals->{'pid_list'}->{$child};

			logMsg('CONTROLLER',$connName,'Child %s, exiting with status %s',$child,$?);
			connStatus($connName,'down');
		}

	}
	$SIG{CHLD} = \&REAPER;  # still loathe sysV
}


# Child reaper
sub INTERRUPT {
	logMsg('CONTROLLER','INTERRUPT','Preparing for shutdown');
	# Set main exit point
	$mainExit = 1 if (!$mainExit);

	foreach my $connName (keys %{$globals->{'connections'}}) {
		my $pid = $globals->{'connections'}->{$connName}->{'_internal'}->{'pid'};

		if ($globals->{'connections'}->{$connName}->{'status'} ne 'down' && defined($pid)) {
			logMsg('CONTROLLER','INTERRUPT',"Signalling '%s' with PID %s to terminate",$connName,$pid);
			kill(15,$pid);
		}
	}

	logMsg('CONTROLLER','INTERRUPT','Shutdown in progress');
}


# Reload config
sub RELOAD {
	logMsg('CONTROLLER','RELOAD','Got reload request!!!');
}


# Print out config
sub SIGUSR1 {
	# Look for state changes
	my %stats; # Connection status stats
	my @connNames = sort keys %{$globals->{'connections'}};  # Lets loop with the links in alphabetical order
	foreach my $connName (@connNames) {
		# Setup some vars we like
		my $connConfig = $config{$connName};
		# The connection
		my $thisConn = $globals->{'connections'}->{$connName};
		my $connInternal = $thisConn->{'_internal'};

		# Stats
		if (defined($stats{$thisConn->{'status'}})) {
			$stats{$thisConn->{'status'}}++;
		} else {
			$stats{$thisConn->{'status'}} = 1;
		}

		if ($thisConn->{'status'} eq "up") {
			logMsg('CONTROLLER','SIGUSR1','%s is %s (pid: %s, %s[%s], %s => %s, uptime %0.2fhr)',$connName,uc($thisConn->{'status'}),
					$connInternal->{'pid'}, $connConfig->{'interface'}, $thisConn->{'_internal'}->{'interface'}, 
					$thisConn->{'_internal'}->{'local_ip'}, $thisConn->{'_internal'}->{'remote_ip'}, 
					($now - $thisConn->{'_internal'}->{'timestamp'}) / 3600);
		} else {
			logMsg('CONTROLLER','SIGUSR1','%s is %s',$connName,uc($thisConn->{'status'}));
		}
	}

	# Output some stats
	my $totalLinks = 0; my $linksNotDown = 0; my $statsLine = "";
	foreach my $s (sort keys %stats) { $statsLine .= " $s = ".$stats{$s}; $totalLinks += $stats{$s}; $linksNotDown++ if ($s ne "down"); }
	$statsLine .= " total = $totalLinks";
	logMsg('CONTROLLER','SIGUSR1',"STATUS => %s\n",$statsLine);

	# Output the dns list
	my @conns = reverse sort keys %{$globals->{'dns'}->{'list'}};
	logMsg('CONTROLLER','SIGUSR1',"DNS priority => %s",join(' -> ',@conns));

	# Output the default route list
	@conns = reverse sort keys %{$globals->{'default_route'}->{'list'}};
	logMsg('CONTROLLER','SIGUSR1',"Default route priority => %s",join(' -> ',@conns));

}

sub SIGUSR2 {
#	logMsg('CONTROLLER','SIGUSR2',"Current state: ".Dumper($globals));
	dumpIPTablesRules();
}


# Run a command fork()'d
sub runCommandForked {
	my @args = @_;


	# Fork of parent to return and child to run command
	my $pid = fork();
	if (!$pid) {
		runCommand(@args);
		exit;
	}
}


# Run a command silently
sub runCommandSilent {
	return _runCommand({
			'silent' => 1
	},@_);
}


# Run a command with default options
sub runCommand {
	return _runCommand({
			'silent' => 0
	},@_);
}


# Run a command
sub _runCommand {
	my ($options,@args) = @_;
	my $silent = (defined($options) && defined($options->{'silent'})) ? $options->{'silent'} : 0;

	# Make pretty string
	my @cmdString;
	foreach my $arg (@args) {
		if ($arg =~ /\s/) {
			$arg = "'$arg'";
		}
		push(@cmdString,$arg);
	}
	my $cmdString = join(' ',@cmdString);

	# Grab PTY
	my $pty = new IO::Pty;
	# Make happy tty
	my $tty = $pty->ttyname();

	# Fork of parent reader, and slave writer
	my $pid = fork();
	if ($pid) {
		# Close slave
		$pty->close_slave();
		$pty->set_raw();

		my $ptyfd = $pty->fileno;

		# Remap stdout & stdin
		close(STDIN);
		open(STDIN, "<&$ptyfd") || die("CMD($$): Failed to remap STDIN on parent to PTY: $!");
		close(STDOUT);
		open(STDOUT, ">&$ptyfd") || die("CMD($$): Failed to remap STDOUT on parent to PTY: $!");

		# We're done with pty, close it
		close($pty);

		# fire up select
		my $select = IO::Select->new();
		$select->add(\*STDIN);
		my $buffer = "";

		# Loop while we not exiting
		my $exit;
		while (!$exit) {
			sleep(0.5);
			if ($select->can_read(10)) {

				# Loop while there is data being read
				my $nread;
				while ($nread = sysread(\*STDIN,$buffer,1024,length($buffer))) {
					last if ($nread < 1024);
				}
				# pipe closed?
				last if (!defined($nread));
			}
		}

		# Wait for child to exit
		waitpid($pid,0);

		# Check buffer length
		if (length($buffer) > 0 && !$silent) {
			# If we have something, then output it
			chomp($buffer);
			print(STDERR "CMD($$): Output from [$cmdString] was [$buffer]\n");
		}

	} else {
		$pty->make_slave_controlling_terminal() || die("CMD($$)/CHILD: Failed to make slave controlling terminal: $!");
		my $slave = $pty->slave();
		close($pty);
		$slave->set_raw();

		# Remap stdout & stdin
		my $ptyfd = $slave->fileno;
		close(STDIN);
		open(STDIN, "<&$ptyfd") || die("CMD($$)/CHILD: Failed to remap STDIN on parent to PTY: $!");
		close(STDOUT);
		open(STDOUT, ">&$ptyfd") || die("CMD($$)/CHILD: Failed to remap STDOUT on parent to PTY: $!");
		close(STDERR);
		open(STDERR, ">&$ptyfd") || die("CMD($$)/CHILD: Failed to remap STDERR on parent to PTY: $!");
		# Close slave
		close($slave);

		# Execute
#		print(STDERR "RUNCOMMAND: ",join(' ',@args));
		exec(@args);
		exit; # This is hit if there is errors with exec()
	}
}



#
# IP Stuff
#

sub runIPRouteAdd {
	my (@params) = @_;
	runCommand('/sbin/ip','route','add',@params);
}

sub runIPRouteFlushTable {
	my (@params) = @_;
	runCommand('/sbin/ip','route','flush','table',@params);
}

sub runIPRuleAdd {
	my (@params) = @_;
	runCommand('/sbin/ip','rule','add',@params);
}

sub runIPRuleDel {
	my (@params) = @_;
	runCommand('/sbin/ip','rule','del',@params);
}


#
# Netfilter Stuff
#

# Run IPTables and store the command
sub runIPTablesNewChain {
	my ($instance,$table,$chain) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-N',$chain);
	$firewall->{'chains'}->{$instance}->{$table}->{$chain} = 1;
}

sub runIPTablesAdd {
	my ($instance,$table,$chain,@extra) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-A',$chain,@extra);
	push(@{$firewall->{'rules'}->{$instance}->{$table}->{$chain}->{'items'}},\@extra);
}

sub runIPTablesDelete {
	my ($instance,$table,$chain,$position) = @_;


	_runIPTables($table,'-D',$chain,$position);
}

sub runIPTablesInsert {
	my ($instance,$table,$chain,$position,@extra) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-I',$chain,$position,@extra);
	unshift(@{$firewall->{'irules'}->{$instance}->{$table}->{$chain}->{'items'}},\@extra);
}

sub runIPTablesAddJump {
	my ($instance,$table,$chain,$dest,@extra) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-A',$chain,@extra,'-j',$dest);
	$firewall->{'jumps'}->{$instance}->{$table}->{$chain}->{$dest} = \@extra;
}

sub runIPTablesInsertJump {
	my ($instance,$table,$chain,$position,$dest,@extra) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-I',$chain,$position,@extra,'-j',$dest);
	$firewall->{'ijumps'}->{$instance}->{$table}->{$chain}->{$dest} = \@extra;
}

sub runIPTablesAddToNew {
	my ($instance,$table,$chain,@extra) = @_;


	runIPTablesNewChain($instance,$table,$chain);
	runIPTablesAdd($instance,$table,$chain,@extra);
}

sub runIPTablesRemoveChain {
	my ($instance,$table,$chain) = @_;
	my $firewall = $globals->{'firewall'};


	_runIPTables($table,'-F',$chain);
	_runIPTables($table,'-X',$chain);

	delete($firewall->{'rules'}->{$instance}->{$table}->{$chain});
	delete($firewall->{'irules'}->{$instance}->{$table}->{$chain});
	delete($firewall->{'chains'}->{$instance}->{$table}->{$chain});
}

sub runIPTablesRemoveJumpChain {
	my ($instance,$table,$chain,$dest) = @_;

	runIPTablesRemoveJump($instance,$table,$chain,$dest);
	runIPTablesRemoveChain($instance,$table,$dest);
}

sub clearInstanceIPTables {
	my $instance = shift;
	my $firewall = $globals->{'firewall'};

	# Run through jumps and ijumps, this is a shortcut!
	foreach my $jump ('jumps','ijumps') {
		# Find tables we added
		foreach my $table (keys %{$firewall->{$jump}->{$instance}}) {
			# Find chains we added jumps to
			foreach my $chain (keys %{$firewall->{$jump}->{$instance}->{$table}}) {

				# Find the jumps we added
				foreach my $dest (keys %{$firewall->{$jump}->{$instance}->{$table}->{$chain}}) {

					# Blow it away
					runIPTablesRemoveJumpChain($instance,$table,$chain,$dest);
				}
			}
		}
	}
}

# Function to dump the iptables rules that builds the ppp-gatekeeper stuff
sub dumpIPTablesRules {
	my $firewall = $globals->{'firewall'};

	logMsg('CONTROLLER','SIGUSR2',"Dumping IPTables rules");

	if (!open(IPTR,"> $fwStateFile.lock")) {
		logMsg('CONTROLLER','SIGUSR2',"ERROR: Failed to open file '$fwStateFile': $!");
		return;
	}
	# Loop with chains, must add these first
	foreach my $instance (keys %{$firewall->{'chains'}}) {
		foreach my $table (keys %{$firewall->{'chains'}->{$instance}}) {
			foreach my $chain (keys %{$firewall->{'chains'}->{$instance}->{$table}}) {
				print(IPTR "/sbin/iptables -t '$table' -N '$chain'\n");
			}
		}
	}
	# Loop with insert rules, these must be before rules
	foreach my $instance (keys %{$firewall->{'irules'}}) {
		foreach my $table (keys %{$firewall->{'irules'}->{$instance}}) {
			foreach my $chain (keys %{$firewall->{'irules'}->{$instance}->{$table}}) {
				foreach my $item (@{$firewall->{'irules'}->{$instance}->{$table}->{$chain}->{'items'}}) {
					print(IPTR "/sbin/iptables -t '$table' -I '$chain' 1 ");
					foreach my $extra (@{$item}) {
						printf(IPTR "'$extra' ");
					}
					print(IPTR "\n");
				}
			}
		}
	}
	# Loop with normal rules
	foreach my $instance (keys %{$firewall->{'rules'}}) {
		foreach my $table (keys %{$firewall->{'rules'}->{$instance}}) {
			foreach my $chain (keys %{$firewall->{'rules'}->{$instance}->{$table}}) {
				foreach my $item (@{$firewall->{'rules'}->{$instance}->{$table}->{$chain}->{'items'}}) {
					print(IPTR "/sbin/iptables -t '$table' -A '$chain' ");
					foreach my $extra (@{$item}) {
						printf(IPTR "'$extra' ");
					}
					print(IPTR "\n");
				}
			}
		}
	}
	# Loop with insert jumps, these must be before jumps
	foreach my $instance (keys %{$firewall->{'ijumps'}}) {
		foreach my $table (keys %{$firewall->{'ijumps'}->{$instance}}) {
			foreach my $chain (keys %{$firewall->{'ijumps'}->{$instance}->{$table}}) {
				foreach my $dest (keys %{$firewall->{'ijumps'}->{$instance}->{$table}->{$chain}}) {
					print(IPTR "/sbin/iptables -t '$table' -I '$chain' 1 ");
					foreach my $extra (@{$firewall->{'ijumps'}->{$instance}->{$table}->{$chain}->{$dest}}) {
						printf(IPTR "'$extra' ");
					}
					print(IPTR " -j $dest\n");
				}
			}
		}
	}
	# Loop with normal jumps
	foreach my $instance (keys %{$firewall->{'jumps'}}) {
		foreach my $table (keys %{$firewall->{'jumps'}->{$instance}}) {
			foreach my $chain (keys %{$firewall->{'jumps'}->{$instance}->{$table}}) {
				foreach my $dest (keys %{$firewall->{'jumps'}->{$instance}->{$table}->{$chain}}) {
					print(IPTR "/sbin/iptables -t '$table' -A '$chain' ");
					foreach my $extra (@{$firewall->{'jumps'}->{$instance}->{$table}->{$chain}->{$dest}}) {
						printf(IPTR "'$extra' ");
					}
					print(IPTR " -j $dest\n");
				}
			}
		}
	}
	close(IPTR);

	# Move file into position
	unlink($fwStateFile);
	rename("$fwStateFile.lock",$fwStateFile);
}



# Remove an IPTables jump
sub runIPTablesRemoveJump {
	my ($instance,$table,$chain,$jumpTo) = @_;
	my $firewall = $globals->{'firewall'};


	# Look through iptables chain to see if we can find our jump
	open(IPT, "/sbin/iptables -t $table -S $chain |");
	my $ruleNum = 0; my @found;
	while (my $line = <IPT>) {
		chomp($line);
		# Look for last column
		if ($line =~ /\s+(\S+)\s*$/) {
			if ($1 eq $jumpTo) {
				push(@found,$ruleNum);
			}
		}
		$ruleNum++;
	}
	close(IPT);

	# Whack the entry dead  (blow them all away)
	foreach $ruleNum (reverse @found) {
		runIPTablesDelete($instance,$table,$chain,$ruleNum);
	}

	delete($firewall->{'jumps'}->{$instance}->{$table}->{$chain}->{$jumpTo});
	delete($firewall->{'ijumps'}->{$instance}->{$table}->{$chain}->{$jumpTo});
}

sub _runIPTables {
	my ($table,@params) = @_;

	runCommand('/sbin/iptables','-t',$table,@params);
}






# Become daemon
sub daemonize {
	chdir '/'
			or die "Can't chdir to /: $!";

	open STDIN, '/dev/null'
			or die "Can't read /dev/null: $!";

	open STDOUT, '> /var/log/ppp-gatekeeper/stdout.log'
			or die "Can't open stdout log: $!";

	defined(my $pid = fork) 
			or die "Can't fork: $!";

	exit if $pid;

	setsid
			or die "Can't start a new session: $!";

	open STDERR, '> /var/log/ppp-gatekeeper/stderr.log'
			or die "Can't open stderr log: $!";
}


# Display usage
sub displayUsage {
	print(<<EOF);
Usage: $0

	--fg                            Run in foreground.
	--help                          Display commandline usage.
	--config                        Config file to use.
EOF
	exit 0;
} 


# vim: ts=4
