Implement 'load=<load-factor>' in providers file.

Signed-off-by: Tom Eastep <teastep@shorewall.net>
This commit is contained in:
Tom Eastep 2012-01-13 16:37:05 -08:00
parent 531474592c
commit 7316a2c51a
3 changed files with 135 additions and 67 deletions

View File

@ -138,6 +138,7 @@ our %EXPORT_TAGS = (
snat_chain
ecn_chain
notrack_chain
load_chain
first_chains
option_chains
reserved_name
@ -666,11 +667,19 @@ sub set_rule_option( $$$ ) {
my $opttype = $opttype{$option} || MATCH;
if ( exists $ruleref->{$option} ) {
assert( defined $ruleref->{$option} );
assert( defined( my $value1 = $ruleref->{$option} ) );
if ( $opttype == MATCH ) {
assert( $globals{KLUDGEFREE} );
$ruleref->{$option} = [ $ruleref->{$option} ] unless reftype $ruleref->{$option};
unless ( reftype $value1 ) {
unless ( reftype $value ) {
return if $value1 eq $value;
}
$ruleref->{$option} = [ $ruleref->{$option} ];
}
push @{$ruleref->{$option}}, ( reftype $value ? @$value : $value );
} elsif ( $opttype == EXCLUSIVE ) {
$ruleref->{$option} .= ",$value";
@ -1775,6 +1784,13 @@ sub notrack_chain( $ )
$_[0] . '_notrk';
}
#
# Load Chain for a provider
#
sub load_chain( $ ) {
'~' . $_[0];
}
#
# SNAT Chain to an interface
#

View File

@ -53,6 +53,7 @@ my @routemarked_providers;
my %routemarked_interfaces;
our @routemarked_interfaces;
my %provider_interfaces;
my @load_providers;
my $balancing;
my $fallback;
@ -86,6 +87,7 @@ sub initialize( $ ) {
%routemarked_interfaces = ();
@routemarked_interfaces = ();
%provider_interfaces = ();
@load_providers = ();
$balancing = 0;
$fallback = 0;
$first_default_route = 1;
@ -110,33 +112,72 @@ sub setup_route_marking() {
add_ijump $mangle_table->{$_} , j => 'CONNMARK', targetopts => "--restore-mark --mask $mask", connmark => "! --mark 0/$mask" for qw/PREROUTING OUTPUT/;
my $chainref = new_chain 'mangle', 'routemark';
my $chainref1 = new_chain 'mangle', 'setsticky';
my $chainref2 = new_chain 'mangle', 'setsticko';
my %marked_interfaces;
if ( @routemarked_providers ) {
my $chainref1 = new_chain 'mangle', 'setsticky';
my $chainref2 = new_chain 'mangle', 'setsticko';
for my $providerref ( @routemarked_providers ) {
my $interface = $providerref->{interface};
my $physical = $providerref->{physical};
my $mark = $providerref->{mark};
my %marked_interfaces;
unless ( $marked_interfaces{$interface} ) {
add_ijump $mangle_table->{PREROUTING} , j => $chainref, i => $physical, mark => "--mark 0/$mask";
add_ijump $mangle_table->{PREROUTING} , j => $chainref1, i => "! $physical", mark => "--mark $mark/$mask";
add_ijump $mangle_table->{OUTPUT} , j => $chainref2, mark => "--mark $mark/$mask";
$marked_interfaces{$interface} = 1;
for my $providerref ( @routemarked_providers ) {
my $interface = $providerref->{interface};
my $physical = $providerref->{physical};
my $mark = $providerref->{mark};
unless ( $marked_interfaces{$interface} ) {
add_ijump $mangle_table->{PREROUTING} , j => $chainref, i => $physical, mark => "--mark 0/$mask";
add_ijump $mangle_table->{PREROUTING} , j => $chainref1, i => "! $physical", mark => "--mark $mark/$mask";
add_ijump $mangle_table->{OUTPUT} , j => $chainref2, mark => "--mark $mark/$mask";
$marked_interfaces{$interface} = 1;
}
if ( $providerref->{shared} ) {
add_commands( $chainref, qq(if [ -n "$providerref->{mac}" ]; then) ), incr_cmd_level( $chainref ) if $providerref->{optional};
add_ijump $chainref, j => 'MARK', targetopts => "--set-mark $providerref->{mark}", imatch_source_dev( $interface ), mac => "--mac-source $providerref->{mac}";
decr_cmd_level( $chainref ), add_commands( $chainref, "fi\n" ) if $providerref->{optional};
} else {
add_ijump $chainref, j => 'MARK', targetopts => "--set-mark $providerref->{mark}", imatch_source_dev( $interface );
}
}
if ( $providerref->{shared} ) {
add_commands( $chainref, qq(if [ -n "$providerref->{mac}" ]; then) ), incr_cmd_level( $chainref ) if $providerref->{optional};
add_ijump $chainref, j => 'MARK', targetopts => "--set-mark $providerref->{mark}", imatch_source_dev( $interface ), mac => "--mac-source $providerref->{mac}";
decr_cmd_level( $chainref ), add_commands( $chainref, "fi\n" ) if $providerref->{optional};
} else {
add_ijump $chainref, j => 'MARK', targetopts => "--set-mark $providerref->{mark}", imatch_source_dev( $interface );
}
add_ijump $chainref, j => 'CONNMARK', targetopts => "--save-mark --mask $mask", mark => "! --mark 0/$mask";
}
add_ijump $chainref, j => 'CONNMARK', targetopts => "--save-mark --mask $mask", mark => "! --mark 0/$mask";
if ( @load_providers ) {
my $chainref1 = new_chain 'mangle', 'balance';
my @match;
add_ijump $chainref, g => $chainref1, mark => "--mark 0/$mask";
add_ijump $mangle_table->{OUTPUT}, j => $chainref1, state_imatch( 'NEW,RELATED' ), mark => "--mark 0/$mask";
for my $providerref ( @load_providers ) {
my $chainref2 = new_chain( 'mangle', load_chain( $providerref->{provider} ) );
dont_optimize $chainref2;
dont_move $chainref2;
dont_delete $chainref2;
if ( $providerref->{optional} ) {
my $dev = chain_base $providerref->{physical};
my $base = uc $dev;
add_commands( $chainref2, qq(if [ -n "\$SW_${base}_IS_USABLE" ]; then) );
incr_cmd_level $chainref2;
}
add_ijump( $chainref2, j => 'MARK', targetopts => "--set-mark $providerref->{mark}/$globals{PROVIDER_MASK}", statistic => "--mode random --probability $providerref->{load}" );
if ( $providerref->{optional} ) {
add_commands( $chainref2 , 'fi' );
decr_cmd_level( $chainref2 );
}
add_ijump ( $chainref1,
j => $chainref2 ,
mark => "--mark 0/$mask" );
}
}
}
sub copy_table( $$$ ) {
@ -366,8 +407,8 @@ sub process_a_provider() {
$gateway = '';
}
my ( $loose, $track, $balance , $default, $default_balance, $optional, $mtu, $local ) =
(0, $config{TRACK_PROVIDERS}, 0 , 0, $config{USE_DEFAULT_RT} ? 1 : 0, interface_is_optional( $interface ), '' , 0 );
my ( $loose, $track, $balance , $default, $default_balance, $optional, $mtu, $local , $load ) =
(0, $config{TRACK_PROVIDERS}, 0 , 0, $config{USE_DEFAULT_RT} ? 1 : 0, interface_is_optional( $interface ), '' , 0 , 0 );
unless ( $options eq '-' ) {
for my $option ( split_list $options, 'option' ) {
@ -408,6 +449,9 @@ sub process_a_provider() {
$local = 1;
$track = 0 if $config{TRACK_PROVIDERS};
$default_balance = 0 if $config{USE_DEFAULT_RT};
} elsif ( $option =~ /^load=(0?\.\d{1,8})/ ) {
$load = $1;
require_capability 'STATISTIC_MATCH', "load=$load", 's';
} else {
fatal_error "Invalid option ($option)";
}
@ -416,6 +460,11 @@ sub process_a_provider() {
fatal_error q(The 'balance' and 'fallback' options are mutually exclusive) if $balance && $default;
if ( $load ) {
fatal_error q(The 'balance=<weight>' and 'load=<load-factor>' options are mutually exclusive) if $balance > 1;
fatal_error q(The 'fallback=<weight>' and 'load=<load-factor>' options are mutually exclusive) if $default > 1;
}
if ( $local ) {
fatal_error "GATEWAY not valid with 'local' provider" unless $gatewaycase eq 'none';
fatal_error "'track' not valid with 'local'" if $track;
@ -488,6 +537,7 @@ sub process_a_provider() {
duplicate => $duplicate ,
address => $address ,
local => $local ,
load => $load ,
rules => [] ,
routes => [] ,
};
@ -506,6 +556,8 @@ sub process_a_provider() {
push @routemarked_providers, $providers{$table};
}
push @load_providers, $providers{$table} if $load;
push @providers, $table;
progress_message " Provider \"$currentline\" $done";
@ -537,6 +589,8 @@ sub add_a_provider( $$ ) {
my $duplicate = $providerref->{duplicate};
my $address = $providerref->{address};
my $local = $providerref->{local};
my $load = $providerref->{load};
my $dev = chain_base $physical;
my $base = uc $dev;
my $realm = '';
@ -671,10 +725,10 @@ sub add_a_provider( $$ ) {
}
emit( '' );
my ( $tbl, $weight );
if ( $optional ) {
if ( $optional ) {
emit( 'if [ $COMMAND = enable ]; then' );
push_indent;
@ -703,6 +757,9 @@ sub add_a_provider( $$ ) {
$weight = 1;
}
emit( 'run_iptables -t mangle -A ' . load_chain( $table ) . ' -m statistic --mode random --probability ' . $load,
'' ) if $load;
unless ( $shared ) {
emit( "setup_${dev}_tc" ) if $tcdevices->{$interface};
}
@ -784,6 +841,9 @@ sub add_a_provider( $$ ) {
emit (". $undo",
"> $undo" );
emit( '',
'run_iptables -t mangle -X ' . load_chain( $table ) ) if $load;
unless ( $shared ) {
emit( '',
"qt \$TC qdisc del dev $physical root",
@ -1232,7 +1292,7 @@ sub setup_providers() {
pop_indent;
emit "fi\n";
setup_route_marking if @routemarked_interfaces;
setup_route_marking if @routemarked_interfaces || @load_providers;
} else {
emit "\nif [ -z \"\$g_noroutes\" ]; then";
@ -1504,7 +1564,7 @@ sub handle_stickiness( $ ) {
}
}
if ( @routemarked_providers ) {
if ( @routemarked_providers || @load_providers ) {
delete_jumps $mangle_table->{PREROUTING}, $setstickyref unless @{$setstickyref->{rules}};
delete_jumps $mangle_table->{OUTPUT}, $setstickoref unless @{$setstickoref->{rules}};
}

View File

@ -1300,9 +1300,8 @@ lillycat: #</programlisting>
<para>Although 'balance' is automatically assumed when
USE_DEFAULT_RT=Yes, you can easily cause all traffic to use one provider
except when you explicitly direct it to use the other provider via
<ulink
url="manpages/shorewall-rtrules.html">shorewall-rtrules</ulink> (5)
or <ulink
<ulink url="manpages/shorewall-rtrules.html">shorewall-rtrules</ulink>
(5) or <ulink
url="manpages/shorewall-tcrules.html">shorewall-tcrules</ulink>
(5).</para>
@ -1354,9 +1353,15 @@ shorewall 2 2 - eth0 192.168.1.254 track,balance=2,optional<
<ulink
url="manpages/shorewall-providers.html">shorewall-providers</ulink> (5)
is available in the form of a PROBABILITY column in <ulink
url="???">shorewall-tcrules</ulink> (5).</para>
url="???">shorewall-tcrules</ulink> (5). This feature requires the
<firstterm>Statistic Match</firstterm> capability in your iptables and
kernel.</para>
<para>Here's an example:</para>
<para>This method works when there are two links to the same ISP where
both links have the same default gateway.</para>
<para>Here's an example that sends 1/3 of the connections through
provider ComcastC and the rest through ComastB.</para>
<para><filename>/etc/shorewall/shorewall.conf</filename>:</para>
@ -1372,7 +1377,7 @@ ZONE_BITS=4
</programlisting>
<note>
<para> PROVIDER_OFFSET=16 and ZONE_BITS=4 means that the provider mask
<para>PROVIDER_OFFSET=16 and ZONE_BITS=4 means that the provider mask
will be 0xf0000.</para>
</note>
@ -1380,11 +1385,10 @@ ZONE_BITS=4
<programlisting>#NAME NUMBER MARK DUPLICATE INTERFACE GATEWAY OPTIONS
ComcastB 1 - - eth1 70.90.191.126 loose,balance
ComcastC 2 - - eth0 detect loose,balance
</programlisting>
ComcastC 2 - - eth0 detect loose,fallback,load=0.33333333</programlisting>
<note>
<para> The <option>loose</option> option is specified so that the
<para>The <option>loose</option> option is specified so that the
compiler will not generate and rules based on interface IP addresses.
That way we have complete control over the priority of such rules
through entries in the rtrules file.</para>
@ -1404,41 +1408,29 @@ ComcastC 2 - - eth0 detect loose,balance
</note>
<note>
<para> Priority = 1000 means that these rules will come before rules
<para>Priority = 1000 means that these rules will come before rules
that select a provider based on marks.</para>
</note>
<para><filename>/etc/shorewall/tcrules</filename>:</para>
<para>As shown in the above example, this technique works best when
there are two providers.</para>
<programlisting> #MARK SOURCE DEST PROTO DEST
# PORT(S)
CONTINUE - 70.90.191.120/29
CONTINUE - 10.0.10.0/24
CONTINUE - - tcp 80
<orderedlist>
<listitem>
<para>One is configured with <option>balance</option> and the other
with <option>fallback</option>.</para>
</listitem>
# 70.90.191.120/29 is the local public subnet. 10.0.10.0/24 is a
# local network on eth1. We don't want to mark TCP 80, because
# we run a transparent proxy on the firewall.
0X10000/0xf0000 eth2 - ; probability=0.66666667
0x20000/0xf0000 eth2 - ; test=0/0x30000
# The above two split traffic entering the firewall through eth2
# (local LAN) between the two providers with 2/3 of the traffic
# going to eth1 and 1/3 going to eth0.
CONTINUE fw:70.90.191.120/29
CONTINUE fw 172.20.1.0/22
CONTINUE fw 70.90.191.120/29
CONTINUE fw 10.0.10.0/24
# Similar to rules above
0X10000/0xf0000 fw - ; probability=0.66666667
0x20000/0xf0000 fw - ; test=0/0x30000
# Again, split traffic from the firewall 2:1 in favor of eth1.
</programlisting>
<listitem>
<para>The provider with the <option>fallback</option> option is
configured whith load=<replaceable>number</replaceable> where the
<replaceable>number</replaceable> has a value in the range 0 &lt;
<replaceable>number</replaceable> &lt;= 1. This
<replaceable>number</replaceable> defines the probability that each
new connection will be sent to the fallback provider and may have up
to 8 digits to the right of the decimal point.</para>
</listitem>
</orderedlist>
</section>
<section id="LinkMonitor">