2007-03-15 22:55:22 +01:00
|
|
|
#
|
|
|
|
# Shorewall 3.9 -- /usr/share/shorewall/Shorewall/Providers.pm
|
|
|
|
#
|
|
|
|
# This program is under GPL [http://www.gnu.org/copyleft/gpl.htm]
|
|
|
|
#
|
|
|
|
# (c) 2007 - Tom Eastep (teastep@shorewall.net)
|
|
|
|
#
|
|
|
|
# Complete documentation is available at http://shorewall.net
|
|
|
|
#
|
|
|
|
# This program is free software; you can redistribute it and/or modify
|
|
|
|
# it under the terms of Version 2 of the GNU General Public License
|
|
|
|
# as published by the Free Software Foundation.
|
|
|
|
#
|
|
|
|
# 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, write to the Free Software
|
|
|
|
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA
|
|
|
|
#
|
|
|
|
#
|
2007-03-15 02:38:04 +01:00
|
|
|
package Shorewall::Providers;
|
|
|
|
require Exporter;
|
|
|
|
use Shorewall::Common;
|
|
|
|
use Shorewall::Config;
|
|
|
|
use Shorewall::Zones;
|
|
|
|
use Shorewall::Chains;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
our @ISA = qw(Exporter);
|
2007-03-21 18:16:47 +01:00
|
|
|
our @EXPORT = qw( setup_providers @routemarked_interfaces);
|
2007-03-15 02:38:04 +01:00
|
|
|
our @EXPORT_OK = ( );
|
|
|
|
our @VERSION = 1.00;
|
|
|
|
|
|
|
|
use constant { LOCAL_NUMBER => 255,
|
|
|
|
MAIN_NUMBER => 254,
|
|
|
|
DEFAULT_NUMBER => 253,
|
|
|
|
UNSPEC_NUMBER => 0
|
|
|
|
};
|
|
|
|
|
2007-03-21 00:13:17 +01:00
|
|
|
our %routemarked_interfaces;
|
2007-03-21 18:16:47 +01:00
|
|
|
our @routemarked_interfaces;
|
2007-03-21 00:13:17 +01:00
|
|
|
|
2007-03-15 02:38:04 +01:00
|
|
|
my $balance = 0;
|
|
|
|
my $first_default_route = 1;
|
|
|
|
|
|
|
|
|
|
|
|
my %providers = ( 'local' => { number => LOCAL_NUMBER , mark => 0 } ,
|
|
|
|
main => { number => MAIN_NUMBER , mark => 0 } ,
|
|
|
|
default => { number => DEFAULT_NUMBER , mark => 0 } ,
|
|
|
|
unspec => { number => UNSPEC_NUMBER , mark => 0 } );
|
|
|
|
|
|
|
|
my @providers;
|
|
|
|
|
|
|
|
#
|
|
|
|
# Set up marking for 'tracked' interfaces. Unline in Shorewall 3.x, we add these rules inconditionally, even if the associated interface isn't up.
|
|
|
|
#
|
|
|
|
sub setup_route_marking() {
|
|
|
|
my $mask = $config{HIGH_ROUTE_MARKS} ? '0xFFFF' : '0xFF';
|
|
|
|
my $mark_op = $config{HIGH_ROUTE_MARKS} ? '--or-mark' : '--set-mark';
|
|
|
|
|
|
|
|
add_rule $mangle_table->{PREROUTING} , "-m connmark ! --mark 0/$mask -j CONNMARK --restore-mark --mask $mask";
|
|
|
|
add_rule $mangle_table->{OUTPUT} , " -m connmark ! --mark 0/$mask -j CONNMARK --restore-mark --mask $mask";
|
|
|
|
|
|
|
|
my $chainref = new_chain 'mangle', 'routemark';
|
|
|
|
|
|
|
|
while ( my ( $interface, $mark ) = ( each %routemarked_interfaces ) ) {
|
|
|
|
add_rule $mangle_table->{PREROUTING} , "-i $interface -m mark --mark 0/$mask -j routemark";
|
|
|
|
add_rule $chainref, " -i $interface -j MARK $mark_op $mark";
|
|
|
|
}
|
|
|
|
|
|
|
|
add_rule $chainref, "-m mark ! --mark 0/$mask -j CONNMARK --save-mark --mask $mask";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub setup_providers() {
|
|
|
|
my $fn = find_file 'providers';
|
|
|
|
my $providers = 0;
|
|
|
|
|
|
|
|
sub copy_table( $$ ) {
|
|
|
|
my ( $duplicate, $number ) = @_;
|
|
|
|
|
|
|
|
emit "ip route show table $duplicate | while read net route; do";
|
|
|
|
emit ' case $net in';
|
|
|
|
emit ' default|nexthop)';
|
|
|
|
emit ' ;;';
|
|
|
|
emit ' *)';
|
|
|
|
emit " run_ip route add table $number \$net \$route";
|
|
|
|
emit ' ;;';
|
|
|
|
emit ' esac';
|
|
|
|
emit "done\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub copy_and_edit_table( $$$ ) {
|
|
|
|
my ( $duplicate, $number, $copy ) = @_;
|
|
|
|
|
|
|
|
my $match = $copy;
|
|
|
|
|
|
|
|
$match =~ s/ /\|/g;
|
|
|
|
|
|
|
|
emit "ip route show table $duplicate | while read net route; do";
|
|
|
|
emit ' case $net in';
|
|
|
|
emit ' default|nexthop)';
|
|
|
|
emit ' ;;';
|
|
|
|
emit ' *)';
|
|
|
|
emit " run_ip route add table $number \$net \$route";
|
|
|
|
emit ' case $(find_device $route) in';
|
|
|
|
emit " $match)";
|
|
|
|
emit " run_ip route add table $number \$net \$route";
|
|
|
|
emit ' ;;';
|
|
|
|
emit ' esac';
|
|
|
|
emit ' ;;';
|
|
|
|
emit ' esac';
|
|
|
|
emit "done\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub balance_default_route( $$$ ) {
|
|
|
|
my ( $weight, $gateway, $interface ) = @_;
|
|
|
|
|
|
|
|
$balance = 1;
|
|
|
|
|
|
|
|
emit '';
|
|
|
|
|
|
|
|
if ( $first_default_route ) {
|
|
|
|
if ( $gateway ) {
|
|
|
|
emit "DEFAULT_ROUTE=\"nexthop via $gateway dev $interface weight $weight\"";
|
|
|
|
} else {
|
|
|
|
emit "DEFAULT_ROUTE=\"nexthop dev $interface weight $weight\"";
|
|
|
|
}
|
|
|
|
|
|
|
|
$first_default_route = 0;
|
|
|
|
} else {
|
|
|
|
if ( $gateway ) {
|
|
|
|
emit "DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop via $gateway dev $interface weight $weight\"";
|
|
|
|
} else {
|
|
|
|
emit "DEFAULT_ROUTE=\"\$DEFAULT_ROUTE nexthop dev $interface weight $weight\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add_a_provider( $$$$$$$$ ) {
|
|
|
|
|
|
|
|
my ($table, $number, $mark, $duplicate, $interface, $gateway, $options, $copy) = @_;
|
|
|
|
|
|
|
|
fatal_error 'Providers require mangle support in your kernel and iptables' unless $capabilities{MANGLE_ENABLED};
|
|
|
|
|
|
|
|
fatal_error "Duplicate provider ( $table )" if $providers{$table};
|
|
|
|
|
|
|
|
for my $provider ( keys %providers ) {
|
|
|
|
fatal_error "Duplicate provider number ( $number )" if $providers{$provider}{number} == $number;
|
|
|
|
}
|
|
|
|
|
|
|
|
emit "#\n# Add Provider $table ($number)\n#";
|
|
|
|
|
|
|
|
emit "if interface_is_usable $interface; then";
|
|
|
|
push_indent;
|
|
|
|
my $iface = chain_base $interface;
|
|
|
|
|
|
|
|
emit "${iface}_up=Yes";
|
|
|
|
emit "qt ip route flush table $number";
|
|
|
|
emit "echo \"qt ip route flush table $number\" >> \${VARDIR}/undo_routing";
|
|
|
|
|
|
|
|
$duplicate = '-' unless $duplicate;
|
|
|
|
$copy = '-' unless $copy;
|
|
|
|
|
|
|
|
if ( $duplicate ne '-' ) {
|
|
|
|
if ( $copy ne '-' ) {
|
|
|
|
if ( $copy eq 'none' ) {
|
|
|
|
$copy = $interface;
|
|
|
|
} else {
|
|
|
|
my @c = ( split /,/, $copy );
|
|
|
|
$copy = "@c";
|
|
|
|
}
|
|
|
|
|
|
|
|
copy_and_edit_table( $duplicate, $number ,$copy );
|
|
|
|
} else {
|
|
|
|
copy_table ( $duplicate, $number );
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fatal_error 'A non-empty COPY column requires that a routing table be specified in the DUPLICATE column' if $copy ne '-';
|
|
|
|
}
|
|
|
|
|
|
|
|
$gateway = '-' unless $gateway;
|
|
|
|
|
|
|
|
if ( $gateway eq 'detect' ) {
|
|
|
|
emit "gateway=\$(detect_gateway $interface)\n";
|
|
|
|
|
|
|
|
emit 'if [ -n "$gateway" ]; then';
|
|
|
|
emit " run_ip route replace \$gateway src \$(find_first_interface_address $interface) dev $interface table $number";
|
|
|
|
emit " run_ip route add default via \$gateway dev $interface table $number";
|
|
|
|
emit 'else';
|
|
|
|
emit " fatal_error \"Unable to detect the gateway through interface $interface\"";
|
|
|
|
emit "fi\n";
|
|
|
|
} elsif ( $gateway && $gateway ne '-' ) {
|
|
|
|
emit "run_ip route replace $gateway src \$(find_first_interface_address $interface) dev $interface table $number";
|
|
|
|
emit "run_ip route add default via $gateway dev $interface table $number";
|
|
|
|
} else {
|
|
|
|
$gateway = '';
|
|
|
|
emit "run_ip route add default dev $interface table $number";
|
|
|
|
}
|
|
|
|
|
|
|
|
$mark = '-' unless $mark;
|
|
|
|
|
|
|
|
my $val = 0;
|
|
|
|
|
|
|
|
if ( $mark ne '-' ) {
|
|
|
|
|
|
|
|
$val = numeric_value $mark;
|
|
|
|
|
|
|
|
verify_mark $mark;
|
|
|
|
|
|
|
|
if ( $val < 256) {
|
|
|
|
fatal_error "Invalid Mark Value ($mark) with HIGH_ROUTE_MARKS=Yes" if $config{HIGH_ROUTE_MARKS};
|
|
|
|
} else {
|
|
|
|
fatal_error "Invalid Mark Value ($mark) with HIGH_ROUTE_MARKS=No" if ! $config{HIGH_ROUTE_MARKS};
|
|
|
|
}
|
|
|
|
|
|
|
|
for my $provider ( keys %providers ) {
|
|
|
|
my $num = $providers{$provider}{mark};
|
|
|
|
fatal_error "Duplicate mark value ( $mark )" if $num == $val;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
emit "qt ip rule del fwmark $mark";
|
|
|
|
my $pref = 10000 + $val;
|
|
|
|
emit "run_ip rule add fwmark $mark pref $pref table $number";
|
|
|
|
emit "echo \"qt ip rule del fwmark $mark\" >> \${VARDIR}/undo_routing";
|
|
|
|
}
|
|
|
|
|
|
|
|
$providers{$table} = {};
|
|
|
|
$providers{$table}{number} = $number;
|
|
|
|
$providers{$table}{mark} = $val;
|
|
|
|
|
|
|
|
my ( $loose, $optional ) = (0,0);
|
|
|
|
|
|
|
|
unless ( $options eq '-' ) {
|
|
|
|
for my $option ( split /,/, $options ) {
|
|
|
|
if ( $option eq 'track' ) {
|
|
|
|
fatal_error "Interface $interface is tracked through an earlier provider" if $routemarked_interfaces{$interface};
|
|
|
|
fatal_error "The 'track' option requires a numeric value in the MARK column - Provider \"$line\"" if $mark eq '-';
|
|
|
|
$routemarked_interfaces{$interface} = $mark;
|
2007-03-21 18:16:47 +01:00
|
|
|
push @routemarked_interfaces, $interface;
|
2007-03-15 02:38:04 +01:00
|
|
|
} elsif ( $option =~ /^balance=(\d+)/ ) {
|
|
|
|
balance_default_route $1 , $gateway, $interface;
|
|
|
|
} elsif ( $option eq 'balance' ) {
|
|
|
|
balance_default_route 1 , $gateway, $interface;
|
|
|
|
} elsif ( $option eq 'loose' ) {
|
|
|
|
$loose = 1;
|
|
|
|
} elsif ( $option eq 'optional' ) {
|
|
|
|
$optional = 1;
|
|
|
|
} else {
|
|
|
|
fatal_error "Invalid option ($option) in provider \"$line\"";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $loose ) {
|
|
|
|
my $rulebase = 20000 + ( 256 * ( $number - 1 ) );
|
|
|
|
|
|
|
|
emit "\nrulenum=0\n";
|
|
|
|
|
|
|
|
emit "find_interface_addresses $interface | while read address; do";
|
|
|
|
emit ' qt ip rule del from $address';
|
|
|
|
emit " run_ip rule add from \$address pref \$(( $rulebase + \$rulenum )) table $number";
|
|
|
|
emit " echo \"qt ip rule del from \$address\" >> \${VARDIR}/undo_routing";
|
|
|
|
emit ' rulenum=$(($rulenum + 1))';
|
|
|
|
emit 'done';
|
|
|
|
} else {
|
|
|
|
emit "\nfind_interface_addresses $interface | while read address; do";
|
|
|
|
emit ' qt ip rule del from $address';
|
|
|
|
emit 'done';
|
|
|
|
}
|
|
|
|
|
|
|
|
emit "\nprogress_message \" Provider $table ($number) Added\"\n";
|
|
|
|
|
|
|
|
pop_indent;
|
|
|
|
emit 'else';
|
|
|
|
|
|
|
|
if ( $optional ) {
|
|
|
|
emit " error_message \"WARNING: Interface $interface is not configured -- Provider $table ($number) not Added\"";
|
|
|
|
emit " ${iface}_up=";
|
|
|
|
} else {
|
|
|
|
emit " fatal_error \"ERROR: Interface $interface is not configured -- Provider $table ($number) Cannot be Added\"";
|
|
|
|
}
|
|
|
|
|
|
|
|
emit "fi\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
sub add_an_rtrule( $$$$ ) {
|
|
|
|
my ( $source, $dest, $provider, $priority ) = @_;
|
|
|
|
|
|
|
|
unless ( $providers{$provider} ) {
|
|
|
|
my $found = 0;
|
|
|
|
|
|
|
|
if ( "\L$provider" =~ /^(0x[a-f0-9]+|0[0-7]*|[0-9]*)$/ ) {
|
|
|
|
my $provider_number = numeric_value $provider;
|
|
|
|
|
|
|
|
for my $provider ( keys %providers ) {
|
|
|
|
if ( $providers{$provider}{number} == $provider_number ) {
|
|
|
|
$found = 1;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fatal_error "Unknown provider $provider in route rule \"$line\"" unless $found;
|
|
|
|
}
|
|
|
|
|
|
|
|
$source = '-' unless $source;
|
|
|
|
$dest = '-' unless $dest;
|
|
|
|
|
|
|
|
fatal_error "You must specify either the source or destination in an rt rule: \"$line\"" if $source eq '-' && $dest eq '-';
|
|
|
|
|
|
|
|
$dest = $dest eq '-' ? '' : "to $dest";
|
|
|
|
|
|
|
|
if ( $source eq '-' ) {
|
|
|
|
$source = '';
|
|
|
|
} elsif ( $source =~ /:/ ) {
|
|
|
|
( my $interface, $source ) = split /:/, $source;
|
|
|
|
$source = "iif $interface from $source";
|
|
|
|
} elsif ( $source =~ /\..*\..*/ ) {
|
|
|
|
$source = "from $source";
|
|
|
|
} else {
|
|
|
|
$source = "iif $source";
|
|
|
|
}
|
|
|
|
|
|
|
|
fatal_error "Invalid priority ($priority) in rule \"$line\"" unless $priority && $priority =~ /^\d{1,5}$/;
|
|
|
|
|
|
|
|
$priority = "priority $priority";
|
|
|
|
|
|
|
|
emit "qt ip rule del $source $dest $priority";
|
|
|
|
emit "run_ip rule add $source $dest $priority table $provider";
|
|
|
|
emit "echo \"qt ip rule del $source $dest $priority\" >> \${VARDIR}/undo_routing";
|
|
|
|
progress_message " Routing rule \"$line\" $done";
|
|
|
|
}
|
|
|
|
#
|
|
|
|
# Setup_Providers() Starts Here....
|
|
|
|
#
|
|
|
|
progress_message2 "$doing $fn ...";
|
|
|
|
|
|
|
|
emit "\nif [ -z \"\$NOROUTES\" ]; then";
|
|
|
|
push_indent;
|
|
|
|
|
|
|
|
emit "#\n# Undo any changes made since the last time that we [re]started -- this will not restore the default route\n#";
|
|
|
|
emit 'undo_routing';
|
|
|
|
emit "#\n# Save current routing table database so that it can be restored later\n#";
|
|
|
|
emit 'cp /etc/iproute2/rt_tables ${VARDIR}/';
|
|
|
|
emit "#\n# Capture the default route(s) if we don't have it (them) already.\n#";
|
|
|
|
emit '[ -f ${VARDIR}/default_route ] || ip route ls | grep -E \'^\s*(default |nexthop )\' > ${VARDIR}/default_route';
|
|
|
|
emit "#\n# Initialize the file that holds 'undo' commands\n#";
|
|
|
|
emit '> ${VARDIR}/undo_routing';
|
|
|
|
|
|
|
|
save_progress_message 'Adding Providers...';
|
|
|
|
|
|
|
|
emit 'DEFAULT_ROUTE=';
|
|
|
|
|
|
|
|
open PV, "$ENV{TMP_DIR}/providers" or fatal_error "Unable to open stripped providers file: $!";
|
|
|
|
|
|
|
|
while ( $line = <PV> ) {
|
|
|
|
chomp $line;
|
|
|
|
$line =~ s/\s+/ /g;
|
|
|
|
|
|
|
|
my ( $table, $number, $mark, $duplicate, $interface, $gateway, $options, $copy, $extra ) = split /\s+/, $line;
|
|
|
|
|
|
|
|
fatal_error "Invalid providers entry: $line" if $extra;
|
|
|
|
|
|
|
|
add_a_provider( $table, $number, $mark, $duplicate, $interface, $gateway, $options, $copy );
|
|
|
|
|
|
|
|
push @providers, $table;
|
|
|
|
|
|
|
|
$providers++;
|
|
|
|
|
|
|
|
progress_message " Provider \"$line\" $done";
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
close PV;
|
|
|
|
|
|
|
|
if ( $providers ) {
|
|
|
|
if ( $balance ) {
|
|
|
|
emit 'if [ -n "$DEFAULT_ROUTE" ]; then';
|
|
|
|
emit ' run_ip route replace default scope global $DEFAULT_ROUTE';
|
|
|
|
emit " progress_message \"Default route '\$(echo \$DEFAULT_ROUTE | sed 's/\$\\s*//')' Added\"";
|
|
|
|
emit 'else';
|
|
|
|
emit ' error_message "WARNING: No Default route added (all \'balance\' providers are down)"';
|
|
|
|
emit ' restore_default_route';
|
|
|
|
emit 'fi';
|
|
|
|
emit '';
|
|
|
|
} else {
|
|
|
|
emit "#\n# We don't have any 'balance' providers so we restore any default route that we've saved\n#";
|
|
|
|
emit 'restore_default_route';
|
|
|
|
}
|
|
|
|
|
|
|
|
emit 'cat > /etc/iproute2/rt_tables <<EOF';
|
|
|
|
emit_unindented "#\n# reserved values\n#";
|
|
|
|
emit_unindented "255\tlocal";
|
|
|
|
emit_unindented "254\tmain";
|
|
|
|
emit_unindented "253\tdefault";
|
|
|
|
emit_unindented "0\tunspec";
|
|
|
|
emit_unindented "#\n# local\n#";
|
|
|
|
emit_unindented "EOF\n";
|
|
|
|
|
|
|
|
emit 'echocommand=$(find_echo)';
|
|
|
|
emit '';
|
|
|
|
|
|
|
|
for my $table ( @providers ) {
|
|
|
|
emit "\$echocommand \"$providers{$table}{number}\\t$table\" >> /etc/iproute2/rt_tables";
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( -s "$ENV{TMP_DIR}/route_rules" ) {
|
|
|
|
my $fn = find_file 'route_rules';
|
|
|
|
progress_message2 "$doing $fn...";
|
|
|
|
|
|
|
|
emit '';
|
|
|
|
|
|
|
|
open RR, "$ENV{TMP_DIR}/route_rules" or fatal_error "Unable to open stripped route rules file: $!";
|
|
|
|
|
|
|
|
while ( $line = <RR> ) {
|
|
|
|
chomp $line;
|
|
|
|
$line =~ s/\s+/ /g;
|
|
|
|
|
|
|
|
my ( $source, $dest, $provider, $priority, $extra ) = split /\s+/, $line;
|
|
|
|
|
|
|
|
fatal_error "Invalid providers entry: $line" if $extra;
|
|
|
|
|
|
|
|
add_an_rtrule( $source, $dest, $provider , $priority );
|
|
|
|
}
|
|
|
|
|
|
|
|
close RR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
emit '';
|
|
|
|
emit 'run_ip route flush cache';
|
|
|
|
pop_indent;
|
|
|
|
emit "fi\n";
|
|
|
|
|
2007-03-21 18:16:47 +01:00
|
|
|
setup_route_marking if @routemarked_interfaces;
|
2007-03-15 02:38:04 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|