diff --git a/Shorewall/Perl/Shorewall/Chains.pm b/Shorewall/Perl/Shorewall/Chains.pm index 19b4f98b7..cdee939e1 100644 --- a/Shorewall/Perl/Shorewall/Chains.pm +++ b/Shorewall/Perl/Shorewall/Chains.pm @@ -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 # diff --git a/Shorewall/Perl/Shorewall/Providers.pm b/Shorewall/Perl/Shorewall/Providers.pm index 776c09372..c85fd2ec3 100644 --- a/Shorewall/Perl/Shorewall/Providers.pm +++ b/Shorewall/Perl/Shorewall/Providers.pm @@ -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=' and 'load=' options are mutually exclusive) if $balance > 1; + fatal_error q(The 'fallback=' and 'load=' 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}}; } diff --git a/docs/MultiISP.xml b/docs/MultiISP.xml index bd5730c84..11d083a3d 100644 --- a/docs/MultiISP.xml +++ b/docs/MultiISP.xml @@ -1300,9 +1300,8 @@ lillycat: # 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 - shorewall-rtrules (5) - or shorewall-rtrules + (5) or shorewall-tcrules (5). @@ -1354,9 +1353,15 @@ shorewall 2 2 - eth0 192.168.1.254 track,balance=2,optional< shorewall-providers (5) is available in the form of a PROBABILITY column in shorewall-tcrules (5). + url="???">shorewall-tcrules (5). This feature requires the + Statistic Match capability in your iptables and + kernel. - Here's an example: + This method works when there are two links to the same ISP where + both links have the same default gateway. + + Here's an example that sends 1/3 of the connections through + provider ComcastC and the rest through ComastB. /etc/shorewall/shorewall.conf: @@ -1372,7 +1377,7 @@ ZONE_BITS=4 - PROVIDER_OFFSET=16 and ZONE_BITS=4 means that the provider mask + PROVIDER_OFFSET=16 and ZONE_BITS=4 means that the provider mask will be 0xf0000. @@ -1380,11 +1385,10 @@ ZONE_BITS=4 #NAME NUMBER MARK DUPLICATE INTERFACE GATEWAY OPTIONS ComcastB 1 - - eth1 70.90.191.126 loose,balance -ComcastC 2 - - eth0 detect loose,balance - +ComcastC 2 - - eth0 detect loose,fallback,load=0.33333333 - The option is specified so that the + The 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. @@ -1404,41 +1408,29 @@ ComcastC 2 - - eth0 detect loose,balance - Priority = 1000 means that these rules will come before rules + Priority = 1000 means that these rules will come before rules that select a provider based on marks. - /etc/shorewall/tcrules: + As shown in the above example, this technique works best when + there are two providers. - #MARK SOURCE DEST PROTO DEST - # PORT(S) - CONTINUE - 70.90.191.120/29 - CONTINUE - 10.0.10.0/24 - CONTINUE - - tcp 80 + + + One is configured with and the other + with . + - # 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. - + + The provider with the option is + configured whith load=number where the + number has a value in the range 0 < + number <= 1. This + number 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. + +