ppp-gatekeeper-v0.1.0-201406111015/ 0000755 0000000 0000000 00000000000 12346026211 014505 5 ustar root root ppp-gatekeeper-v0.1.0-201406111015/docs/ 0000755 0000000 0000000 00000000000 12346026211 015435 5 ustar root root ppp-gatekeeper-v0.1.0-201406111015/docs/ppp-gatekeeper.sgml 0000644 0000000 0000000 00000010167 12346026211 021237 0 ustar root root manpage.1'. You may view
the manual page with: `docbook-to-man manpage.sgml | nroff -man |
less'. A typical entry in a Makefile or Makefile.am is:
manpage.1: manpage.sgml
docbook-to-man $< > $@
The docbook-to-man binary is found in the docbook-to-man package.
Please remember that if you create the nroff version in one of the
debian/rules file targets (such as build), you will need to include
docbook-to-man in your Build-Depends control field.
-->
Nigel">
Kukard">
2012-09-08 12:20">
8">
nkukard@lbsd.net">
PPP-GATEKEEPER">
Debian">
GNU">
GPL">
]>
&dhemail;
&dhfirstname;
&dhsurname;
2010-2012&dhusername;
&dhdate;
&dhucpackage;
&dhsection;
&dhpackage;PPP Gatekeeper is a daemon that manages PPPOE connections.&dhpackage;DESCRIPTIONPPP Gatekeeper is a daemon that manages PPPOE connections and supports various levels of redundancy and failover.OPTIONSThese programs follow the usual &gnu; command line syntax,
with long options starting with two dashes (`-'). A summary of
options is included below.Specify config file to use.Run in foreground, don't daemonize.Show summary of options.SEE ALSOpppd (8).AUTHORThis manual page was written by &author; <&dhemail;>.
Permission is granted to copy, distribute and/or modify this
document under the terms of the &gnu; General Public License,
Version 2 any later version published by the Free Software
Foundation.
On Debian systems, the complete text of the GNU General Public
License can be found in /usr/share/common-licenses/GPL.
ppp-gatekeeper-v0.1.0-201406111015/TODO 0000644 0000000 0000000 00000000053 12346026211 015173 0 ustar root root * Trigger scripts
* Poll interface stats
ppp-gatekeeper-v0.1.0-201406111015/INSTALL 0000644 0000000 0000000 00000000507 12346026211 015540 0 ustar root root Debian:
1. Install ipset & ipset module
$ apt-get install ipset ipset-source
$ module-assistent auto-install ipset
2. Short circuit ip-up
$ cat < /etc/ppp/ip-up.local
#!/bin/bash
EOF
$ chmod 0755 /etc/ppp/ip-up.local
3. For shorewall change these in /etc/shorewall/shorewall.conf
IP_FORWARDING=On
FORWARD_CLEAR_MARK=No
ppp-gatekeeper-v0.1.0-201406111015/ppp-gatekeeper.conf 0000644 0000000 0000000 00000005154 12346026211 020272 0 ustar root root #
# G L O B A L S E T T I N G S
#
[global]
# Local networks
# Comma or whitespace separated
localnets=10.0.0.0/24,192.168.0.0/16
#
# Default Route Load Balancing
#
# The default route policy to use when routing over multiple default routes.
# The format of this is PRIORITY:POLICY. Where priority would be the priority
# specified below in the use_default_route=X setting. Policy is one of the
# following values:
# rr = round robin
# random = random
# wrandon = weighted random
# atp = auto balance traffic throughput - TODO
#
# Multiple policies can be specified by separating them with a comma or
# whitespace. 7:random,9:rr,1:atp
default_route_policy=7:random
#
# C O N N E C T I O N S E T T I N G S
#
[connection1]
#
# PPPOE Settings
#
# Interface to pppoe over
interface=eth2
# PPP unit to use ppp
ppp_unit=100
# Username and password
username=myuser
password=mypass
#
# DNS
#
# Use this link as DNS if set
# The value is a priority, 1-99, higher is better
use_dns=9
#
# Explicit Routing
#
# Routing table
# Format:
# route [priority 1-99] [weight 1-99]
# Eg.
# 172.16.0.1/32
# 172.16.1.0/24 40 weight
#
# Priority defaults to 50 if not specified.
routing_table=/etc/ppp/local-routes
# Routing table exclusions
# Same format as above, weight is not supported.
routing_table_exclusions=/etc/ppp/local-routes-excl
#
# Default Routes
#
# Use this link as a default route if set. The value format is specified
# either as a priority, 1-99 (higher is better), or as a PRIORITY:WEIGHT.
# When a WEIGHT is specified this will determine how many times more traffic
# will be routed over the interface. Weight defaults to 10 if not specified.
use_default_route=7
#
# Scripts
#
# Run this before we think about starting pppd
# Paremeters passed:
# none
#init=/bin/true
# Run this when connection is up
# Parameters passed:
# $link_name $interface $remote_ip $local_ip $dns1 $dns2
#ifup=/bin/true
# Run this when connection goes down
# Parameters passed:
# $link_name $interface $remote_ip $local_ip $dns1 $dns2
#ifdown=/bin/true
# Run to bring the interface up, init argument to pppd
# Parameters passed:
# none
#ppp_init=/bin/true
# Run to connect the interface, connect argument to pppd
# Parameters passed:
# none
#ppp_connect=/bin/true
# Run to disconnect the interface, disconnect argument to pppd
# Parameters passed:
# none
#ppp_disconnect=/bin/true
#
# Traffic Shaping Options
#
# If a numeric value is specified, this will be interpreted as the max
# link speed in kbit/s. It will be used internally to set various values
# to optimize the shaping process
#
# use_shaping=4096
use_shaping=yes
ppp-gatekeeper-v0.1.0-201406111015/contrib/ 0000755 0000000 0000000 00000000000 12346026211 016145 5 ustar root root ppp-gatekeeper-v0.1.0-201406111015/contrib/ppp-gatekeeper.modprobe 0000644 0000000 0000000 00000000112 12346026211 022601 0 ustar root root options xt_recent ip_list_tot=16384 ip_pkt_list_tot=1 ip_list_hash_size=0
ppp-gatekeeper-v0.1.0-201406111015/contrib/ppp-gatekeeper.dnsmasq 0000644 0000000 0000000 00000000201 12346026211 022437 0 ustar root root # Don't forward local domains or local addies
domain-needed
bogus-priv
# Resolver config
resolv-file=/etc/ppp/resolv-pppgk.conf
ppp-gatekeeper-v0.1.0-201406111015/contrib/start.shorewall 0000644 0000000 0000000 00000000773 12346026211 021233 0 ustar root root #!/bin/bash
# If no pid file, just exit
[ ! -e "/var/run/ppp-gatekeeper/ppp-gatekeeper.pid" ] && exit 0
PPPGK=$(cat /var/run/ppp-gatekeeper/ppp-gatekeeper.pid)
if [ -e "/proc/$PPPGK/cmdline" ]
then
rm -f "/var/run/ppp-gatekeeper/ppp-gatekeeper.ipt"
kill -USR2 "$PPPGK"
i=1
while [ "$i" -lt 10 ]
do
if [ -e "/var/run/ppp-gatekeeper/ppp-gatekeeper.ipt" ]
then
. "/var/run/ppp-gatekeeper/ppp-gatekeeper.ipt"
exit 0;
fi
sleep .5;
i=$((i+1))
done
fi
# vim: ts=4
ppp-gatekeeper-v0.1.0-201406111015/ppp-gatekeeper 0000755 0000000 0000000 00000173126 12346026211 017356 0 ustar root root #!/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 .
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 = ) {
# 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 = ) {
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+/,)) {
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 = ;
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 = ) {
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(<