shorewall_code/Shorewall/Perl/Shorewall/Misc.pm
Tom Eastep a9c2ee3a76 Major cleanup of DYNAMIC_BLACKLIST code
1) Avoid having to parse the setting in the Zones, Misc and rules modules
2) Apply ipset match rule after dealing with exclusions rather than before
3) Correct handling of src-dst

Signed-off-by: Tom Eastep <teastep@shorewall.net>
2024-03-05 14:45:41 -08:00

2945 lines
90 KiB
Perl

#
# Shorewall 5.2 -- /usr/share/shorewall/Shorewall/Misc.pm
#
# This program is under GPL [http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt]
#
# (c) 2007-2019 - Tom Eastep (teastep@shorewall.net)
#
# Complete documentation is available at https://shorewall.org
#
# This program is part of Shorewall.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 2 of the license or, at your
# option, any later version.
#
# 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, see <http://www.gnu.org/licenses/>.
#
# This module contains those routines that don't seem to fit well elsewhere. It
# was carved from the Rules module in 4.4.16.
#
package Shorewall::Misc;
require Exporter;
use Shorewall::Config qw(:DEFAULT :internal);
use Shorewall::IPAddrs;
use Shorewall::Zones;
use Shorewall::Chains qw(:DEFAULT :internal);
use Shorewall::Rules;
use Shorewall::Proc;
use sort 'stable';
use strict;
our @ISA = qw(Exporter);
our @EXPORT = qw( process_tos
setup_ecn
add_common_rules
setup_mac_lists
convert_routestopped
process_stoppedrules
compile_stop_firewall
generate_matrix
);
our @EXPORT_OK = qw( initialize );
our $VERSION = 'MODULEVERSION';
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;
}
#
# Warn that the tos file is no longer supported
#
sub process_tos() {
if ( my $fn = open_file 'tos' ) {
my $first_entry = 1;
my ( $pretosref, $outtosref );
first_entry( sub { progress_message2 "$doing $fn...";
warning_message "Use of the tos file is no longer supported -- use the TOS target in the mangle file instead";
}
);
while ( read_a_line( NORMAL_READ )) { 1; }
}
}
#
# Setup ECN disabling rules
#
sub setup_ecn()
{
my %interfaces;
my @hosts;
my $interfaceref;
if ( my $fn = open_file 'ecn' ) {
first_entry( sub { progress_message2 "$doing $fn...";
require_mangle_capability 'MANGLE_ENABLED', 'Entries in the ecn file', '';
warning_message 'ECN will not be applied to forwarded packets' unless have_capability 'MANGLE_FORWARD';
} );
while ( read_a_line( NORMAL_READ ) ) {
my ($interface, $hosts ) = split_line1( 'ecn file entry',
{ interface => 0, host => 1, hosts => 1 },
{},
2 );
fatal_error 'INTERFACE must be specified' if $interface eq '-';
fatal_error "Unknown interface ($interface)" unless $interfaceref = known_interface( $interface );
if ( $interfaceref->{root} ) {
$interface = $interfaceref->{name} if $interface eq $interfaceref->{physical};
} else {
$interface = $interfaceref->{name};
}
my $lineinfo = shortlineinfo( '' );
$interfaces{$interface} ||= $lineinfo;
$hosts = ALLIP if $hosts eq '-';
for my $host( split_list $hosts, 'address' ) {
validate_host( $host , 1 );
push @hosts, [ $interface, $lineinfo, $host ];
}
}
if ( @hosts ) {
my @interfaces = ( sortkeysiftest %interfaces );
progress_message "$doing ECN control on @interfaces...";
for my $interface ( @interfaces ) {
my $chainref = ensure_chain 'mangle', ecn_chain( $interface );
add_ijump_extended $mangle_table->{POSTROUTING} , j => $chainref, $interfaces{$interface}, p => 'tcp', imatch_dest_dev( $interface ) if have_capability 'MANGLE_FORWARD';
add_ijump_extended $mangle_table->{OUTPUT}, j => $chainref, $interfaces{$interface}, p => 'tcp', imatch_dest_dev( $interface );
}
for my $host ( @hosts ) {
add_ijump_extended( $mangle_table->{ecn_chain $host->[0]}, j => 'ECN', $host->[1], targetopts => '--ecn-tcp-remove', p => 'tcp', imatch_dest_net( $host->[2] ) );
}
}
}
}
#
# Add a logging rule followed by a jump
#
sub add_rule_pair( $$$$$ ) {
my ($chainref , $predicate , $target , $level, $tag ) = @_;
log_rule_limit( $level,
$chainref,
$chainref->{name},
"\U$target",
$globals{LOGLIMIT},
$tag,
'add',
$predicate ) if supplied $level;
add_jump( $chainref , $target, 0, $predicate );
}
#
# Remove instances of 'blacklist' from the passed file.
#
sub remove_blacklist( $ ) {
my $file = shift;
my $fn = find_file $file;
return 1 unless -f $file;
my $oldfile = open_file $fn;
my $newfile;
my $changed;
open $newfile, '>', "$fn.new" or fatal_error "Unable to open $fn.new for output: $!";
while ( read_a_line( EMBEDDED_ENABLED | EXPAND_VARIABLES ) ) {
my ( $rule, $comment ) = split '#', $currentline, 2;
if ( $rule && $rule =~ /blacklist/ ) {
$changed = 1;
if ( $comment ) {
$comment =~ s/^/ / while $rule =~ s/blacklist,// || $rule =~ s/,blacklist//;
$rule =~ s/blacklist/ /g;
$currentline = join( '#', $rule, $comment );
} else {
$currentline =~ s/blacklist,//g;
$currentline =~ s/,blacklist//g;
$currentline =~ s/blacklist/ /g;
}
}
print $newfile "$currentline\n";
}
close $newfile;
if ( $changed ) {
rename $fn, "$fn.bak" or fatal_error "Unable to rename $fn to $fn.bak: $!";
rename "$fn.new", $fn or fatal_error "Unable to rename $fn.new to $fn: $!";
transfer_permissions( "$fn.bak", $fn );
progress_message2 "\u$file file $fn saved in $fn.bak"
}
}
#
# Convert a pre-4.4.25 blacklist to a 4.4.25 blrules file
#
sub convert_blacklist() {
my $zones = find_zones_by_option 'blacklist', 'in';
my $zones1 = find_zones_by_option 'blacklist', 'out';
my ( $level, $disposition ) = @config{'BLACKLIST_LOG_LEVEL', 'BLACKLIST_DISPOSITION' };
my $tag = $globals{MACLIST_LOG_TAG};
my $audit = $disposition =~ /^A_/;
my $target = $disposition;
my $orig_target = $target;
my $warnings = 0;
my @rules;
if ( @$zones || @$zones1 ) {
if ( supplied $level ) {
$target = supplied $tag ? "$target:$level:$tag":"$target:$level";
}
my $fn = open_file( 'blacklist' );
unless ( $fn ) {
if ( -f ( $fn = find_file( 'blacklist' ) ) ) {
if ( unlink( $fn ) ) {
warning_message "Empty blacklist file ($fn) removed";
} else {
warning_message "Unable to remove empty blacklist file $fn: $!";
}
}
return 0;
}
directive_callback(
sub ()
{
warning_message "Omitted rules and compiler directives were not translated" unless $warnings++;
}
);
first_entry "Converting $fn...";
while ( read_a_line( NORMAL_READ ) ) {
my ( $networks, $protocol, $ports, $options ) =
split_rawline2( 'blacklist file',
{ networks => 0, proto => 1, port => 2, options => 3 },
{},
4,
);
if ( $options eq '-' ) {
$options = 'src';
} elsif ( $options eq 'audit' ) {
$options = 'audit,src';
}
my ( $to, $from, $whitelist, $auditone ) = ( 0, 0, 0, 0 );
my @options = split_list $options, 'option';
for ( @options ) {
$whitelist++ if $_ eq 'whitelist';
$auditone++ if $_ eq 'audit';
}
warning_message "Duplicate 'whitelist' option ignored" if $whitelist > 1;
my $tgt = $whitelist ? 'WHITELIST' : $target;
if ( $auditone ) {
fatal_error "'audit' not allowed in whitelist entries" if $whitelist;
if ( $audit ) {
warning_message "Superfluous 'audit' option ignored";
} else {
warning_message "Duplicate 'audit' option ignored" if $auditone > 1;
}
}
for ( @options ) {
if ( $_ =~ /^(?:src|from)$/ ) {
if ( $from++ ) {
warning_message "Duplicate 'src' ignored";
} else {
if ( @$zones ) {
push @rules, [ 'src', $tgt, $networks, $protocol, $ports ];
} else {
warning_message '"src" entry ignored because there are no "blacklist in" zones';
}
}
} elsif ( $_ =~ /^(?:dst|to)$/ ) {
if ( $to++ ) {
warning_message "Duplicate 'dst' ignored";
} else {
if ( @$zones1 ) {
push @rules, [ 'dst', $tgt, $networks, $protocol, $ports ];
} else {
warning_message '"dst" entry ignored because there are no "blacklist out" zones';
}
}
} else {
fatal_error "Invalid blacklist option($_)" unless $_ eq 'whitelist' || $_ eq 'audit';
}
}
}
directive_callback(0);
if ( @rules ) {
my $fn1 = find_writable_file( 'blrules' );
my $blrules;
my $date = compiletime;
if ( -f $fn1 ) {
open $blrules, '>>', $fn1 or fatal_error "Unable to open $fn1: $!";
} else {
open $blrules, '>', $fn1 or fatal_error "Unable to open $fn1: $!";
transfer_permissions( $fn, $fn1 );
print $blrules <<'EOF';
#
# Shorewall - Blacklist Rules File
#
# For information about entries in this file, type "man shorewall-blrules"
#
# Please see https://shorewall.org/blacklisting_support.htm for additional
# information.
#
###################################################################################################################################################################################################
#ACTION SOURCE DEST PROTO DEST SOURCE ORIGINAL RATE USER/ MARK CONNLIMIT TIME HEADERS SWITCH
# PORT PORT(S) DEST LIMIT GROUP
EOF
}
print( $blrules
"#\n" ,
"# Rules generated from blacklist file $fn by Shorewall $globals{VERSION} - $date\n" ,
"#\n" );
for ( @rules ) {
my ( $srcdst, $tgt, $networks, $protocols, $ports ) = @$_;
$tgt .= "\t\t";
my $list = $srcdst eq 'src' ? $zones : $zones1;
for my $zone ( @$list ) {
my $rule = $tgt;
if ( $srcdst eq 'src' ) {
if ( $networks ne '-' ) {
$rule .= "$zone:$networks\tall\t\t";
} else {
$rule .= "$zone\t\t\tall\t\t";
}
} else {
if ( $networks ne '-' ) {
$rule .= "all\t\t\t$zone:$networks\t";
} else {
$rule .= "all\t\t\t$zone\t\t\t";
}
}
$rule .= "\t$protocols" if $protocols ne '-';
$rule .= "\t$ports" if $ports ne '-';
print $blrules "$rule\n";
}
}
close $blrules;
} else {
warning_message q(There are interfaces or zones with the 'blacklist' option but the 'blacklist' file is empty or does not exist) unless @rules;
}
if ( -f $fn ) {
rename $fn, "$fn.bak";
progress_message2 "Blacklist file $fn saved in $fn.bak";
}
for my $file ( qw(zones interfaces hosts) ) {
remove_blacklist $file;
}
progress_message2 "Blacklist successfully converted";
return 1;
} else {
my $fn = find_file 'blacklist';
if ( -f $fn ) {
rename $fn, "$fn.bak" or fatal_error "Unable to rename $fn to $fn.bak: $!";
warning_message "No zones have the blacklist option - the blacklist file was saved in $fn.bak";
}
return 0;
}
}
#
# Convert a routestopped file into an equivalent stoppedrules file
#
sub convert_routestopped() {
if ( my $fn = open_file 'routestopped' ) {
my ( @allhosts, %source, %dest , %notrack, @rule );
my $seq = 0;
my $warnings = 0;
my $date = compiletime;
my ( $stoppedrules, $fn1 );
if ( -f ( $fn1 = find_writable_file( 'stoppedrules' ) ) ) {
open $stoppedrules, '>>', $fn1 or fatal_error "Unable to open $fn1: $!";
} else {
open $stoppedrules, '>', $fn1 or fatal_error "Unable to open $fn1: $!";
transfer_permissions( $fn, $fn1 );
print $stoppedrules <<'EOF';
#
# Shorewall - Stopped Rules File
#
# For information about entries in this file, type "man shorewall-stoppedrules"
#
# The manpage is also online at
# https://shorewall.org/manpages/shorewall-stoppedrules.html
#
# See https://shorewall.org/starting_and_stopping_shorewall.htm for additional
# information.
#
###############################################################################
#ACTION SOURCE DEST PROTO DEST SOURCE
# PORT(S) PORT(S)
EOF
}
directive_callback(
sub ()
{
warning_message "Omitted rules and compiler directives were not translated" unless $warnings++;
}
);
first_entry(
sub {
my $date = compiletime;
progress_message2 "$doing $fn...";
print( $stoppedrules
"#\n" ,
"# Rules generated from routestopped file $fn by Shorewall $globals{VERSION} - $date\n" ,
"#\n" );
}
);
while ( read_a_line ( NORMAL_READ ) ) {
my ($interface, $hosts, $options , $proto, $ports, $sports ) =
split_rawline2( 'routestopped file',
{ interface => 0, hosts => 1, options => 2, proto => 3, dport => 4, sport => 5 },
{},
6,
0,
);
my $interfaceref;
fatal_error 'INTERFACE must be specified' if $interface eq '-';
$hosts = ALLIP unless $hosts && $hosts ne '-';
my $routeback = 0;
my @hosts;
$seq++;
my $rule = "$proto\t$ports\t$sports";
$hosts = ALLIP if $hosts eq '-';
for my $host ( split /,/, $hosts ) {
push @hosts, "$interface|$host|$seq";
push @rule, $rule;
}
unless ( $options eq '-' ) {
for my $option (split /,/, $options ) {
if ( $option eq 'routeback' ) {
if ( $routeback ) {
warning_message "Duplicate 'routeback' option ignored";
} else {
$routeback = 1;
}
} elsif ( $option eq 'source' ) {
for my $host ( split /,/, $hosts ) {
$source{"$interface|$host|$seq"} = 1;
}
} elsif ( $option eq 'dest' ) {
for my $host ( split /,/, $hosts ) {
$dest{"$interface|$host|$seq"} = 1;
}
} elsif ( $option eq 'notrack' ) {
for my $host ( split /,/, $hosts ) {
$notrack{"$interface|$host|$seq"} = 1;
}
} else {
warning_message "Unknown routestopped option ( $option ) ignored" unless $option eq 'critical';
warning_message "The 'critical' option is no longer supported (or needed)";
}
}
}
if ( $routeback || $interfaceref->{options}{routeback} ) {
my $chainref = $filter_table->{FORWARD};
for my $host ( split /,/, $hosts ) {
print $stoppedrules "ACCEPT\t$interface:$host\t$interface:$host\n";
}
}
push @allhosts, @hosts;
}
directive_callback(0);
for my $host ( @allhosts ) {
my ( $interface, $h, $seq ) = split /\|/, $host;
my $rule = shift @rule;
print $stoppedrules "ACCEPT\t$interface:$h\t\$FW\t$rule\n";
print $stoppedrules "ACCEPT\t\$FW\t$interface:$h\t$rule\n" unless $config{ADMINISABSENTMINDED};
my $matched = 0;
if ( $source{$host} ) {
print $stoppedrules "ACCEPT\t$interface:$h\t-\t$rule\n";
$matched = 1;
}
if ( $dest{$host} ) {
print $stoppedrules "ACCEPT\t-\t$interface:$h\t$rule\n";
$matched = 1;
}
if ( $notrack{$host} ) {
print $stoppedrules "NOTRACK\t$interface:$h\t-\t$rule\n";
print $stoppedrules "NOTRACK\t\$FW\t$interface:$h\t$rule\n";
}
unless ( $matched ) {
for my $host1 ( @allhosts ) {
unless ( $host eq $host1 ) {
my ( $interface1, $h1 , $seq1 ) = split /\|/, $host1;
print $stoppedrules "ACCEPT\t$interface:$h\t$interface1:$h1\t$rule\n";
}
}
}
}
rename $fn, "$fn.bak";
progress_message2 "Routestopped file $fn saved in $fn.bak";
close $stoppedrules;
} elsif ( -f ( my $fn1 = find_file( 'routestopped' ) ) ) {
if ( unlink( $fn1 ) ) {
warning_message "Empty routestopped file ($fn1) removed";
} else {
warning_message "Unable to remove empty routestopped file $fn1: $!";
}
}
}
#
# Process the stoppedrules file. Returns true if the file was non-empty.
#
sub process_stoppedrules() {
my $fw = firewall_zone;
my $result;
if ( my $fn = open_file 'stoppedrules' , 1, 1 ) {
first_entry sub () {
progress_message2( "$doing $fn..." );
unless ( $config{ADMINISABSENTMINDED} ) {
insert_ijump $filter_table ->{$_}, j => 'ACCEPT', 0, state_imatch 'ESTABLISHED,RELATED' for qw/INPUT FORWARD/;
}
};
while ( read_a_line( NORMAL_READ ) ) {
$result = 1;
my ( $target, $source, $dest, $protos, $ports, $sports ) =
split_line1( 'stoppedrules file',
{ target => 0, source => 1, dest => 2, proto => 3, dport => 4, sport => 5 } );
fatal_error( "Invalid TARGET ($target)" ) unless $target =~ /^(?:ACCEPT|DROP|NOTRACK)$/;
my $tableref;
my $raw;
my $chainref;
my $restriction = NO_RESTRICT;
if ( $raw = ( $target eq 'NOTRACK' || $target eq 'DROP' ) ) {
$tableref = $raw_table;
require_capability 'RAW_TABLE', 'NOTRACK', 's';
$chainref = $raw_table->{PREROUTING};
$restriction = PREROUTE_RESTRICT | DESTIFACE_DISALLOW;
} else {
$tableref = $filter_table;
}
if ( $source eq $fw ) {
$chainref = ( $raw ? $raw_table : $filter_table)->{OUTPUT};
$source = '';
$restriction = OUTPUT_RESTRICT;
} elsif ( $source =~ s/^($fw):// ) {
$chainref = ( $raw ? $raw_table : $filter_table)->{OUTPUT};
$restriction = OUTPUT_RESTRICT;
}
if ( $dest eq $fw ) {
fatal_error "\$FW may not be specified as the destination of a NOTRACK or DROP rule" if $raw;
$chainref = $filter_table->{INPUT};
$dest = '';
$restriction = INPUT_RESTRICT;
} elsif ( $dest =~ s/^($fw):// ) {
fatal_error "\$FW may not be specified as the destination of a NOTRACK or DROP rule" if $raw;
$chainref = $filter_table->{INPUT};
$restriction = INPUT_RESTRICT;
}
$chainref = $tableref->{FORWARD} unless $chainref;
my $disposition = $target;
$target = 'CT --notrack' if $target eq 'NOTRACK' and have_capability( 'CT_TARGET' );
unless ( $restriction == OUTPUT_RESTRICT
&& $target eq 'ACCEPT'
&& $config{ADMINISABSENTMINDED} ) {
for my $proto ( split_list $protos, 'Protocol' ) {
expand_rule( $chainref ,
$restriction ,
'' ,
do_proto( $proto, $ports, $sports ) ,
$source ,
$dest ,
'' ,
$target,
'',
$disposition,
do_proto( $proto, '-', '-' ),
'');
}
} else {
warning_message "Redundant OUTPUT rule ignored because ADMINISABSENTMINDED=Yes";
}
}
}
$result;
}
#
# Generate the rules required when DOCKER=Yes
#
sub create_docker_rules() {
my $bridge = $config{DOCKER_BRIDGE};
add_commands( $nat_table->{PREROUTING} , '[ -n "$g_docker" ] && echo "-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER" >&3' );
my $chainref = $filter_table->{FORWARD};
add_commands( $chainref, '[ -n "$g_dockeringress" ] && echo "-A FORWARD -j DOCKER-INGRESS" >&3' );
add_commands( $chainref, '[ -n "$g_dockeruser" ] && echo "-A FORWARD -j DOCKER-USER" >&3' );
add_commands( $chainref, '[ -n "$g_dockeriso" ] && echo "-A FORWARD -j DOCKER-ISOLATION" >&3' );
add_commands( $chainref, '[ -n "$g_dockerisostage" ] && echo "-A FORWARD -j DOCKER-ISOLATION-STAGE-1" >&3' );
if ( my $dockerref = known_interface( $bridge ) ) {
add_commands( $chainref, 'if [ -n "$g_docker" ]; then' );
incr_cmd_level( $chainref );
add_ijump( $chainref, j => 'DOCKER', o => $bridge );
add_ijump( $chainref, j => 'ACCEPT', o => $bridge , state_imatch 'ESTABLISHED,RELATED' );
add_ijump( $chainref, j => 'ACCEPT', i => $bridge , o => "! $bridge" );
add_ijump( $chainref, j => 'ACCEPT', i => $bridge , o => $bridge ) if $dockerref->{options}{routeback};
decr_cmd_level( $chainref );
add_commands( $chainref, 'fi' );
my $outputref;
add_commands( $outputref = $filter_table->{OUTPUT}, 'if [ -n "$g_docker" ]; then' );
incr_cmd_level( $outputref );
add_ijump( $outputref, j => 'DOCKER' );
decr_cmd_level( $outputref );
add_commands( $outputref, 'fi' );
}
add_commands( $chainref, '[ -f ${VARDIR}/.filter_FORWARD ] && cat $VARDIR/.filter_FORWARD >&3', );
}
sub setup_mss();
sub add_ipset_dbl_ijump( $$$@) {
my ( $chainref, $target, $ipset_dir ) = ( shift, shift, shift );
add_ijump_extended( $chainref, j => $target, $origin{DYNAMIC_BLACKLIST}, @_, "set --match-set" => $ipset_dir );
}
#
# Add rules generated by .conf options and interface options
#
sub add_common_rules ( $ ) {
my ( $upgrade ) = @_;
my $interface;
my $chainref;
my $target;
my $target1;
my $rule;
my $list;
my $chain;
my $dynamicref;
my @state = state_imatch( $globals{BLACKLIST_STATES} );
my $faststate = $config{RELATED_DISPOSITION} eq 'ACCEPT' && $config{RELATED_LOG_LEVEL} eq '' ? 'ESTABLISHED,RELATED' : 'ESTABLISHED';
my $level = $config{BLACKLIST_LOG_LEVEL};
my $tag = $globals{BLACKLIST_LOG_TAG};
my $rejectref = $filter_table->{reject};
my $dbl_type;
my $dbl_ipset;
my $dbl_level;
my $dbl_tag;
my $dbl_timeout;
my $dbl_src_target;
my $dbl_dst_target;
if ( $config{REJECT_ACTION} ) {
process_reject_action;
fatal_error( "The REJECT_ACTION ($config{REJECT_ACTION}) is not terminating" ) unless terminating( $rejectref );
} else {
if ( have_capability( 'ADDRTYPE' ) ) {
add_ijump $rejectref , j => 'DROP' , addrtype => '--src-type BROADCAST';
} else {
if ( $family == F_IPV4 ) {
add_commands $rejectref, 'for address in $ALL_BCASTS; do';
} else {
add_commands $rejectref, 'for address in $ALL_ACASTS; do';
}
incr_cmd_level $rejectref;
add_ijump $rejectref, j => 'DROP', d => '$address';
decr_cmd_level $rejectref;
add_commands $rejectref, 'done';
}
if ( $family == F_IPV4 ) {
add_ijump $rejectref , j => 'DROP', s => '224.0.0.0/4';
} else {
add_ijump $rejectref , j => 'DROP', s => IPv6_MULTICAST;
}
add_ijump $rejectref , j => 'DROP', p => 2;
add_ijump $rejectref , j => 'REJECT', targetopts => '--reject-with tcp-reset', p => 6;
if ( have_capability( 'ENHANCED_REJECT' ) ) {
add_ijump $rejectref , j => 'REJECT', p => 17;
if ( $family == F_IPV4 ) {
add_ijump $rejectref, j => 'REJECT --reject-with icmp-host-unreachable', p => 1;
add_ijump $rejectref, j => 'REJECT --reject-with icmp-host-prohibited';
} else {
add_ijump $rejectref, j => 'REJECT --reject-with icmp6-addr-unreachable', p => 58;
add_ijump $rejectref, j => 'REJECT --reject-with icmp6-adm-prohibited';
}
} else {
add_ijump $rejectref , j => 'REJECT';
}
}
#
# Insure that Docker jumps are early in the builtin chains
#
create_docker_rules if $config{DOCKER};
if ( my $dbl = $globals{DBL} ) {
if ( $dbl & DBL_CLASSIC ) {
#
# Classic chain-based backlisting
#
add_rule_pair( set_optflags( new_standard_chain( 'logdrop' ) , DONT_OPTIMIZE | DONT_DELETE ), '' , 'DROP' , $level , $tag);
add_rule_pair( set_optflags( new_standard_chain( 'logreject' ), DONT_OPTIMIZE | DONT_DELETE ), '' , 'reject' , $level , $tag);
$dynamicref = set_optflags( new_standard_chain( 'dynamic' ) , DONT_OPTIMIZE );
add_commands( $dynamicref, '[ -f ${VARDIR}/.dynamic ] && cat ${VARDIR}/.dynamic >&3' );
}
if ( $dbl & DBL_IPSET ) {
#
# ipset-based blacklisting - not mutually exclusive with the classic type
#
( $dbl_ipset, $dbl_level, $dbl_tag , $dbl_timeout) = ( $globals{DBL_IPSET_NAME}, $globals{DBL_LEVEL}, $globals{DBL_TAG}, $globals{DBL_TIMEOUT} );
if ( $dbl_timeout ) {
$dbl_src_target = ( ($dbl & DBL_SRC_DST) == DBL_SRC_DST ) ? 'dbl_src' : 'dbl_log';
my $chainref = new_standard_chain( $dbl_src_target );
log_rule_limit( $dbl_level,
$chainref,
'dbl_log',
'DROP',
$globals{LOGLIMIT},
$dbl_tag,
'add',
'',
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset src --exist --timeout $globals{DBL_TIMEOUT}", $origin{DYNAMIC_BLACKLIST} ) unless $dbl & DBL_NOUPDATE;;
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
if ( $dbl_src_target eq 'dbl_src' ) {
$chainref = new_standard_chain( $dbl_dst_target = 'dbl_dst' );
log_rule_limit( $dbl_level,
$chainref,
'dbl_log',
'DROP',
$globals{LOGLIMIT},
$dbl_tag,
'add',
'',
$origin{DYNAMIC_BLACKLIST} ) if $dbl_level;
add_ijump_extended( $chainref, j => "SET --add-set $dbl_ipset dst --exist --timeout $globals{DBL_TIMEOUT}", $origin{DYNAMIC_BLACKLIST} );
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
} else {
$dbl_dst_target = $dbl_src_target;
}
} elsif ( $dbl_level ) {
my $chainref = new_standard_chain( $dbl_src_target = $dbl_dst_target = 'dbl_log' );
log_rule_limit( $dbl_level,
$chainref,
'dbl_log',
'DROP',
$globals{LOGLIMIT},
$dbl_tag,
'add',
'',
$origin{DYNAMIC_BLACKLIST} );
add_ijump_extended( $chainref, j => 'DROP', $origin{DYNAMIC_BLACKLIST} );
} else {
$dbl_src_target = $dbl_dst_target = 'DROP';
}
}
}
setup_mss;
if ( $config{FASTACCEPT} ) {
add_ijump_extended( $filter_table->{OUTPUT} , j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )
}
my $policy = $config{SFILTER_DISPOSITION};
$level = $config{SFILTER_LOG_LEVEL};
$tag = $config{SFILTER_LOG_TAG};
my $audit = $policy =~ s/^A_//;
my @ipsec = have_ipsec ? ( policy => '--pol none --dir in' ) : ();
my $origin = $origin{SFILTER_DISPOSITION};
if ( $level || $audit ) {
#
# Create a chain to log and/or audit and apply the policy
#
$chainref = new_standard_chain 'sfilter';
log_rule_limit( $level,
$chainref,
$chainref->{name},
$policy,
$globals{LOGLIMIT},
$tag,
'add',
'',
$origin{SFILTER_LOG_LEVEL} ) if $level ne '';
add_ijump_extended( $chainref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $policy ) if $audit;
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
$target = 'sfilter';
} else {
$target = $policy eq 'REJECT' ? 'reject' : $policy;
}
if ( @ipsec ) {
#
# sfilter1 will be used in the FORWARD chain where we allow traffic entering the interface
# to leave the interface encrypted. We need a separate chain because '--dir out' cannot be
# used in the input chain
#
$chainref = new_standard_chain 'sfilter1';
add_ijump ( $chainref, j => 'RETURN', policy => '--pol ipsec --dir out' );
log_rule_limit( $level,
$chainref,
$chainref->{name},
$policy,
$globals{LOGLIMIT},
$tag,
'add',
'' ,
$origin ) if $level ne '';
add_ijump_extended( $chainref, j => 'AUDIT', $origin{SFILTER_DISPOSITION}, targetopts => '--type ' . lc $policy ) if $audit;
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
$target1 = 'sfilter1';
} else {
#
# No IPSEC -- use the same target in both INPUT and FORWARD
#
$target1 = $target;
}
#
# Interface-specific processing
#
for $interface ( all_real_interfaces ) {
ensure_chain( 'filter', $_ )->{origin} = interface_origin( $interface )
for first_chains( $interface ), output_chain( $interface ), option_chains( $interface ), output_option_chain( $interface );
my $interfaceref = find_interface $interface;
unless ( $interfaceref->{physical} eq loopback_interface ) {
#
# sfilter
#
unless ( $interfaceref->{options}{ignore} & NO_SFILTER || $interfaceref->{options}{rpfilter} ) {
my @filters = @{$interfaceref->{filter}};
my $origin = $interfaceref->{origin};
$chainref = $filter_table->{forward_option_chain $interface};
if ( @filters ) {
add_ijump_extended( $chainref , @ipsec ? 'j' : 'g' => $target1, $origin, imatch_source_net( $_ ), @ipsec ), $chainref->{filtered}++ for @filters;
} elsif ( $interfaceref->{bridge} eq $interface ) {
add_ijump_extended( $chainref , @ipsec ? 'j' : 'g' => $target1, $origin, imatch_dest_dev( $interface ), @ipsec ), $chainref->{filtered}++
unless( $config{ROUTE_FILTER} eq 'on' ||
$interfaceref->{options}{routeback} ||
$interfaceref->{options}{routefilter} ||
$interfaceref->{physical} eq '+' );
}
if ( @filters ) {
$chainref = $filter_table->{input_option_chain $interface};
add_ijump_extended( $chainref , g => $target, $origin, imatch_source_net( $_ ), @ipsec ), $chainref->{filtered}++ for @filters;
}
}
#
# Dynamic Blacklisting
#
my ( $input_option_chainref,
$forward_option_chainref,
$output_option_chainref,
$classic_target_chain,
)
=
( $filter_table->{input_option_chain($interface)},
$filter_table->{forward_option_chain($interface)},
$filter_table->{output_option_chain($interface)},
$dynamicref,
);
if ( ( my $setting = get_interface_option( $interface, 'dbl' ) ) != DBL_NONE ) {
my @matches = @state;
my ( @src_exclude, @dst_exclude, @classic_exclude );
my @nodbl = @{$interfaceref->{nodbl}};
if ( @nodbl ) {
#
# We have blacklisting exclusions defined in the hosts file
#
if ( @nodbl > 1 ) {
#
# We need to create an intermediate chain
#
if ( $dbl_ipset ) {
$chainref = new_standard_chain( nodbl_src_chain( $interface ));
for (@nodbl) {
add_ijump( $chainref, j => 'RETURN', s => $_ );
}
add_ijump( $input_option_chainref, j => $chainref->{name} );
add_ijump( $forward_option_chainref, j => $chainref->{name} );
$input_option_chainref = $forward_option_chainref = $chainref;
if ( $dbl_src_target ne $dbl_dst_target ) {
$chainref = new_standard_chain( nodbl_dst_chain( $interface ));
for ( @nodbl ){
add_ijump( $chainref, j => 'RETURN', -d => $_ );
}
add_ijump( $output_option_chainref, j => $chainref->{name} );
$output_option_chainref = $chainref,
}
}
if ( $setting & DBL_CLASSIC ) {
$chainref = new_standard_chain( nodbl_classic_chain( $interface ));
for (@nodbl) {
add_ijump( $chainref, j => 'RETURN', s => $_ );
add_ijump( $chainref, j => 'RETURN', d => $_ );
}
add_ijump( $chainref, j => $dynamicref->{name} );
$classic_target_chain = $chainref;
}
} else {
#
# Easy case
#
@matches = ( s => "! $nodbl[0]" , @matches );
@matches = ( d => "! $nodbl[0]" , @matches );
}
}
if ( $dbl_ipset ) {
if ( $setting & DBL_SRC) {
#
# src or src-dst
#
add_ipset_dbl_ijump( $input_option_chainref, $dbl_src_target, "$dbl_ipset src", @matches );
add_ipset_dbl_ijump( $forward_option_chainref, $dbl_src_target, "$dbl_ipset src", @matches );
}
if ( $setting & DBL_DST ) {
#
# src-dst
#
add_ipset_dbl_ijump( $forward_option_chainref, $dbl_dst_target, "$dbl_ipset dst", @matches );
add_ipset_dbl_ijump( $output_option_chainref, $dbl_dst_target, "$dbl_ipset dst", @matches );
}
}
if ( $setting & DBL_CLASSIC ) {
add_ijump_extended( $input_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state );
add_ijump_extended( $forward_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state );
add_ijump_extended( $output_option_chainref, j => $classic_target_chain, $origin{DYNAMIC_BLACKLIST}, @state ) if $setting & DBL_DST;
}
}
#
# Finish FASTACCEPT
#
if ( $config{FASTACCEPT} ) {
for ( option_chains( $interface ) ) {
add_ijump_extended( $filter_table->{$_}, j => 'ACCEPT', $origin{FASTACCEPT}, state_imatch $faststate )->{comment} = '';
}
}
} #Not loopback interface
}
#
# Delete 'sfilter' chains unless there are referenced to them
#
for ( qw/sfilter sfilter1/ ) {
if ( $chainref = $filter_table->{$_} ) {
$chainref->{referenced} = 0 unless keys %{$chainref->{references}};
}
}
#
# rfilter
#
$list = find_interfaces_by_option('rpfilter');
if ( @$list ) {
$policy = $config{RPFILTER_DISPOSITION};
$level = $config{RPFILTER_LOG_LEVEL};
$tag = $globals{RPFILTER_LOG_TAG};
$audit = $policy =~ s/^A_//;
my $origin
= $origin{RPFILTER_DISPOSITION};
if ( $level || $audit ) {
#
# Create a chain to log and/or audit and apply the policy
#
$chainref = ensure_mangle_chain 'rplog';
log_rule_limit( $level,
$chainref,
$chainref->{name},
$policy,
$globals{LOGLIMIT},
$tag,
'add',
'',
$origin{RPFILTER_LOG_LEVEL} );
add_ijump_extended( $chainref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $policy ) if $audit;
add_ijump_extended( $chainref, g => $policy eq 'REJECT' ? 'reject' : $policy, $origin );
$target = 'rplog';
} else {
$target = $policy eq 'REJECT' ? 'reject' : $policy;
}
my $rpfilterref = ensure_mangle_chain( 'rpfilter' );
if ( $family == F_IPV4 ) {
for $interface ( @$list ) {
if ( get_interface_option( $interface, 'dhcp' ) ) {
add_ijump_extended( $rpfilterref,
j => 'RETURN',
get_interface_origin( $interface ),
s => NILIPv4,
p => UDP,
dport => 67,
sport => 68
);
last;
}
}
}
add_ijump_extended( $rpfilterref,
j => $target,
$origin,
rpfilter => '--validmark --invert',
state_imatch 'NEW,RELATED,INVALID',
@ipsec
);
}
run_user_exit 'initdone';
if ( $upgrade ) {
convert_blacklist;
} elsif ( -f ( my $fn = find_file 'blacklist' ) ) {
warning_message "The blacklist file is no longer supported -- use '$product update' to convert $fn to the equivalent blrules file";
}
#
# Smurfs
#
$list = find_hosts_by_option 'nosmurfs';
if ( @$list ) {
progress_message2 'Adding Anti-smurf Rules';
$chainref = new_standard_chain 'smurfs';
my $smurfdest = $config{SMURF_DISPOSITION};
my $origin = $origin{SMURF_DISPOSITION};
if ( supplied $config{SMURF_LOG_LEVEL} ) {
my $smurfref = new_chain( 'filter', 'smurflog' );
log_irule_limit( $config{SMURF_LOG_LEVEL},
$smurfref,
'smurfs' ,
'DROP',
$globals{LOGILIMIT},
$globals{SMURF_LOG_TAG},
'add',
$origin{SMURF_LOG_LEVEL} );
add_ijump_extended( $smurfref, j => 'AUDIT', $origin, targetopts => '--type drop' ) if $smurfdest eq 'A_DROP';
add_ijump_extended( $smurfref, j => 'DROP' , $origin );
$smurfdest = 'smurflog';
} else {
verify_audit( $smurfdest ) if $smurfdest eq 'A_DROP';
}
if ( have_capability( 'ADDRTYPE' ) ) {
if ( $family == F_IPV4 ) {
add_ijump $chainref , j => 'RETURN', s => '0.0.0.0'; ;
} else {
add_ijump $chainref , j => 'RETURN', s => '::';
}
add_ijump_extended( $chainref, g => $smurfdest, $origin, addrtype => '--src-type BROADCAST' ) ;
} else {
if ( $family == F_IPV4 ) {
add_commands $chainref, 'for address in $ALL_BCASTS; do';
} else {
add_commands $chainref, 'for address in $ALL_ACASTS; do';
}
incr_cmd_level $chainref;
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => '$address' );
decr_cmd_level $chainref;
add_commands $chainref, 'done';
}
if ( $family == F_IPV4 ) {
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => '224.0.0.0/4' );
} else {
add_ijump_extended( $chainref, g => $smurfdest, $origin, s => IPv6_MULTICAST );
}
my @state = have_capability( 'RAW_TABLE' ) ? state_imatch 'NEW,INVALID,UNTRACKED' : state_imatch 'NEW,INVALID';
for my $hostref ( @$list ) {
$interface = $hostref->[0];
my $ipsec = $hostref->[1];
my $net = $hostref->[2];
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
my $target = source_exclusion( $hostref->[3], $chainref );
my $origin = $hostref->[5];
for $chain ( option_chains $interface ) {
add_ijump_extended( $filter_table->{$chain} , j => $target, $origin, @state, imatch_source_net( $net ), @policy );
}
}
}
#
# DHCP
#
$list = find_interfaces_by_option 'dhcp';
if ( @$list ) {
progress_message2 'Adding rules for DHCP';
my $ports = $family == F_IPV4 ? '67:68' : '546:547';
for $interface ( @$list ) {
my $origin = get_interface_origin($interface);
set_rule_option( add_ijump_extended( $filter_table->{$_} ,
j => 'ACCEPT',
$origin,
p => "udp --dport $ports" ) ,
'dhcp',
1 ) for input_option_chain( $interface ), output_option_chain( $interface );
add_ijump_extended( $filter_table->{forward_option_chain $interface} ,
j => 'ACCEPT',
$origin,
p => "udp --dport $ports" ,
imatch_dest_dev( $interface ) )
if get_interface_option( $interface, 'bridge' );
unless ( $family == F_IPV6 || get_interface_option( $interface, 'allip' ) ) {
add_ijump_extended( $filter_table->{input_chain( $interface ) } ,
j => 'ACCEPT' ,
$origin ,
p => "udp --dport $ports" ,
s => NILIPv4 . '/' . VLSMv4 );
}
}
}
#
# tcpflags
#
$list = find_hosts_by_option 'tcpflags';
if ( @$list ) {
my $level = $config{TCP_FLAGS_LOG_LEVEL};
my $tag = $globals{TCP_FLAGS_LOG_TAG};
my $disposition = $config{TCP_FLAGS_DISPOSITION};
my $audit = $disposition =~ /^A_/;
my $origin = $origin{TCP_FLAGS_DISPOSITION};
progress_message2 "$doing TCP Flags filtering...";
$chainref = new_standard_chain 'tcpflags';
if ( $level ) {
my $logflagsref = new_standard_chain 'logflags';
my $savelogparms = $globals{LOGPARMS};
$globals{LOGPARMS} = "$globals{LOGPARMS}--log-ip-options ";
log_rule_limit( $level,
$logflagsref,
'logflags',
$disposition,
$globals{LOGLIMIT},
$tag,
'add',
'' ,
$origin{TCP_FLAGS_LOG_LEVEL} );
$globals{LOGPARMS} = $savelogparms;
if ( $audit ) {
$disposition =~ s/^A_//;
add_ijump_extended( $logflagsref, j => 'AUDIT', $origin, targetopts => '--type ' . lc $disposition );
}
if ( $disposition eq 'REJECT' ) {
add_ijump_extended $logflagsref , j => 'REJECT', $origin, targetopts => '--reject-with tcp-reset', p => 6;
} else {
add_ijump_extended $logflagsref , j => $disposition, $origin;
}
$disposition = 'logflags';
} elsif ( $audit ) {
require_capability( 'AUDIT_TARGET', "TCP_FLAGS_DISPOSITION=$disposition", 's' );
verify_audit( $disposition );
}
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ALL FIN,URG,PSH';
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ALL NONE';
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags SYN,RST SYN,RST';
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags FIN,RST FIN,RST';
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags SYN,FIN SYN,FIN';
add_ijump $chainref , g => $disposition, p => 'tcp --tcp-flags ACK,PSH,FIN PSH,FIN';
add_ijump $chainref , g => $disposition, p => 'tcp --syn --sport 0';
for my $hostref ( @$list ) {
my $interface = $hostref->[0];
my $target = source_exclusion( $hostref->[3], $chainref );
my $ipsec = $hostref->[1];
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
my $origin = $hostref->[5];
for $chain ( option_chains $interface ) {
add_ijump_extended( $filter_table->{$chain} , j => $target, $origin, p => 'tcp', imatch_source_net( $hostref->[2] ), @policy );
}
}
}
my $announced = 0;
#
# upnp
#
$list = find_interfaces_by_option 'upnp';
if ( @$list ) {
progress_message2 "$doing UPnP";
$chainref = set_optflags( new_nat_chain( 'UPnP' ), DONT_OPTIMIZE );
add_commands( $chainref, '[ -s /${VARDIR}/.UPnP ] && cat ${VARDIR}/.UPnP >&3' );
my $chainref1;
if ( $config{MINIUPNPD} ) {
$chainref1 = set_optflags( new_nat_chain( 'MINIUPNPD-POSTROUTING' ), DONT_OPTIMIZE );
add_commands( $chainref, '[ -s /${VARDIR}/.MINIUPNPD-POSTROUTING ] && cat ${VARDIR}/.MINIUPNPD-POSTROUTING >&3' );
}
$announced = 1;
for $interface ( @$list ) {
add_ijump_extended $nat_table->{PREROUTING} , j => 'UPnP', get_interface_origin($interface), imatch_source_dev ( $interface );
add_ijump_extended $nat_table->{$globals{POSTROUTING}} , j => 'MINIUPNPD-POSTROUTING' , $origin{MINIUPNPD} , imatch_dest_dev ( $interface ) if $chainref1;
}
}
#
# upnp client
#
$list = find_interfaces_by_option 'upnpclient';
if ( @$list ) {
progress_message2 "$doing UPnP" unless $announced;
for $interface ( @$list ) {
my $chainref = $filter_table->{input_option_chain $interface};
my $base = uc var_base get_physical $interface;
my $optional = interface_is_optional( $interface );
my $variable = get_interface_gateway( $interface, ! $optional );
my $origin = get_interface_origin( $interface );
if ( $optional ) {
add_commands( $chainref,
qq(if [ -n "SW_\$${base}_IS_USABLE" -a -n "$variable" ]; then) );
incr_cmd_level( $chainref );
add_ijump_extended( $chainref, j => 'ACCEPT', $origin, imatch_source_dev( $interface ), s => $variable, p => 'udp' );
decr_cmd_level( $chainref );
add_commands( $chainref, 'fi' );
} else {
add_ijump_extended( $chainref, j => 'ACCEPT', $origin, imatch_source_dev( $interface ), s => $variable, p => 'udp' );
}
}
}
setup_syn_flood_chains;
}
my %maclist_targets = ( ACCEPT => { target => 'RETURN' , mangle => 1 } ,
REJECT => { target => 'reject' , mangle => 0 } ,
DROP => { target => 'DROP' , mangle => 1 } );
#
# Create rules generated by the 'maclist' option and by entries in the maclist file.
#
# The function is called twice. The first call passes '1' and causes the maclist file
# to be processed. The second call passes '2' and generates the jumps for 'maclist'
# interfaces.
#
sub setup_mac_lists( $ ) {
my $phase = $_[0];
my %maclist_interfaces;
my $table = $config{MACLIST_TABLE};
my $maclist_hosts = find_hosts_by_option 'maclist';
my $target = $globals{MACLIST_TARGET};
my $level = $config{MACLIST_LOG_LEVEL};
my $tag = $globals{MACLIST_LOG_TAG};
my $disposition = $config{MACLIST_DISPOSITION};
my $audit = ( $disposition =~ s/^A_// );
my $ttl = $config{MACLIST_TTL};
progress_message2 "$doing MAC Filtration -- Phase $phase...";
for my $hostref ( @$maclist_hosts ) {
$maclist_interfaces{ $hostref->[0] } = 1;
}
my @maclist_interfaces = ( sortkeysiftest %maclist_interfaces );
if ( $phase == 1 ) {
for my $interface ( @maclist_interfaces ) {
my $chainref = new_chain $table , mac_chain $interface;
if ( $family == F_IPV4 ) {
add_ijump $chainref , j => 'RETURN', s => '0.0.0.0', d => '255.255.255.255', p => 'udp --dport 67:68'
if $table eq 'mangle' && get_interface_option( $interface, 'dhcp');
} else {
#
# Accept any packet with a link-level source or destination address
#
add_ijump $chainref , j => 'RETURN', s => 'ff80::/10';
add_ijump $chainref , j => 'RETURN', d => 'ff80::/10';
#
# Accept Multicast
#
add_ijump $chainref , j => 'RETURN', d => IPv6_MULTICAST;
}
if ( $ttl ) {
my $chain1ref = new_chain $table, macrecent_target $interface;
my $chain = $chainref->{name};
add_ijump $chainref, j => 'RETURN', recent => "--rcheck --seconds $ttl --name $chain";
add_ijump $chainref, j => $chain1ref;
add_ijump $chainref, j => 'RETURN', recent => "--update --name $chain";
add_irule $chainref, recent => "--set --name $chain";
}
}
if ( my $fn = open_file 'maclist', 1, 1 ) {
first_entry "$doing $fn...";
while ( read_a_line( NORMAL_READ ) ) {
my ( $original_disposition, $interface, $mac, $addresses ) =
split_line1( 'maclist file',
{ disposition => 0, interface => 1, mac => 2, addresses => 3 } );
my ( $disposition, $level, $remainder) = split( /:/, $original_disposition, 3 );
fatal_error "Invalid DISPOSITION ($original_disposition)" if defined $remainder || ! $disposition;
my $targetref = $maclist_targets{$disposition};
fatal_error "Invalid DISPOSITION ($original_disposition)" if ! $targetref || ( ( $table eq 'mangle' ) && ! $targetref->{mangle} );
fatal_error "Unknown Interface ($interface)" unless known_interface( $interface );
fatal_error "No hosts on $interface have the maclist option specified" unless $maclist_interfaces{$interface};
my $chainref = $chain_table{$table}{( $ttl ? macrecent_target $interface : mac_chain $interface )};
$mac = '' unless $mac && ( $mac ne '-' );
$addresses = '' unless defined $addresses && ( $addresses ne '-' );
fatal_error "You must specify a MAC address or an IP address" unless $mac || $addresses;
$mac = do_mac $mac if $mac;
if ( $addresses ) {
for my $address ( split ',', $addresses ) {
my $source = match_source_net $address;
log_rule_limit $level, $chainref , mac_chain( $interface) , $disposition, '', '', 'add' , "${mac}${source}"
if supplied $level;
add_ijump( $chainref , j => 'AUDIT', targetopts => '--type ' . lc $disposition ) if $audit && $disposition ne 'ACCEPT';
add_jump( $chainref , $targetref->{target}, 0, "${mac}${source}" );
}
} else {
log_rule_limit $level, $chainref , mac_chain( $interface) , $disposition, '', '', 'add' , $mac
if supplied $level;
add_ijump( $chainref , j => 'AUDIT', targetopts => '--type ' . lc $disposition ) if $audit && $disposition ne 'ACCEPT';
add_jump ( $chainref , $targetref->{target}, 0, "$mac" );
}
progress_message " Maclist entry \"$currentline\" $done";
}
}
#
# Generate jumps from the input and forward chains
#
for my $hostref ( $test ? sort { $a->[0] cmp $b->[0] } @$maclist_hosts : @$maclist_hosts ) {
my $interface = $hostref->[0];
my $ipsec = $hostref->[1];
my @policy = $ipsec && have_ipsec ? ( policy => "--pol $ipsec --dir in" ) : ();
my @source = imatch_source_net $hostref->[2];
my $origin = $hostref->[5];
my @state = have_capability( 'RAW_TABLE' ) ? state_imatch 'NEW,UNTRACKED' : state_imatch 'NEW';
if ( $table eq 'filter' ) {
my $chainref = source_exclusion( $hostref->[3], $filter_table->{mac_chain $interface} );
for my $chain ( option_chains $interface ) {
add_ijump_extended $filter_table->{$chain} , j => $chainref, $origin, @source, @state, @policy;
}
} else {
my $chainref = source_exclusion( $hostref->[3], $mangle_table->{mac_chain $interface} );
add_ijump_extended $mangle_table->{PREROUTING}, j => $chainref, $origin, imatch_source_dev( $interface ), @source, @state, @policy;
}
}
} else {
#
# Phase II
#
ensure_audit_chain( $target, $disposition, undef, $table ) if $audit;
for my $interface ( @maclist_interfaces ) {
my $chainref = $chain_table{$table}{( $ttl ? macrecent_target $interface : mac_chain $interface )};
my $chain = $chainref->{name};
if ( $family == F_IPV4 ) {
if ( $level ne '' || $disposition ne 'ACCEPT' ) {
my $variable = get_interface_addresses source_port_to_bridge( $interface );
if ( have_capability( 'ADDRTYPE' ) ) {
add_commands( $chainref, "for address in $variable; do" );
incr_cmd_level( $chainref );
add_ijump( $chainref, j => 'RETURN', s => '$address', addrtype => '--dst-type BROADCAST' );
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '224.0.0.0/4' );
decr_cmd_level( $chainref );
add_commands( $chainref, 'done' );
} else {
my $bridge = source_port_to_bridge( $interface );
my $bridgeref = find_interface( $bridge );
add_commands( $chainref,
"for address in $variable; do" );
incr_cmd_level( $chainref );
if ( $bridgeref->{broadcasts} ) {
for my $address ( @{$bridgeref->{broadcasts}}, '255.255.255.255' ) {
add_ijump( $chainref, j => 'RETURN', s => '$address', d => $address );
}
} else {
my $variable1 = get_interface_bcasts $bridge;
add_commands( $chainref,
" for address1 in $variable1; do" );
incr_cmd_level( $chainref );
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '$address1' );
decr_cmd_level( $chainref );
add_commands( $chainref, 'done' );
}
add_ijump( $chainref, j => 'RETURN', s => '$address', d => '224.0.0.0/4' );
decr_cmd_level( $chainref );
add_commands( $chainref, 'done' );
}
}
}
log_irule_limit $level, $chainref , $chain , $disposition, [], $tag, 'add', '' if $level ne '';
add_ijump $chainref, j => $target;
}
}
}
#
# Helper functions for generate_matrix()
#-----------------------------------------
#
# Return the target for rules from $zone to $zone1.
#
sub rules_target( $$ ) {
my ( $zone, $zone1 ) = @_;
my $chain = rules_chain( ${zone}, ${zone1} );
my $chainref = $filter_table->{$chain};
return $chain if $chainref && $chainref->{referenced};
return 'ACCEPT' if $zone eq $zone1;
assert( $chainref );
if ( $chainref->{policy} ne 'CONTINUE' ) {
my $policyref = $filter_table->{$chainref->{policychain}};
assert( $policyref );
return $policyref->{name} if $policyref ne $chainref;
return $chainref->{policy} eq 'REJECT' ? 'reject' : $chainref->{policy};
}
''; # CONTINUE policy
}
#
# Generate rules for one destination zone
#
sub generate_dest_rules( $$$$;@ ) {
my ( $chainref, $chain, $z2, $origin, @matches ) = @_;
my $z2ref = find_zone( $z2 );
my $type2 = $z2ref->{type};
if ( $type2 & VSERVER ) {
for my $hostref ( @{$z2ref->{hosts}{ip}{'%vserver%'}} ) {
my $exclusion = dest_exclusion( $hostref->{exclusions}, $chain);
my $origin = $hostref->{origin};
for my $net ( @{$hostref->{hosts}} ) {
add_ijump_extended( $chainref,
j => $exclusion ,
$origin,
imatch_dest_net ( $net ),
@matches );
}
}
} else {
add_ijump_extended( $chainref, j => $chain, $origin, @matches );
}
}
#
# Generate rules for one vserver source zone
#
sub generate_source_rules( $$$;@ ) {
my ( $outchainref, $z1, $z2, @matches ) = @_;
my $chain = rules_target ( $z1, $z2 );
if ( $chain && $chain ne 'NONE' ) {
#
# Not a CONTINUE policy with no rules
#
for my $hostref ( @{defined_zone( $z1 )->{hosts}{ip}{'%vserver%'}} ) {
my @ipsec_match = match_ipsec_in $z1 , $hostref;
my $exclusion = source_exclusion( $hostref->{exclusions}, $chain);
my $origin = $hostref->{origin};
for my $net ( @{$hostref->{hosts}} ) {
generate_dest_rules( $outchainref,
$exclusion,
$z2,
$origin,
imatch_source_net( $net ),
@matches ,
@ipsec_match
);
}
}
}
}
#
# Loopback traffic -- this is where we assemble the intra-firewall chains
#
sub handle_loopback_traffic() {
my @zones = ( vserver_zones, firewall_zone );
my $natout = $nat_table->{OUTPUT};
my $rawout = $raw_table->{OUTPUT};
my $rulenum = 0;
my $loopback = loopback_zones;
my $loref = known_interface(loopback_interface);
my $unmanaged;
my $outchainref;
my @rule;
if ( @zones > 1 ) {
#
# We have a vserver zone -- route output through a separate chain
#
$outchainref = new_standard_chain 'loopback';
if ( have_capability 'IFACE_MATCH' ) {
add_ijump $filter_table->{OUTPUT}, j => $outchainref, iface => '--dev-out --loopback';
} else {
add_ijump $filter_table->{OUTPUT}, j => $outchainref, o => loopback_interface;
}
} else {
#
# Only the firewall -- just use the OUTPUT chain
#
if ( $unmanaged = $loref && $loref->{options}{unmanaged} ) {
if ( have_capability 'IFACE_MATCH' ) {
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', iface => '--dev-out --loopback' );
} else {
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', o => loopback_interface );
}
} else {
$outchainref = $filter_table->{OUTPUT};
if ( have_capability 'IFACE_MATCH' ) {
@rule = ( iface => '--dev-out --loopback' );
} else {
@rule = ( o => loopback_interface );
}
}
}
for my $z1 ( @zones ) {
my $z1ref = find_zone( $z1 );
my $type1 = $z1ref->{type};
my $natref = $nat_table->{dnat_chain $z1};
my $notrackref = $raw_table->{notrack_chain( $z1 )};
#
# Add jumps in the 'output' chain to the rules chains
#
if ( $type1 == FIREWALL ) {
for my $z2 ( @zones ) {
next if $z1 eq $z2 && ( $loopback || $unmanaged );
my $chain = rules_target( $z1, $z2 );
generate_dest_rules( $outchainref, $chain, $z2, '', @rule ) if $chain;
}
#
# Handle conntrack
#
if ( $notrackref ) {
add_ijump $rawout, j => $notrackref if $notrackref->{referenced};
}
} else {
for my $z2 ( @zones ) {
generate_source_rules( $outchainref, $z1, $z2, @rule );
}
#
# Handle conntrack rules
#
if ( $notrackref->{referenced} ) {
for my $hostref ( @{defined_zone( $z1 )->{hosts}{ip}{'%vserver%'}} ) {
my $exclusion = source_exclusion( $hostref->{exclusions}, $notrackref);
my @ipsec_match = match_ipsec_in $z1 , $hostref;
for my $net ( @{$hostref->{hosts}} ) {
insert_ijump( $rawout,
j => $exclusion ,
$rawout->{insert}++,
imatch_source_net $net,
@ipsec_match );
}
}
}
}
if ( $natref && $natref->{referenced} ) {
#
# There are DNAT rules with this zone as the source -- add jumps from the nat OUTPUT chain
#
my $source_hosts_ref = defined_zone( $z1 )->{hosts};
for my $typeref ( values %{$source_hosts_ref} ) {
for my $hostref ( @{$typeref->{'%vserver%'}} ) {
my $exclusion = source_exclusion( $hostref->{exclusions}, $natref);
for my $net ( @{$hostref->{hosts}} ) {
insert_ijump( $natout,
j => $exclusion,
$rulenum++,
imatch_source_net( $net , 0, ) );
}
}
}
}
}
}
#
# Add jumps from the builtin chains to the interface-chains that are used by this configuration
#
sub add_interface_jumps {
our %input_jump_added;
our %output_jump_added;
our %forward_jump_added;
my @interfaces = grep $_ ne '%vserver%', @_;
my $dummy;
my $lo_jump_added = interface_zone( loopback_interface ) && ! get_interface_option( loopback_interface, 'destonly' );
#
# Add Nat jumps
#
for my $interface ( @_ ) {
addnatjump $globals{POSTROUTING} , snat_chain( $interface ), imatch_dest_dev( $interface );
}
addnatjump( 'POSTROUTING', 'SHOREWALL' ) if $config{DOCKER};
for my $interface ( @interfaces ) {
addnatjump 'PREROUTING' , input_chain( $interface ) , imatch_source_dev( $interface );
addnatjump $globals{POSTROUTING} , output_chain( $interface ) , imatch_dest_dev( $interface );
addnatjump $globals{POSTROUTING} , masq_chain( $interface ) , imatch_dest_dev( $interface );
add_ijump( $mangle_table->{PREROUTING}, j => 'rpfilter' , imatch_source_dev( $interface ) ) if interface_has_option( $interface, 'rpfilter', $dummy );
}
#
# Add the jumps to the interface chains from filter FORWARD, INPUT, OUTPUT
#
for my $interface ( @interfaces ) {
my $forwardref = $filter_table->{forward_chain $interface};
my $inputref = $filter_table->{input_chain $interface};
my $outputref = $filter_table->{output_chain $interface};
my $interfaceref = find_interface($interface);
if ( $interfaceref->{physical} eq '+' && ! $lo_jump_added++ ) {
if ( have_capability 'IFACE_MATCH' ) {
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', iface => '--dev-in --loopback';
} else {
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', i => loopback_interface;
}
}
if ( $interfaceref->{options}{port} ) {
my $bridge = $interfaceref->{bridge};
add_ijump ( $filter_table->{forward_chain $bridge},
j => 'ACCEPT',
imatch_source_dev( $interface, 1),
imatch_dest_dev( $interface, 1)
) unless $interfaceref->{nets};
add_ijump( $filter_table->{forward_chain $bridge} ,
j => $forwardref ,
imatch_source_dev( $interface, 1 )
) unless $forward_jump_added{$interface} || ! use_forward_chain $interface, $forwardref;
add_ijump( $filter_table->{input_chain $bridge },
j => $inputref ,
imatch_source_dev( $interface, 1 )
) unless $input_jump_added{$interface} || ! use_interface_chain( $interface, 'use_input_chain' );
unless ( $output_jump_added{$interface} || ! use_interface_chain( $interface, 'use_output_chain') ) {
add_ijump( $filter_table->{output_chain $bridge} ,
j => $outputref ,
imatch_dest_dev( $interface, 1 ) )
unless get_interface_option( $interface, 'port' );
}
} else {
add_ijump ( $filter_table->{FORWARD}, j => 'ACCEPT', imatch_source_dev( $interface) , imatch_dest_dev( $interface) ) unless $interfaceref->{nets} || ! $interfaceref->{options}{bridge};
add_ijump( $filter_table->{FORWARD} , j => $forwardref , imatch_source_dev( $interface ) ) if use_forward_chain( $interface, $forwardref ) && ! $forward_jump_added{$interface}++;
add_ijump( $filter_table->{INPUT} , j => $inputref , imatch_source_dev( $interface ) ) if use_interface_chain( $interface, 'use_input_chain' ) && ! $input_jump_added{$interface}++;
if ( use_interface_chain( $interface, 'use_output_chain' ) ) {
add_ijump $filter_table->{OUTPUT} , j => $outputref , imatch_dest_dev( $interface ) unless get_interface_option( $interface, 'port' ) || $output_jump_added{$interface}++;
}
}
}
unless ( $lo_jump_added++ ) {
if ( have_capability 'IFACE_MATCH' ) {
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', iface => '--dev-in --loopback';
} else {
add_ijump $filter_table->{INPUT} , j => 'ACCEPT', i => loopback_interface;
}
}
handle_loopback_traffic;
}
#
# Do the initial matrix processing for a complex zone
#
sub handle_complex_zone( $$ ) {
my ( $zone, $zoneref ) = @_;
our %input_jump_added;
our %output_jump_added;
our %forward_jump_added;
our %ipsec_jump_added;
#
# Complex zone or we have more than two off-firewall zones -- Shorewall::Rules::classic_blacklist created a zone forwarding chain
#
my $frwd_ref = $filter_table->{zone_forward_chain( $zone )};
assert( $frwd_ref, $zone );
#
# Add Zone mark if any
#
add_ijump( $frwd_ref , j => 'MARK --set-mark ' . in_hex( $zoneref->{mark} ) . '/' . in_hex( $globals{ZONE_MASK} ) ) if $zoneref->{mark};
if ( have_ipsec ) {
#
# In general, policy match can only match an 'in' or an 'out' policy (but not both), so we place the
# '--pol ipsec --dir in' rules at the front of the (interface) forwarding chains. Otherwise, decrypted packets
# can match '--pol none --dir out' rules and send the packets down the wrong rules chain.
#
my $type = $zoneref->{type};
my $source_ref = ( $zoneref->{hosts}{ipsec} ) || {};
for my $interface ( sortkeysiftest %$source_ref ) {
my $sourcechainref = $filter_table->{forward_chain $interface};
my @interfacematch;
my $interfaceref = find_interface $interface;
next if $interfaceref->{options}{destonly};
if ( use_forward_chain( $interface, $sourcechainref ) ) {
#
# Use the interface forward chain
#
if ( $interfaceref->{ports} && $interfaceref->{options}{bridge} ) {
#
# This is a bridge with ports
#
@interfacematch = imatch_source_dev $interface;
#
# Copy the rules from the interface forward chain to the zone forward chain unless they have already been copied
#
copy_rules( $sourcechainref, $frwd_ref, 1 ) unless $ipsec_jump_added{$zone}++;
#
# Jump directly from FORWARD to the zone forward chain
#
$sourcechainref = $filter_table->{FORWARD};
} elsif ( $interfaceref->{options}{port} ) {
#
# The forwarding chain for a bridge with ports is always used -- use physdev match for this interface
#
add_ijump( $filter_table->{ forward_chain $interfaceref->{bridge} } ,
j => $sourcechainref ,
imatch_source_dev( $interface , 1 ) )
unless $forward_jump_added{$interface}++;
} else {
#
# Add jump from FORWARD to the intrface forward chain
#
add_ijump $filter_table->{FORWARD} , j => $sourcechainref, imatch_source_dev( $interface ) unless $forward_jump_added{$interface}++;
}
} else {
if ( $interfaceref->{options}{port} ) {
#
# The forwarding chain for a bridge with ports is always used -- use physdev match
#
$sourcechainref = $filter_table->{ forward_chain $interfaceref->{bridge} };
@interfacematch = imatch_source_dev $interface, 1;
} else {
$sourcechainref = $filter_table->{FORWARD};
@interfacematch = imatch_source_dev $interface;
}
#
# copy any rules from the interface forward chain to the zone forward chain
#
move_rules( $filter_table->{forward_chain $interface} , $frwd_ref );
}
my $arrayref = $source_ref->{$interface};
#
# Now add the jumps from the source chain (interface forward or FORWARD) to the zone forward chain
#
for my $hostref ( @{$arrayref} ) {
my @ipsec_match = match_ipsec_in $zone , $hostref;
my $origin = $hostref->{origin};
for my $net ( @{$hostref->{hosts}} ) {
add_ijump_extended(
$sourcechainref,
@{$zoneref->{parents}} ? 'j' : 'g' => source_exclusion( $hostref->{exclusions}, $frwd_ref ),
$origin,
@interfacematch ,
imatch_source_net( $net ),
@ipsec_match
);
}
}
}
}
}
#
# The passed zone is a sub-zone. We need to determine if
#
# a) A parent zone defines DNAT/REDIRECT or notrack rules; and
# b) The current zone has a CONTINUE policy to some other zone.
#
# If a) but not b), then we must avoid sending packets from this
# zone through the DNAT/REDIRECT or notrack chain for the parent.
#
sub handle_nested_zone( $$ ) {
my ( $zone, $zoneref ) = @_;
#
# Function returns this 3-tuple
#
my ( $nested, $parenthasnat, $parenthasnotrack ) = ( 1, 0, 0 );
for my $parent ( @{$zoneref->{parents}} ) {
my $ref1 = $nat_table->{dnat_chain $parent} || {};
my $ref2 = $raw_table->{notrack_chain $parent} || {};
$parenthasnat = 1 if $ref1->{referenced};
$parenthasnotrack = 1 if $ref2->{referenced};
last if $parenthasnat && $parenthasnotrack;
}
if ( $parenthasnat || $parenthasnotrack ) {
for my $zone1 ( all_zones ) {
if ( $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy} eq 'CONTINUE' ) {
#
# This zone has a continue policy to another zone. We must
# send packets from this zone through the parent's DNAT/REDIRECT/NOTRACK chain.
#
$nested = 0;
last;
}
}
} else {
#
# No parent has DNAT or notrack so there is nothing to worry about. Don't bother to generate needless RETURN rules in the 'dnat' or 'notrack' chain.
#
$nested = 0;
}
( $nested, $parenthasnat, $parenthasnotrack );
}
#
# Add output jump to the passed zone:interface:hostref:net
#
sub add_output_jumps( $$$$$$$$ ) {
my ( $zone, $interface, $hostref, $net, $exclusions, $isport, $bridge, $origin ) = @_;
our @vservers;
our %output_jump_added;
my $chain1 = rules_target( firewall_zone , $zone );
my $chain1ref = $filter_table->{$chain1};
my $nextchain = dest_exclusion( $exclusions, $chain1 );
my $outputref;
my $interfacechainref = $filter_table->{output_chain $interface};
my @interfacematch;
my $use_output = 0;
my @dest = imatch_dest_net $net;
my @ipsec_out_match = match_ipsec_out $zone , $hostref;
my @zone_interfaces = sortkeysiftest %{zone_interfaces( $zone )};
if ( @vservers || use_interface_chain( $interface, 'use_output_chain' ) || ( @{$interfacechainref->{rules}} && ! $chain1ref ) || @zone_interfaces > 1 ) {
#
# - There are vserver zones (so OUTPUT will have multiple source; or
# - We must use the interface output chain; or
# - There are rules in the interface chain and none in the rules chain
# - The zone has multiple interfaces
#
# In any of these cases use the inteface output chain
#
$outputref = $interfacechainref;
if ( $isport ) {
#
# It is a bridge port zone -- use the bridges output chain and match the physdev
#
add_ijump_extended( $filter_table->{ output_chain $bridge },
j => $outputref ,
$origin ,
imatch_dest_dev( $interface, 1 ) )
unless $output_jump_added{$interface}++;
} else {
#
# Not a bridge -- match the output interface
#
add_ijump_extended $filter_table->{OUTPUT}, j => $outputref, $origin, imatch_dest_dev( $interface ) unless $output_jump_added{$interface}++;
}
$use_output = 1;
unless ( lc $net eq IPv6_LINKLOCAL ) {
#
# Generate output rules for the vservers
#
for my $vzone ( @vservers ) {
generate_source_rules ( $outputref, $vzone, $zone, @dest );
}
}
} elsif ( $isport ) {
#
# It is a bridge port zone -- use the bridges output chain and match the physdev
#
$outputref = $filter_table->{ output_chain $bridge };
@interfacematch = imatch_dest_dev $interface, 1;
} else {
#
# Just put the jump in the OUTPUT chain
#
$outputref = $filter_table->{OUTPUT};
@interfacematch = imatch_dest_dev $interface;
}
#
# Add the jump
#
add_ijump_extended $outputref , j => $nextchain, $origin, @interfacematch, @dest, @ipsec_out_match;
#
# Add jump for broadcast
#
add_ijump_extended( $outputref , j => $nextchain, get_interface_origin( $interface ), @interfacematch, d => '255.255.255.255' , @ipsec_out_match )
if $family == F_IPV4 && $hostref->{options}{broadcast};
#
# Move the rules from the interface output chain if we didn't use it
#
move_rules( $interfacechainref , $chain1ref ) unless $use_output;
}
#
# Add prerouting jumps from the passed zone:interface:hostref:net
#
sub add_prerouting_jumps( $$$$$$$$$ ) {
my ( $zone, $interface, $hostref, $net, $exclusions, $nested, $parenthasnat, $parenthasnotrack , $origin ) = @_;
my $dnatref = $nat_table->{dnat_chain( $zone )};
my $preroutingref = $nat_table->{PREROUTING};
my $rawref = $raw_table->{PREROUTING};
my $notrackref = ensure_chain 'raw' , notrack_chain( $zone );
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
my @source = imatch_source_net $net;
if ( $dnatref->{referenced} ) {
#
# There are DNAT/REDIRECT rules with this zone as the source.
# Add a jump from this source network to this zone's DNAT/REDIRECT chain
#
add_ijump_extended( $preroutingref,
j => source_exclusion( $exclusions, $dnatref),
$origin,
imatch_source_dev( $interface),
@source,
@ipsec_in_match );
check_optimization( $dnatref ) if @source;
}
if ( $notrackref->{referenced} ) {
#
# There are notrack rules with this zone as the source.
# Add a jump from this source network to this zone's notrack chain
#
insert_ijump $rawref, j => source_exclusion( $exclusions, $notrackref), $rawref->{insert}++, imatch_source_dev( $interface), @source, @ipsec_in_match;
}
#
# If this zone has parents with DNAT/REDIRECT or notrack rules and there are no CONTINUE polcies with this zone as the source
# then add a RETURN jump for this source network.
#
if ( $nested ) {
if ( $parenthasnat ) {
add_ijump_extended $preroutingref, j => 'RETURN', $origin, imatch_source_dev( $interface), @source, @ipsec_in_match;
}
if ( $parenthasnotrack ) {
my $rawref = $raw_table->{PREROUTING};
insert_ijump $rawref, j => 'RETURN', $rawref->{insert}++, imatch_source_dev( $interface), @source, @ipsec_in_match;
}
}
}
#
# Add input jump from the passed zone:interface:hostref:net
#
sub add_input_jumps( $$$$$$$$$ ) {
my ( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) = @_;
our @vservers;
our %input_jump_added;
my $chain2 = rules_target $zone, firewall_zone;
my $chain2ref = $filter_table->{$chain2};
my $inputchainref;
my $interfacechainref = $filter_table->{input_chain $interface};
my @interfacematch;
my $use_input;
my @source = imatch_source_net $net;
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
if ( @vservers || use_interface_chain( $interface, 'use_input_chain' ) || ! $chain2 || ( @{$interfacechainref->{rules}} && ! $chain2ref ) ) {
#
# - There are vserver zones (so INPUT will have multiple destinations; or
# - We must use the interface input chain; or
# - The zone->firewall policy is CONTINUE; or
# - There are rules in the interface chain and none in the rules chain
#
# In any of these cases use the inteface input chain
#
$inputchainref = $interfacechainref;
if ( $isport ) {
#
# It is a bridge port zone -- use the bridges input chain and match the physdev
#
add_ijump_extended( $filter_table->{ input_chain $bridge },
j => $inputchainref ,
$origin ,
imatch_source_dev($interface, 1) )
unless $input_jump_added{$interface}++;
} else {
#
# Not a bridge -- match the input interface
#
add_ijump_extended $filter_table->{INPUT}, j => $inputchainref, $origin, imatch_source_dev($interface) unless $input_jump_added{$interface}++;
}
$use_input = 1;
unless ( lc $net eq IPv6_LINKLOCAL ) {
#
# Generate input rules for the vservers
#
for my $vzone ( @vservers ) {
my $target = rules_target( $zone, $vzone );
generate_dest_rules( $inputchainref, $target, $vzone, $origin, @source, @ipsec_in_match ) if $target;
}
}
} elsif ( $isport ) {
#
# It is a bridge port zone -- use the bridges input chain and match the physdev
#
$inputchainref = $filter_table->{ input_chain $bridge };
@interfacematch = imatch_source_dev $interface, 1;
} else {
#
# Just put the jump in the INPUT chain
#
$inputchainref = $filter_table->{INPUT};
@interfacematch = imatch_source_dev $interface;
}
if ( $chain2 ) {
#
# Add the jump from the input chain to the rules chain
#
add_ijump_extended $inputchainref, j => source_exclusion( $exclusions, $chain2 ), $origin, @interfacematch, @source, @ipsec_in_match;
move_rules( $interfacechainref , $chain2ref ) unless $use_input;
}
}
#
# This function is called when there is forwarding and this net isn't IPSEC protected. It adds the jump for this net to the zone forwarding chain.
#
sub add_forward_jump( $$$$$$$$$ ) {
my ( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) = @_;
our %forward_jump_added;
my @source = imatch_source_net $net;
my @ipsec_in_match = match_ipsec_in $zone , $hostref;
my $ref = source_exclusion( $exclusions, $frwd_ref );
my $forwardref = $filter_table->{forward_chain $interface};
if ( use_forward_chain $interface, $forwardref ) {
#
# We must use the interface forwarding chain -- add the jump from the interface forward chain to the zone forward chain.
#
add_ijump_extended $forwardref , j => $ref, $origin, @source, @ipsec_in_match;
if ( $isport ) {
#
# It is a bridge port zone -- use the bridges input chain and match the physdev
#
add_ijump_extended( $filter_table->{ forward_chain $bridge } ,
j => $forwardref ,
$origin ,
imatch_source_dev( $interface , 1 ) )
unless $forward_jump_added{$interface}++;
} else {
#
# Not a bridge -- match the input interface
#
add_ijump_extended $filter_table->{FORWARD} , j => $forwardref, $origin, imatch_source_dev( $interface ) unless $forward_jump_added{$interface}++;
}
} else {
if ( $isport ) {
#
# It is a bridge port zone -- use the bridges input chain and match the physdev
#
add_ijump_extended( $filter_table->{ forward_chain $bridge } ,
j => $ref ,
$origin ,
imatch_source_dev( $interface, 1 ) ,
@source,
@ipsec_in_match );
} else {
#
# Not a bridge -- match the input interface
#
add_ijump_extended $filter_table->{FORWARD} , j => $ref, $origin, imatch_source_dev( $interface ) , @source, @ipsec_in_match;
}
move_rules ( $forwardref , $frwd_ref );
}
}
#
# Generate the list of destination zones from the passed source zone when optimization level 1 is selected
#
# - Drop zones where the policy to that zone is 'NONE'
# - Drop this zone if it has only one interface without 'routeback'
# - Drop BPORT zones that are not on the same bridge
# - Eliminate duplicate zones that have the same '2all' (-all) rules chain.
#
sub optimize1_zones( $$@ ) {
my $zone = shift;
my $zoneref = shift;
my $last_chain = '';
my @dest_zones;
my @temp_zones;
for my $zone1 ( @_ ) {
my $zone1ref = find_zone( $zone1 );
my $policy = $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy};
next if $policy eq 'NONE';
my $chain = rules_target $zone, $zone1;
next unless $chain;
if ( $zone eq $zone1 ) {
next if ( scalar ( keys( %{ $zoneref->{interfaces}} ) ) < 2 ) && ! $zoneref->{options}{in_out}{routeback};
}
if ( $zone1ref->{type} & BPORT ) {
next unless $zoneref->{bridge} eq $zone1ref->{bridge};
}
if ( $chain =~ /(2all|-all)$/ ) {
if ( $chain ne $last_chain ) {
$last_chain = $chain;
push @dest_zones, @temp_zones;
@temp_zones = ( $zone1 );
} elsif ( $policy eq 'ACCEPT' ) {
push @temp_zones , $zone1;
} else {
$last_chain = $chain;
@temp_zones = ( $zone1 );
}
} else {
push @dest_zones, @temp_zones, $zone1;
@temp_zones = ();
$last_chain = '';
}
}
if ( $last_chain && @temp_zones == 1 ) {
push @dest_zones, @temp_zones;
$last_chain = '';
}
( $last_chain, @dest_zones );
}
# Generate the rules matrix.
#
# Stealing a comment from the Burroughs B6700 MCP Operating System source, "generate_matrix makes a sow's ear out of a silk purse".
#
# The biggest disadvantage of the zone-policy-rule model used by Shorewall is that it doesn't scale well as the number of zones increases (Order N**2 where N = number of zones).
# A major goal of the rewrite of the compiler in Perl was to restrict those scaling effects to this function and the rules that it generates.
#
# The function traverses the full "source-zone by destination-zone" matrix and generates the rules necessary to direct traffic through the right set of filter-table, raw-table and
# nat-table rules.
#
sub generate_matrix() {
my @interfaces = ( managed_interfaces );
my @zones = off_firewall_zones;
our @vservers = vserver_zones;
my $interface_jumps_added = 0;
our %input_jump_added = ();
our %output_jump_added = ();
our %forward_jump_added = ();
our %ipsec_jump_added = ();
progress_message2 'Generating Rule Matrix...';
progress_message ' Handling complex zones...';
#
# Special processing for configurations with more than 2 off-firewall zones or with other special considerations like IPSEC.
# Don't be tempted to move this logic into the zone loop below -- it won't work.
#
for my $zone ( @zones ) {
my $zoneref = find_zone( $zone );
unless ( $zoneref->{type} == LOCAL ) {
if ( @zones > 2 || $zoneref->{complex} ) {
handle_complex_zone( $zone, $zoneref );
} else {
new_standard_chain zone_forward_chain( $zone ) if @zones > 1;
}
}
}
#
# Main source-zone matrix-generation loop
#
progress_message ' Entering main matrix-generation loop...';
for my $zone ( @zones ) {
my $zoneref = find_zone( $zone );
my $source_hosts_ref = $zoneref->{hosts};
my $frwd_ref = $filter_table->{zone_forward_chain $zone};
my $nested = @{$zoneref->{parents}};
my $parenthasnat = 0;
my $parenthasnotrack = 0;
my $type = $zoneref->{type};
#
# Create the zone's dnat chain
#
ensure_chain 'nat', dnat_chain( $zone );
( $nested, $parenthasnat, $parenthasnotrack) = handle_nested_zone( $zone, $zoneref ) if $nested;
#
# Take care of PREROUTING, INPUT and OUTPUT jumps
#
for my $type ( sortkeysiftest %$source_hosts_ref ) {
my $typeref = $source_hosts_ref->{$type};
for my $interface ( sortkeysiftest %$typeref ) {
if ( get_physical( $interface ) eq '+' ) {
#
# Insert the interface-specific jumps before this one which is not interface-specific
#
add_interface_jumps(@interfaces) unless $interface_jumps_added++;
}
my $interfaceref = find_interface $interface;
my $isport = $interfaceref->{options}{port};
my $bridge = $interfaceref->{bridge};
for my $hostref ( @{$typeref->{$interface}} ) {
my $exclusions = $hostref->{exclusions};
my $origin = $hostref->{origin};
for my $net ( @{$hostref->{hosts}} ) {
#
# OUTPUT
#
if ( rules_target( firewall_zone, $zone ) && ! ( zone_type( $zone) & BPORT ) ) {
#
# Policy from the firewall to this zone is not 'CONTINUE' and this isn't a bport zone
#
add_output_jumps( $zone, $interface, $hostref, $net, $exclusions, $isport, $bridge, $origin );
}
clearrule;
unless( $hostref->{options}{destonly} ) {
#
# PREROUTING
#
add_prerouting_jumps( $zone, $interface, $hostref, $net, $exclusions, $nested, $parenthasnat, $parenthasnotrack , $origin );
#
# INPUT
#
add_input_jumps( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge , $origin );
#
# FORWARDING Jump for non-IPSEC host group
#
add_forward_jump( $zone, $interface, $hostref, $net, $exclusions, $frwd_ref, $isport, $bridge, $origin ) if $frwd_ref && $hostref->{ipsec} ne 'ipsec';
}
} # Subnet Loop
} # Hostref Loop
} # Interface Loop
} #Type Loop
if ( $frwd_ref ) {
#
# F O R W A R D I N G
#
my @dest_zones;
my $last_chain = '';
if ( $config{OPTIMIZE} & 1 ) {
( $last_chain , @dest_zones ) = optimize1_zones($zone, $zoneref, @zones );
} else {
@dest_zones = @zones ;
}
#
# We now loop through the destination zones creating jumps to the rules chain for each source/dest combination.
# @dest_zones is the list of destination zones that we need to handle from this source zone
#
for my $zone1 ( @dest_zones ) {
my $zone1ref = find_zone( $zone1 );
my $type1 = $zone1ref->{type};
next if $filter_table->{rules_chain( ${zone}, ${zone1} )}->{policy} eq 'NONE';
my $chain = rules_target $zone, $zone1;
next unless $chain; # CONTINUE policy with no rules
my $num_ifaces = 0;
if ( $zone eq $zone1 ) {
next if ( $num_ifaces = scalar( keys ( %{$zoneref->{interfaces}} ) ) ) < 2 && ! $zoneref->{options}{in_out}{routeback};
}
if ( $type1 & BPORT ) {
next unless $zoneref->{bridge} eq $zone1ref->{bridge};
}
my $chainref = $filter_table->{$chain}; #Will be null if $chain is a Netfilter Built-in target like ACCEPT
for my $type ( sortkeysiftest %{$zone1ref->{hosts}} ) {
my $typeref = $zone1ref->{hosts}{$type};
for my $interface ( sortkeysiftest %$typeref ) {
for my $hostref ( @{$typeref->{$interface}} ) {
next if $hostref->{options}{sourceonly};
if ( $zone ne $zone1 || $num_ifaces > 1 || $hostref->{options}{routeback} ) {
my @ipsec_out_match = match_ipsec_out $zone1 , $hostref;
my $dest_exclusion = dest_exclusion( $hostref->{exclusions}, $chain);
my $origin = $hostref->{origin};
for my $net ( @{$hostref->{hosts}} ) {
add_ijump_extended $frwd_ref, j => $dest_exclusion, $origin, imatch_dest_dev( $interface) , imatch_dest_net($net), @ipsec_out_match;
}
}
}
}
}
}
#
# E N D F O R W A R D I N G
#
# Now add an unconditional jump to the last unique policy-only chain determined above, if any
#
add_ijump $frwd_ref , g => $last_chain if $frwd_ref && $last_chain;
} # Forwarding required
} # Source Zone Loop
progress_message ' Finishing matrix...';
#
# Make sure that the 1:1 NAT jumps are last in PREROUTING
#
addnatjump 'PREROUTING' , 'nat_in';
addnatjump $globals{POSTROUTING} , 'nat_out';
add_interface_jumps @interfaces unless $interface_jumps_added;
unless ( $config{COMPLETE} ) {
for ( unmanaged_interfaces ) {
my $physical = get_physical $_;
add_ijump( $filter_table->{INPUT}, j => 'ACCEPT', i => $physical );
add_ijump( $filter_table->{OUTPUT}, j => 'ACCEPT', o => $physical );
}
complete_standard_chain $filter_table->{INPUT} , 'all' , firewall_zone , 'DROP';
complete_standard_chain $filter_table->{OUTPUT} , firewall_zone , 'all', 'REJECT';
complete_standard_chain $filter_table->{FORWARD} , 'all' , 'all', 'REJECT';
}
if ( $config{LOGALLNEW} ) {
my %builtins = ( mangle => [ qw/PREROUTING INPUT FORWARD POSTROUTING/ ] ,
nat=> [ qw/PREROUTING OUTPUT POSTROUTING/ ] ,
filter=> [ qw/INPUT FORWARD OUTPUT/ ] );
my $origin = $origin{LOGALLNEW};
for my $table ( qw/mangle nat filter/ ) {
for my $chain ( @{$builtins{$table}} ) {
log_rule_limit( $config{LOGALLNEW} ,
$chain_table{$table}{$chain} ,
$table ,
$chain ,
'' ,
'' ,
'insert' ,
state_match('NEW') ,
$origin );
}
}
}
}
#
# Generate MSS rules
#
sub setup_mss( ) {
my $clampmss = $config{CLAMPMSS};
my $option;
my @match;
my $chainref = $mangle_table->{FORWARD};
if ( $clampmss ) {
if ( "\L$clampmss" eq 'yes' ) {
$option = '--clamp-mss-to-pmtu';
} else {
@match = ( tcpmss => "--mss $clampmss:" ) if have_capability( 'TCPMSS_MATCH' );
$option = "--set-mss $clampmss";
}
push @match, ( policy => '--pol none --dir out' ) if have_ipsec;
}
my $interfaces = find_interfaces_by_option( 'mss' );
if ( @$interfaces ) {
#
# Since we will need multiple rules, we create a separate chain
#
$chainref = new_chain 'filter', 'settcpmss';
#
# Send all forwarded SYN packets to the 'settcpmss' chain
#
add_ijump $filter_table->{FORWARD} , j => $chainref, p => 'tcp --tcp-flags SYN,RST SYN';
my @in_match = ();
my @out_match = ();
if ( have_ipsec ) {
@in_match = ( policy => '--pol none --dir in' );
@out_match = ( policy => '--pol none --dir out' );
}
for ( @$interfaces ) {
my $mss = get_interface_option( $_, 'mss' );
my @mssmatch = have_capability( 'TCPMSS_MATCH' ) ? ( tcpmss => "--mss $mss:" ) : ();
my @source = imatch_source_dev $_;
my @dest = imatch_dest_dev $_;
add_ijump $chainref, j => 'TCPMSS', targetopts => "--set-mss $mss", @dest, p => 'tcp --tcp-flags SYN,RST SYN', @mssmatch, @out_match;
add_ijump $chainref, j => 'RETURN', @dest if $clampmss;
add_ijump $chainref, j => 'TCPMSS', targetopts => "--set-mss $mss", @source, p => 'tcp --tcp-flags SYN,RST SYN', @mssmatch, @in_match;
add_ijump $chainref, j => 'RETURN', @source if $clampmss;
}
}
add_ijump $chainref , j => 'TCPMSS', targetopts => $option, p => 'tcp --tcp-flags SYN,RST SYN', @match if $clampmss;
}
#
# Compile the stop_firewall() function
#
sub compile_stop_firewall( $$$$ ) {
my ( $test, $export, $have_arptables, $convert ) = @_;
my $input = $filter_table->{INPUT};
my $output = $filter_table->{OUTPUT};
my $forward = $filter_table->{FORWARD};
my $absentminded = $config{ ADMINISABSENTMINDED };
emit <<'EOF';
#
# Stop/restore the firewall after an error or because of a 'stop' or 'clear' command
#
stop_firewall() {
EOF
$output->{policy} = 'ACCEPT' if $absentminded;
if ( $family == F_IPV4 ) {
emit <<'EOF';
deletechain() {
qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1
}
case $COMMAND in
stop|clear|restore)
if chain_exists dynamic; then
${IPTABLES}-save -t filter | grep '^-A dynamic' | fgrep -v -- '-j ACCEPT' > ${VARDIR}/.dynamic
fi
;;
*)
set +x
EOF
} else {
emit <<'EOF';
deletechain() {
qt $IPTABLES -L $1 -n && qt $IPTABLES -F $1 && qt $IPTABLES -X $1
}
case $COMMAND in
stop|clear|restore)
if chain_exists dynamic; then
${IP6TABLES}-save -t filter | grep '^-A dynamic' | fgrep -v -- '-j ACCEPT' > ${VARDIR}/.dynamic
fi
;;
*)
set +x
EOF
}
emit <<'EOF';
case $COMMAND in
start)
mylogger daemon.err "ERROR:$g_product start failed"
;;
reload)
mylogger daemon.err "ERROR:$g_product reload failed"
;;
enable)
mylogger daemon.err "ERROR:$g_product 'enable $g_interface' failed"
;;
esac
if [ "$RESTOREFILE" = NONE ]; then
COMMAND=clear
clear_firewall
echo "$g_product Cleared"
kill $$
exit 2
else
g_restorepath=${VARDIR}/$RESTOREFILE
if [ -x $g_restorepath ]; then
echo Restoring ${g_product:=Shorewall}...
g_recovering=Yes
if run_it $g_restorepath restore; then
echo "$g_product restored from $g_restorepath"
set_state "Restored from $g_restorepath"
else
set_state "Unknown"
fi
kill $$
exit 2
fi
fi
;;
esac
if [ -n "$g_stopping" ]; then
kill $$
exit 1
fi
set_state "Stopping"
g_stopping="Yes"
deletechain shorewall
run_stop_exit
#
# Enable automatic helper association on kernel 3.5.0 and later
#
if [ $COMMAND = clear -a -f /proc/sys/net/netfilter/nf_conntrack_helper ]; then
echo 1 > /proc/sys/net/netfilter/nf_conntrack_helper
fi
EOF
if ( $config{DOCKER} ) {
push_indent;
emit( 'if [ $COMMAND = stop ]; then' );
push_indent;
save_docker_rules( $family == F_IPV4 ? '${IPTABLES}' : '${IP6TABLES}');
pop_indent;
emit( "fi\n");
pop_indent;
}
if ( have_capability( 'NAT_ENABLED' ) ) {
emit<<'EOF';
if [ -f ${VARDIR}/nat ]; then
while read external interface; do
del_ip_addr $external $interface
done < ${VARDIR}/nat
rm -f ${VARDIR}/nat
fi
EOF
}
if ( $family == F_IPV4 ) {
emit <<'EOF';
if [ -f ${VARDIR}/proxyarp ]; then
while read address interface external haveroute; do
qtnoin $IP -4 neigh del proxy $address dev $external
[ -z "${haveroute}${g_noroutes}" ] && qtnoin $IP -4 route del $address/32 dev $interface
f=/proc/sys/net/ipv4/conf/$interface/proxy_arp
[ -f $f ] && echo 0 > $f
done < ${VARDIR}/proxyarp
rm -f ${VARDIR}/proxyarp
fi
EOF
} else {
emit <<'EOF';
if [ -f ${VARDIR}/proxyndp ]; then
while read address interface external haveroute; do
qtnoin $IP -6 neigh del proxy $address dev $external
[ -z "${haveroute}${g_noroutes}" ] && qtnoin $IP -6 route del $address/128 dev $interface
f=/proc/sys/net/ipv4/conf/$interface/proxy_ndp
[ -f $f ] && echo 0 > $f
done < ${VARDIR}/proxyndp
rm -f ${VARDIR}/proxyndp
fi
EOF
}
push_indent;
emit 'delete_tc1' if $config{CLEAR_TC};
emit( 'undo_routing',
"restore_default_route $config{USE_DEFAULT_RT}"
);
#
# Insure that Docker jumps are early in the builtin chains
#
create_docker_rules if $config{DOCKER};
if ( $absentminded ) {
add_ijump $filter_table ->{$_}, j => 'ACCEPT', state_imatch 'ESTABLISHED,RELATED' for qw/INPUT FORWARD/;
}
if ( $family == F_IPV6 ) {
add_ijump $input, j => 'ACCEPT', s => IPv6_LINKLOCAL;
add_ijump $input, j => 'ACCEPT', d => IPv6_LINKLOCAL;
add_ijump $input, j => 'ACCEPT', d => IPv6_MULTICAST;
unless ( $absentminded ) {
add_ijump $output, j => 'ACCEPT', d => IPv6_LINKLOCAL;
add_ijump $output, j => 'ACCEPT', d => IPv6_MULTICAST;
}
}
if ( $convert ) {
convert_routestopped;
} elsif ( -f ( my $fn = find_file 'routestopped' ) ) {
warning_message "The routestopped file is no longer supported - use '$product update' to convert $fn to an equivalent 'stoppedrules' file";
}
process_stoppedrules;
if ( $family == F_IPV6 ) {
my $chain = new_action_chain( 'filter', 'AllowICMPs' );
for my $type ( 1, 2, 3, 4, 130, 131, 132, 133, 134, 135, 136, 137, 141, 142, 143, 148, 149, 151, 152, 153 ) {
add_ijump( $chain, j => 'ACCEPT', p => IPv6_ICMP . " --icmpv6-type $type" );
}
for $chain ( $input, $output, $forward ) {
next if $chain eq $output && $absentminded;
add_ijump( $chain, j => 'AllowICMPs', p => IPv6_ICMP );
}
}
if ( have_capability 'IFACE_MATCH' ) {
add_ijump $input, j => 'ACCEPT', iface => '--dev-in --loopback';
add_ijump $output, j => 'ACCEPT', iface => '--dev-out --loopback' unless $absentminded;
} else {
add_ijump $input, j => 'ACCEPT', i => loopback_interface;
add_ijump $output, j => 'ACCEPT', o => loopback_interface unless $absentminded;
}
my $interfaces = find_interfaces_by_option 'dhcp';
if ( @$interfaces ) {
my $ports = $family == F_IPV4 ? '67:68' : '546:547';
for my $interface ( @$interfaces ) {
add_ijump $input, j => 'ACCEPT', p => "udp --dport $ports", imatch_source_dev( $interface );
add_ijump $output, j => 'ACCEPT', p => "udp --dport $ports", imatch_dest_dev( $interface ) unless $absentminded;
#
# This might be a bridge
#
add_ijump $forward, j => 'ACCEPT', p => "udp --dport $ports", imatch_source_dev( $interface ), imatch_dest_dev( $interface );
}
}
emit '';
create_stop_load $test;
if ( $family == F_IPV4 ) {
emit( '$ARPTABLES -F',
'' ) if $have_arptables;
if ( $config{IP_FORWARDING} eq 'on' ) {
emit( 'echo 1 > /proc/sys/net/ipv4/ip_forward',
'progress_message2 IPv4 Forwarding Enabled' );
} elsif ( $config{IP_FORWARDING} eq 'off' ) {
emit( 'echo 0 > /proc/sys/net/ipv4/ip_forward',
'progress_message2 IPv4 Forwarding Disabled!'
);
}
} else {
for my $interface ( all_bridges ) {
emit "do_iptables -A FORWARD -p 58 " . match_source_dev( $interface ) . match_dest_dev( $interface ) . "-j ACCEPT";
}
if ( $config{IP_FORWARDING} eq 'on' ) {
emit( 'echo 1 > /proc/sys/net/ipv6/conf/all/forwarding',
'progress_message2 IPv6 Forwarding Enabled' );
} elsif ( $config{IP_FORWARDING} eq 'off' ) {
emit( 'echo 0 > /proc/sys/net/ipv6/conf/all/forwarding',
'progress_message2 IPv6 Forwarding Disabled!'
);
}
}
pop_indent;
emit '
rm -f ${VARDIR}/*.address
rm -f ${VARDIR}/*.gateway
run_stopped_exit';
my @ipsets = all_ipsets;
if ( @ipsets || @{$globals{SAVED_IPSETS}} || ( $config{SAVE_IPSETS} && have_ipset_rules ) ) {
emit( '',
' save_ipsets ${VARDIR}/ipsets.save' );
}
emit '
set_state "Stopped"
mylogger daemon.info "$g_product Stopped"
case $COMMAND in
stop|clear)
;;
*)
#
# The firewall is being stopped when we were trying to do something
# else. Kill the shell in case we\'re running in a subshell
#
kill $$
;;
esac
}
';
}
1;