# # Shorewall 4.4 -- /usr/share/shorewall/Shorewall/Tc.pm # # This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt] # # (c) 2007,2008,2009,2010 - Tom Eastep (teastep@shorewall.net) # # Traffic Control is from tc4shorewall Version 0.5 # (c) 2005 Arne Bernin # Modified by Tom Eastep for integration into the Shorewall distribution # published under GPL Version 2# # # 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. # # This module deals with Traffic Shaping and the tcrules file. # 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; use strict; our @ISA = qw(Exporter); our @EXPORT = qw( setup_tc ); our @EXPORT_OK = qw( process_tc_rule initialize ); our $VERSION = '4.4_19'; our %tcs = ( T => { chain => 'tcpost', connmark => 0, fw => 1, fwi => 0, } , CT => { chain => 'tcpost' , target => 'CONNMARK --set-mark' , connmark => 1 , fw => 1 , fwi => 0, } , C => { target => 'CONNMARK --set-mark' , connmark => 1 , fw => 1 , fwi => 1 , } , P => { chain => 'tcpre' , connmark => 0 , fw => 0 , fwi => 0 , } , CP => { chain => 'tcpre' , target => 'CONNMARK --set-mark' , connmark => 1 , fw => 0 , fwi => 0 , } , F => { chain => 'tcfor' , connmark => 0 , fw => 0 , fwi => 0 , } , CF => { chain => 'tcfor' , connmark => 1 , fw => 0 , fwi => 0 , } , ); use constant { NOMARK => 0 , SMALLMARK => 1 , HIGHMARK => 2 }; our %flow_keys = ( 'src' => 1, 'dst' => 1, 'proto' => 1, 'proto-src' => 1, 'proto-dst' => 1, 'iif' => 1, 'priority' => 1, 'mark' => 1, 'nfct' => 1, 'nfct-src' => 1, 'nfct-dst' => 1, 'nfct-proto-src' => 1, 'nfct-proto-dst' => 1, 'rt-classid' => 1, 'sk-uid' => 1, 'sk-gid' => 1, 'vlan-tag' => 1 ); our %tosoptions = ( 'tos-minimize-delay' => '0x10/0x10' , 'tos-maximize-throughput' => '0x08/0x08' , 'tos-maximize-reliability' => '0x04/0x04' , 'tos-minimize-cost' => '0x02/0x02' , 'tos-normal-service' => '0x00/0x1e' ); our %classids; our @deferred_rules; # # Perl version of Arn Bernin's 'tc4shorewall'. # # TCDevices Table # # %tcdevices { => {in_bandwidth => , # out_bandwidth => , # number => , # classify => 0|1 # tablenumber => # default => # redirected => [ , , ... ] # nextclass => # occurs => Has one or more occurring classes # qdisc => htb|hfsc # guarantee => # name => # } # our @tcdevices; our %tcdevices; our @devnums; our $devnum; our $sticky; our $ipp2p; # # TCClasses Table # # %tcclasses { device => , # mark => , # number => , # rate => , # umax => , # dmax => , # ceiling => , # priority => , # occurs => # 0 means that this is a class generated by another class with occurs > 1 # parent => # leaf => 0|1 # guarantee => # options => { tos => [ , , ... ]; # tcp_ack => 1 , # ... # our @tcclasses; our %tcclasses; our %restrictions = ( tcpre => PREROUTE_RESTRICT , tcpost => POSTROUTE_RESTRICT , tcfor => NO_RESTRICT , tcin => INPUT_RESTRICT , tcout => OUTPUT_RESTRICT ); our $family; # # Rather than initializing globals in an INIT block or during declaration, # we initialize them in a function. This is done for two reasons: # # 1. Proper initialization depends on the address family which isn't # known until the compiler has started. # # 2. The compiler can run multiple times in the same process so it has to be # able to re-initialize its dependent modules' state. # sub initialize( $ ) { $family = shift; %classids = (); @deferred_rules = (); @tcdevices = (); %tcdevices = (); @tcclasses = (); %tcclasses = (); @devnums = (); $devnum = 0; $sticky = 0; $ipp2p = 0; } sub process_tc_rule( ) { my ( $originalmark, $source, $dest, $proto, $ports, $sports, $user, $testval, $length, $tos , $connbytes, $helper, $headers ) = split_line1 2, 13, 'tcrules file'; our @tccmd; if ( $originalmark eq 'COMMENT' ) { process_comment; return; } my ( $mark, $designator, $remainder ) = split( /:/, $originalmark, 3 ); fatal_error "Invalid MARK ($originalmark)" if defined $remainder || ! defined $mark || $mark eq ''; my $chain = $globals{MARKING_CHAIN}; my $target = 'MARK --set-mark'; my $tcsref; my $connmark = 0; my $classid = 0; my $device = ''; my $fw = firewall_zone; my $list; if ( $source ) { if ( $source eq $fw ) { $chain = 'tcout'; $source = ''; } else { $chain = 'tcout' if $source =~ s/^($fw)://; } } if ( $dest ) { if ( $dest eq $fw ) { $chain = 'tcin'; $dest = ''; } else { $chain = 'tcin' if $dest =~ s/^($fw)://; } } if ( $designator ) { $tcsref = $tcs{$designator}; if ( $tcsref ) { if ( $chain eq 'tcout' ) { fatal_error "Invalid chain designator for source $fw" unless $tcsref->{fw}; } elsif ( $chain eq 'tcin' ) { fatal_error "Invalid chain designator for dest $fw" unless $tcsref->{fwi}; } $chain = $tcsref->{chain} if $tcsref->{chain}; $target = $tcsref->{target} if $tcsref->{target}; $mark = "$mark/" . in_hex( $globals{TC_MASK} ) if $connmark = $tcsref->{connmark}; require_capability ('CONNMARK' , "CONNMARK Rules", '' ) if $connmark; } else { fatal_error "Invalid MARK ($originalmark)" unless $mark =~ /^([0-9]+|0x[0-9a-f]+)$/ and $designator =~ /^([0-9]+|0x[0-9a-f]+)$/; if ( $config{TC_ENABLED} eq 'Internal' || $config{TC_ENABLED} eq 'Shared' ) { $originalmark =~ s/0x//g; fatal_error "Unknown Class ($originalmark)}" unless ( $device = $classids{$originalmark} ); fatal_error "IFB Classes may not be specified in tcrules" if @{$tcdevices{$device}{redirected}}; } $chain = 'tcpost'; $classid = 1; $mark = $originalmark; $target = 'CLASSIFY --set-class'; } } my ($cmd, $rest) = split( '/', $mark, 2 ); $list = ''; my $restriction = 0; unless ( $classid ) { MARK: { for my $tccmd ( @tccmd ) { if ( $tccmd->{match}($cmd) ) { fatal_error "$mark not valid with :C[FPT]" if $connmark; require_capability ('CONNMARK' , "SAVE/RESTORE Rules", '' ) if $tccmd->{connmark}; $target = $tccmd->{target}; my $marktype = $tccmd->{mark}; if ( $marktype == NOMARK ) { $mark = '' } else { $mark =~ s/^[|&]//; } if ( $target eq 'sticky' ) { if ( $chain eq 'tcout' ) { $target = 'sticko'; } else { fatal_error "SAME rules are only allowed in the PREROUTING and OUTPUT chains" if $chain ne 'tcpre'; } $restriction = DESTIFACE_DISALLOW; ensure_mangle_chain($target); $sticky++; } elsif ( $target eq 'IPMARK' ) { my ( $srcdst, $mask1, $mask2, $shift ) = ('src', 255, 0, 0 ); require_capability 'IPMARK_TARGET', 'IPMARK', 's'; if ( $cmd =~ /^IPMARK\((.+?)\)$/ ) { my $params = $1; my $val; my ( $sd, $m1, $m2, $s , $bad ) = split ',', $params; fatal_error "Invalid IPMARK parameters ($params)" if $bad; fatal_error "Invalid IPMARK parameter ($sd)" unless ( $sd eq 'src' || $sd eq 'dst' ); $srcdst = $sd; if ( defined $m1 && $m1 ne '' ) { $val = numeric_value ($m1); fatal_error "Invalid Mask ($m1)" unless defined $val && $val && $val <= 0xffffffff; $mask1 = $m1; } if ( defined $m2 && $m2 ne '' ) { $val = numeric_value ($m2); fatal_error "Invalid Mask ($m2)" unless defined $val && $val <= 0xffffffff; $mask2 = $m2; } if ( defined $s ) { $val = numeric_value ($s); fatal_error "Invalid Shift Bits ($s)" unless defined $val && $val < 128; $shift = $s; } } else { fatal_error "Invalid MARK/CLASSIFY ($cmd)" unless $cmd eq 'IPMARK'; } $target = "IPMARK --addr $srcdst --and-mask $mask1 --or-mask $mask2 --shift $shift"; } elsif ( $target eq 'TPROXY' ) { require_capability( 'TPROXY_TARGET', 'Use of TPROXY', 's'); fatal_error "Invalid TPROXY specification( $cmd/$rest )" if $rest; $chain = 'tcpre'; $cmd =~ /TPROXY\((.+?)\)$/; my $params = $1; fatal_error "Invalid TPROXY specification( $cmd )" unless defined $params; ( $mark, my $port, my $ip, my $bad ) = split ',', $params; fatal_error "Invalid TPROXY specification( $cmd )" if defined $bad; if ( $port ) { $port = validate_port( 'tcp', $port ); } else { $port = 0; } $target .= "--on-port $port"; if ( defined $ip && $ip ne '' ) { validate_address $ip, 1; $target .= " --on-ip $ip"; } $target .= ' --tproxy-mark'; } if ( $rest ) { fatal_error "Invalid MARK ($originalmark)" if $marktype == NOMARK; $mark = $rest if $tccmd->{mask}; if ( $marktype == SMALLMARK ) { verify_small_mark $mark; } else { validate_mark $mark; } } elsif ( $tccmd->{mask} ) { $mark = $tccmd->{mask}; } last MARK; } } validate_mark $mark; if ( $config{PROVIDER_OFFSET} ) { my $val = numeric_value( $cmd ); fatal_error "Invalid MARK/CLASSIFY ($cmd)" unless defined $val; my $limit = $globals{TC_MASK}; unless ( have_capability 'FWMARK_RT_MASK' ) { fatal_error "Marks <= $limit may not be set in the PREROUTING or OUTPUT chains when HIGH_ROUTE_MARKS=Yes" if $cmd && ( $chain eq 'tcpre' || $chain eq 'tcout' ) && $val <= $limit; } } } } if ( ( my $result = expand_rule( ensure_chain( 'mangle' , $chain ) , $restrictions{$chain} | $restriction, do_proto( $proto, $ports, $sports) . do_user( $user ) . do_test( $testval, $globals{TC_MASK} ) . do_length( $length ) . do_tos( $tos ) . do_connbytes( $connbytes ) . do_helper( $helper ) . do_headers( $headers ) , $source , $dest , '' , $mark ? "$target $mark" : $target, '' , $target , '' ) ) && $device ) { # # expand_rule() returns destination device if any # fatal_error "Class Id $originalmark is not associated with device $result" if $device ne $result &&( $config{TC_ENABLED} eq 'Internal' || $config{TC_ENABLED} eq 'Shared' ); } progress_message " TC Rule \"$currentline\" $done"; } sub rate_to_kbit( $ ) { my $rate = $_[0]; return 0 if $rate eq '-'; return $1 if $rate =~ /^((\d+)(\.\d+)?)kbit$/i; return $1 * 1000 if $rate =~ /^((\d+)(\.\d+)?)mbit$/i; return $1 * 8000 if $rate =~ /^((\d+)(\.\d+)?)mbps$/i; return $1 * 8 if $rate =~ /^((\d+)(\.\d+)?)kbps$/i; return ($1/125) if $rate =~ /^((\d+)(\.\d+)?)(bps)?$/; fatal_error "Invalid Rate ($rate)"; } sub calculate_r2q( $ ) { my $rate = rate_to_kbit $_[0]; my $r2q= $rate / 200 ; $r2q <= 5 ? 5 : $r2q; } sub calculate_quantum( $$ ) { my ( $rate, $r2q ) = @_; $rate = rate_to_kbit $rate; int( ( $rate * 125 ) / $r2q ); } sub process_flow($) { my $flow = shift; my @flow = split /,/, $flow; for ( @flow ) { fatal_error "Invalid flow key ($_)" unless $flow_keys{$_}; } $flow; } sub process_simple_device() { my ( $device , $type , $in_bandwidth , $out_part ) = split_line 1, 4, 'tcinterfaces'; fatal_error "Duplicate INTERFACE ($device)" if $tcdevices{$device}; fatal_error "Invalid INTERFACE name ($device)" if $device =~ /[:+]/; my $number = in_hexp( $tcdevices{$device} = ++$devnum ); fatal_error "Unknown interface( $device )" unless known_interface $device; my $physical = physical_name $device; my $dev = chain_base( $physical ); if ( $type ne '-' ) { if ( lc $type eq 'external' ) { $type = 'nfct-src'; } elsif ( lc $type eq 'internal' ) { $type = 'dst'; } else { fatal_error "Invalid TYPE ($type)"; } } my $in_burst = '10kb'; if ( $in_bandwidth =~ /:/ ) { my ( $in_band, $burst ) = split /:/, $in_bandwidth, 2; if ( defined $burst && $burst ne '' ) { fatal_error "Invalid IN-BANDWIDTH" if $burst =~ /:/; fatal_error "Invalid burst ($burst)" unless $burst =~ /^\d+(k|kb|m|mb|mbit|kbit|b)?$/; $in_burst = $burst; } $in_bandwidth = rate_to_kbit( $in_band ); } else { $in_bandwidth = rate_to_kbit( $in_bandwidth ); } emit "if interface_is_up $physical; then"; push_indent; emit ( "${dev}_exists=Yes", "qt \$TC qdisc del dev $physical root", "qt \$TC qdisc del dev $physical ingress\n" ); emit ( "run_tc qdisc add dev $physical handle ffff: ingress", "run_tc filter add dev $physical parent ffff: protocol all prio 10 u32 match ip src " . ALLIPv4 . " police rate ${in_bandwidth}kbit burst $in_burst drop flowid :1\n", "run_tc filter add dev $physical parent ffff: protocol all prio 10 u32 match ip6 src " . ALLIPv6 . " police rate ${in_bandwidth}kbit burst $in_burst drop flowid :1\n" ) if $in_bandwidth; if ( $out_part ne '-' ) { my ( $out_bandwidth, $burst, $latency, $peak, $minburst ) = split ':', $out_part; fatal_error "Invalid Out-BANDWIDTH ($out_part)" if ( defined $minburst && $minburst =~ /:/ ) || $out_bandwidth eq ''; $out_bandwidth = rate_to_kbit( $out_bandwidth ); my $command = "run_tc qdisc add dev $physical root handle $number: tbf rate ${out_bandwidth}kbit"; if ( defined $burst && $burst ne '' ) { fatal_error "Invalid burst ($burst)" unless $burst =~ /^\d+(?:\.\d+)?(k|kb|m|mb|mbit|kbit|b)?$/; $command .= " burst $burst"; } else { $command .= ' burst 10kb'; } if ( defined $latency && $latency ne '' ) { fatal_error "Invalid latency ($latency)" unless $latency =~ /^\d+(?:\.\d+)?(s|sec|secs|ms|msec|msecs|us|usec|usecs)?$/; $command .= " latency $latency"; } else { $command .= ' latency 200ms'; } $command .= ' mpu 64'; #Assume Ethernet if ( defined $peak && $peak ne '' ) { fatal_error "Invalid peak ($peak)" unless $peak =~ /^\d+(?:\.\d+)?(k|kb|m|mb|mbit|kbit|b)?$/; $command .= " peakrate $peak"; } if ( defined $minburst && $minburst ne '' ) { fatal_error "Invalid minburst ($minburst)" unless $minburst =~ /^\d+(?:\.\d+)?(k|kb|m|mb|mbit|kbit|b)?$/; $command .= " minburst $minburst"; } emit $command; my $id = $number; $number = in_hexp( $devnum | 0x100 ); emit "run_tc qdisc add dev $physical parent $id: handle $number: prio bands 3 priomap $config{TC_PRIOMAP}"; } else { emit "run_tc qdisc add dev $physical root handle $number: prio bands 3 priomap $config{TC_PRIOMAP}"; } for ( my $i = 1; $i <= 3; $i++ ) { emit "run_tc qdisc add dev $physical parent $number:$i handle ${number}${i}: sfq quantum 1875 limit 127 perturb 10"; emit "run_tc filter add dev $physical protocol all prio 2 parent $number: handle $i fw classid $number:$i"; emit "run_tc filter add dev $physical protocol all prio 1 parent ${number}$i: handle ${number}${i} flow hash keys $type divisor 1024" if $type ne '-' && have_capability 'FLOW_FILTER'; emit ''; } emit "run_tc filter add dev $physical parent $number:0 protocol all prio 1 u32 match ip protocol 6 0xff match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33 flowid $number:1\n"; emit "run_tc filter add dev $physical parent $number:0 protocol all prio 1 u32 match ip6 protocol 6 0xff match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33 flowid $number:1\n"; save_progress_message_short qq(" TC Device $physical defined."); pop_indent; emit 'else'; push_indent; emit qq(error_message "WARNING: Device $physical is not in the UP state -- traffic-shaping configuration skipped"); emit "${dev}_exists="; pop_indent; emit "fi\n"; progress_message " Simple tcdevice \"$currentline\" $done."; } sub validate_tc_device( ) { my ( $device, $inband, $outband , $options , $redirected ) = split_line 3, 5, 'tcdevices'; fatal_error "Invalid tcdevices entry" if $outband eq '-'; 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 = hex_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, $pfifo, $flow, $qdisc ) = (0, 0, '', 'htb' ); if ( $options ne '-' ) { for my $option ( split_list1 $options, 'option' ) { if ( $option eq 'classify' ) { $classify = 1; } elsif ( $option =~ /^flow=(.*)$/ ) { fatal_error "The 'flow' option is not allowed with 'pfifo'" if $pfifo; $flow = process_flow $1; } elsif ( $option eq 'pfifo' ) { fatal_error "The 'pfifo'' option is not allowed with 'flow='" if $flow; $pfifo = 1; } elsif ( $option eq 'hfsc' ) { $qdisc = 'hfsc'; } elsif ( $option eq 'htb' ) { $qdisc = 'htb'; } else { fatal_error "Unknown device option ($option)"; } } } my @redirected = (); @redirected = split_list( $redirected , 'device' ) if defined $redirected && $redirected ne '-'; if ( @redirected ) { fatal_error "IFB devices may not have IN-BANDWIDTH" if $inband ne '-' && $inband; $classify = 1; 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} != 0; } } my $in_burst = '10kb'; if ( $inband =~ /:/ ) { my ( $in_band, $burst ) = split /:/, $inband, 2; if ( defined $burst && $burst ne '' ) { fatal_error "Invalid IN-BANDWIDTH" if $burst =~ /:/; fatal_error "Invalid burst ($burst)" unless $burst =~ /^\d+(k|kb|m|mb|mbit|kbit|b)?$/; $in_burst = $burst; } $inband = $in_band; } $tcdevices{$device} = { in_bandwidth => rate_to_kbit( $inband ), in_burst => $in_burst, out_bandwidth => rate_to_kbit( $outband ) . 'kbit', number => $devnumber, classify => $classify, flow => $flow, pfifo => $pfifo, tablenumber => 1 , redirected => \@redirected, default => 0, nextclass => 2, qdisc => $qdisc, guarantee => 0, name => $device, physical => physical_name $device } , push @tcdevices, $device; $tcclasses{$device} = {}; progress_message " Tcdevice \"$currentline\" $done."; } sub convert_rate( $$$$ ) { my ($full, $rate, $column, $max) = @_; if ( $rate =~ /\bfull\b/ ) { $rate =~ s/\bfull\b/$full/g; fatal_error "Invalid $column ($_[1])" if $rate =~ m{[^0-9*/+()-]}; no warnings; $rate = eval "int( $rate )"; use warnings; fatal_error "Invalid $column ($_[1])" unless defined $rate; } else { $rate = rate_to_kbit $rate } fatal_error "$column may not be zero" unless $rate; fatal_error "$column ($_[1]) exceeds $max (${full}kbit)" if $rate > $full; $rate; } sub convert_delay( $ ) { my $delay = shift; return 0 unless $delay; return $1 if $delay =~ /^(\d+)(ms)?$/; fatal_error "Invalid Delay ($delay)"; } sub convert_size( $ ) { my $size = shift; return '' unless $size; return $1 if $size =~ /^(\d+)b?$/; fatal_error "Invalid Size ($size)"; } sub dev_by_number( $ ) { my $dev = $_[0]; my $devnum = uc $dev; my $devref; if ( $devnum =~ /^\d+$/ ) { $dev = $devnums[ $devnum ]; fatal_error "Undefined INTERFACE number ($_[0])" unless defined $dev; $devref = $tcdevices{$dev}; assert( $devref ); } else { $devref = $tcdevices{$dev}; fatal_error "Unknown INTERFACE ($dev)" unless $devref; } ( $dev , $devref ); } sub validate_tc_class( ) { my ( $devclass, $mark, $rate, $ceil, $prio, $options ) = split_line 4, 6, 'tcclasses file'; my $classnumber = 0; my $devref; my $device = $devclass; my $occurs = 1; my $parentclass = 1; my $parentref; if ( $devclass =~ /:/ ) { ( $device, my ($number, $subnumber, $rest ) ) = split /:/, $device, 4; fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $rest; if ( $device =~ /^(\d+|0x[\da-fA-F]+)$/ ) { ( $number , $classnumber ) = ( hex_value $device, hex_value $number ); ( $device , $devref) = dev_by_number( $number ); } else { $classnumber = hex_value $number; ($device, $devref ) = dev_by_number( $device); $number = $devref->{number}; } if ( defined $number ) { if ( defined $subnumber ) { fatal_error "Invalid interface/class number ($devclass)" unless defined $classnumber && $classnumber; $parentclass = $classnumber; $classnumber = hex_value $subnumber; } fatal_error "Invalid interface/class number ($devclass)" unless defined $classnumber && $classnumber; fatal_error "Reserved class number (1)" if $classnumber == 1; fatal_error "Duplicate interface:class number ($number:$classnumber}" if $tcclasses{$device}{$classnumber}; } 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}; my $ratemax = $full; my $ceilmax = $full; my $ratename = 'OUT-BANDWIDTH'; my $ceilname = 'OUT-BANDWIDTH'; my $tcref = $tcclasses{$device}; my $markval = 0; if ( $mark ne '-' ) { if ( $devref->{classify} ) { warning_message "INTERFACE $device has the 'classify' option - MARK value ($mark) ignored"; } else { fatal_error "MARK may not be specified when TC_BITS=0" unless $config{TC_BITS}; $markval = numeric_value( $mark ); fatal_error "Invalid MARK ($markval)" unless defined $markval; fatal_error "Invalid Mark ($mark)" unless $markval <= $globals{TC_MAX}; if ( $classnumber ) { fatal_error "Duplicate Class NUMBER ($classnumber)" if $tcref->{$classnumber}; } else { $classnumber = $config{WIDE_TC_MARKS} ? $devref->{nextclass}++ : hex_value( $devnum . $markval ); fatal_error "Duplicate MARK ($mark)" if $tcref->{$classnumber}; } } } else { fatal_error "Duplicate Class NUMBER ($classnumber)" if $tcref->{$classnumber}; } if ( $parentclass != 1 ) { # # Nested Class # $parentref = $tcref->{$parentclass}; fatal_error "Unknown Parent class ($parentclass)" unless $parentref && $parentref->{occurs} == 1; fatal_error "The class ($parentclass) specifies UMAX and/or DMAX; it cannot serve as a parent" if $parentref->{dmax}; fatal_error "The class ($parentclass) specifies flow; it cannot serve as a parent" if $parentref->{flow}; $parentref->{leaf} = 0; $ratemax = $parentref->{rate}; $ratename = q(the parent class's RATE); $ceilmax = $parentref->{ceiling}; $ceilname = q(the parent class's CEIL); } my ( $umax, $dmax ) = ( '', '' ); if ( $devref->{qdisc} eq 'hfsc' ) { ( my $trate , $dmax, $umax , my $rest ) = split ':', $rate , 4; fatal_error "Invalid RATE ($rate)" if defined $rest; $rate = convert_rate ( $ratemax, $trate, 'RATE', $ratename ); $dmax = convert_delay( $dmax ); $umax = convert_size( $umax ); fatal_error "DMAX must be specified when UMAX is specified" if $umax && ! $dmax; } else { $rate = convert_rate ( $ratemax, $rate, 'RATE' , $ratename ); } if ( $parentref ) { warning_message "Total RATE of sub classes ($parentref->{guarantee}kbits) exceeds RATE of parent class ($parentref->{rate}kbits)" if ( $parentref->{guarantee} += $rate ) > $parentref->{rate}; } else { warning_message "Total RATE of classes ($devref->{guarantee}kbits) exceeds OUT-BANDWIDTH (${full}kbits)" if ( $devref->{guarantee} += $rate ) > $full; } fatal_error "Invalid PRIO ($prio)" unless defined numeric_value $prio; $tcref->{$classnumber} = { tos => [] , rate => $rate , umax => $umax , dmax => $dmax , ceiling => convert_rate( $ceilmax, $ceil, 'CEIL' , $ceilname ) , priority => $prio eq '-' ? 1 : $prio , mark => $markval , flow => '' , pfifo => 0, occurs => 1, parent => $parentclass, leaf => 1, guarantee => 0, limit => 127, }; $tcref = $tcref->{$classnumber}; fatal_error "RATE ($tcref->{rate}) exceeds CEIL ($tcref->{ceiling})" if $tcref->{rate} > $tcref->{ceiling}; unless ( $options eq '-' ) { for my $option ( split_list1 "\L$options", 'option' ) { my $optval = $tosoptions{$option}; $option = "tos=$optval" if $optval; if ( $option eq 'default' ) { fatal_error "Only one default class may be specified for device $device" if $devref->{default}; fatal_error "The $option option is not valid with 'occurs" if $tcref->{occurs} > 1; $devref->{default} = $classnumber; } elsif ( $option eq 'tcp-ack' ) { fatal_error "The $option option is not valid with 'occurs" if $tcref->{occurs} > 1; $tcref->{tcp_ack} = 1; } elsif ( $option =~ /^tos=0x[0-9a-f]{2}$/ ) { fatal_error "The $option option is not valid with 'occurs" if $tcref->{occurs} > 1; ( undef, $option ) = split /=/, $option; push @{$tcref->{tos}}, "$option/0xff"; } elsif ( $option =~ /^tos=0x[0-9a-f]{2}\/0x[0-9a-f]{2}$/ ) { fatal_error "The $option option is not valid with 'occurs" if $tcref->{occurs} > 1; ( undef, $option ) = split /=/, $option; push @{$tcref->{tos}}, $option; } elsif ( $option =~ /^flow=(.*)$/ ) { fatal_error "The 'flow' option is not allowed with 'pfifo'" if $tcref->{pfifo}; $tcref->{flow} = process_flow $1; } elsif ( $option eq 'pfifo' ) { fatal_error "The 'pfifo'' option is not allowed with 'flow='" if $tcref->{flow}; $tcref->{pfifo} = 1; } elsif ( $option =~ /^occurs=(\d+)$/ ) { my $val = $1; $occurs = numeric_value($val); fatal_error q(The 'occurs' option is only valid for IPv4) if $family == F_IPV6; fatal_error q(The 'occurs' option may not be used with 'classify') if $devref->{classify}; fatal_error "Invalid 'occurs' ($val)" unless defined $occurs && $occurs > 1 && $occurs <= 256; fatal_error "Invalid 'occurs' ($val)" if $occurs > $globals{TC_MAX}; fatal_error q(Duplicate 'occurs') if $tcref->{occurs} > 1; fatal_error q(The 'occurs' option is not valid with 'default') if $devref->{default} == $classnumber; fatal_error q(The 'occurs' option is not valid with 'tos') if @{$tcref->{tos}}; warning_message "MARK ($mark) is ignored on an occurring class" if $mark ne '-'; $tcref->{occurs} = $occurs; $devref->{occurs} = 1; } elsif ( $option =~ /^limit=(\d+)$/ ) { warning_message "limit ignored with pfifo queuing" if $tcref->{pfifo}; fatal_error "Invalid limit ($1)" if $1 < 3 || $1 > 128; $tcref->{limit} = $1; } else { fatal_error "Unknown option ($option)"; } } } unless ( $devref->{classify} || $occurs > 1 ) { fatal_error "Missing MARK" if $mark eq '-'; warning_message "Class NUMBER ignored -- INTERFACE $device does not have the 'classify' option" if $devclass =~ /:/; } $tcref->{flow} = $devref->{flow} unless $tcref->{flow}; $tcref->{pfifo} = $devref->{pfifo} unless $tcref->{flow} || $tcref->{pfifo}; push @tcclasses, "$device:$classnumber"; while ( --$occurs ) { fatal_error "Duplicate class number ($classnumber)" if $tcclasses{$device}{++$classnumber}; $tcclasses{$device}{$classnumber} = { tos => [] , rate => $tcref->{rate} , ceiling => $tcref->{ceiling} , priority => $tcref->{priority} , mark => 0 , flow => $tcref->{flow} , pfifo => $tcref->{pfifo}, occurs => 0, parent => $parentclass, limit => $tcref->{limit}, }; push @tcclasses, "$device:$classnumber"; }; progress_message " Tcclass \"$currentline\" $done."; } my %validlengths = ( 32 => '0xffe0', 64 => '0xffc0', 128 => '0xff80', 256 => '0xff00', 512 => '0xfe00', 1024 => '0xfc00', 2048 => '0xf800', 4096 => '0xf000', 8192 => '0xe000' ); # # Process a record from the tcfilters file # sub process_tc_filter() { my ( $devclass, $source, $dest , $proto, $portlist , $sportlist, $tos, $length ) = split_line 2, 8, 'tcfilters file'; my ($device, $class, $rest ) = split /:/, $devclass, 3; our $lastdevice; fatal_error "Invalid INTERFACE:CLASS ($devclass)" if defined $rest || ! ($device && $class ); my ( $ip, $ip32, $prio , $lo ) = $family == F_IPV4 ? ('ip', 'ip', 10, 2 ) : ('ipv6', 'ip6', 11 , 4 ); ( $device , my $devref ) = dev_by_number( $device ); my $devnum = in_hexp $devref->{number}; my $tcref = $tcclasses{$device}; fatal_error "No Classes were defined for INTERFACE $device" unless $tcref; my $classnum = hex_value $class; fatal_error "Invalid CLASS ($class)" unless defined $classnum; $tcref = $tcref->{$classnum}; fatal_error "Unknown CLASS ($devclass)" unless $tcref && $tcref->{occurs}; fatal_error "Filters may not specify an occurring CLASS" if $tcref->{occurs} > 1; if ( $devref->{physical} ne $lastdevice ) { if ( $lastdevice ) { pop_indent; emit "fi\n"; } $lastdevice = $devref->{physical}; emit "if interface_is_up $lastdevice; then"; push_indent; } my $rule = "filter add dev $devref->{physical} protocol $ip parent $devnum:0 prio $prio u32"; if ( $source ne '-' ) { my ( $net , $mask ) = decompose_net( $source ); $rule .= "\\\n match $ip32 src $net/$mask"; } if ( $dest ne '-' ) { my ( $net , $mask ) = decompose_net( $dest ); $rule .= "\\\n match $ip32 dst $net/$mask"; } if ( $tos ne '-' ) { my $tosval = $tosoptions{$tos}; my $mask; $tosval = $tos unless $tosval; if ( $tosval =~ /^0x[0-9a-f]{2}$/ ) { $mask = '0xff'; } elsif ( $tosval =~ /^(0x[0-9a-f]{2})\/(0x[0-9a-f]{2})$/ ) { $tosval = $1; $mask = $2; } else { fatal_error "Invalid TOS ($tos)"; } $rule .= "\\\n match $ip32 tos $tosval $mask"; } if ( $length ne '-' ) { my $len = numeric_value( $length ) || 0; my $mask = $validlengths{$len}; fatal_error "Invalid LENGTH ($length)" unless $mask; $rule .="\\\n match u16 0x0000 $mask at $lo"; } my $protonumber = 0; unless ( $proto eq '-' ) { $protonumber = resolve_proto $proto; fatal_error "Unknown PROTO ($proto)" unless defined $protonumber; $rule .= "\\\n match $ip32 protocol $protonumber 0xff" if $protonumber; } if ( $portlist eq '-' && $sportlist eq '-' ) { emit( "\nrun_tc $rule\\" , " flowid $devref->{number}:$class" , '' ); } else { fatal_error "Ports may not be specified without a PROTO" unless $protonumber; our $lastrule; our $lasttnum; # # In order to be able to access the protocol header, we must create another hash table and link to it. # # Create the Table. # my $tnum; if ( $lastrule eq $rule ) { # # The source, dest and protocol are the same as the last rule that specified a port # Use the same table # $tnum = $lasttnum } else { $tnum = in_hex3 $devref->{tablenumber}++; $lasttnum = $tnum; $lastrule = $rule; emit( "\nrun_tc filter add dev $devref->{physical} parent $devnum:0 protocol $ip prio $prio handle $tnum: u32 divisor 1" ); } # # And link to it using the current contents of $rule # if ( $family == F_IPV4 ) { emit( "\nrun_tc $rule\\" , " link $tnum:0 offset at 0 mask 0x0F00 shift 6 plus 0 eat" ); } else { emit( "\nrun_tc $rule\\" , " link $tnum:0 offset plus 40 eat" ); } # # The rule to match the port(s) will be inserted into the new table # $rule = "filter add dev $devref->{physical} protocol $ip parent $devnum:0 prio $prio u32 ht $tnum:0"; if ( $portlist eq '-' ) { fatal_error "Only TCP, UDP and SCTP may specify SOURCE PORT" unless $protonumber == TCP || $protonumber == UDP || $protonumber == SCTP; for my $sportrange ( split_list $sportlist , 'port list' ) { my @sportlist = expand_port_range $protonumber , $sportrange; while ( @sportlist ) { my ( $sport, $smask ) = ( shift @sportlist, shift @sportlist ); my $rule1; if ( $protonumber == TCP ) { $rule1 = join( ' ', 'match tcp src', hex_value( $sport ), "0x$smask" ); } elsif ( $protonumber == UDP ) { $rule1 = join( ' ', 'match udp src', hex_value( $sport ), "0x$smask" ); } else { $rule1 = "match u32 0x${sport}0000 0x${smask}0000 at nexthdr+0" , } emit( "\nrun_tc $rule\\" , " $rule1\\" , " flowid $devref->{number}:$class" ); } } } else { fatal_error "Only TCP, UDP, SCTP and ICMP may specify DEST PORT" unless $protonumber == TCP || $protonumber == UDP || $protonumber == SCTP || $protonumber == ICMP; for my $portrange ( split_list $portlist, 'port list' ) { if ( $protonumber == ICMP ) { fatal_error "ICMP not allowed with IPv6" unless $family == F_IPV4; fatal_error "SOURCE PORT(S) are not allowed with ICMP" if $sportlist ne '-'; my ( $icmptype , $icmpcode ) = split '/', validate_icmp( $portrange ); my $rule1 = " match icmp type $icmptype 0xff"; $rule1 .= "\\\n match icmp code $icmpcode 0xff" if defined $icmpcode; emit( "\nrun_tc ${rule}\\" , "$rule1\\" , " flowid $devref->{number}:$class" ); } elsif ( $protonumber == IPv6_ICMP ) { fatal_error "IPv6 ICMP not allowed with IPv4" unless $family == F_IPV4; fatal_error "SOURCE PORT(S) are not allowed with IPv6 ICMP" if $sportlist ne '-'; my ( $icmptype , $icmpcode ) = split '/', validate_icmp6( $portrange ); my $rule1 = " match icmp6 type $icmptype 0xff"; $rule1 .= "\\\n match icmp6 code $icmpcode 0xff" if defined $icmpcode; emit( "\nrun_tc ${rule}\\" , "$rule1\\" , " flowid $devref->{number}:$class" ); } else { my @portlist = expand_port_range $protonumber , $portrange; while ( @portlist ) { my ( $port, $mask ) = ( shift @portlist, shift @portlist ); my $rule1; if ( $protonumber == TCP ) { $rule1 = join( ' ', 'match tcp dst', hex_value( $port ), "0x$mask" ); } elsif ( $protonumber == UDP ) { $rule1 = join( ' ', 'match udp dst', hex_value( $port ), "0x$mask" ); } else { $rule1 = "match u32 0x0000${port} 0x0000${mask} at nexthdr+0"; } if ( $sportlist eq '-' ) { emit( "\nrun_tc ${rule}\\" , " $rule1\\" , " flowid $devref->{number}:$class" ); } else { for my $sportrange ( split_list $sportlist , 'port list' ) { my @sportlist = expand_port_range $protonumber , $sportrange; while ( @sportlist ) { my ( $sport, $smask ) = ( shift @sportlist, shift @sportlist ); my $rule2; if ( $protonumber == TCP ) { $rule2 = join( ' ', 'match tcp src', hex_value( $sport ), "0x$smask" ); } elsif ( $protonumber == UDP ) { $rule2 = join( ' ', 'match udp src', hex_value( $sport ), "0x$smask" ); } else { $rule2 = "match u32 0x${sport}0000 0x${smask}0000 at nexthdr+0" , } emit( "\nrun_tc ${rule}\\", " $rule1\\" , " $rule2\\" , " flowid $devref->{number}:$class" ); } } } } } } } } emit ''; if ( $family == F_IPV4 ) { progress_message " IPv4 TC Filter \"$currentline\" $done"; $currentline =~ s/\s+/ /g; save_progress_message_short qq(' IPv4 TC Filter \"$currentline\" defined.'); } else { progress_message " IPv6 TC Filter \"$currentline\" $done"; $currentline =~ s/\s+/ /g; save_progress_message_short qq(' IPv6 TC Filter \"$currentline\" defined.'); } emit ''; } sub process_tcfilters() { my $fn = open_file 'tcfilters'; our $lastdevice = ''; if ( $fn ) { my @family = ( $family ); first_entry( sub { progress_message2 "$doing $fn..."; save_progress_message q("Adding TC Filters"); } ); while ( read_a_line ) { if ( $currentline =~ /^\s*IPV4\s*$/ ) { Shorewall::IPAddrs::initialize( $family = F_IPV4 ) unless $family == F_IPV4; } elsif ( $currentline =~ /^\s*IPV6\s*$/ ) { Shorewall::IPAddrs::initialize( $family = F_IPV6 ) unless $family == F_IPV6; } elsif ( $currentline =~ /^\s*ALL\s*$/ ) { $family = 0; } elsif ( $family ) { process_tc_filter; } else { push @family, $family; for ( F_IPV4, F_IPV6 ) { Shorewall::IPAddrs::initialize( $family = $_ ); process_tc_filter; } Shorewall::IPAddrs::initialize( $family = pop @family ); } } Shorewall::IPAddrs::initialize( $family = pop @family ); if ( $lastdevice ) { pop_indent; emit "fi\n"; } } } sub process_tc_priority() { my ( $band, $proto, $ports , $address, $interface, $helper ) = split_line1 1, 6, 'tcpri'; if ( $band eq 'COMMENT' ) { process_comment; return; } my $val = numeric_value $band; fatal_error "Invalid PRIORITY ($band)" unless $val && $val <= 3; my $rule = do_helper( $helper ) . "-j MARK --set-mark $band"; $rule .= join('', '/', in_hex( $globals{TC_MASK} ) ) if have_capability( 'EXMARK' ); if ( $interface ne '-' ) { fatal_error "Invalid combination of columns" unless $address eq '-' && $proto eq '-' && $ports eq '-'; my $forwardref = $mangle_table->{tcfor}; add_rule( $forwardref , join( '', match_source_dev( $interface) , $rule ) , 1 ); } else { my $postref = $mangle_table->{tcpost}; if ( $address ne '-' ) { fatal_error "Invalid combination of columns" unless $proto eq '-' && $ports eq '-'; add_rule( $postref , join( '', match_source_net( $address) , $rule ) , 1 ); } else { add_rule( $postref , join( '', do_proto( $proto, $ports, '-' , 0 ) , $rule ) , 1 ); if ( $ports ne '-' ) { my $protocol = resolve_proto $proto; if ( $proto =~ /^ipp2p/ ) { fatal_error "ipp2p may not be used when there are tracked providers and PROVIDER_OFFSET=0" if @routemarked_interfaces && $config{PROVIDER_OFFSET} == 0; $ipp2p = 1; } add_rule( $postref , join( '' , do_proto( $proto, '-', $ports, 0 ) , $rule ) , 1 ) unless $proto =~ /^ipp2p/ || $protocol == ICMP || $protocol == IPv6_ICMP; } } } } sub setup_simple_traffic_shaping() { my $interfaces; save_progress_message q("Setting up Traffic Control..."); my $fn = open_file 'tcinterfaces'; if ( $fn ) { first_entry "$doing $fn..."; process_simple_device, $interfaces++ while read_a_line; } else { $fn = find_file 'tcinterfaces'; } my $fn1 = open_file 'tcpri'; if ( $fn1 ) { first_entry sub { progress_message2 "$doing $fn1..."; warning_message "There are entries in $fn1 but $fn was empty" unless $interfaces || $family == F_IPV6; }; process_tc_priority while read_a_line; clear_comment; if ( $ipp2p ) { insert_rule1 $mangle_table->{tcpost} , 0 , '-m mark --mark 0/' . in_hex( $globals{TC_MASK} ) . ' -j CONNMARK --restore-mark --ctmask ' . in_hex( $globals{TC_MASK} ); add_rule $mangle_table->{tcpost} , '-m mark ! --mark 0/' . in_hex( $globals{TC_MASK} ) . ' -j CONNMARK --save-mark --ctmask ' . in_hex( $globals{TC_MASK} ); } } } sub setup_traffic_shaping() { our $lastrule = ''; save_progress_message q("Setting up Traffic Control..."); my $fn = open_file 'tcdevices'; if ( $fn ) { first_entry "$doing $fn..."; validate_tc_device while read_a_line; } my $sfq = $devnum; my $sfqinhex; $devnum = $devnum > 10 ? 10 : 1; $fn = open_file 'tcclasses'; if ( $fn ) { first_entry "$doing $fn..."; validate_tc_class while read_a_line; } for my $device ( @tcdevices ) { my $devref = $tcdevices{$device}; my $defmark = in_hexp ( $devref->{default} || 0 ); my $devnum = in_hexp $devref->{number}; my $r2q = int calculate_r2q $devref->{out_bandwidth}; $device = physical_name $device; my $dev = chain_base( $device ); unless ( $config{TC_ENABLED} eq 'Shared' ) { emit "if interface_is_up $device; then"; push_indent; emit ( "${dev}_exists=Yes", "qt \$TC qdisc del dev $device root", "qt \$TC qdisc del dev $device ingress", "${dev}_mtu=\$(get_device_mtu $device)", "${dev}_mtu1=\$(get_device_mtu1 $device)" ); if ( $devref->{qdisc} eq 'htb' ) { emit ( "run_tc qdisc add dev $device root handle $devnum: htb default $defmark r2q $r2q" , "run_tc class add dev $device parent $devnum: classid $devnum:1 htb rate $devref->{out_bandwidth} \$${dev}_mtu1" ); } else { emit ( "run_tc qdisc add dev $device root handle $devnum: hfsc default $defmark" , "run_tc class add dev $device parent $devnum: classid $devnum:1 hfsc sc rate $devref->{out_bandwidth} ul rate $devref->{out_bandwidth}" ); } if ( $devref->{occurs} ) { # # The following command may succeed yet generate an error message and non-zero exit status :-(. We thus run it silently # and check the result. Note that since this is the first filter added after the root qdisc was added, the 'ls | grep' test # is fairly robust # my $command = "\$TC filter add dev $device parent $devnum:0 prio 65535 protocol all fw"; emit( qq(if ! qt $command ; then) , qq( if ! \$TC filter list dev $device | grep -q 65535; then) , qq( error_message "ERROR: Command '$command' failed"), qq( stop_firewall), qq( exit 1), qq( fi), qq(fi) ); } if ( $devref->{in_bandwidth} ) { emit ( "run_tc qdisc add dev $device handle ffff: ingress", "run_tc filter add dev $device parent ffff: protocol all prio 10 u32 match ip src 0.0.0.0/0 police rate $devref->{in_bandwidth}kbit burst $devref->{in_burst} drop flowid :1" ); } 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 all u32 match u32 0 0 action mirred egress redirect dev $device > /dev/null" ); } save_progress_message_short qq(" TC Device $device defined."); pop_indent; emit 'else'; push_indent; emit qq(error_message "WARNING: Device $device is not in the UP state -- traffic-shaping configuration skipped"); emit "${dev}_exists="; pop_indent; emit "fi\n"; } } my $lastdevice = ''; for my $class ( @tcclasses ) { # # The class number in the tcclasses array is expressed in decimal. # my ( $device, $decimalclassnum ) = split /:/, $class; # # For inclusion in 'tc' commands, we also need the hex representation # my $classnum = in_hexp $decimalclassnum; my $devref = $tcdevices{$device}; # # The decimal value of the class number is also used as the key for the hash at $tcclasses{$device} # my $tcref = $tcclasses{$device}{$decimalclassnum}; my $mark = $tcref->{mark}; my $devicenumber = in_hexp $devref->{number}; my $classid = join( ':', $devicenumber, $classnum); my $rate = "$tcref->{rate}kbit"; my $quantum = calculate_quantum $rate, calculate_r2q( $devref->{out_bandwidth} ); $classids{$classid}=$device; $device = physical_name $device; unless ( $config{TC_ENABLED} eq 'Shared' ) { my $dev = chain_base $device; my $priority = $tcref->{priority} << 8; my $parent = in_hexp $tcref->{parent}; if ( $lastdevice ne $device ) { if ( $lastdevice ) { pop_indent; emit "fi\n"; } emit qq(if [ -n "\$${dev}_exists" ]; then); push_indent; $lastdevice = $device; } emit ( "[ \$${dev}_mtu -gt $quantum ] && quantum=\$${dev}_mtu || quantum=$quantum" ); if ( $devref->{qdisc} eq 'htb' ) { emit ( "run_tc class add dev $device parent $devicenumber:$parent classid $classid htb rate $rate ceil $tcref->{ceiling}kbit prio $tcref->{priority} \$${dev}_mtu1 quantum \$quantum" ); } else { my $dmax = $tcref->{dmax}; if ( $dmax ) { my $umax = $tcref->{umax} ? "$tcref->{umax}b" : "\${${dev}_mtu}b"; emit ( "run_tc class add dev $device parent $devicenumber:$parent classid $classid hfsc sc umax $umax dmax ${dmax}ms rate $rate ul rate $tcref->{ceiling}kbit" ); } else { emit ( "run_tc class add dev $device parent $devicenumber:$parent classid $classid hfsc sc rate $rate ul rate $tcref->{ceiling}kbit" ); } } if ( $tcref->{leaf} && ! $tcref->{pfifo} ) { $sfqinhex = in_hexp( ++$sfq); emit( "run_tc qdisc add dev $device parent $classid handle $sfqinhex: sfq quantum \$quantum limit $tcref->{limit} perturb 10" ); } # # add filters # unless ( $devref->{classify} ) { emit "run_tc filter add dev $device protocol all parent $devicenumber:0 prio " . ( $priority | 20 ) . " handle $mark fw classid $classid" if $tcref->{occurs} == 1; } emit "run_tc filter add dev $device protocol all prio 1 parent $sfqinhex: handle $classnum flow hash keys $tcref->{flow} divisor 1024" if $tcref->{flow}; # # options # emit "run_tc filter add dev $device parent $devref->{number}:0 protocol ip prio " . ( $priority | 10 ) ." u32 match ip protocol 6 0xff match u8 0x05 0x0f at 0 match u16 0x0000 0xffc0 at 2 match u8 0x10 0xff at 33 flowid $classid" if $tcref->{tcp_ack}; for my $tospair ( @{$tcref->{tos}} ) { my ( $tos, $mask ) = split q(/), $tospair; emit "run_tc filter add dev $device parent $devicenumber:0 protocol ip prio " . ( $priority | 10 ) . " u32 match ip tos $tos $mask flowid $classid"; } save_progress_message_short qq(" TC Class $classid defined."); emit ''; } } if ( $lastdevice ) { pop_indent; emit "fi\n"; } process_tcfilters; } # # Process a record in the secmarks file # sub process_secmark_rule() { my ( $secmark, $chainin, $source, $dest, $proto, $dport, $sport, $user, $mark ) = split_line1( 2, 9 , 'Secmarks file' ); if ( $secmark eq 'COMMENT' ) { process_comment; return; } my %chns = ( T => 'tcpost' , P => 'tcpre' , F => 'tcfor' , I => 'tcin' , O => 'tcout' , ); my %state = ( N => 'NEW' , E => 'ESTABLISHED' , ER => 'ESTABLISHED,RELATED' ); my ( $chain , $state, $rest) = split ':', $chainin , 3; fatal_error "Invalid CHAIN:STATE ($chainin)" if $rest || ! $chain; my $chain1= $chns{$chain}; fatal_error "Invalid or missing CHAIN ( $chain )" unless $chain1; fatal_error "USER/GROUP may only be used in the OUTPUT chain" if $user ne '-' && $chain1 ne 'tcout'; if ( ( $state ||= '' ) ne '' ) { my $state1; fatal_error "Invalid STATE ( $state )" unless $state1 = $state{$state}; $state = "$globals{STATEMATCH} $state1 "; } my $target = $secmark eq 'SAVE' ? 'CONNSECMARK --save' : $secmark eq 'RESTORE' ? 'CONNSECMARK --restore' : "SECMARK --selctx $secmark"; my $disposition = $target; $disposition =~ s/ .*//; expand_rule( ensure_mangle_chain( $chain1 ) , $restrictions{$chain1} , $state . do_proto( $proto, $dport, $sport ) . do_user( $user ) . do_test( $mark, $globals{TC_MASK} ) , $source , $dest , '' , $target , '' , $disposition, '' ); progress_message "Secmarks rule \"$currentline\" $done"; } # # Process the tcrules file and setup traffic shaping # sub setup_tc() { if ( $config{MANGLE_ENABLED} ) { ensure_mangle_chain 'tcpre'; ensure_mangle_chain 'tcout'; if ( have_capability( 'MANGLE_FORWARD' ) ) { ensure_mangle_chain 'tcfor'; ensure_mangle_chain 'tcpost'; ensure_mangle_chain 'tcin'; } my $mark_part = ''; if ( @routemarked_interfaces && ! $config{TC_EXPERT} ) { $mark_part = '-m mark --mark 0/' . in_hex( $globals{PROVIDER_MASK} ) . ' '; unless ( $config{TRACK_PROVIDERS} ) { # # This is overloading TRACK_PROVIDERS a bit but sending tracked packets through PREROUTING is a PITA for users # for my $interface ( @routemarked_interfaces ) { add_jump $mangle_table->{PREROUTING} , 'tcpre', 0, match_source_dev( $interface ); } } } add_jump $mangle_table->{PREROUTING} , 'tcpre', 0, $mark_part; add_jump $mangle_table->{OUTPUT} , 'tcout', 0, $mark_part; if ( have_capability( 'MANGLE_FORWARD' ) ) { my $mask = have_capability 'EXMARK' ? have_capability 'FWMARK_RT_MASK' ? '/' . in_hex $globals{PROVIDER_MASK} : '' : ''; add_rule( $mangle_table->{FORWARD}, "-j MARK --set-mark 0${mask}" ) if $config{FORWARD_CLEAR_MARK}; add_jump $mangle_table->{FORWARD} , 'tcfor', 0; add_jump $mangle_table->{POSTROUTING} , 'tcpost', 0; add_jump $mangle_table->{INPUT} , 'tcin' , 0; } } if ( $globals{TC_SCRIPT} ) { save_progress_message q('Setting up Traffic Control...'); append_file $globals{TC_SCRIPT}; } elsif ( $config{TC_ENABLED} eq 'Internal' || $config{TC_ENABLED} eq 'Shared' ) { setup_traffic_shaping; } elsif ( $config{TC_ENABLED} eq 'Simple' ) { setup_simple_traffic_shaping; } if ( $config{TC_ENABLED} ) { our @tccmd = ( { match => sub ( $ ) { $_[0] eq 'SAVE' } , target => 'CONNMARK --save-mark --mask' , mark => SMALLMARK , mask => in_hex( $globals{TC_MASK} ) , connmark => 1 } , { match => sub ( $ ) { $_[0] eq 'RESTORE' }, target => 'CONNMARK --restore-mark --mask' , mark => SMALLMARK , mask => in_hex( $globals{TC_MASK} ) , connmark => 1 } , { match => sub ( $ ) { $_[0] eq 'CONTINUE' }, target => 'RETURN' , mark => NOMARK , mask => '' , connmark => 0 } , { match => sub ( $ ) { $_[0] eq 'SAME' }, target => 'sticky' , mark => NOMARK , mask => '' , connmark => 0 } , { match => sub ( $ ) { $_[0] =~ /^IPMARK/ }, target => 'IPMARK' , mark => NOMARK, mask => '', connmark => 0 } , { match => sub ( $ ) { $_[0] =~ '\|.*'} , target => 'MARK --or-mark' , mark => HIGHMARK , mask => '' } , { match => sub ( $ ) { $_[0] =~ '&.*' }, target => 'MARK --and-mark' , mark => HIGHMARK , mask => '' , connmark => 0 } , { match => sub ( $ ) { $_[0] =~ /^TPROXY/ }, target => 'TPROXY', mark => HIGHMARK, mask => '', connmark => '' }, ); if ( my $fn = open_file 'tcrules' ) { first_entry "$doing $fn..."; process_tc_rule while read_a_line; clear_comment; } } if ( $config{MANGLE_ENABLED} ) { if ( my $fn = open_file 'secmarks' ) { first_entry "$doing $fn..."; process_secmark_rule while read_a_line; clear_comment; } add_rule ensure_chain( 'mangle' , 'tcpost' ), $_ for @deferred_rules; handle_stickiness( $sticky ); } } 1;