Implement SNAT actions and inlines

Signed-off-by: Tom Eastep <teastep@shorewall.net>
This commit is contained in:
Tom Eastep 2016-10-18 10:09:07 -07:00
parent 9796af5d80
commit d52a4b1c9d
No known key found for this signature in database
GPG Key ID: 96E6B3F2423A4D10

View File

@ -1303,18 +1303,19 @@ sub finish_section ( $ ) {
#
sub normalize_action( $$$ ) {
my ( $action, $level, $param ) = @_;
my $caller = ''; #We assume that the function doesn't use @CALLER
my $caller = ''; #We assume that the action doesn't use @CALLER
( $level, my $tag ) = split ':', $level;
if ( $actions{$action}{options} & LOGJUMP_OPT ) {
$level = 'none';
$tag = '';
} else {
$level = 'none' unless supplied $level;
$tag = '' unless defined $tag;
}
#
# Note: SNAT actions store the current interface's name in the tag
#
$tag = '' unless defined $tag;
$param = '' unless defined $param;
$param = '' if $param eq '-';
@ -1612,6 +1613,41 @@ sub merge_macro_source_dest( $$ ) {
$body || '';
}
#
# This one is used by snat inline
#
sub merge_inline_source_dest( $$ ) {
my ( $body, $invocation ) = @_;
if ( $invocation ) {
if ( $body ) {
return $body if $invocation eq '-';
if ( $family == F_IPV4 ) {
fatal_error 'Interface names cannot appear in the DEST column within an action body' if $body =~ /:/;
if ( $invocation =~ /:/ ) {
$invocation =~ s/:.*//;
return join( ':', $invocation, $body );
}
} else {
fatal_error 'Interface names cannot appear in the DEST column within an action body' if $body =~ /:\[|:\+|/;
if ( $invocation =~ /:\[|:\+/ ) {
$invocation =~ s/:.*//;
return join( ':', $invocation, $body );
}
}
return "$invocation:$body";
}
return $invocation;
}
$body || '';
}
sub merge_macro_column( $$ ) {
my ( $body, $invocation ) = @_;
@ -1838,6 +1874,7 @@ my %builtinops = ( 'dropBcast' => \&dropBcast,
sub process_rule ( $$$$$$$$$$$$$$$$$$$$ );
sub process_mangle_rule1( $$$$$$$$$$$$$$$$$$ );
sub process_snat1( $$$$$$$$$$$$ );
sub perl_action_helper( $$;$$ );
#
@ -1886,7 +1923,63 @@ sub process_action(\$\$$) {
my $save_comment = push_comment;
while ( read_a_line( NORMAL_READ ) ) {
if ( $type & MANGLE_TABLE ) {
unless ( $type & ( MANGLE_TABLE | NAT_TABLE | RAW_TABLE ) ) {
my ($target, $source, $dest, $protos, $ports, $sports, $origdest, $rate, $users, $mark, $connlimit, $time, $headers, $condition, $helper );
if ( $file_format == 1 ) {
fatal_error( "FORMAT-1 actions are no longer supported" );
} else {
($target, $source, $dest, $protos, $ports, $sports, $origdest, $rate, $users, $mark, $connlimit, $time, $headers, $condition, $helper )
= split_line2( 'action file',
\%rulecolumns,
$action_commands,
undef,
1 );
}
fatal_error 'TARGET must be specified' if $target eq '-';
if ( $target eq 'DEFAULTS' ) {
default_action_params( $action, split_list $source, 'defaults' );
if ( my $state = $actionref->{state} ) {
my ( $action ) = get_action_params( 1 );
if ( my $check = check_state( $state ) ) {
perl_action_helper( $action, $check == 1 ? state_match( $state ) : '' , $state );
}
}
next;
}
for my $proto ( split_list( $protos, 'Protocol' ) ) {
for my $user ( split_list( $users, 'User/Group' ) ) {
process_rule( $chainref,
'',
'',
$nolog ? $target : merge_levels( join(':', @actparams{'chain','loglevel','logtag'}), $target ),
'',
$source,
$dest,
$proto,
$ports,
$sports,
$origdest,
$rate,
$user,
$mark,
$connlimit,
$time,
$headers,
$condition,
$helper,
0 );
set_inline_matches( $matches );
}
}
} elsif ( $type & MANGLE_TABLE ) {
my ( $originalmark, $source, $dest, $protos, $ports, $sports, $user, $testval, $length, $tos , $connbytes, $helper, $headers, $probability , $dscp , $state, $time );
if ( $family == F_IPV4 ) {
@ -1970,60 +2063,45 @@ sub process_action(\$\$$) {
set_inline_matches( $matches );
}
} else {
my ($target, $source, $dest, $protos, $ports, $sports, $origdest, $rate, $users, $mark, $connlimit, $time, $headers, $condition, $helper );
if ( $file_format == 1 ) {
fatal_error( "FORMAT-1 actions are no longer supported" );
} else {
($target, $source, $dest, $protos, $ports, $sports, $origdest, $rate, $users, $mark, $connlimit, $time, $headers, $condition, $helper )
= split_line2( 'action file',
\%rulecolumns,
$action_commands,
undef,
my ( $action, $source, $dest, $protos, $port, $ipsec, $mark, $user, $condition, $origdest, $probability) =
split_line2( 'snat file',
{ action =>0,
source => 1,
dest => 2,
proto => 3,
port => 4,
ipsec => 5,
mark => 6,
user => 7,
switch => 8,
origdest => 9,
probability => 10,
},
{},
11,
1 );
}
fatal_error 'TARGET must be specified' if $target eq '-';
if ( $target eq 'DEFAULTS' ) {
default_action_params( $action, split_list $source, 'defaults' );
if ( my $state = $actionref->{state} ) {
my ( $action ) = get_action_params( 1 );
if ( my $check = check_state( $state ) ) {
perl_action_helper( $action, $check == 1 ? state_match( $state ) : '' , $state );
}
}
fatal_error 'ACTION must be specified' if $action eq '-';
if ( $action eq 'DEFAULTS' ) {
default_action_params( $chainref, split_list( $source, 'defaults' ) );
next;
}
for my $proto ( split_list( $protos, 'Protocol' ) ) {
for my $user ( split_list( $users, 'User/Group' ) ) {
process_rule( $chainref,
'',
'',
$nolog ? $target : merge_levels( join(':', @actparams{'chain','loglevel','logtag'}), $target ),
'',
for my $proto (split_list( $protos, 'Protocol' ) ) {
process_snat1( $chainref,
$action,
$source,
$dest,
$proto,
$ports,
$sports,
$origdest,
$rate,
$user,
$port,
$ipsec,
$mark,
$connlimit,
$time,
$headers,
$user,
$condition,
$helper,
0 );
set_inline_matches( $matches );
}
$origdest,
$probability,
);
}
}
}
@ -2177,9 +2255,10 @@ sub process_actions() {
make_terminating( $action ) if $opts & TERMINATING_OPT
} else {
fatal_error "Only the 'mangle' and 'filter' table may be specified for non-builtin actions" if $opts & ( RAW_OPT | NAT_OPT );
fatal_error "The 'raw' table may not be specified for non-builtin actions" if $opts & RAW_OPT;
$type |= MANGLE_TABLE if $opts & MANGLE_OPT;
$type |= NAT_TABLE if $opts & NAT_OPT;
my $actionfile = find_file( "action.$action" );
@ -5163,12 +5242,99 @@ sub process_mangle_rule( $ ) {
}
}
sub process_snat_inline( $$$$$$$$$$$$$ ) {
my ($inline, $chainref, $params, $source, $dest, $protos, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability ) = @_;
my $oldparms = push_action_params( $inline,
$chainref,
$params,
'none',
'' ,
$chainref->{name} );
my $inlinefile = $actions{$inline}{file};
my $matches = fetch_inline_matches;
progress_message "..Expanding inline action $inlinefile...";
push_open $inlinefile, 2, 1, undef , 2;
my $save_comment = push_comment;
while ( read_a_line( NORMAL_READ ) ) {
my ( $maction, $msource, $mdest, $mprotos, $mports, $mipsec, $mmark, $muser, $mcondition, $morigdest, $mprobability) =
split_line2( 'snat file',
{ action =>0,
source => 1,
dest => 2,
proto => 3,
port => 4,
ipsec => 5,
mark => 6,
user => 7,
switch => 8,
origdest => 9,
probability => 10,
},
{},
11,
1 );
fatal_error 'ACTION must be specified' if $maction eq '-';
if ( $maction eq 'DEFAULTS' ) {
default_action_params( $chainref, split_list( $msource, 'defaults' ) );
next;
}
$msource = $source if $msource eq '-';
if ( $mdest eq '-' ) {
$mdest = $dest;
} else {
$mdest = merge_inline_source_dest( $mdest, $dest );
}
$mprotos = $protos if $mprotos eq '-';
for my $proto (split_list( $mprotos, 'Protocol' ) ) {
process_snat1( $chainref,
$maction,
$msource,
$mdest,
$proto,
merge_macro_column( $mports, $ports ),
merge_macro_column( $mipsec, $ipsec ),
merge_macro_column( $mmark, $mark ),
merge_macro_column( $muser, $user ),
merge_macro_column( $mcondition, $condition ),
merge_macro_column( $morigdest , $origdest ),
merge_macro_column( $mprobability, $probability ),
);
}
progress_message " Rule \"$currentline\" $done";
set_inline_matches( $matches );
}
pop_comment( $save_comment );
pop_open;
progress_message "..End inline action $inlinefile";
pop_action_params( $oldparms );
}
#
# Process a record in the snat file
#
sub process_one_snat1( $$$$$$$$$$$ ) {
my ($action, $source, $dest, $proto, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability ) = @_;
sub process_snat1( $$$$$$$$$$$$ ) {
my ( $chainref, $action, $source, $dest, $proto, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability ) = @_;
my $inchain;
my $inaction;
my $pre_nat;
my $add_snat_aliases = $family == F_IPV4 && $config{ADD_SNAT_ALIASES};
my $destnets = '';
@ -5178,10 +5344,15 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
my $options = '';
my $addresses;
my $target;
my $params;
my $actiontype;
my $interfaces;
my $interface;
my $normalized_action;
if ( $action =~ /^MASQUERADE(\+)?\((.+)\)$/ ) {
$target = 'MASQUERADE';
$action = $target;
$actiontype = $builtin_target{$action = $target};
$pre_nat = $1;
$addresses = $2;
$options = 'random' if $addresses =~ s/:?random$//;
@ -5189,50 +5360,60 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
$pre_nat = $1;
$addresses = $2;
$target = 'SNAT';
$action = $target;
$actiontype = $builtin_target{$action = $target};
$options .= ':persistent' if $addresses =~ s/:persistent//;
$options .= ':random' if $addresses =~ s/:random//;
$options =~ s/^://;
} elsif ( $action =~ /^CONTINUE(\+)?$/ ) {
$add_snat_aliases = 0;
$target = 'RETURN';
$actiontype = $builtin_target{$target = 'RETURN'};
$pre_nat = $1;
} elsif ( $action eq 'MASQUERADE' ) {
$target = 'MASQUERADE';
$actiontype = $builtin_target{$target = 'MASQUERADE'};
} else {
fatal_error "Invalid ACTION ($action)";
( $target , $params ) = get_target_param1( $action );
$actiontype = $targets{$target};
fatal_error "Invalid ACTION ($action)" unless $actiontype & ( ACTION | INLINE );
}
if ( $inchain = defined $chainref ) {
( $inaction, undef, $interfaces, undef, undef ) = split /:/, $normalized_action = $chainref->{action}, 5 if $chainref->{action};
}
#
# Next, parse the DEST column
#
if ( $family == F_IPV4 ) {
if ( $inaction ) {
fatal_error q('*' is not allowed within an action body) if $pre_nat;
$destnets = $dest;
} elsif ( $family == F_IPV4 ) {
if ( $dest =~ /^([^:]+)::([^:]*)$/ ) {
$add_snat_aliases = 0;
$destnets = $2;
$dest = $1;
$interfaces = $1;
} elsif ( $dest =~ /^([^:]+:[^:]+):([^:]+)$/ ) {
$destnets = $2;
$dest = $1;
$interfaces = $1;
} elsif ( $dest =~ /^([^:]+):$/ ) {
$add_snat_aliases = 0;
$dest = $1;
$interfaces = $1;
} elsif ( $dest =~ /^([^:]+):([^:]*)$/ ) {
my ( $one, $two ) = ( $1, $2 );
if ( $2 =~ /\./ || $2 =~ /^%/ ) {
$dest = $one;
$interfaces = $one;
$destnets = $two;
}
} else {
$interfaces = $dest;
}
} elsif ( $dest =~ /^(.+?):(.+)$/ ) {
$dest = $1;
$interfaces = $1;
$destnets = $2;
} else {
$interfaces = $dest;
}
#
# If there is no source or destination then allow all addresses
#
$source = ALLIP if $source eq '-';
$destnets = ALLIP if $destnets eq '-';
#
# Handle IPSEC options, if any
#
if ( $ipsec ne '-' ) {
@ -5259,10 +5440,12 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
$baserule .= do_user( $user ) if $user ne '-';
$baserule .= do_probability( $probability ) if $probability ne '-';
for $interface ( split_list( $interfaces, 'interface' ) ) {
my $rule = '';
my $saveaddresses = $addresses;
( my $interface = $dest ) =~ s/:.*//;
unless ( $inaction ) {
if ( $interface =~ /(.*)[(](\w*)[)]$/ ) {
$interface = $1;
my $provider = $2;
@ -5286,7 +5469,8 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
$interface = $interfaceref->{name};
}
my $chainref = ensure_chain('nat', $pre_nat ? snat_chain $interface : masq_chain $interface);
$chainref = ensure_chain('nat', $pre_nat ? snat_chain $interface : masq_chain $interface);
}
$baserule .= do_condition( $condition , $chainref->{name} );
@ -5418,7 +5602,54 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
$target .= " --to-ports $addresses";
}
}
#
# And Generate the Rule(s)
#
if ( $actiontype & INLINE ) {
fatal_error( qq(Action $target may not be used in the snat file) ) unless $actiontype & NAT_TABLE;
process_snat_inline( $target,
$chainref,
$params,
$source,
supplied $destnets && $destnets ne '-' ? $inaction ? $destnets : join( ':', $interface, $destnets ) : $inaction ? '-' : $interface,
$proto,
$ports,
$ipsec,
$mark,
$user,
$condition,
$origdest,
$probability );
} else {
if ( $actiontype & ACTION ) {
fatal_error( qq(Action $target may not be used in the snat file) ) unless $actiontype & NAT_TABLE;
#
# Create the action:level:tag:param tuple. Since we don't allow logging out of nat POSTROUTING, we store
# the interface name in the log tag
#
my $normalized_target = normalize_action( $target, "none:$interface", $params );
fatal_error( "Action $target invoked Recursively (" . join( '->', map( external_name( $_ ), @actionstack , $normalized_target ) ) . ')' ) if $active{$target};
my $ref = use_action( 'nat', $normalized_target );
if ( $ref ) {
#
# First reference to this tuple - process_action may modify both $normalized_target and $ref!!!
#
process_action( $normalized_target, $ref, $chainref->{name} );
#
# Capture the name of the action chain
#
} else {
#
# We've seen this tuple before
#
$ref = $usedactions{$normalized_target};
}
$target = $ref->{name};
} else {
for my $option ( split_list2( $options , 'option' ) ) {
if ( $option eq 'random' ) {
$target .= ' --random';
@ -5431,10 +5662,13 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
fatal_error "Invalid $action option ($option)";
}
}
}
#
# If there is no source or destination then allow all addresses
#
$source = ALLIP if $source eq '-';
$destnets = ALLIP unless supplied $destnets && $destnets ne '-';
#
# And Generate the Rule(s)
#
expand_rule( $chainref ,
POSTROUTE_RESTRICT ,
$prerule ,
@ -5450,12 +5684,16 @@ sub process_one_snat1( $$$$$$$$$$$ ) {
unless unreachable_warning( 0, $chainref );
conditional_rule_end( $chainref ) if $detectaddress || $conditional;
}
$addresses = $saveaddresses;
}
progress_message " Snat record \"$currentline\" $done"
}
sub process_one_snat( )
sub process_snat( )
{
my ($action, $source, $dest, $protos, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability ) =
split_line2( 'snat file',
@ -5468,7 +5706,7 @@ sub process_one_snat( )
fatal_error 'DEST must be specified' if $dest eq '-';
for my $proto ( split_list $protos, 'Protocol' ) {
process_one_snat1( $action, $source, $dest, $proto, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability );
process_snat1( undef, $action, $source, $dest, $proto, $ports, $ipsec, $mark, $user, $condition, $origdest, $probability );
}
}
@ -5487,7 +5725,7 @@ sub setup_snat( $ ) # Convert masq->snat if true
process_one_masq(0) while read_a_line( NORMAL_READ );
} elsif ( $fn = open_file( 'snat', 1, 1 ) ) {
process_one_snat while read_a_line( NORMAL_READ );
process_snat while read_a_line( NORMAL_READ );
}
}