diff --git a/Shorewall-common/tcclasses b/Shorewall-common/tcclasses index b6cad0058..44e63a103 100644 --- a/Shorewall-common/tcclasses +++ b/Shorewall-common/tcclasses @@ -6,5 +6,5 @@ # See http://shorewall.net/traffic_shaping.htm for additional information. # ############################################################################### -#INTERFACE MARK RATE CEIL PRIORITY OPTIONS +#INTERFACE:CLASS MARK RATE CEIL PRIORITY OPTIONS #LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE diff --git a/Shorewall-common/tcdevices b/Shorewall-common/tcdevices index f1a3e5363..2a93faadd 100644 --- a/Shorewall-common/tcdevices +++ b/Shorewall-common/tcdevices @@ -6,6 +6,6 @@ # See http://shorewall.net/traffic_shaping.htm for additional information. # ############################################################################### -#INTERFACE IN-BANDWITH OUT-BANDWIDTH OPTIONS REDIRECTED -# INTERFACES +#NUMBER: IN-BANDWITH OUT-BANDWIDTH OPTIONS REDIRECTED +#INTERFACE INTERFACES #LAST LINE -- ADD YOUR ENTRIES BEFORE THIS ONE -- DO NOT REMOVE diff --git a/Shorewall-perl/Shorewall/Config.pm b/Shorewall-perl/Shorewall/Config.pm index 68a0fa5f0..eab0c0628 100644 --- a/Shorewall-perl/Shorewall/Config.pm +++ b/Shorewall-perl/Shorewall/Config.pm @@ -56,6 +56,7 @@ our %EXPORT_TAGS = ( internal => [ qw( create_temp_object finalize_object numeric_value in_hex + in_hex8 emit emit_unindented save_progress_message @@ -528,6 +529,10 @@ sub in_hex( $ ) { sprintf '0x%x', $_[0]; } +sub in_hex8( $ ) { + sprintf '0x%08x', $_[0]; +} + # # Write the arguments to the object file (if any) with the current indentation. # diff --git a/Shorewall-perl/Shorewall/IPAddrs.pm b/Shorewall-perl/Shorewall/IPAddrs.pm index 0d1897231..63af9be5e 100644 --- a/Shorewall-perl/Shorewall/IPAddrs.pm +++ b/Shorewall-perl/Shorewall/IPAddrs.pm @@ -26,7 +26,7 @@ # package Shorewall::IPAddrs; require Exporter; -use Shorewall::Config qw( :DEFAULT split_list require_capability ); +use Shorewall::Config qw( :DEFAULT split_list require_capability in_hex8); use strict; @@ -39,6 +39,7 @@ our @EXPORT = qw( ALLIPv4 validate_address validate_net + decompose_net validate_host validate_range ip_range_explicit @@ -63,6 +64,40 @@ use constant { ALLIPv4 => '0.0.0.0/0' , ICMP => 1, TCP => 6, UDP => 17 , SCTP => our @rfc1918_networks = ( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" ); +our @vlsm_to_mask = ( '0x00000000' , + '0x80000000' , + '0xC0000000' , + '0xE0000000' , + '0xF0000000' , + '0xF8000000' , + '0xFC000000' , + '0xFE000000' , + '0xFF000000' , + '0xFF800000' , + '0xFFC00000' , + '0xFFE00000' , + '0xFFF00000' , + '0xFFF80000' , + '0xFFFC0000' , + '0xFFFE0000' , + '0xFFFF0000' , + '0xFFFF8000' , + '0xFFFFC000' , + '0xFFFFE000' , + '0xFFFFF000' , + '0xFFFFF800' , + '0xFFFFFC00' , + '0xFFFFFE00' , + '0xFFFFFF00' , + '0xFFFFFF80' , + '0xFFFFFFC0' , + '0xFFFFFFE0' , + '0xFFFFFFF0' , + '0xFFFFFFF8' , + '0xFFFFFFFC' , + '0xFFFFFFFE' , + '0xFFFFFFFF' ); + sub valid_address( $ ) { my $address = $_[0]; @@ -96,23 +131,6 @@ sub validate_address( $$ ) { defined wantarray ? wantarray ? @addrs : $addrs[0] : undef; } -sub validate_net( $$ ) { - my ($net, $vlsm, $rest) = split( '/', $_[0], 3 ); - my $allow_name = $_[1]; - - fatal_error "Missing address" if $net eq ''; - fatal_error "An ipset name ($net) is not allowed in this context" if substr( $net, 0, 1 ) eq '+'; - - if ( defined $vlsm ) { - fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= 32; - fatal_error "Invalid Network address ($_[0])" if defined $rest; - fatal_error "Invalid IP address ($net)" unless valid_address $net; - } else { - fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net; - validate_address $net, $_[1]; - } -} - sub decodeaddr( $ ) { my $address = $_[0]; @@ -139,6 +157,33 @@ sub encodeaddr( $ ) { $result; } +sub validate_net( $$ ) { + my ($net, $vlsm, $rest) = split( '/', $_[0], 3 ); + my $allow_name = $_[1]; + + fatal_error "Missing address" if $net eq ''; + fatal_error "An ipset name ($net) is not allowed in this context" if substr( $net, 0, 1 ) eq '+'; + + if ( defined $vlsm ) { + fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= 32; + fatal_error "Invalid Network address ($_[0])" if defined $rest; + fatal_error "Invalid IP address ($net)" unless valid_address $net; + } else { + fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net; + validate_address $net, $_[1]; + $vlsm = 32; + } + + if ( defined wantarray ) { + fatal_error "Internal Error in validate_net()" if $allow_name; + if ( wantarray ) { + ( decodeaddr( $net ) , $vlsm ); + } else { + "$net/$vlsm"; + } + } +} + sub validate_range( $$ ) { my ( $low, $high ) = @_; @@ -178,6 +223,17 @@ sub ip_range_explicit( $ ) { @result; } +sub decompose_net( $ ) { + my $net = $_[0]; + + return ( qw/0x00000000 0x00000000/ ) if $net eq '-'; + + ( $net, my $vlsm ) = validate_net( $net , 0 ); + + ( in_hex8( $net ) , $vlsm_to_mask[ $vlsm ] ); + +} + sub validate_host( $$ ) { my ( $host, $allow_name ) = $_[0]; diff --git a/Shorewall-perl/Shorewall/Tc.pm b/Shorewall-perl/Shorewall/Tc.pm index 6232789c3..f20572f6b 100644 --- a/Shorewall-perl/Shorewall/Tc.pm +++ b/Shorewall-perl/Shorewall/Tc.pm @@ -30,6 +30,7 @@ package Shorewall::Tc; require Exporter; use Shorewall::Config qw(:DEFAULT :internal); +use Shorewall::IPAddrs; use Shorewall::Zones; use Shorewall::Chains qw(:DEFAULT :internal); use Shorewall::Providers; @@ -119,17 +120,21 @@ our @deferred_rules; # # %tcdevices { -> {in_bandwidth => , # out_bandwidth => , -# number => , +# number => , # default => } # our @tcdevices; our %tcdevices; +our @devnums; +our $devnum; + # # TCClasses Table # # %tcclasses { device => , # mark => , +# number => , # rate => , # ceiling => , # priority => , @@ -141,8 +146,6 @@ our %tcdevices; our @tcclasses; our %tcclasses; -our $prefix; - our %restrictions = ( tcpre => PREROUTE_RESTRICT , tcpost => POSTROUTE_RESTRICT , tcfor => NO_RESTRICT , @@ -163,7 +166,8 @@ sub initialize() { %tcdevices = (); @tcclasses = (); %tcclasses = (); - $prefix = '1'; + @devnums = (); + $devnum = 0; } INIT { @@ -321,8 +325,29 @@ sub calculate_quantum( $$ ) { sub validate_tc_device( $$$$$ ) { my ( $device, $inband, $outband , $options , $redirected ) = @_; - fatal_error "Duplicate device ($device)" if $tcdevices{$device}; - fatal_error "Invalid device name ($device)" if $device =~ /[:+]/; + my $devnumber; + + if ( $device =~ /:/ ) { + ( my $number, $device, my $rest ) = split /:/, $device, 3; + + fatal_error "Invalid NUMBER:INTERFACE ($device:$number:$rest)" if defined $rest; + + if ( defined $number ) { + $devnumber = numeric_value( $number ); + fatal_error "Invalid interface NUMBER ($number)" unless defined $devnumber && $devnumber; + fatal_error "Duplicate interface number ($number)" if defined $devnums[ $devnumber ]; + $devnum = $devnumber if $devnumber > $devnum; + } else { + fatal_error "Missing interface NUMBER"; + } + } else { + $devnumber = ++$devnum; + } + + $devnums[ $devnumber ] = $device; + + fatal_error "Duplicate INTERFACE ($device)" if $tcdevices{$device}; + fatal_error "Invalid INTERFACE name ($device)" if $device =~ /[:+]/; my $classify = 0; @@ -336,24 +361,20 @@ sub validate_tc_device( $$$$$ ) { } } - $inband = rate_to_kbit( $inband ); - my @redirected = (); @redirected = split_list( $redirected , 'device' ) if defined $redirected && $redirected ne '-'; - fatal_error "IN-BANDWIDTH must be zero for IFB devides" if @redirected && $inband; - for my $rdevice ( @redirected ) { fatal_error "Invalid device name ($rdevice)" if $rdevice =~ /[:+]/; my $rdevref = $tcdevices{$rdevice}; fatal_error "REDIRECTED device ($rdevice) has not been defined in this file" unless $rdevref; fatal_error "IN-BANDWIDTH must be zero for REDIRECTED devices" if $rdevref->{in_bandwidth} ne '0kbit'; - fatal_error "IFB may not be redirected" if @{$rdevref->{redirected}}; } - $tcdevices{$device} = { in_bandwidth => $inband . 'kbit' , + $tcdevices{$device} = { in_bandwidth => rate_to_kbit( $inband ) . 'kbit' , out_bandwidth => rate_to_kbit( $outband ) . 'kbit' , + number => $devnumber, classify => $classify , redirected => \@redirected }; @@ -375,8 +396,27 @@ sub convert_rate( $$ ) { "${rate}kbit"; } +sub dev_by_number( $ ) { + my $dev = $_[0]; + my $devnum = numeric_value( $dev ); + my $devref; + + if ( defined $devnum ) { + $dev = $devnums[ $devnum ]; + fatal_error "Undefined INTERFACE number ($_[0])" unless defined $dev; + $devref = $tcdevices{$dev}; + fatal_error "Internal Error in dev_by_number()" unless $devref; + } else { + $devref = $tcdevices{$dev}; + fatal_error "Unknown INTERFACE ($dev)" unless $devref; + } + + ( $dev , $devref ); + +} + sub validate_tc_class( $$$$$$ ) { - my ( $device, $mark, $rate, $ceil, $prio, $options ) = @_; + my ( $devclass, $mark, $rate, $ceil, $prio, $options ) = @_; my %tosoptions = ( 'tos-minimize-delay' => 'tos=0x10/0x10' , 'tos-maximize-throughput' => 'tos=0x08/0x08' , @@ -384,25 +424,61 @@ sub validate_tc_class( $$$$$$ ) { 'tos-minimize-cost' => 'tos=0x02/0x02' , 'tos-normal-service' => 'tos=0x00/0x1e' ); - my $devref = $tcdevices{$device}; - fatal_error "Unknown Device ($device)" unless $devref; + my $classnumber = 0; + my $devref; + my $device = $devclass; + + if ( $devclass =~ /:/ ) { + ( $device, my ($number, $rest ) ) = split /:/, $device, 3; + fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $rest; + + ( $device , $devref) = dev_by_number( $device ); + + if ( defined $number ) { + if ( $devref->{classify} ) { + $classnumber = numeric_value( $number ); + fatal_error "Invalid interface NUMBER ($number)" unless defined $classnumber && $classnumber; + fatal_error "Duplicate interface/class number ($number)" if defined $devnums[ $classnumber ]; + } else { + warning_message "Class NUMBER ignored -- INTERFACE $device does not have the 'classify' option"; + } + } else { + fatal_error "Missing interface NUMBER"; + } + } else { + ($device, $devref ) = dev_by_number( $device ); + fatal_error "Missing class NUMBER" if $devref->{classify}; + } + my $full = rate_to_kbit $devref->{out_bandwidth}; $tcclasses{$device} = {} unless $tcclasses{$device}; my $tcref = $tcclasses{$device}; + + my $markval; - fatal_error "Invalid Mark ($mark)" unless $mark =~ /^([0-9]+|0x[0-9a-f]+)$/ && numeric_value( $mark ) <= 0xff; + if ( $mark ne '-' ) { + if ( $devref->{classify} ) { + warning_message "INTERFACE $device has the 'classify' option - MARK value ($mark) ignored"; + } else { + fatal_error "Invalid Mark ($mark)" unless $mark =~ /^([0-9]+|0x[0-9a-f]+)$/ && numeric_value( $mark ) <= 0xff; - my $markval = numeric_value( $mark ); - fatal_error "Duplicate Mark ($mark)" if $tcref->{$markval}; + $markval = numeric_value( $mark ); + fatal_error "Duplicate MARK ($mark)" if $tcref->{$classnumber}; + $classnumber = $devnum + $mark; + } + } else { + fatal_error "Missing MARK" unless $devref->{classify}; + fatal_error "Duplicate Class NUMBER ($classnumber)" if $tcref->{$classnumber}; + } - $tcref->{$markval} = { tos => [] , - rate => convert_rate( $full, $rate ) , - ceiling => convert_rate( $full, $ceil ) , - priority => $prio eq '-' ? 1 : $prio - }; + $tcref->{$classnumber} = { tos => [] , + rate => convert_rate( $full, $rate ) , + ceiling => convert_rate( $full, $ceil ) , + priority => $prio eq '-' ? 1 : $prio + }; - $tcref = $tcref->{$markval}; + $tcref = $tcref->{$classnumber}; unless ( $options eq '-' ) { for my $option ( split_list "\L$options", 'option' ) { @@ -412,7 +488,7 @@ sub validate_tc_class( $$$$$$ ) { if ( $option eq 'default' ) { fatal_error "Only one default class may be specified for device $device" if $devref->{default}; - $devref->{default} = $markval; + $devref->{default} = $classnumber; } elsif ( $option eq 'tcp-ack' ) { $tcref->{tcp_ack} = 1; } elsif ( $option =~ /^tos=0x[0-9a-f]{2}$/ ) { @@ -427,10 +503,71 @@ sub validate_tc_class( $$$$$$ ) { } } - push @tcclasses, "$device:$markval"; + push @tcclasses, "$device:$classnumber"; progress_message " Tcclass \"$currentline\" $done."; } +sub process_tc_filter( $$$$$$ ) { + my ($devclass , $source, $dest , $proto, $port , $sport ) = @_; + + my ($device, $class, $rest ) = split /:/, $devclass, 3; + + fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $rest || ! ($device && $class ); + + ( $device , my $devref ) = dev_by_number( $device ); + + my $tcref = $tcclasses{$device}; + + fatal_error "No Classes were defined for INTERFACE $device" unless $tcref; + + $tcref = $tcref->{$class}; + + fatal_error "Unknown CLASS ($class)" unless $tcref; + + my $rule = "filter add dev $device protocol ip parent $devref->{number}:0 pref 10 u32"; + + my ( $net , $mask ) = decompose_net( $source ); + + $rule .= "\\\n match u32 $net $mask at 12" unless $mask eq '0x00000000'; + + ( $net , $mask ) = decompose_net( $dest ); + + $rule .= "\\\n match u32 $net $mask at 16" unless $mask eq '0x00000000'; + + my $protonumber = 0; + + unless ( $proto eq '-' ) { + $protonumber = resolve_proto $proto; + fatal_error "Unknown PROTO ($proto)" unless defined $protonumber; + + $rule .= "\\\n match u8 $protonumber 0xFF at 9"; + } + + unless ( $port eq '-' ) { + fatal_error "Only TCP, UDP and SCTP may specify DEST PORT" + unless $protonumber == TCP || $protonumber == UDP || $protonumber == SCTP; + my $portnumber = in_hex8 validate_port( $protonumber , $port ); + + $rule .= "\\\n match u32 $portnumber 0x0000ffff at nexthdr+0"; + } + + unless ( $sport eq '-' ) { + fatal_error "Only TCP, UDP and SCTP may specify SOURCE PORT" + unless $protonumber == TCP || $protonumber == UDP || $protonumber == SCTP; + my $portnumber = in_hex8 validate_port( $protonumber , $sport ); + + $portnumber =~ s/0x0000/0x/; + + $rule .= "\\\n match u32 ${portnumber}0000 0xffff0000 at nexthdr+0"; + } + + emit( "run_tc $rule\\" , + " flowid $devref->{number}:$class" , + '' ); + + progress_message " TC Filter \"$currentline\" $done"; +} + sub setup_traffic_shaping() { save_progress_message "Setting up Traffic Control..."; @@ -448,6 +585,8 @@ sub setup_traffic_shaping() { } } + $devnum = $devnum > 10 ? 1000 : 100; + $fn = open_file 'tcclasses'; if ( $fn ) { @@ -461,16 +600,11 @@ sub setup_traffic_shaping() { } } - my $devnum = 1; - - $prefix = '10' if @tcdevices > 10; - for my $device ( @tcdevices ) { my $dev = chain_base( $device ); my $devref = $tcdevices{$device}; my $defmark = $devref->{default} || 0; - - $defmark = "${prefix}${defmark}" if $defmark; + my $devnum = $devref->{number}; emit "if interface_is_up $device; then"; @@ -491,15 +625,14 @@ sub setup_traffic_shaping() { emit ( "run_tc qdisc add dev $device handle ffff: ingress", "run_tc filter add dev $device parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ${inband}kbit burst 10k drop flowid :1" ); + } elsif ( @{$devref->{redirected}} ) { + emit ( "run_tc qdisc add dev $device handle ffff: ingress" ); } for my $rdev ( @{$devref->{redirected}} ) { - emit ( "run_tc qdisc add dev $rdev handle ffff: ingress" ); emit( "run_tc filter add dev $rdev parent ffff: protocol ip u32 match u32 0 0 action mirred egress redirect dev $device" ); } - $devref->{number} = $devnum++; - save_progress_message_short " TC Device $device defined."; pop_indent; @@ -518,8 +651,8 @@ sub setup_traffic_shaping() { my ( $device, $mark ) = split /:/, $class; my $devref = $tcdevices{$device}; my $tcref = $tcclasses{$device}{$mark}; - my $devnum = $devref->{number}; - my $classid = join( '', $devnum, ':', $prefix, $mark); + my $devicenumber = $devref->{number}; + my $classid = join( '', $devicenumber, ':', $mark); my $rate = $tcref->{rate}; my $quantum = calculate_quantum $rate, calculate_r2q( $devref->{out_bandwidth} ); my $dev = chain_base $device; @@ -539,12 +672,12 @@ sub setup_traffic_shaping() { emit ( "[ \$${dev}_mtu -gt $quantum ] && quantum=\$${dev}_mtu || quantum=$quantum", "run_tc class add dev $device parent $devref->{number}:1 classid $classid htb rate $rate ceil $tcref->{ceiling} prio $tcref->{priority} \$${dev}_mtu1 quantum \$quantum", - "run_tc qdisc add dev $device parent $classid handle ${prefix}${mark}: sfq perturb 10" + "run_tc qdisc add dev $device parent $classid handle ${mark}: sfq perturb 10" ); # # add filters # - emit "run_tc filter add dev $device protocol ip parent $devnum:0 prio 1 handle $mark fw classid $classid" unless $devref->{classify}; + emit "run_tc filter add dev $device protocol ip parent $devicenumber:0 prio 1 handle $mark fw classid $classid" unless $devref->{classify}; # #options # @@ -552,7 +685,7 @@ sub setup_traffic_shaping() { for my $tospair ( @{$tcref->{tos}} ) { my ( $tos, $mask ) = split q(/), $tospair; - emit "run_tc filter add dev $device parent $devnum:0 protocol ip prio 10 u32 match ip tos $tos $mask flowid $classid"; + emit "run_tc filter add dev $device parent $devicenumber:0 protocol ip prio 10 u32 match ip tos $tos $mask flowid $classid"; } save_progress_message_short qq(" TC Class $class defined."); @@ -563,6 +696,19 @@ sub setup_traffic_shaping() { pop_indent; emit "fi\n"; } + + $fn = open_file 'tcfilters'; + + if ( $fn ) { + first_entry "$doing $fn..."; + + while ( read_a_line ) { + + my ( $devclass, $source, $dest, $proto, $port, $sport ) = split_line 2, 6, 'tcfilters file'; + + process_tc_filter( $devclass, $source, $dest, $proto, $port, $sport ); + } + } } #