diff --git a/Shorewall/Perl/Shorewall/Chains.pm b/Shorewall/Perl/Shorewall/Chains.pm index 1f8d5ee65..da1f3e214 100644 --- a/Shorewall/Perl/Shorewall/Chains.pm +++ b/Shorewall/Perl/Shorewall/Chains.pm @@ -4768,15 +4768,20 @@ sub verify_small_mark( $ ) { sub validate_mark( $ ) { my $mark = shift; + my $val; fatal_error "Missing MARK" unless supplied $mark; if ( $mark =~ '/' ) { my @marks = split '/', $mark; fatal_error "Invalid MARK ($mark)" unless @marks == 2; verify_mark $_ for @marks; + $val = @marks[0]; } else { verify_mark $mark; + $val = $mark; } + + return numeric_value $val if defined( WANTARRAY ); } # diff --git a/Shorewall/Perl/Shorewall/Tc.pm b/Shorewall/Perl/Shorewall/Tc.pm index ef5001bc4..814dac2bc 100644 --- a/Shorewall/Perl/Shorewall/Tc.pm +++ b/Shorewall/Perl/Shorewall/Tc.pm @@ -42,45 +42,6 @@ our @EXPORT = qw( process_tc setup_tc ); our @EXPORT_OK = qw( process_tc_rule initialize ); our $VERSION = 'MODULEVERSION'; -my %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 @@ -207,6 +168,45 @@ sub initialize( $ ) { sub process_tc_rule1( $$$$$$$$$$$$$$$$ ) { my ( $originalmark, $source, $dest, $proto, $ports, $sports, $user, $testval, $length, $tos , $connbytes, $helper, $headers, $probability , $dscp , $state ) = @_; + my %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 , + } , + ); + our %tccmd; unless ( %tccmd ) { @@ -852,7 +852,496 @@ sub process_tc_rule( ) { process_tc_rule1( $originalmark, $source, $dest, $proto, $ports, $sports, $user, $testval, $length, $tos , $connbytes, $helper, $headers, $probability , $dscp , $state ); } } + +sub process_mark_rule( $$$$$$$$$$$$$$$$ ) { + my ( $action, $source, $dest, $proto, $ports, $sports, $user, $testval, $length, $tos , $connbytes, $helper, $headers, $probability , $dscp , $state ) = @_; + use constant { + PREROUTING => 1, + INPUT => 2, + FORWARD => 4, + OUTPUT => 8, + POSTROUTING => 16, + ALL => 31, + STICKY => 32, + STICKO => 64, + }; + + my %designators { + P => PREROUTING, + I => INPUT, + F => FORWARD, + O => OUTPUT, + T => POSTROUTING }; + + my %chainnames = ( PREROUTING => 'tcpre', + INPUT => 'tcin', + FORWARD => 'tcfor', + OUTPUT => 'tcout', + POSTROUTING => 'tcpost', + sticky => 'sticky', + stocko => 'sticko' ); + + our $target = ''; + my $junk = ''; + my $raw_matches = ''; + our $chain = ''; + our $matches = ''; + our $params = ''; + our $done = 0; + my $default_chain; + my $divertref; + my $restrictions = 0; + + sub handle_mark_param( $ ) { + my ( $option ) = @_; + my $and_or = ''; + my $and_or = $1 if $params =~ s/^([|&])//; + + if ( $params =~ /-/ ) { + # + # A Mark Range + # + fatal_error "'|' and '&' may not be used with a mark range" if $and_or; + fatal_error "A mark range is is not allowed with ACTION $command" if $command !~ /^(?:CONN)?MARK$/; + my ( $mark, $mark2 ) = split /-/, $mark, 2; + my $markval = validate_mark $mark; + fatal_error "Invalid mark range ($mark-$mark2)" if $mark =~ m'/'; + my $mark2val = validate_mark $mark2; + fatal_error "Invalid mark range ($mark-$mark2)" unless $markval < $mark2val; + require_capability 'STATISTIC_MATCH', 'A mark range', 's'; + ( $mark2, my $mask ) = split '/', $mark2; + $mask = $globals{TC_MASK} unless supplied $mask; + + my $increment = 1; + my $shift = 0; + + $increment <<= 1, $shift++ until $increment & $mask; + + $mask = in_hex $mask; + + my $marks = ( ( $mark2val - $markval ) >> $shift ) + 1; + + my $chainref = ensure_chain( 'mangle', $chain = $chainnames{$chain} ); + + for ( my $packet = 0; $packet < $marks; $packet++, $markval += $increment ) { + my $match = "-m statistic --mode nth --every $marks --packet $packet "; + + expand_rule( $chainref, + $restrictions{$chain} | $restriction, + '' , + $match . + do_user( $user ) . + do_test( $testval, $globals{TC_MASK} ) . + do_test( $testval, $globals{TC_MASK} ) . + do_length( $length ) . + do_tos( $tos ) . + do_connbytes( $connbytes ) . + do_helper( $helper ) . + do_headers( $headers ) . + do_probability( $probability ) . + do_dscp( $dscp ) . + state_match( $state ) . + $raw_matches , + $source , + $dest , + '' , + "$target " . join( '/', in_hex( $markval ) , $mask ) , + '', + $target , + $exceptionrule ); + } + + $done = 1; + } else { + my $val = validate_mark( $mark = $params ); + # + # A Single Mark + # + if ( $config{PROVIDER_OFFSET} ) { + 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 $val && ( $chain && ( PREROUTING | OUTPUT ) ) && $val <= $globals{TC_MASK}; + } + } + + if ( $option ) { + $target = join( ' ', $target, $option ); + } else { + $target = join( ' ', $target, $and_or eq '|' ? '--or-mark' : $and_or ? '--and-mark' : '--set-mark' ); + } + + ( $mark, my $mask ) = split '/', $mark; + + $mask = $globals{TC_MASK} unless supplied $mask; + + $target = join( ' ', $target , join( in_hex( $mark ) , in_hex( $mask ) ) ); + } + }; + + my ( $command, $designator ) = split_action( $action ); + + if ( supplied $designator ) { + fatal_error "Invalid chain designator ( $designator )" unless $designators{$designator}; + } + + ( $command , $params ) = get_target_param1( $command ); + + my $commandref = $commands{$command}; + + fatal_error "Invalid ACTION ($command)" unless $commandref; + + if ( $command eq 'INLINE' ) { + ( $action, $command, $params, $junk, $raw ) = handle_inline( $action, $command, $params, '' ); + } elsif ( $config{INLINE_MATCHES} ) { + $raw = get_inline_matches( $command eq 'JUMP' ); + } + + if ( $source =~ /^\$FW/ ) { + fatal_error 'Rules with SOURCE $FW must use the OUTPUT chain' if $designator && $designator ne OUTPUT; + $default_chain = OUTPUT; + } elsif ( $dest =~ /^\$FW/ ) { + fatal_error 'Rules with DEST $FW must use the INPUT chain' if $designator && $designator ne INPUT; + $default_chain = INPUT; + } else { + $default_chain = $config{MARK_IN_FORWARD_CHAIN} ? FORWARD | PREROUTING; + } + + my %commands = ( + CHECKSUM => { + defaultchain => 0, + allowedchains => ALL, + minparams => 0, + maxparams => 0 , + function => sub() { + $target = 'CHECKSUM'; + }, + }, + + CLASSIFY => { + defaultchain => POSTROUTING, + allowdchains => PREROUTING, + minparams => 1, + maxparams => 1, + function => sub() { + fatal_error "Valid class ID expected ($params)" unless $params =~ /^([0-9a-fA-F]+:[0-9a-fA-F]+)$/; + $target = "CLASSIFY --set-class $params"; + }, + }, + + CONNMARK => { + defaultchain => 0, + allowedchains => ALL, + minparams => 1, + maxparams => 1, + function => sub () { + $chain = $designator || $default_chain; + $target = 'CONNTRACK'; + handle_mark_param(''); + }, + }, + + CONTINUE => { + defaultchain => 0, + allowedchains => ALL, + minparams => 0, + maxparams => 0, + function => sub () { + $chain = $designator || $default_chain; + $target = 'RETURN'; + }, + }, + + DIVERT => { + defaultchain => PREROUTING, + allowedchains => PREROUTING, + minparams => 0, + maxparams => 0, + function => sub () { + fatal_error 'DIVERT is only allowed in the PREROUTING chain' if $designator && $designator != PREROUTING; + $chain = PREROUTING; + + my $mark = in_hex( $globals{TPROXY_MARK} ) . '/' . in_hex( $globals{TPROXY_MARK} ); + + unless ( $divertref ) { + $divertref = new_chain( 'mangle', 'divert' ); + add_ijump( $divertref , j => 'MARK', targetopts => "--set-mark $mark" ); + add_ijump( $divertref , j => 'ACCEPT' ); + } + + $target = 'divert'; + + $matches = '! --tcp-flags FIN,SYN,RST,ACK SYN -m socket --transparent '; + }, + }, + + DSCP => { + defaultchain => 0, + allowedchains => PREROUTING | FORWARD | OUTPUT | POSTROUTING, + minparams => 1, + maxparams => 1, + function => sub () { + require_capability 'DSCP_TARGET', 'The DSCP action', 's'; + my $dscp = numeric_value( $params ); + $dscp = $dscpmap{$1} unless defined $dscp; + fatal_error( "Invalid DSCP ($params)" ) unless defined $dscp && $dscp <= 0x38 && ! ( $dscp & 1 ); + $target = 'DSCP --set-dscp ' . in_hex( $dscp ); + }, + }, + + HL => { + defaultchain => FORWARD, + allowedchains => PREROUTING | FORWARD, + minparams => 1, + maxparams => 1, + function => sub() { + fatal_error "HL is not supported in IPv4 - use TTL instead" if $family == F_IPV4; + + $chain = $designator || 'tcfor'; + + $params =~ /^([-+]?(\d+))$/; + + fatal_error "Invalid HL specification( $cmd )" unless supplied( $1 ) && ( $1 eq $2 || $2 != 0 ) && ( $params = abs $params ) < 256; + + $target = 'HL'; + + if ( $1 =~ /^\+/ ) { + $target .= " --hl-inc $params"; + } elsif ( $1 =~ /\-/ ) { + $target .= " --hl-dec $params"; + } else { + $target .= " --hl-set $params"; + }; + }, + }, + + IMQ => { + defaultchain => PREROUTING, + allowedchains => PREROUTING, + minparams => 1, + maxparams => 1, + function => sub () { + require_capability 'IMQ_TARGET', 'IMQ', 's'; + my $imq = numeric_value( $params ); + fatal_error "Invalid IMQ number ($params)" unless defined $imq; + $target = "IMQ --todev $imq"; + }, + }, + + JUMP => { + defaultchain => 0, + allowedchains => ALL, + minparams => 0, + maxparams => 1, + function => sub () { + my ( $tgt, $options ) = split( ' ', $params ); + fatal_error "Unknown target ( $tgt )" unless supplied $tgt; + $target = $params; + }, + }, + + IPMARK => { + defaultchain => 0, + allowedchains => ALL, + minparams => 0, + maxparams => 1, + function => sub () { + my ( $srcdst, $mask1, $mask2, $shift ) = ('src', 255, 0, 0 ); + + require_capability 'IPMARK_TARGET', 'IPMARK', 's'; + $chain = $designator || $default_chain; + + if ( $supplied $params ) { + 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 ( supplied $m1 ) { + $val = numeric_value ($m1); + fatal_error "Invalid Mask ($m1)" unless defined $val && $val && $val <= 0xffffffff; + $mask1 = in_hex ( $val & 0xffffffff ); + } + + if ( supplied $m2 ) { + $val = numeric_value ($m2); + fatal_error "Invalid Mask ($m2)" unless defined $val && $val <= 0xffffffff; + $mask2 = in_hex ( $val & 0xffffffff ); + } + + if ( defined $s ) { + $val = numeric_value ($s); + fatal_error "Invalid Shift Bits ($s)" unless defined $val && $val >= 0 && $val < 128; + $shift = $s; + } + }; + + $target = "IPMARK --addr $srcdst --and-mask $mask1 --or-mask $mask2 --shift $shift"; + }, + }, + + MARK => { + defaultchain => 0, + allowedchains => ALL, + minparams => 1, + maxparams => 1, + function => sub () { + $chain = $designator || $default_chain; + $target = 'CONNTRACK'; + handle_mark_param(''); + }, + }, + + RESTORE => { + defaultchain => 0, + allowedchains => PREROUTING | FORWARD, + minparams => 0, + maxparams => 1, + function => sub () { + $chain = $designator || $default_chain; + $target = 'CONNTRACK'; + handle_mark_param( '--restore-mark' ); + }, + }, + + SAME => { + defaultchain => PREROUTING, + allowedchains => PREROUTING | OUTPUT | STICKY | STICKO, + minparams => 0, + maxparams => 1, + function => sub() { + $chain = $designator || $default_chain; + fatal_error "SAME rules are only allowed in the PREROUTING and OUTPUT chains" unless $chain & ( PREROUTING | OUTPUT ); + $chain = $chain == PREROUTING ? STICKY : STICKO; + $restriction = DESTIFACE_DISALLOW; + ensure_mangle_chain($target); + $sticky++; + }, + }, + + SAVE => { + defaultchain => PREROUTING, + allowedchains => PREROUTING | FORWARD, + minparams => 0, + maxparams => 1, + function => sub () { + $chain = $designator || $default_chain; + $target = 'CONNTRACK'; + handle_mark_param( '--save-mark' ); + }, + }, + + TOS => { + defaultchain => 0, + allowedchains => PREROUTING | FORWARD | OUTPUT | POSTROUTING, + minparams => 1, + maxparams => 1, + function => sub() { + $target .= decode_tos( $1 , 2 ); + }, + }, + + TPROXY => { + defaultchain => PREROUTING, + allowedchains => PREROUTING, + minparams => 0, + maxparams => 2, + function => sub() { + require_capability( 'TPROXY_TARGET', 'Use of TPROXY', 's'); + + $chain = 'PREROUTING'; + + my ( $port, $ip, $bad ); + + if ( $params ) { + ( $port, $ip, $bad ) = split_list $params, 'Parameter'; + fatal_error "Invalid TPROXY specification( TPROXY($params) )" if defined $bad; + } + + $mark = in_hex( $globals{TPROXY_MARK} ) . '/' . in_hex( $globals{TPROXY_MARK} ); + + if ( $port ) { + $port = validate_port( 'tcp', $port ); + } else { + $port = 0; + } + + $target = "TPROXY --on-port $port"; + + if ( supplied $ip ) { + if ( $family == F_IPV6 ) { + if ( $ip =~ /^\[(.+)\]$/ || $ip =~ /^<(.+)>$/ ) { + $ip = $1; + } elsif ( $ip =~ /^\[(.+)\]\/(\d+)$/ ) { + $ip = join( '/', $1, $2 ); + } + } + + validate_address $ip, 1; + $target .= " --on-ip $ip"; + } + + $target .= " --tproxy-mark $mark"; + + $exceptionrule = '-p tcp '; + + }, + }, + + TTL => { + defaultchain => FORWARD, + allowedchains => PREROUTING | FORWARD, + minparams => 1, + maxparams => 1, + function => sub() { + fatal_error "TTL is not supported in IPv6 - use HL instead" if $family == F_IPV6; + fatal_error "Invalid TTL specification( $cmd/$rest )" if $rest; + + $chain = $designator || FORWARD; + $target = 'TTL'; + + $params =~ /^([-+]?(\d+))$/; + + fatal_error "Invalid TTL specification( TTL($params) )" unless supplied( $1 ) && ( $1 eq $2 || $2 != 0 ) && ( $param = abs $param ) < 256; + + if ( $1 =~ /^\+/ ) { + $target .= " --ttl-inc $params"; + } elsif ( $1 =~ /\-/ ) { + $target .= " --ttl-dec $params"; + } else { + $target .= " --ttl-set $params"; + } + }, + }, + + ); + + my @params = split_list1( $params ); + + if ( @params > $commandref->{maxparams} } { + if ( $commandref->{maxparams} == 1 ) { + fatal_error "The $command ACTION only accepts one parmeter"; + } else { + fatal_error "The $command ACTION only accepts $commandref->{maxparams} parmeters"; + } + } + + if ( @params < $commandref->{minparams} ) { + if ( $commandref->{maxparams} == 1 ) { + fatal_error "The $command requires a parameter"; + } else { + fatal_error "The $command ACTION only requires at least $commandref->{maxparams} parmeters"; + } + } + + $commandref->function(); + + $chain ||= $designator; + + +} sub rate_to_kbit( $ ) { my $rate = $_[0]; diff --git a/Shorewall/manpages/shorewall-marks.xml b/Shorewall/manpages/shorewall-marks.xml index 54ebc44d6..e42fcb30b 100644 --- a/Shorewall/manpages/shorewall-marks.xml +++ b/Shorewall/manpages/shorewall-marks.xml @@ -109,10 +109,10 @@ A chain-designator may not be specified if the SOURCE or DEST columns begin with '$FW'. When the SOURCE is $FW, the generated rule - is always placed in the OUTPUT chain.if DEST is '$FW', then the rule - is placed in the OUTPUT chain. + is always placed in the OUTPUT chain. If DEST is '$FW', then the + rule is placed in the OUTPUT chain. - Where a command takes parameteers, those parameters are + Where a command takes parameters, those parameters are enclosed in parentheses ("(....)") and separated by commas. The command may be one of the