diff --git a/Shorewall-common/lib.base b/Shorewall-common/lib.base
index 82e2cc9c7..9969635c6 100644
--- a/Shorewall-common/lib.base
+++ b/Shorewall-common/lib.base
@@ -35,7 +35,7 @@
#
SHOREWALL_LIBVERSION=40000
-SHOREWALL_CAPVERSION=40200
+SHOREWALL_CAPVERSION=40202
[ -n "${VARDIR:=/var/lib/shorewall}" ]
[ -n "${SHAREDIR:=/usr/share/shorewall}" ]
@@ -1047,6 +1047,7 @@ determine_capabilities() {
CONNTRACK_MATCH=
NEW_CONNTRACK_MATCH=
+ OLD_CONNTRACK_MATCH=
MULTIPORT=
XMULTIPORT=
POLICY_MATCH=
@@ -1105,6 +1106,7 @@ determine_capabilities() {
if [ -n "$CONNTRACK_MATCH" ]; then
qt $IPTABLES -A $chain -m conntrack -p tcp --ctorigdstport 22 -j ACCEPT && NEW_CONNTRACK_MATCH=Yes
+ qt $IPTABLES -A $chain -m conntrack ! --ctorigdst 1.2.3.4 || OLD_CONNTRACK_MATCH=Yes
fi
if qt $IPTABLES -A $chain -p tcp -m multiport --dports 21,22 -j ACCEPT; then
@@ -1211,7 +1213,10 @@ report_capabilities() {
report_capability "Multi-port Match" $MULTIPORT
[ -n "$MULTIPORT" ] && report_capability "Extended Multi-port Match" $XMULTIPORT
report_capability "Connection Tracking Match" $CONNTRACK_MATCH
- [ -n "$CONNTRACK_MATCH" ] && report_capability "Extended Connection Tracking Match Support" $NEW_CONNTRACK_MATCH
+ if [ -n "$CONNTRACK_MATCH" ]; then
+ report_capability "Extended Connection Tracking Match Support" $NEW_CONNTRACK_MATCH
+ report_capability "Old Connection Tracking Match Syntax" $OLD_CONNTRACK_MATCH
+ fi
report_capability "Packet Type Match" $USEPKTTYPE
report_capability "Policy Match" $POLICY_MATCH
report_capability "Physdev Match" $PHYSDEV_MATCH
@@ -1263,6 +1268,7 @@ report_capabilities1() {
report_capability1 XMULTIPORT
report_capability1 CONNTRACK_MATCH
report_capability1 NEW_CONNTRACK_MATCH
+ report_capability1 OLD_CONNTRACK_MATCH
report_capability1 USEPKTTYPE
report_capability1 POLICY_MATCH
report_capability1 PHYSDEV_MATCH
diff --git a/Shorewall-perl/Shorewall/Actions.pm b/Shorewall-perl/Shorewall/Actions.pm
index a06476a91..e10c6431c 100644
--- a/Shorewall-perl/Shorewall/Actions.pm
+++ b/Shorewall-perl/Shorewall/Actions.pm
@@ -43,9 +43,11 @@ our @EXPORT = qw( merge_levels
process_actions3
find_macro
+ find_6macro
split_action
substitute_param
merge_macro_source_dest
+ merge_6macro_source_dest
merge_macro_column
%usedactions
@@ -53,6 +55,7 @@ our @EXPORT = qw( merge_levels
%actions
%macros
+ %macros6
$macro_commands
);
our @EXPORT_OK = qw( initialize );
@@ -82,6 +85,7 @@ our %actions;
our %logactionchains;
our %macros;
+our %macros6;
#
# Commands that can be embedded in a macro file and how many total tokens on the line (0 => unlimited).
@@ -105,6 +109,8 @@ sub initialize() {
QUEUE => 'none' );
%actions = ();
%logactionchains = ();
+ %macros = ();
+ %macros6 = ();
}
INIT {
@@ -162,6 +168,19 @@ sub find_macro( $ )
}
}
+sub find_6macro( $ )
+{
+ my $macro = $_[0];
+ my $macrofile = find_file "macro.$macro";
+
+ if ( -f $macrofile ) {
+ $macros6{$macro} = $macrofile;
+ $targets6{$macro} = MACRO;
+ } else {
+ 0;
+ }
+}
+
#
# Return ( action, level[:tag] ) from passed full action
#
@@ -212,6 +231,22 @@ sub merge_macro_source_dest( $$ ) {
$body || '';
}
+sub merge_6macro_source_dest( $$ ) {
+ my ( $body, $invocation ) = @_;
+
+ if ( $invocation ) {
+ if ( $body ) {
+ return $body if $invocation eq '-';
+ return "$body;$invocation" if $invocation =~ /:|.*?\.*?\.|^\+|^~|^!~/;
+ return "$invocation;$body";
+ }
+
+ return $invocation;
+ }
+
+ $body || '';
+}
+
sub merge_macro_column( $$ ) {
my ( $body, $invocation ) = @_;
diff --git a/Shorewall-perl/Shorewall/Chains.pm b/Shorewall-perl/Shorewall/Chains.pm
index aa2df052e..4ba0be5fb 100644
--- a/Shorewall-perl/Shorewall/Chains.pm
+++ b/Shorewall-perl/Shorewall/Chains.pm
@@ -47,6 +47,15 @@ our @EXPORT = qw(
$nat_table
$mangle_table
$filter_table
+
+ new_6chain
+ new_manual_6chain
+ ensure_manual_6chain
+
+ %chain6_table
+ $mangle6_table
+ $filter6_table
+
);
our %EXPORT_TAGS = (
@@ -91,11 +100,6 @@ our %EXPORT_TAGS = (
syn_flood_chain
mac_chain
macrecent_target
- dynamic_fwd
- dynamic_in
- dynamic_out
- dynamic_chains
- zone_dynamic_chain
dnat_chain
snat_chain
ecn_chain
@@ -114,6 +118,7 @@ our %EXPORT_TAGS = (
newexclusionchain
clearrule
do_proto
+ do_proto6
mac_match
verify_mark
verify_small_mark
@@ -149,6 +154,32 @@ our %EXPORT_TAGS = (
$section
%sections
%targets
+
+ use_output_6chain
+ ensure_6chain
+ ensure_accounting_6chain
+ ensure_mangle_6chain
+ new_standard_6chain
+ new_builtin_6chain
+ ensure_filter_6chain
+ initialize_6chain_table
+ finish_6section
+ newexclusion6chain
+ match_source_6net
+ match_dest_6net
+ match_source_6dev
+ match_dest_6dev
+ get_interface_6address
+ get_interface_6addresses
+ get_interface_6bcasts
+ get_interface_6gateway
+ expand_6rule
+ set_global_6variables
+ create_netfilter6_load
+ create_6chainlist_reload
+ $section6
+ %sections6
+ %targets6
) ],
);
@@ -157,27 +188,27 @@ Exporter::export_ok_tags('internal');
our $VERSION = 4.1.5;
#
-# Chain Table
+# Chain Table6
#
-# %chain_table {
=> { => { name =>
-# table =>
-# is_policy => undef|1 -- if 1, this is a policy chain
-# is_optional => undef|1 -- See below.
-# referenced => undef|1 -- If 1, will be written to the iptables-restore-input.
-# builtin => undef|1 -- If 1, one of Netfilter's built-in chains.
-# manual => undef|1 -- If 1, a manual chain.
-# accounting => undef|1 -- If 1, an accounting chain
-# log =>
-# policy =>
-# policychain => -- self-reference if this is a policy chain
-# policypair => [ , ] -- Used for reporting duplicated policies
-# loglevel =>
-# synparams =>
-# synchain =>
-# default =>
-# cmdlevel =>
-# rules => [
-#
+# %chain[6]_table { => { => { name =>
+# table =>
+# is_policy => undef|1 -- if 1, this is a policy chain
+# is_optional => undef|1 -- See below.
+# referenced => undef|1 -- If 1, will be written to the iptables-restore-input.
+# builtin => undef|1 -- If 1, one of Netfilter's built-in chains.
+# manual => undef|1 -- If 1, a manual chain.
+# accounting => undef|1 -- If 1, an accounting chain
+# log =>
+# policy =>
+# policychain => -- self-reference if this is a policy chain
+# policypair => [ , ] -- Used for reporting duplicated policies
+# loglevel =>
+# synparams =>
+# synchain =>
+# default =>
+# cmdlevel =>
+# rules => [
+#
# ...
# ]
# } ,
@@ -196,6 +227,11 @@ our %chain_table;
our $nat_table;
our $mangle_table;
our $filter_table;
+
+our %chain6_table;
+our $mangle6_table;
+our $filter6_table;
+
#
# It is a layer violation to keep information about the rules file sections in this module but in Shorewall, the rules file
# and the filter table are very closely tied. By keeping the information here, we avoid making several other modules dependent
@@ -204,6 +240,9 @@ our $filter_table;
our %sections;
our $section;
+our %sections6;
+our $section6;
+
our $comment;
use constant { STANDARD => 1, #defined by Netfilter
@@ -220,6 +259,8 @@ use constant { STANDARD => 1, #defined by Netfilter
};
our %targets;
+our %targets6;
+
#
# expand_rule() restrictions
#
@@ -240,6 +281,11 @@ our %interfacenets;
our %interfacemacs;
our %interfacebcasts;
our %interfacegateways;
+our %interface6addr;
+our %interface6addrs;
+our %interface6nets;
+our %interface6bcasts;
+our %interface6gateways;
our @builtins = qw(PREROUTING INPUT FORWARD OUTPUT POSTROUTING);
@@ -341,6 +387,59 @@ sub initialize() {
%interfacemacs = ();
%interfacebcasts = ();
%interfacegateways = ();
+
+ %chain6_table = ( raw => {} ,
+ mangle => {},
+ filter => {} );
+
+ $mangle6_table = $chain6_table{mangle};
+ $filter6_table = $chain6_table{filter};
+
+ #
+ # These get set to 1 as sections are encountered.
+ #
+ %sections6 = ( ESTABLISHED => 0,
+ RELATED => 0,
+ NEW => 0
+ );
+ #
+ # Current rules file section.
+ #
+ $section6 = 'ESTABLISHED';
+ #
+ # As new targets (Actions and Macros) are discovered, they are added to the table
+ #
+ %targets6 = ('ACCEPT' => STANDARD,
+ 'ACCEPT!' => STANDARD,
+ 'DROP' => STANDARD,
+ 'DROP!' => STANDARD,
+ 'REJECT' => STANDARD,
+ 'REJECT!' => STANDARD,
+ 'LOG' => STANDARD + LOGRULE,
+ 'CONTINUE' => STANDARD,
+ 'CONTINUE!' => STANDARD,
+ 'QUEUE' => STANDARD,
+ 'QUEUE!' => STANDARD,
+ 'NFQUEUE' => STANDARD + NFQ,
+ 'NFQUEUE!' => STANDARD + NFQ,
+ 'dropBcast' => BUILTIN + ACTION,
+ 'allowBcast' => BUILTIN + ACTION,
+ 'dropNotSyn' => BUILTIN + ACTION,
+ 'rejNotSyn' => BUILTIN + ACTION,
+ 'dropInvalid' => BUILTIN + ACTION,
+ 'allowInvalid' => BUILTIN + ACTION,
+ 'allowinUPnP' => BUILTIN + ACTION,
+ 'forwardUPnP' => BUILTIN + ACTION,
+ 'Limit' => BUILTIN + ACTION,
+ );
+ #
+ # Keep track of which interfaces have active '6address', '6addresses', '6networks', etc. variables
+ #
+ %interface6addr = ();
+ %interface6addrs = ();
+ %interface6nets = ();
+ %interface6bcasts = ();
+ %interface6gateways = ();
}
INIT {
@@ -652,13 +751,13 @@ sub use_input_chain($) {
#
# Interface associated with a single zone -- use the zone's input chain if it has one
#
- my $chainref = $filter_table->{zone_input_chain $interfaceref->{zone4}};
+ my $chainref = $filter_table->{zone_input_chain $interfaceref->{zone}};
return 0 if $chainref;
#
# Use the '2fw' chain if it is referenced.
#
- $chainref = $filter_table->{join( '' , $interfaceref->{zone4} , '2' , firewall_zone )};
+ $chainref = $filter_table->{join( '' , $interfaceref->{zone} , '2' , firewall_zone )};
! $chainref->{referenced};
}
@@ -696,13 +795,13 @@ sub use_output_chain($) {
#
# Interface associated with a single zone -- use the zone's output chain if it has one
#
- my $chainref = $filter_table->{zone_output_chain $interfaceref->{zone4}};
+ my $chainref = $filter_table->{zone_output_chain $interfaceref->{zone}};
return 0 if $chainref;
#
# Use the 'fw2' chain if it is referenced.
#
- $chainref = $filter_table->{join( '', firewall_zone , '2', $interfaceref->{zone4} )};
+ $chainref = $filter_table->{join( '', firewall_zone , '2', $interfaceref->{zone} )};
! $chainref->{referenced};
}
@@ -735,36 +834,6 @@ sub macrecent_target($)
$config{MACLIST_TTL} ? chain_base($_[0]) . '_rec' : 'RETURN';
}
-#
-# Functions for creating dynamic zone rules
-#
-sub dynamic_fwd( $ )
-{
- chain_base($_[0]) . '_dynf';
-}
-
-sub dynamic_in( $ )
-{
- chain_base($_[0]) . '_dyni';
-}
-
-sub dynamic_out( $ ) # $1 = interface
-{
- chain_base($_[0]) . '_dyno';
-}
-
-sub dynamic_chains( $ ) #$1 = interface
-{
- my $c = chain_base($_[0]);
-
- ( $c . '_dyni' , $c . '_dynf' , $c . '_dyno' );
-}
-
-sub zone_dynamic_chain( $ ) # $1 = zone
-{
- $_[0] . '_dyn';
-
-}
#
# DNAT Chain from a zone
#
@@ -1187,6 +1256,105 @@ sub do_proto( $$$ )
$output;
}
+sub do_proto6( $$$ )
+{
+ my ($proto, $ports, $sports ) = @_;
+ #
+ # Return the number of ports represented by the passed list
+ #
+ sub port_count( $ ) {
+ ( $_[0] =~ tr/,:/,:/ ) + 1;
+ }
+
+ my $output = '';
+
+ $proto = '' if $proto eq '-';
+ $ports = '' if $ports eq '-';
+ $sports = '' if $sports eq '-';
+
+ if ( $proto ne '' ) {
+
+ my $synonly = ( $proto =~ s/:syn$//i );
+
+ my $protonum = resolve_proto $proto;
+
+ if ( defined $protonum ) {
+ #
+ # Protocol is numeric and <= 65535 or is defined in /etc/protocols or NSS equivalent
+ #
+ my $pname = proto_name( $proto = $protonum );
+ #
+ # $proto now contains the protocol number and $pname contains the canonical name of the protocol
+ #
+ unless ( $synonly ) {
+ $output = "-p $proto ";
+ } else {
+ fatal_error '":syn" is only allowed with tcp' unless $proto == TCP;
+ $output = "-p $proto --syn ";
+ }
+
+ PROTO:
+ {
+
+ if ( $proto == TCP || $proto == UDP || $proto == SCTP ) {
+ my $multiport = 0;
+
+ if ( $ports ne '' ) {
+ if ( $ports =~ tr/,/,/ > 0 || $sports =~ tr/,/,/ > 0 ) {
+ fatal_error "Port lists require Multiport support in your kernel/iptables" unless $capabilities{MULTIPORT};
+ fatal_error "Multiple ports not supported with SCTP" if $proto == SCTP;
+ $ports = validate_port_list $pname , $ports;
+ $output .= "-m multiport --dports $ports ";
+ $multiport = 1;
+ } else {
+ $ports = validate_portpair $pname , $ports;
+ $output .= "--dport $ports ";
+ }
+ } else {
+ $multiport = ( ( $sports =~ tr/,/,/ ) > 0 );
+ }
+
+ if ( $sports ne '' ) {
+ if ( $multiport ) {
+ fatal_error "Too many entries in SOURCE PORT(S) list" if port_count( $sports ) > 15;
+ $sports = validate_port_list $pname , $sports;
+ $output .= "-m multiport --sports $sports ";
+ } else {
+ $sports = validate_portpair $pname , $sports;
+ $output .= "--sport $sports ";
+ }
+ }
+
+ last PROTO; }
+
+ if ( $proto == IPv6_ICMP ) {
+ if ( $ports ne '' ) {
+ fatal_error 'Multiple ICMP types are not permitted' if $ports =~ /,/;
+ $ports = validate_icmp6 $ports;
+ $output .= "--icmpv6-type $ports ";
+ }
+
+ fatal_error 'SOURCE PORT(S) not permitted with IPv6-ICMP' if $sports ne '';
+
+ last PROTO; }
+
+ fatal_error "SOURCE/DEST PORT(S) not allowed with PROTO $pname" if $ports ne '' || $sports ne '';
+
+ } # PROTO
+
+ } else {
+ fatal_error "Invalid/Unknown protocol ($proto)"
+ }
+ } else {
+ #
+ # No protocol
+ #
+ fatal_error "SOURCE/DEST PORT(S) not allowed without PROTO" if $ports ne '' || $sports ne '';
+ }
+
+ $output;
+}
+
sub mac_match( $ ) {
my $mac = $_[0];
@@ -1437,6 +1605,16 @@ sub match_source_dev( $ ) {
}
}
+sub match_source_6dev( $ ) {
+ my $interface = shift;
+ my $interfaceref = known_6interface( $interface );
+ if ( $interfaceref && $interfaceref->{options}{port} ) {
+ "-i $interfaceref->{bridge} -m physdev --physdev-in $interface ";
+ } else {
+ "-i $interface ";
+ }
+}
+
#
# Match Dest device
#
@@ -1454,6 +1632,20 @@ sub match_dest_dev( $ ) {
}
}
+sub match_dest_6dev( $ ) {
+ my $interface = shift;
+ my $interfaceref = known_6interface( $interface );
+ if ( $interfaceref && $interfaceref->{options}{port} ) {
+ if ( $capabilities{PHYSDEV_BRIDGE} ) {
+ "-o $interfaceref->{bridge} -m physdev --physdev-is-bridged --physdev-out $interface ";
+ } else {
+ "-o $interfaceref->{bridge} -m physdev --physdev-out $interface ";
+ }
+ } else {
+ "-o $interface ";
+ }
+}
+
#
# Avoid generating a second '-m iprange' in a single rule.
#
@@ -1557,7 +1749,7 @@ sub match_orig_dest ( $ ) {
if ( $net =~ s/^!// ) {
validate_net $net, 1;
- $capabilities{NEW_CONNTRACK_MATCH} ? "-m conntrack ! --ctorigdst $net " : "-m conntrack --ctorigdst ! $net ";
+ $capabilities{OLD_CONNTRACK_MATCH} ? "-m conntrack --ctorigdst ! $net " : "-m conntrack ! --ctorigdst $net ";
} else {
validate_net $net, 1;
$net eq ALLIPv4 ? '' : "-m conntrack --ctorigdst $net ";
@@ -2578,4 +2770,918 @@ sub create_chainlist_reload($) {
emit "}\n";
}
+#
+# Returns true if we're to use the interface's output chain
+#
+sub use_output_6chain($) {
+ my $interface = $_[0];
+ my $interfaceref = find_interface6($interface);
+ my $nets = $interfaceref->{nets};
+ #
+ # We must use the interfaces's chain if the interface is associated with multiple zone nets
+ #
+ return 1 if $nets > 1;
+ #
+ # Don't need it if it isn't associated with any zone
+ #
+ return 0 unless $nets;
+ #
+ # Interface associated with a single zone -- use the zone's output chain if it has one
+ #
+ my $chainref = $filter6_table->{zone_output_chain $interfaceref->{zone4}};
+
+ return 0 if $chainref;
+ #
+ # Use the 'fw2' chain if it is referenced.
+ #
+ $chainref = $filter6_table->{join( '', firewall_zone , '2', $interfaceref->{zone} )};
+
+ ! $chainref->{referenced};
+}
+
+#
+# Create a new 6chain and return a reference to it.
+#
+sub new_6chain($$)
+{
+ my ($table, $chain) = @_;
+
+ warning_message "Internal error in new_6chain()" if $chain6_table{$table}{$chain};
+
+ $chain6_table{$table}{$chain} = { name => $chain,
+ rules => [],
+ table => $table,
+ loglevel => '',
+ log => 1,
+ cmdlevel => 0 };
+}
+
+#
+# Create a 6chain if it doesn't exist already
+#
+sub ensure_6chain($$)
+{
+ my ($table, $chain) = @_;
+
+ my $ref = $chain6_table{$table}{$chain};
+
+ return $ref if $ref;
+
+ new_6chain $table, $chain;
+}
+
+sub finish_6chain_section( $$ );
+
+#
+# Create a filter chain if necessary. Optionally populate it with the appropriate ESTABLISHED,RELATED rule(s) and perform SYN rate limiting.
+#
+sub ensure_filter_6chain( $$ )
+{
+ my ($chain, $populate) = @_;
+
+ my $chainref = $filter6_table->{$chain};
+
+ $chainref = new_6chain 'filter' , $chain unless $chainref;
+
+ if ( $populate and ! $chainref->{referenced} ) {
+ if ( $section eq 'NEW' or $section eq 'DONE' ) {
+ finish_6chain_section $chainref , 'ESTABLISHED,RELATED';
+ } elsif ( $section eq 'RELATED' ) {
+ finish_chain_section $chainref , 'ESTABLISHED';
+ }
+ }
+
+ $chainref->{referenced} = 1;
+
+ $chainref;
+}
+
+#
+# Create an accounting chain if necessary.
+#
+sub ensure_accounting_6chain( $ )
+{
+ my ($chain) = @_;
+
+ my $chainref = $filter6_table->{$chain};
+
+ if ( $chainref ) {
+ fatal_error "Non-accounting chain ($chain) used in accounting rule" if ! $chainref->{accounting};
+ } else {
+ $chainref = new_6chain 'filter' , $chain unless $chainref;
+ $chainref->{accounting} = 1;
+ $chainref->{referenced} = 1;
+ }
+
+ $chainref;
+}
+
+sub ensure_mangle_6chain($) {
+ my $chain = $_[0];
+
+ my $chainref = ensure_6chain 'mangle', $chain;
+
+ $chainref->{referenced} = 1;
+
+ $chainref;
+}
+
+#
+# Add a builtin chain
+#
+sub new_builtin_6chain($$$)
+{
+ my ( $table, $chain, $policy ) = @_;
+
+ my $chainref = new_6chain $table, $chain;
+ $chainref->{referenced} = 1;
+ $chainref->{policy} = $policy;
+ $chainref->{builtin} = 1;
+}
+
+sub new_standard_6chain($) {
+ my $chainref = new_6chain 'filter' ,$_[0];
+ $chainref->{referenced} = 1;
+ $chainref;
+}
+
+sub new_manual_6chain($) {
+ my $chain = $_[0];
+ fatal_error "Duplicate Chain Name ($chain)" if $targets6{$chain} || $filter6_table->{$chain};
+ $targets6{$chain} = CHAIN;
+ ( my $chainref = ensure_filter_6chain( $chain, 0) )->{manual} = 1;
+ $chainref->{referenced} = 1;
+ $chainref;
+}
+
+sub ensure_manual_6chain($) {
+ my $chain = $_[0];
+ my $chainref = $filter6_table->{$chain} || new_manual_6chain($chain);
+ fatal_error "$chain exists and is not a manual chain" unless $chainref->{manual};
+ $chainref;
+}
+
+#
+# Add all builtin chains to the chain table
+#
+#
+sub initialize_6chain_table()
+{
+ for my $chain qw(OUTPUT PREROUTING) {
+ new_builtin_6chain 'raw', $chain, 'ACCEPT';
+ }
+
+ for my $chain qw(INPUT OUTPUT FORWARD) {
+ new_builtin_6chain 'filter', $chain, 'DROP';
+ }
+
+ for my $chain qw(PREROUTING INPUT FORWARD OUTPUT POSTROUTING ) {
+ new_builtin_6chain 'mangle', $chain, 'ACCEPT';
+ }
+}
+
+#
+# Add ESTABLISHED,RELATED rules and synparam jumps to the passed chain
+#
+sub finish_6chain_section ($$) {
+ my ($chainref, $state ) = @_;
+ my $chain = $chainref->{name};
+ my $savecomment = $comment;
+
+ $comment = '';
+
+ add_rule $chainref, "-m state --state $state -j ACCEPT" unless $config{FASTACCEPT};
+
+ if ($sections6{NEW} ) {
+ if ( $chainref->{is_policy} ) {
+ if ( $chainref->{synparams} ) {
+ my $synchainref = ensure_6chain 'filter', syn_flood_chain $chainref;
+ if ( $section eq 'DONE' ) {
+ if ( $chainref->{policy} =~ /^(ACCEPT|CONTINUE|QUEUE|NFQUEUE)/ ) {
+ add_rule $chainref, "-p tcp --syn -j $synchainref->{name}";
+ }
+ } else {
+ add_rule $chainref, "-p tcp --syn -j $synchainref->{name}";
+ }
+ }
+ } else {
+ my $policychainref = $filter6_table->{$chainref->{policychain}};
+ if ( $policychainref->{synparams} ) {
+ my $synchainref = ensure_6chain 'filter', syn_flood_chain $policychainref;
+ add_rule $chainref, "-p tcp --syn -j $synchainref->{name}";
+ }
+ }
+ }
+
+ $comment = $savecomment;
+}
+
+#
+# Do section-end processing
+#
+sub finish_6section ( $ ) {
+ my $sections = $_[0];
+
+ for my $section ( split /,/, $sections ) {
+ $sections6{$section} = 1;
+ }
+
+ for my $zone ( all_6zones ) {
+ for my $zone1 ( all_6zones ) {
+ my $chainref = $chain6_table{'filter'}{"${zone}2${zone1}"};
+ if ( $chainref->{referenced} ) {
+ finish_6chain_section $chainref, $sections;
+ }
+ }
+ }
+}
+
+#
+# Match a Source. Handles IP addresses and ranges and MAC addresses
+#
+sub match_source_6net( $;$ ) {
+ my ( $net, $restriction) = @_;
+
+ $restriction |= NO_RESTRICT;
+
+ if ( $net =~ /^!?~/ ) {
+ fatal_error "MAC address cannot be used in this context" if $restriction >= OUTPUT_RESTRICT;
+ mac_match $net;
+ } elsif ( $net =~ /^(!?)\+/ ) {
+ require_capability( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '' );
+ join( '', '-m set ', $1 ? '! ' : '', get_set_flags( $net, 'src' ) );
+ } elsif ( $net =~ s/^!// ) {
+ validate_6net $net, 1;
+ "-s ! $net ";
+ } else {
+ validate_6net $net, 1;
+ $net eq ALLIPv6 ? '' : "-s $net ";
+ }
+}
+
+#
+# Match a Source. Currently only handles IP addresses and ranges
+#
+sub match_dest_6net( $ ) {
+ my $net = $_[0];
+
+ if ( $net =~ /^(!?)\+/ ) {
+ require_capability( 'IPSET_MATCH' , 'ipset names in Shorewall configuration files' , '');
+ join( '', '-m set ', $1 ? '! ' : '', get_set_flags( $net, 'dst' ) );
+ } elsif ( $net =~ /^!/ ) {
+ $net =~ s/!//;
+ validate_6net $net, 1;
+ "-d ! $net ";
+ } else {
+ validate_6net $net, 1;
+ $net eq ALLIPv4 ? '' : "-d $net ";
+ }
+}
+
+#
+# Returns the name of the shell variable holding the first address of the passed interface
+#
+sub interface_6address( $ ) {
+ my $variable = chain_base( $_[0] ) . '_6address';
+ uc $variable;
+}
+
+#
+# Record that the ruleset requires the first IP address on the passed interface
+#
+sub get_interface_6address ( $ ) {
+ my ( $interface ) = $_[0];
+
+ my $variable = interface_6address( $interface );
+ my $function = interface6_is_optional( $interface ) ? 'find_first_interface_6address_if_any' : 'find_first_interface_6address';
+
+ $interface6addr{$interface} = "$variable=\$($function $interface)\n";
+
+ "\$$variable";
+}
+
+#
+# Returns the name of the shell variable holding the broadcast addresses of the passed interface
+#
+sub interface_6bcasts( $ ) {
+ my $variable = chain_base( $_[0] ) . '_6bcasts';
+ uc $variable;
+}
+
+#
+# Record that the ruleset requires the broadcast addresses on the passed interface
+#
+sub get_interface_6bcasts ( $ ) {
+ my ( $interface ) = $_[0];
+
+ my $variable = interface_6bcasts( $interface );
+
+ $interface6bcasts{$interface} = qq($variable="\$(get_interface_6bcasts $interface));
+
+ "\$$variable";
+}
+
+#
+# Returns the name of the shell variable holding the gateway through the passed interface
+#
+sub interface_6gateway( $ ) {
+ my $variable = chain_base( $_[0] ) . '_6gateway';
+ uc $variable;
+}
+
+#
+# Record that the ruleset requires the gateway address on the passed interface
+#
+sub get_interface_6gateway ( $ ) {
+ my ( $interface ) = $_[0];
+
+ my $variable = interface_6gateway( $interface );
+
+ if ( interface_is_optional $interface ) {
+ $interface6gateways{$interface} = qq([ -n "\$$variable" ] || $variable=\$(detect_6gateway $interface)\n);
+ } else {
+ $interface6gateways{$interface} = qq([ -n "\$$variable" ] || $variable=\$(detect_6gateway $interface)
+[ -n "\$$variable" ] || fatal_error "Unable to detect the IPv6 gateway through interface $interface"
+);
+ }
+
+ "\$$variable";
+}
+
+#
+# Returns the name of the shell variable holding the addresses of the passed interface
+#
+sub interface_6addresses( $ ) {
+ my $variable = chain_base( $_[0] ) . '_6addresses';
+ uc $variable;
+}
+
+#
+# Record that the ruleset requires the IP addresses on the passed interface
+#
+sub get_interface_6addresses ( $ ) {
+ my ( $interface ) = $_[0];
+
+ my $variable = interface_6addresses( $interface );
+
+ if ( interface_is_optional $interface ) {
+ $interface6addrs{$interface} = qq($variable=\$(find_interface_6addresses $interface)\n);
+ } else {
+ $interface6addrs{$interface} = qq($variable=\$(find_interface_6addresses $interface)
+[ -n "\$$variable" ] || fatal_error "Unable to determine the IP address(es) of $interface"
+);
+ }
+
+ "\$$variable";
+}
+
+#
+# Returns the name of the shell variable holding the networks routed out of the passed interface
+#
+sub interface_6nets( $ ) {
+ my $variable = chain_base( $_[0] ) . '_6networks';
+ uc $variable;
+}
+
+#
+# Record that the ruleset requires the networks routed out of the passed interface
+#
+sub get_interface_6nets ( $ ) {
+ my ( $interface ) = $_[0];
+
+ my $variable = interface_6nets( $interface );
+
+ if ( interface_is_optional $interface ) {
+ $interface6nets{$interface} = qq($variable=\$(get_routed_6networks $interface)\n);
+ } else {
+ $interface6nets{$interface} = qq($variable=\$(get_routed_6networks $interface)
+[ -n "\$$variable" ] || fatal_error "Unable to determine the IPv6 routes through interface \\"$interface\\""
+);
+ }
+
+ "\$$variable";
+
+}
+
+#
+# This function provides a uniform way to generate rules (something the original Shorewall sorely needed).
+#
+# Returns the destination interface specified in the rule, if any.
+#
+sub expand_6rule( $$$$$$$$$ )
+{
+ my ($chainref , # Chain
+ $restriction, # Determines what to do with interface names in the SOURCE or DEST
+ $rule, # Caller's matches that don't depend on the SOURCE, DEST and ORIGINAL DEST
+ $source, # SOURCE
+ $dest, # DEST
+ $target, # Target ('-j' part of the rule)
+ $loglevel , # Log level (and tag)
+ $disposition, # Primative part of the target (RETURN, ACCEPT, ...)
+ $exceptionrule # Caller's matches used in exclusion case
+ ) = @_;
+
+ my ($iiface, $diface, $inets, $dnets, $iexcl, $dexcl );
+ my $chain = $chainref->{name};
+
+ our @ends = ();
+ #
+ # In the generated rules, we sometimes need run-time loops or conditional blocks. This function is used
+ # to define such a loop or block.
+ #
+ # $chainref = Reference to the chain
+ # $command = The shell command that begins the loop or conditional
+ # $end = The shell keyword ('done' or 'fi') that ends the loop or conditional
+ #
+ # All open loops and conditionals are closed just before expand_rule() exits
+ #
+ sub push_6command( $$$ ) {
+ my ( $chainref, $command, $end ) = @_;
+
+ add_command $chainref, $command;
+ incr_cmd_level $chainref;
+ push @ends, $end;
+ }
+ #
+ # Handle Log Level
+ #
+ my $logtag;
+
+ if ( $loglevel ne '' ) {
+ ( $loglevel, $logtag, my $remainder ) = split( /:/, $loglevel, 3 );
+
+ fatal_error "Invalid log tag" if defined $remainder;
+
+ if ( $loglevel =~ /^none!?$/i ) {
+ return if $disposition eq 'LOG';
+ $loglevel = $logtag = '';
+ } else {
+ $loglevel = validate_level( $loglevel );
+ $logtag = '' unless defined $logtag;
+ }
+ } elsif ( $disposition eq 'LOG' ) {
+ fatal_error "LOG requires a level";
+ }
+ #
+ # Mark Target as referenced, if it's a chain
+ #
+ if ( $disposition ) {
+ my $targetref = $chain6_table{$chainref->{table}}{$disposition};
+ $targetref->{referenced} = 1 if $targetref;
+ }
+
+ #
+ # Isolate Source Interface, if any
+ #
+ if ( $source ) {
+ if ( $source eq '-' ) {
+ $source = '';
+ } elsif ( $source =~ /^(.+);(.+)$/ ) {
+ $iiface = $1;
+ $inets = $2;
+ } elsif ( $source =~ /\+|~|\..*\.|:/ ) {
+ $inets = $source;
+ } else {
+ $iiface = $source;
+ }
+ } else {
+ $source = '';
+ }
+
+ #
+ # Verify Interface, if any
+ #
+ if ( $iiface ) {
+ fatal_error "Unknown IPv6 Interface ($iiface)" unless known_6interface $iiface;
+
+ if ( $restriction & POSTROUTE_RESTRICT ) {
+ #
+ # An interface in the SOURCE column of a masq file
+ #
+ fatal_error "Bridge ports may not appear in the SOURCE column of this file" if port_to_bridge( $iiface );
+
+ my $networks = get_interface_6nets ( $iiface );
+
+ push_6command $chainref, join( '', 'for source in ', $networks, '; do' ), 'done';
+
+ $rule .= '-s $source ';
+
+ } else {
+ fatal_error "Source Interface ($iiface) not allowed when the source zone is the firewall zone" if $restriction & OUTPUT_RESTRICT;
+ $rule .= match_source_6dev( $iiface );
+ }
+ }
+
+ #
+ # Isolate Destination Interface, if any
+ #
+ if ( $dest ) {
+ if ( $dest eq '-' ) {
+ $dest = '';
+ } elsif ( $dest =~ /^(.+);(.+)$/ ) {
+ $diface = $1;
+ $dnets = $2;
+ } elsif ( $dest =~ /\+|~|\..*\.|:/ ) {
+ $dnets = $dest;
+ } else {
+ $diface = $dest;
+ }
+ } else {
+ $dest = '';
+ }
+
+ #
+ # Verify Destination Interface, if any
+ #
+ if ( $diface ) {
+ fatal_error "Unknown IPv6 Interface ($diface)" unless known_6interface $diface;
+
+ if ( $restriction & PREROUTE_RESTRICT ) {
+ #
+ # ADDRESS 'detect' in the masq file.
+ #
+ fatal_error "Bridge port ($diface) not allowed" if port_to_bridge( $diface );
+ push_6command( $chainref , 'for dest in ' . get_interface_addresses( $diface) . '; do', 'done' );
+ $rule .= '-d $dest ';
+ } else {
+ fatal_error "Bridge Port ($diface) not allowed in OUTPUT or POSTROUTING rules" if ( $restriction & ( POSTROUTE_RESTRICT + OUTPUT_RESTRICT ) ) && port_to_6bridge( $diface );
+ fatal_error "Destination Interface ($diface) not allowed when the destination zone is the firewall zone" if $restriction & INPUT_RESTRICT;
+
+ if ( $iiface ) {
+ my $bridge = port_to_6bridge( $diface );
+ fatal_error "Source interface ($iiface) is not a port on the same bridge as the destination interface ( $diface )" if $bridge && $bridge ne source_port_to_bridge( $iiface );
+ }
+
+ $rule .= match_dest_6dev( $diface );
+ }
+ } else {
+ $diface = '';
+ }
+
+ #
+ # Determine if there is Source Exclusion
+ #
+ if ( $inets ) {
+ fatal_error "Invalid SOURCE" if $inets =~ /^([^!]+)?,!([^!]+)$/ || $inets =~ /.*!.*!/;
+
+ if ( $inets =~ /^([^!]+)?!([^!]+)$/ ) {
+ $inets = $1;
+ $iexcl = $2;
+ } else {
+ $iexcl = '';
+ }
+
+ unless ( $inets || ( $iiface && $restriction & POSTROUTE_RESTRICT ) ) {
+ my @iexcl = mysplit $iexcl;
+ if ( @iexcl == 1 ) {
+ $rule .= match_source_net "!$iexcl" , $restriction;
+ $iexcl = '';
+ }
+
+ }
+ } else {
+ $iexcl = '';
+ }
+
+ #
+ # Determine if there is Destination Exclusion
+ #
+ if ( $dnets ) {
+ fatal_error "Invalid DEST" if $dnets =~ /^([^!]+)?,!([^!]+)$/ || $dnets =~ /.*!.*!/;
+
+ if ( $dnets =~ /^([^!]+)?!([^!]+)$/ ) {
+ $dnets = $1;
+ $dexcl = $2;
+ } else {
+ $dexcl = '';
+ }
+
+ unless ( $dnets ) {
+ my @dexcl = mysplit $dexcl;
+ if ( @dexcl == 1 ) {
+ $rule .= match_dest_net "!$dexcl";
+ $dexcl = '';
+ }
+ }
+ } else {
+ $dexcl = '';
+ }
+
+ $inets = ALLIPv6 unless $inets;
+ $dnets = ALLIPv6 unless $dnets;
+
+ fatal_error "Input interface may not be specified with a source IP address in the POSTROUTING chain" if $restriction == POSTROUTE_RESTRICT && $iiface && $inets ne ALLIPv6;
+ fatal_error "Output interface may not be specified with a destination IP address in the PREROUTING chain" if $restriction == PREROUTE_RESTRICT && $diface && $dnets ne ALLIPv6;
+
+ if ( $iexcl || $dexcl ) {
+ #
+ # We have non-trivial exclusion -- need to create an exclusion chain
+ #
+ fatal_error "Exclusion is not possible in ACCEPT+/CONTINUE rules" if $disposition eq 'RETURN';
+
+ my $echain = newexclusionchain;
+
+ #
+ # Use the current rule and sent all possible matches to the exclusion chain
+ #
+ for my $inet ( mysplit $inets ) {
+ for my $dnet ( mysplit $dnets ) {
+ #
+ # We evaluate the source net match in the inner loop to accomodate systems without $capabilities{KLUDGEFREE}
+ #
+ add_rule( $chainref, join( '', $rule, match_source_6net( $inet, $restriction ), match_dest_6net( $dnet ), "-j $echain" ), 1 );
+ }
+ }
+ #
+ # Create the Exclusion Chain
+ #
+ my $echainref = new_6chain $chainref->{table}, $echain;
+
+ #
+ # Generate RETURNs for each exclusion
+ #
+ add_rule $echainref, ( match_source_net $_ , $restriction ) . '-j RETURN' for ( mysplit $iexcl );
+ add_rule $echainref, ( match_dest_net $_ ) . '-j RETURN' for ( mysplit $dexcl );
+ #
+ # Log rule
+ #
+ log_rule_limit $loglevel , $echainref , $chain, $disposition , '', $logtag , 'add' , '' if $loglevel;
+ #
+ # Generate Final Rule
+ #
+ add_rule( $echainref, $exceptionrule . $target, 1 ) unless $disposition eq 'LOG';
+ } else {
+ #
+ # No exclusions
+ #
+ for my $inet ( mysplit $inets ) {
+ #
+ # We defer evaluating the source net match to accomodate system without $capabilities{KLUDGEFREE}
+ #
+ for my $dnet ( mysplit $dnets ) {
+ if ( $loglevel ne '' ) {
+ log_rule_limit(
+ $loglevel ,
+ $chainref ,
+ $chain,
+ $disposition ,
+ '' ,
+ $logtag ,
+ 'add' ,
+ join( '', $rule, match_source_net( $inet , $restriction ) , match_dest_net( $dnet ) ) );
+ }
+
+ unless ( $disposition eq 'LOG' ) {
+ add_rule(
+ $chainref,
+ join( '', $rule, match_source_net ($inet , $restriction ), match_dest_net( $dnet ), $target ) ,
+ 1 );
+ }
+ }
+ }
+ }
+
+ while ( @ends ) {
+ decr_cmd_level $chainref;
+ add_command $chainref, pop @ends;
+ }
+
+ $diface;
+}
+
+#
+# Generate setting of global variables
+#
+sub set_global_6variables() {
+
+ our ( $emitted_comment, $emitted_test ) = (0, 0);
+
+ for ( values %interface6addr ) {
+ emit_comment unless $emitted_comment;
+ emit $_;
+ }
+
+ for ( values %interface6gateways ) {
+ emit_comment unless $emitted_comment;
+ emit $_;
+ }
+
+ for ( values %interface6addrs ) {
+ emit_comment unless $emitted_comment;
+ emit_test unless $emitted_test;
+ emit $_;
+ }
+
+ for ( values %interface6nets ) {
+ emit_comment unless $emitted_comment;
+ emit_test unless $emitted_test;
+ emit $_;
+ }
+
+ pop_indent, emit "fi\n" if $emitted_test;
+
+}
+
+#
+# Generate the netfilter input
+#
+sub create_netfilter_6_load() {
+
+ my @table_list;
+
+ push @table_list, 'raw' if $capabilities{RAW_TABLE};
+ push @table_list, 'mangle' if $capabilities{MANGLE_ENABLED} && $config{MANGLE_ENABLED};
+ push @table_list, 'filter';
+
+ $mode = NULL_MODE;
+
+ emit ( 'setup_netfilter_6()',
+ '{'
+ );
+
+ push_indent;
+
+ save_progress_message "Preparing ip6tables-restore input...";
+
+ emit '';
+
+ emit 'exec 3>${VARDIR}/.ip6tables-restore-input';
+
+ enter_cat_mode;
+
+ for my $table ( @table_list ) {
+ emit_unindented "*$table";
+
+ my @chains;
+ #
+ # iptables-restore seems to be quite picky about the order of the builtin chains
+ #
+ for my $chain ( @builtins ) {
+ my $chainref = $chain6_table{$table}{$chain};
+ if ( $chainref ) {
+ fatal_error "Internal error in create_netfilter_6_load()" if $chainref->{cmdlevel};
+ emit_unindented ":$chain $chainref->{policy} [0:0]";
+ push @chains, $chainref;
+ }
+ }
+ #
+ # First create the chains in the current table
+ #
+ for my $chain ( grep $chain6_table{$table}{$_}->{referenced} , ( sort keys %{$chain6_table{$table}} ) ) {
+ my $chainref = $chain6_table{$table}{$chain};
+ unless ( $chainref->{builtin} ) {
+ fatal_error "Internal error in create_netfilter_load()" if $chainref->{cmdlevel};
+ emit_unindented ":$chainref->{name} - [0:0]";
+ push @chains, $chainref;
+ }
+ }
+ #
+ # Then emit the rules
+ #
+ for my $chainref ( @chains ) {
+ emitr $_ for ( @{$chainref->{rules}} );
+ }
+ #
+ # Commit the changes to the table
+ #
+ enter_cat_mode unless $mode == CAT_MODE;
+ emit_unindented 'COMMIT';
+ }
+
+ enter_cmd_mode;
+ #
+ # Now generate the actual ip6tables-restore command
+ #
+ emit( 'exec 3>&-',
+ '',
+ '[ -n "$DEBUG" ] && command=debug_restore_input || command=$IP6TABLES_RESTORE',
+ '',
+ 'progress_message2 "Running $command..."',
+ '',
+ 'cat ${VARDIR}/.ip6tables-restore-input | $command # Use this nonsensical form to appease SELinux',
+ 'if [ $? != 0 ]; then',
+ ' fatal_error "iptables-restore Failed. Input is in ${VARDIR}/.ip6tables-restore-input"',
+ "fi\n"
+ );
+
+ pop_indent;
+
+ emit "}\n";
+}
+
+#
+# Generate the netfilter input for refreshing a list of chains
+#
+sub create_6chainlist_reload($) {
+
+ my $chains = $_[0];
+
+ my @chains = split_list $chains, 'chain';
+
+ unless ( @chains ) {
+ @chains = qw( blacklst ) if $filter6_table->{blacklst};
+ push @chains, 'mangle:' if $capabilities{MANGLE_ENABLED} && $config{MANGLE_ENABLED};
+ $chains = join( ',', @chains ) if @chains;
+ }
+
+ $mode = NULL_MODE;
+
+ emit( 'chainlist_6_reload()',
+ '{'
+ );
+
+ push_indent;
+
+ if ( @chains ) {
+ if ( @chains == 1 ) {
+ progress_message2 "Compiling ip6tables-restore input for chain @chains...";
+ save_progress_message "Preparing ip6tables-restore input for chain @chains...";
+ } else {
+ progress_message2 "Compiling ip6tables-restore input for chains $chains...";
+ save_progress_message "Preparing ip6tables-restore input for chains $chains...";
+ }
+
+ emit '';
+
+ my $table = 'filter';
+
+ my %chains;
+
+ for my $chain ( @chains ) {
+ ( $table , $chain ) = split ':', $chain if $chain =~ /:/;
+
+ fatal_error "Invalid table ( $table )" unless $table =~ /^(mangle|filter)$/;
+
+ $chains{$table} = [] unless $chains{$table};
+
+ if ( $chain ) {
+ fatal_error "No $table chain found with name $chain" unless $chain6_table{$table}{$chain};
+ fatal_error "Built-in chains may not be refreshed" if $chain6_table{table}{$chain}{builtin};
+ push @{$chains{$table}}, $chain;
+ } else {
+ while ( my ( $chain, $chainref ) = each %{$chain6_table{$table}} ) {
+ push @{$chains{$table}}, $chain if $chainref->{referenced} && ! $chainref->{builtin};
+ }
+ }
+ }
+
+ emit 'exec 3>${VARDIR}/.ip6tables-restore-input';
+
+ enter_cat_mode;
+
+ for $table qw(mangle filter) {
+ next unless $chains{$table};
+
+ emit_unindented "*$table";
+
+ my $tableref=$chain6_table{$table};
+
+ @chains = sort @{$chains{$table}};
+
+ for my $chain ( @chains ) {
+ my $chainref = $tableref->{$chain};
+ emit_unindented ":$chainref->{name} - [0:0]";
+ }
+
+ for my $chain ( @chains ) {
+ my $chainref = $tableref->{$chain};
+ my @rules = @{$chainref->{rules}};
+
+ @rules = () unless @rules;
+ #
+ # Emit the chain rules
+ #
+ emitr $_ for ( @rules );
+ }
+ #
+ # Commit the changes to the table
+ #
+ enter_cat_mode unless $mode == CAT_MODE;
+
+ emit_unindented 'COMMIT';
+ }
+
+ enter_cmd_mode;
+
+ #
+ # Now generate the actual iptables-restore command
+ #
+ emit( 'exec 3>&-',
+ '',
+ 'progress_message2 "Running ip6tables-restore..."',
+ '',
+ 'cat ${VARDIR}/.ip6tables-restore-input | $IP6TABLES_RESTORE -n # Use this nonsensical form to appease SELinux',
+ 'if [ $? != 0 ]; then',
+ ' fatal_error "ip6tables-restore Failed. Input is in ${VARDIR}/.ip6tables-restore-input"',
+ "fi\n"
+ );
+ } else {
+ emit('true');
+ }
+
+ pop_indent;
+
+ emit "}\n";
+}
+
1;
diff --git a/Shorewall-perl/Shorewall/Compiler.pm b/Shorewall-perl/Shorewall/Compiler.pm
index d4e6f7549..6e20446f8 100644
--- a/Shorewall-perl/Shorewall/Compiler.pm
+++ b/Shorewall-perl/Shorewall/Compiler.pm
@@ -41,7 +41,7 @@ use Shorewall::Proxyarp;
our @ISA = qw(Exporter);
our @EXPORT = qw( compiler EXPORT TIMESTAMP DEBUG );
our @EXPORT_OK = qw( $export );
-our $VERSION = 4.1.4;
+our $VERSION = 4.3.0;
our $export;
@@ -168,6 +168,13 @@ sub generate_script_1() {
emit( 'IPTABLES_RESTORE=${IPTABLES}-restore',
'[ -x "$IPTABLES_RESTORE" ] || startup_error "$IPTABLES_RESTORE does not exist or is not executable"' );
+ if ( $config{IPV6} eq 'On' ) {
+ emit( 'IP6TABLES=$(dirname ${IPTABLES})/ip6tables',
+ '[ -x "$IPTABLES_RESTORE" ] || startup_error "$IP6TABLES_RESTORE does not exist or is not executable"' );
+ emit( 'IP6TABLES_RESTORE=$(dirname ${IPTABLES})/ip6tables-restore',
+ '[ -x "$IP6TABLES_RESTORE" ] || startup_error "$IP6TABLES_RESTORE does not exist or is not executable"' );
+ }
+
append_file 'params' if $config{EXPORTPARAMS};
emit ( '',
diff --git a/Shorewall-perl/Shorewall/Config.pm b/Shorewall-perl/Shorewall/Config.pm
index be3b8cfe1..ade1bc212 100644
--- a/Shorewall-perl/Shorewall/Config.pm
+++ b/Shorewall-perl/Shorewall/Config.pm
@@ -114,7 +114,7 @@ our %EXPORT_TAGS = ( internal => [ qw( create_temp_object
Exporter::export_ok_tags('internal');
-our $VERSION = 4.2.0;
+our $VERSION = 4.3.0;
#
# describe the current command, it's present progressive, and it's completion.
@@ -163,7 +163,7 @@ our %config;
#
# Config options and global settings that are to be copied to object script
#
-our @propagateconfig = qw/ DISABLE_IPV6 MODULESDIR MODULE_SUFFIX LOGFORMAT SUBSYSLOCK LOCKFILE /;
+our @propagateconfig = qw/ IPV6 MODULESDIR MODULE_SUFFIX LOGFORMAT SUBSYSLOCK LOCKFILE /;
our @propagateenv = qw/ LOGLIMIT LOGTAGONLY LOGRULENUMBERS /;
#
# From parsing the capabilities file
@@ -177,6 +177,8 @@ our %capdesc = ( NAT_ENABLED => 'NAT',
MULTIPORT => 'Multi-port Match' ,
XMULTIPORT => 'Extended Multi-port Match',
CONNTRACK_MATCH => 'Connection Tracking Match',
+ OLD_CONNTRACK_MATCH =>
+ 'Old conntrack match syntax',
NEW_CONNTRACK_MATCH =>
'Extended Connection Tracking Match',
USEPKTTYPE => 'Packet Type Match',
@@ -271,7 +273,7 @@ sub initialize() {
LOGPARMS => '',
TC_SCRIPT => '',
VERSION => "4.2.1",
- CAPVERSION => 40200 ,
+ CAPVERSION => 40202 ,
);
#
# From shorewall.conf file
@@ -348,6 +350,7 @@ sub initialize() {
DELAYBLACKLISTLOAD => undef,
MODULE_SUFFIX => undef,
DISABLE_IPV6 => undef,
+ IPV6 => undef,
DYNAMIC_ZONES => undef,
PKTTYPE=> undef,
RFC1918_STRICT => undef,
@@ -389,6 +392,7 @@ sub initialize() {
XMULTIPORT => undef,
CONNTRACK_MATCH => undef,
NEW_CONNTRACK_MATCH => undef,
+ OLD_CONNTRACK_MATCH => undef,
USEPKTTYPE => undef,
POLICY_MATCH => undef,
PHYSDEV_MATCH => undef,
@@ -765,12 +769,14 @@ sub copy( $ ) {
open IF , $file or fatal_error "Unable to open $file: $!";
while ( ) {
+ chomp;
if ( /^\s*$/ ) {
print $object "\n" unless $lastlineblank;
$lastlineblank = 1;
} else {
s/^/$indent/ if $indent;
print $object $_;
+ print $object "\n";
$lastlineblank = 0;
}
}
@@ -791,6 +797,7 @@ sub copy1( $ ) {
my $do_indent = 1;
while ( ) {
+ chomp;
if ( /^\s*$/ ) {
print $object "\n";
$do_indent = 1;
@@ -799,6 +806,7 @@ sub copy1( $ ) {
s/^/$indent/ if $indent && $do_indent;
print $object $_;
+ print $object "\n";
$do_indent = ! ( /\\$/ );
}
@@ -1557,8 +1565,9 @@ sub determine_capabilities( $ ) {
$capabilities{CONNTRACK_MATCH} = qt1( "$iptables -A $sillyname -m conntrack --ctorigdst 192.168.1.1 -j ACCEPT" );
- if ( $capabilities{CONNTRACL_MATCH} ) {
+ if ( $capabilities{CONNTRACK_MATCH} ) {
$capabilities{NEW_CONNTRACK_MATCH} = qt1( "$iptables -A $sillyname -m conntrack -p tcp --ctorigdstport 22 -j ACCEPT" );
+ $capabilities{OLD_CONNTRACK_MATCH} = ! qt1( "$iptables -A $sillyname -m conntrack ! --ctorigdstport 1.2.3.4" );
}
if ( qt1( "$iptables -A $sillyname -p tcp -m multiport --dports 21,22 -j ACCEPT" ) ) {
@@ -1892,7 +1901,22 @@ sub get_configuration( $ ) {
default_yes_no 'ADMINISABSENTMINDED' , '';
default_yes_no 'BLACKLISTNEWONLY' , '';
- default_yes_no 'DISABLE_IPV6' , '';
+
+ if ( defined $config{IPV6} ) {
+ if ( $config{IPV6} =~ /on/i ) {
+ $config{IPV6} = 'On';
+ } elsif ( $config{IPV6} =~ /off/i ) {
+ $config{IPV6} = 'Off';
+ } elsif ( $config{IPV6} =~ /keep/i ) {
+ $config{IPV6} = '';
+ }
+ }
+
+ default_yes_no 'DISABLE_IPV6' , '';
+
+ fatal_error "Incompatible settings of IPV6 (On) and DISABLE_IPV6 (Yes)" if $config{IPV6} eq 'On' && $config{DISABLE_IPV6} eq 'Yes';
+
+ $config{IPV6} = $config{DISABLE_IPV6} ? 'Off' : '' unless defined $config{IPV6};
unsupported_yes_no 'DYNAMIC_ZONES';
unsupported_yes_no 'BRIDGING';
diff --git a/Shorewall-perl/Shorewall/IPAddrs.pm b/Shorewall-perl/Shorewall/IPAddrs.pm
index 5e72637a6..292777203 100644
--- a/Shorewall-perl/Shorewall/IPAddrs.pm
+++ b/Shorewall-perl/Shorewall/IPAddrs.pm
@@ -26,25 +26,36 @@
#
package Shorewall::IPAddrs;
require Exporter;
+use Socket6;
use Shorewall::Config qw( :DEFAULT split_list require_capability in_hex8);
use strict;
our @ISA = qw(Exporter);
our @EXPORT = qw( ALLIPv4
+ ALLIPv6
TCP
UDP
ICMP
+ IPv6_ICMP
SCTP
+ F_INET
+ F_INET6
+
validate_address
+ validate_6address
validate_net
+ validate_6net
decompose_net
validate_host
+ validate_6host
validate_range
+ validate_6range
ip_range_explicit
expand_port_range
allipv4
+ allipv6
rfc1918_networks
resolve_proto
proto_name
@@ -54,14 +65,23 @@ our @EXPORT = qw( ALLIPv4
validate_icmp
);
our @EXPORT_OK = qw( );
-our $VERSION = 4.1.5;
+our $VERSION = 4.3.0;
#
-# Some IPv4 useful stuff
+# Some IPv4/6 useful stuff
#
our @allipv4 = ( '0.0.0.0/0' );
+our @allipv6 = ( '::/0' );
-use constant { ALLIPv4 => '0.0.0.0/0' , ICMP => 1, TCP => 6, UDP => 17 , SCTP => 132 };
+use constant { ALLIPv4 => '0.0.0.0/0' ,
+ ALLIPv6 => '::/0' ,
+ F_INET => 1,
+ F_INET6 => 2,
+ ICMP => 1,
+ TCP => 6,
+ UDP => 17,
+ ICMPv6_ICMP => 58,
+ SCTP => 132 };
our @rfc1918_networks = ( "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16" );
@@ -95,8 +115,7 @@ sub validate_address( $$ ) {
if ( defined wantarray ) {
shift @addrs for (1..4);
for ( @addrs ) {
- my (@a) = unpack('C4',$_);
- $_ = join('.', @a );
+ $_ = inet_htoa $_;
}
}
}
@@ -223,6 +242,10 @@ sub allipv4() {
@allipv4;
}
+sub allipv6() {
+ @allipv6;
+}
+
sub rfc1918_networks() {
@rfc1918_networks
}
@@ -231,7 +254,7 @@ sub rfc1918_networks() {
# Protocol/port validation
#
-our %nametoproto = ( all => 0, ALL => 0, icmp => 1, ICMP => 1, tcp => 6, TCP => 6, udp => 17, UDP => 17 );
+our %nametoproto = ( all => 0, ALL => 0, icmp => 1, ICMP => 1, tcp => 6, TCP => 6, udp => 17, UDP => 17 );
our @prototoname = ( 'all', 'icmp', '', '', '', '', 'tcp', '', '', '', '', '', '', '', '', '', '', 'udp' );
#
@@ -419,4 +442,121 @@ sub expand_port_range( $$ ) {
}
}
+sub valid_6address( $ ) {
+ my $address = $_[0];
+
+ my @address = split /:/, $address;
+
+ return 0 if @address > 8;
+ return 0 if @address < 8 && ! $address =~ /::/;
+ return 0 if $address =~ /:::/ || $address =~ /::.*::/;
+
+ if ( $address =~ /^:/ ) {
+ unless ( $address eq '::' ) {
+ return 0 if $address =~ /:$/ || $address =~ /^:.*::/;
+ }
+ } elsif ( $address =~ /:$/ ) {
+ return 0 if $address =~ /::.*:$/;
+ }
+
+ for my $a ( @address ) {
+ return 0 unless $a eq '' || ( $a =~ /^[a-fA-f\d]+$/ && oct "0x$a" < 65536 );
+ }
+
+ 1;
+}
+
+sub validate_6address( $$ ) {
+ my ( $addr, $allow_name ) = @_;
+
+ my @addrs = ( $addr );
+
+ unless ( valid_6address $addr ) {
+ fatal_error "Invalid IPv6 Address ($addr)" unless $allow_name;
+ fatal_error "Unknown Host ($addr)" unless (@addrs = gethostbyname2 $addr, AF_INET6);
+
+ if ( defined wantarray ) {
+ shift @addrs for (1..4);
+ for ( @addrs ) {
+ $_ = inet_ntop AF_INET6, $_;
+ }
+ }
+ }
+
+ defined wantarray ? wantarray ? @addrs : $addrs[0] : undef;
+}
+}
+
+sub validate_6net( $ ) {
+ my ($net, $vlsm, $rest) = split( '/', $_[0], 3 );
+
+ fatal_error "An ipset name ($net) is not allowed in this context" if substr( $net, 0, 1 ) eq '+';
+
+ if ( defined $vlsm ) {
+ fatal_error "Invalid VLSM ($vlsm)" unless $vlsm =~ /^\d+$/ && $vlsm <= 64;
+ fatal_error "Invalid Network address ($_[0])" if defined $rest;
+ fatal_error "Invalid IPv6 address ($net)" unless valid_6address $net;
+ } else {
+ fatal_error "Invalid Network address ($_[0])" if $_[0] =~ '/' || ! defined $net;
+ validate_6address $net;
+ }
+}
+
+sub validate_6range( $$ ) {
+ my ( $low, $high ) = @_;
+
+ validate_6address $low, 0;
+ validate_6address $high, 0;
+
+ my @low = split ":", $low;
+ my @high = split ":", $high;
+
+ if ( @low == @high ) {
+ my ( $l, $h) = ( pop @low, pop @high );
+
+ return 1 if hex "0x$l" <= hex "0x$h" && join( ":", @low ) eq join( ":", @high );
+ }
+
+ fatal_error "Invalid IPv6 Range ($low-$high)";
+}
+
+my %ipv6_icmp_types = ( any => 'any',
+ 'destination-unreachable' => 1,
+ 'no-route' => '1/0',
+ 'communication-prohibited' => '1/1',
+ 'address-unreachable' => '1/2',
+ 'port-unreachable' => '1/3',
+ 'packet-too-big' => 2,
+ 'time-exceeded' => 3,
+ 'ttl-exceeded' => 3,
+ 'ttl-zero-during-transit' => '3/0',
+ 'ttl-zero-during-reassembly' => '3/1',
+ 'parameter-problem' => 4
+ 'bad-header' => '4/0',
+ 'unknown-header-type' => '4/1',
+ 'unknown-option' => '4/2',
+ 'echo-request' => 128,
+ 'echo-reply' => 129,
+ 'router-solicitation' => 133,
+ 'router-advertisement' => 134,
+ 'neighbour-solicitation' => 135,
+ 'neighbour-advertisement' => 136,
+ redirect => 137 );
+
+
+sub validate_icmp6( $ ) {
+ my $type = $_[0];
+
+ my $value = $ipv6_icmp_types{$type};
+
+ return $value if defined $value;
+
+ if ( $type =~ /^(\d+)(\/(\d+))?$/ ) {
+ return $type if $1 < 256 && ( ! $2 || $3 < 256 );
+ }
+
+ fatal_error "Invalid IPv6 ICMP Type ($type)"
+}
+
+
1;
diff --git a/Shorewall-perl/Shorewall/Policy.pm b/Shorewall-perl/Shorewall/Policy.pm
index 2d9d3c9c3..4ee7ebcb3 100644
--- a/Shorewall-perl/Shorewall/Policy.pm
+++ b/Shorewall-perl/Shorewall/Policy.pm
@@ -26,20 +26,33 @@ package Shorewall::Policy;
require Exporter;
use Shorewall::Config qw(:DEFAULT :internal);
use Shorewall::Zones;
+use Shorewall::IPAddrs;
use Shorewall::Chains qw( :DEFAULT :internal) ;
use Shorewall::Actions;
use strict;
our @ISA = qw(Exporter);
-our @EXPORT = qw( validate_policy apply_policy_rules complete_standard_chain sub setup_syn_flood_chains );
+our @EXPORT = qw( validate_policy
+ validate_6policy
+ apply_policy_rules
+ apply_6policy_rules
+ complete_standard_chain
+ complete_standard_6chain
+ setup_syn_flood_chains
+ setup_syn_flood_6chains );
+
our @EXPORT_OK = qw( );
-our $VERSION = 4.1.1;
+our $VERSION = 4.3.0;
# @policy_chains is a list of references to policy chains in the filter table
our @policy_chains;
+# @policy_6chains is a list of references to policy chains in the filter6 table
+
+our @policy_6chains;
+
#
# Initialize globals -- we take this novel approach to globals initialization to allow
# the compiler to run multiple times in the same process. The
@@ -51,6 +64,7 @@ our @policy_chains;
sub initialize() {
@policy_chains = ();
+ @policy_6chains = ();
}
INIT {
@@ -85,6 +99,20 @@ sub new_policy_chain($$$$)
$chainref;
}
+#
+# Create a new policy 6chain and return a reference to it.
+#
+sub new_policy_6chain($$$$)
+{
+ my ($source, $dest, $policy, $optional) = @_;
+
+ my $chainref = new_6chain( 'filter', "${source}2${dest}" );
+
+ convert_to_policy_chain( $chainref, $source, $dest, $policy, $optional );
+
+ $chainref;
+}
+
#
# Set the passed chain's policychain and policy to the passed values.
#
@@ -142,6 +170,21 @@ sub add_or_modify_policy_chain( $$ ) {
}
}
+sub add_or_modify_policy_6chain( $$ ) {
+ my ( $zone, $zone1 ) = @_;
+ my $chain = "${zone}2${zone1}";
+ my $chainref = $filter6_table->{$chain};
+
+ if ( $chainref ) {
+ unless( $chainref->{is_policy} ) {
+ convert_to_policy_chain( $chainref, $zone, $zone1, 'CONTINUE', OPTIONAL );
+ push @policy_6chains, $chainref;
+ }
+ } else {
+ push @policy_6chains, ( new_policy_chain $zone, $zone1, 'CONTINUE', OPTIONAL );
+ }
+}
+
sub print_policy($$$$) {
my ( $source, $dest, $policy , $chain ) = @_;
unless ( ( $source eq 'all' ) || ( $dest eq 'all' ) ) {
@@ -220,11 +263,17 @@ sub validate_policy()
my $clientwild = ( "\L$client" eq 'all' );
- fatal_error "Undefined zone ($client)" unless $clientwild || defined_zone( $client );
+ unless ( $clientwild ) {
+ fatal_error "Undefined zone ($client)" unless defined_zone( $client );
+ fatal_error "IPv6 zone ($client) not permitted in policy file" unless zone_family($client) & F_INET;
+ }
my $serverwild = ( "\L$server" eq 'all' );
- fatal_error "Undefined zone ($server)" unless $serverwild || defined_zone( $server );
+ unless ( $serverwild ) {
+ fatal_error "Undefined zone ($server)" unless defined_zone( $server );
+ fatal_error "IPv6 zone ($server) not permitted in policy file" unless zone_family($server) & F_INET;
+ }
my ( $policy, $default, $remainder ) = split( /:/, $originalpolicy, 3 );
@@ -344,6 +393,205 @@ sub validate_policy()
}
}
+
+sub validate_6policy()
+{
+ my %validpolicies = (
+ ACCEPT => undef,
+ REJECT => undef,
+ DROP => undef,
+ CONTINUE => undef,
+ QUEUE => undef,
+ NFQUEUE => undef,
+ NONE => undef
+ );
+
+ my %map = ( DROP_DEFAULT => 'DROP' ,
+ REJECT_DEFAULT => 'REJECT' ,
+ ACCEPT_DEFAULT => 'ACCEPT' ,
+ QUEUE_DEFAULT => 'QUEUE' ,
+ NFQUEUE_DEFAULT => 'NFQUEUE' );
+
+ my $zone;
+ my @zonelist = $config{EXPAND_POLICIES} ? all_6zones : ( all_6zones, 'all' );
+
+ for my $option qw/DROP_DEFAULT REJECT_DEFAULT ACCEPT_DEFAULT QUEUE_DEFAULT NFQUEUE_DEFAULT/ {
+ my $action = $config{$option};
+ next if $action eq 'none';
+ my $actiontype = $targets{$action};
+
+ if ( defined $actiontype ) {
+ fatal_error "Invalid setting ($action) for $option" unless $actiontype & ACTION;
+ } else {
+ fatal_error "Default Action $option=$action not found";
+ }
+
+ unless ( $usedactions{$action} ) {
+ $usedactions{$action} = 1;
+ createactionchain $action;
+ }
+
+ $default_actions{$map{$option}} = $action;
+ }
+
+ for $zone ( all_6zones ) {
+ push @policy_6chains, ( new_policy_6chain $zone, $zone, 'ACCEPT', OPTIONAL );
+
+ if ( $config{IMPLICIT_CONTINUE} && ( @{find_zone( $zone )->{parents}} ) ) {
+ for my $zone1 ( all_6zones ) {
+ unless( $zone eq $zone1 ) {
+ add_or_modify_policy_chain( $zone, $zone1 );
+ add_or_modify_policy_chain( $zone1, $zone );
+ }
+ }
+ }
+ }
+
+ my $fn = open_file '6policy';
+
+ first_entry "$doing $fn...";
+
+ while ( read_a_line ) {
+
+ my ( $client, $server, $originalpolicy, $loglevel, $synparams, $connlimit ) = split_line 3, 6, '6policy file';
+
+ $loglevel = '' if $loglevel eq '-';
+ $synparams = '' if $synparams eq '-';
+ $connlimit = '' if $connlimit eq '-';
+
+ my $clientwild = ( "\L$client" eq 'all' );
+
+ unless ( $clientwild ) {
+ fatal_error "Undefined zone ($client)" unless defined_zone( $client );
+ fatal_error "IPv4 zone ($client) not permitted in policy file" unless zone_family($client) & F_INET6;
+ }
+
+ my $serverwild = ( "\L$server" eq 'all' );
+
+ unless ( $serverwild ) {
+ fatal_error "Undefined zone ($server)" unless defined_zone( $server );
+ fatal_error "IPv4 zone ($server) not permitted in policy file" unless zone_family($server) & F_INET6;
+ }
+
+ my ( $policy, $default, $remainder ) = split( /:/, $originalpolicy, 3 );
+
+ fatal_error "Invalid or missing POLICY ($originalpolicy)" unless $policy;
+
+ fatal_error "Invalid default action ($default:$remainder)" if defined $remainder;
+
+ ( $policy , my $queue ) = get_target_param $policy;
+
+ if ( $default ) {
+ if ( "\L$default" eq 'none' ) {
+ $default = 'none';
+ } else {
+ my $defaulttype = $targets6{$default} || 0;
+
+ if ( $defaulttype & ACTION ) {
+ unless ( $usedactions{$default} ) {
+ $usedactions{$default} = 1;
+ createactionchain $default;
+ }
+ } else {
+ fatal_error "Unknown Default Action ($default)";
+ }
+ }
+ } else {
+ $default = $default_actions{$policy} || '';
+ }
+
+ fatal_error "Invalid policy ($policy)" unless exists $validpolicies{$policy};
+
+ if ( defined $queue ) {
+ fatal_error "Invalid policy ($policy($queue))" unless $policy eq 'NFQUEUE';
+ require_capability( 'NFQUEUE_TARGET', 'An NFQUEUE Policy', 's' );
+ my $queuenum = numeric_value( $queue );
+ fatal_error "Invalid NFQUEUE queue number ($queue)" unless defined( $queuenum) && $queuenum <= 65535;
+ $policy = "NFQUEUE --queue-num $queuenum";
+ } elsif ( $policy eq 'NONE' ) {
+ fatal_error "NONE policy not allowed with \"all\""
+ if $clientwild || $serverwild;
+ fatal_error "NONE policy not allowed to/from firewall zone"
+ if ( zone_type( $client ) eq 'firewall' ) || ( zone_type( $server ) eq 'firewall' );
+ }
+
+ unless ( $clientwild || $serverwild ) {
+ if ( zone_type( $server ) eq 'bport6' ) {
+ fatal_error "Invalid policy - DEST zone is a Bridge Port zone but the SOURCE zone is not associated with the same bridge"
+ unless find_zone( $client )->{bridge} eq find_zone( $server)->{bridge} || single_interface( $client ) eq find_zone( $server )->{bridge};
+ }
+ }
+
+ my $chain = "${client}2${server}";
+ my $chainref;
+
+ if ( defined $filter_table->{$chain} ) {
+ $chainref = $filter_table->{$chain};
+
+ if ( $chainref->{is_policy} ) {
+ if ( $chainref->{is_optional} ) {
+ $chainref->{is_optional} = 0;
+ $chainref->{policy} = $policy;
+ } else {
+ fatal_error qq(Policy "$client $server $policy" duplicates earlier policy "@{$chainref->{policypair}} $chainref->{policy}");
+ }
+ } elsif ( $chainref->{policy} ) {
+ fatal_error qq(Policy "$client $server $policy" duplicates earlier policy "@{$chainref->{policypair}} $chainref->{policy}");
+ } else {
+ convert_to_policy_chain( $chainref, $client, $server, $policy, 0 );
+ push @policy_6chains, ( $chainref ) unless $config{EXPAND_POLICIES} && ( $clientwild || $serverwild );
+ }
+ } else {
+ $chainref = new_policy_6chain $client, $server, $policy, 0;
+ push @policy_6chains, ( $chainref ) unless $config{EXPAND_POLICIES} && ( $clientwild || $serverwild );
+ }
+
+ $chainref->{loglevel} = validate_level( $loglevel ) if defined $loglevel && $loglevel ne '';
+
+ if ( $synparams ne '' || $connlimit ne '' ) {
+ my $value = '';
+ fatal_error "Invalid CONNLIMIT ($connlimit)" if $connlimit =~ /^!/;
+ $value = do_ratelimit $synparams, 'ACCEPT' if $synparams ne '';
+ $value .= do_connlimit $connlimit if $connlimit ne '';
+ $chainref->{synparams} = $value;
+ $chainref->{synchain} = $chain
+ }
+
+ $chainref->{default} = $default if $default;
+
+ if ( $clientwild ) {
+ if ( $serverwild ) {
+ for my $zone ( @zonelist ) {
+ for my $zone1 ( @zonelist ) {
+ set_policy_chain $client, $server, "${zone}2${zone1}", $chainref, $policy;
+ print_policy $zone, $zone1, $policy, $chain;
+ }
+ }
+ } else {
+ for my $zone ( all_zones ) {
+ set_policy_chain $client, $server, "${zone}2${server}", $chainref, $policy;
+ print_policy $zone, $server, $policy, $chain;
+ }
+ }
+ } elsif ( $serverwild ) {
+ for my $zone ( @zonelist ) {
+ set_policy_chain $client, $server, "${client}2${zone}", $chainref, $policy;
+ print_policy $client, $zone, $policy, $chain;
+ }
+
+ } else {
+ print_policy $client, $server, $policy, $chain;
+ }
+ }
+
+ for $zone ( all_zones ) {
+ for my $zone1 ( all_zones ) {
+ fatal_error "No policy defined from zone $zone to zone $zone1" unless $filter_table->{"${zone}2${zone1}"}{policy};
+ }
+ }
+}
+
+
#
# Policy Rule application
#
@@ -361,6 +609,19 @@ sub policy_rules( $$$$$ ) {
}
}
+sub policy_6rules( $$$$$ ) {
+ my ( $chainref , $target, $loglevel, $default, $dropmulticast ) = @_;
+
+ unless ( $target eq 'NONE' ) {
+ add_rule $chainref, "-j $default" if $default && $default ne 'none';
+ log_rule $loglevel , $chainref , $target , '' if $loglevel ne '';
+ fatal_error "Null target in policy_rules()" unless $target;
+ $target = 'reject' if $target eq 'REJECT';
+
+ add_jump( $chainref , $target ) unless $target eq 'CONTINUE';
+ }
+}
+
sub report_syn_flood_protection() {
progress_message ' Enabled SYN flood protection';
}
@@ -400,8 +661,43 @@ sub default_policy( $$$ ) {
}
+sub default_6policy( $$$ ) {
+ my $chainref = $_[0];
+ my $policyref = $filter6_table->{$chainref->{policychain}};
+ my $synparams = $policyref->{synparams};
+ my $default = $policyref->{default};
+ my $policy = $policyref->{policy};
+ my $loglevel = $policyref->{loglevel};
+
+ fatal_error "Internal error in default_6policy()" unless $policyref;
+
+ if ( $chainref eq $policyref ) {
+ policy_6rules $chainref , $policy, $loglevel , $default, $config{MULTICAST};
+ } else {
+ if ( $policy eq 'ACCEPT' || $policy eq 'QUEUE' || $policy =~ /^NFQUEUE/ ) {
+ if ( $synparams ) {
+ report_syn_flood_protection;
+ policy_6rules $chainref , $policy , $loglevel , $default, $config{MULTICAST};
+ } else {
+ add_jump $chainref, $policyref;
+ $chainref = $policyref;
+ }
+ } elsif ( $policy eq 'CONTINUE' ) {
+ report_syn_flood_protection if $synparams;
+ policy_6rules $chainref , $policy , $loglevel , $default, $config{MULTICAST};
+ } else {
+ report_syn_flood_protection if $synparams;
+ add_jump $chainref , $policyref;
+ $chainref = $policyref;
+ }
+ }
+
+ progress_message " Policy $policy from $_[1] to $_[2] using chain $chainref->{name}";
+
+}
+
sub apply_policy_rules() {
- progress_message2 'Applying Policies...';
+ progress_message2 'Applying IPv4 Policies...';
for my $chainref ( @policy_chains ) {
my $policy = $chainref->{policy};
@@ -434,6 +730,40 @@ sub apply_policy_rules() {
}
}
+sub apply_6policy_rules() {
+ progress_message2 'Applying IPv6 Policies...';
+
+ for my $chainref ( @policy_6chains ) {
+ my $policy = $chainref->{policy};
+ my $loglevel = $chainref->{loglevel};
+ my $optional = $chainref->{is_optional};
+ my $default = $chainref->{default};
+ my $name = $chainref->{name};
+
+ if ( $policy ne 'NONE' ) {
+ if ( ! $chainref->{referenced} && ( ! $optional && $policy ne 'CONTINUE' ) ) {
+ ensure_filter_6chain $name, 1;
+ }
+
+ if ( $name =~ /^all2|2all$/ ) {
+ run_user_exit $chainref;
+ policy_6rules $chainref , $policy, $loglevel , $default, $config{MULTICAST};
+ }
+ }
+ }
+
+ for my $zone ( all_6zones ) {
+ for my $zone1 ( all_6zones ) {
+ my $chainref = $filter6_table->{"${zone}2${zone1}"};
+
+ if ( $chainref->{referenced} ) {
+ run_user_exit $chainref;
+ default_6policy $chainref, $zone, $zone1;
+ }
+ }
+ }
+}
+
#
# Complete a standard chain
#
@@ -461,6 +791,33 @@ sub complete_standard_chain ( $$$$ ) {
policy_rules $stdchainref , $policy , $loglevel, $defaultaction, 0;
}
+#
+# Complete a standard chain
+#
+# - run any supplied user exit
+# - search the policy file for an applicable policy and add rules as
+# appropriate
+# - If no applicable policy is found, add rules for an assummed
+# policy of DROP INFO
+#
+sub complete_standard_6chain ( $$$$ ) {
+ my ( $stdchainref, $zone, $zone2, $default ) = @_;
+
+ add_rule $stdchainref, '-m state --state ESTABLISHED,RELATED -j ACCEPT' unless $config{FASTACCEPT};
+
+ run_user_exit $stdchainref;
+
+ my $ruleschainref = $filter6_table->{"${zone}2${zone2}"};
+ my ( $policy, $loglevel, $defaultaction ) = ( $default , 6, $config{$default . '_DEFAULT'} );
+ my $policychainref;
+
+ $policychainref = $filter6_table->{$ruleschainref->{policychain}} if $ruleschainref;
+
+ ( $policy, $loglevel, $defaultaction ) = @{$policychainref}{'policy', 'loglevel', 'default' } if $policychainref;
+
+ policy_rules $stdchainref , $policy , $loglevel, $defaultaction, 0;
+}
+
#
# Create and populate the synflood chains corresponding to entries in /etc/shorewall/policy
#
@@ -478,4 +835,21 @@ sub setup_syn_flood_chains() {
}
}
+#
+# Create and populate the synflood chains corresponding to entries in /etc/shorewall/policy
+#
+sub setup_syn_flood_6chains() {
+ for my $chainref ( @policy_6chains ) {
+ my $limit = $chainref->{synparams};
+ if ( $limit && ! $filter6_table->{syn_flood_chain $chainref} ) {
+ my $level = $chainref->{loglevel};
+ my $synchainref = new_6chain 'filter' , syn_flood_chain $chainref;
+ add_rule $synchainref , "${limit}-j RETURN";
+ log_rule_limit $level , $synchainref , $chainref->{name} , 'DROP', '-m limit --limit 5/min --limit-burst 5 ' , '' , 'add' , ''
+ if $level ne '';
+ add_rule $synchainref, '-j DROP';
+ }
+ }
+}
+
1;
diff --git a/Shorewall-perl/Shorewall/Rules.pm b/Shorewall-perl/Shorewall/Rules.pm
index 0b5622a65..861f0d8d7 100644
--- a/Shorewall-perl/Shorewall/Rules.pm
+++ b/Shorewall-perl/Shorewall/Rules.pm
@@ -52,6 +52,7 @@ our $VERSION = 4.1.5;
# Set to one if we find a SECTION
#
our $sectioned;
+our $sectioned6;
our $macro_nest_level;
our $current_param;
our @param_stack;
@@ -74,6 +75,7 @@ my %rules_commands = ( COMMENT => 0,
sub initialize() {
$sectioned = 0;
+ $sectioned6 = 0;
$macro_nest_level = 0;
$current_param = '';
@param_stack = ();
@@ -386,6 +388,125 @@ sub process_criticalhosts() {
\@critical;
}
+sub setup_6blacklist() {
+
+ my $hosts = find_hosts_by_option '6blacklist';
+ my $chainref;
+ my ( $level, $disposition ) = @config{'BLACKLIST_LOGLEVEL', 'BLACKLIST_DISPOSITION' };
+ my $target = $disposition eq 'REJECT' ? 'reject' : $disposition;
+
+ if ( @$hosts ) {
+ $chainref = new_standard_6chain 'blacklst';
+
+ if ( defined $level && $level ne '' ) {
+ my $logchainref = new_standard_6chain 'blacklog';
+
+ log_rule_limit( $level , $logchainref , 'blacklst' , $disposition , "$globals{LOGLIMIT}" , '', 'add', '' );
+
+ add_rule $logchainref, "-j $target" ;
+
+ $target = 'blacklog';
+ }
+ }
+
+ BLACKLIST:
+ {
+ if ( my $fn = open_file '6blacklist' ) {
+
+ my $first_entry = 1;
+
+ first_entry "$doing $fn...";
+
+ while ( read_a_line ) {
+
+ if ( $first_entry ) {
+ unless ( @$hosts ) {
+ warning_message q(The entries in $fn have been ignored because there are no 'blacklist' interfaces);
+ close_file;
+ last BLACKLIST;
+ }
+
+ $first_entry = 0;
+ }
+
+ my ( $networks, $protocol, $ports ) = split_line 1, 3, 'blacklist file';
+
+ expand_rule(
+ $chainref ,
+ NO_RESTRICT ,
+ do_proto6( $protocol , $ports, '' ) ,
+ $networks ,
+ '' ,
+ '' ,
+ '' ,
+ "-j $target" ,
+ '' ,
+ $disposition ,
+ '' );
+
+ progress_message " \"$currentline\" added to blacklist";
+ }
+ }
+
+ my $state = $config{BLACKLISTNEWONLY} ? '-m state --state NEW,INVALID ' : '';
+
+ for my $hostref ( @$hosts ) {
+ my $interface = $hostref->[0];
+ my $ipsec = $hostref->[1];
+ my $policy = $capabilities{POLICY_MATCH} ? "-m policy --pol $ipsec --dir in " : '';
+ my $network = $hostref->[2];
+ my $source = match_source_6net $network;
+
+ for my $chain ( first_chains $interface ) {
+ add_rule $filter_table->{$chain} , "${source}${state}${policy}-j blacklst";
+ }
+
+ progress_message " Blacklisting enabled on ${interface};${network}";
+ }
+ }
+}
+
+sub process_critical6hosts() {
+
+ my @critical = ();
+
+ my $fn = open_file '6routestopped';
+
+ first_entry "$doing $fn for critical IPv6 hosts...";
+
+ while ( read_a_line ) {
+
+ my $routeback = 0;
+
+ my ($interface, $hosts, $options ) = split_line 1, 3, 'routestopped file';
+
+ fatal_error "Unknown interface ($interface)" unless known_6interface $interface;
+
+ $hosts = ALLIPv6 unless $hosts ne '-';
+
+ my @hosts;
+
+ for my $host ( split_list $hosts, 'host' ) {
+ validate_host $host, 1;
+ push @hosts, "$interface;$host";
+ }
+
+ unless ( $options eq '-' ) {
+ for my $option (split_list $options, 'option' ) {
+ unless ( $option eq 'routeback' || $option eq 'source' || $option eq 'dest' ) {
+ if ( $option eq 'critical' ) {
+ push @critical, @hosts;
+ } else {
+ warning_message "Unknown routestopped option ( $option ) ignored";
+ }
+ }
+ }
+ }
+ }
+
+ \@critical;
+}
+
sub process_routestopped() {
my ( @allhosts, %source, %dest );
@@ -480,6 +601,100 @@ sub process_routestopped() {
}
}
+sub process_6routestopped() {
+
+ my ( @allhosts, %source, %dest );
+
+ my $fn = open_file '6routestopped';
+
+ first_entry "$doing $fn...";
+
+ while ( read_a_line ) {
+
+ my $routeback = 0;
+
+ my ($interface, $hosts, $options ) = split_line 1, 3, '6routestopped file';
+
+ fatal_error "Unknown interface ($interface)" unless known_6interface $interface;
+
+ $hosts = ALLIPv6 unless $hosts && $hosts ne '-';
+
+ my @hosts;
+
+ for my $host ( split /,/, $hosts ) {
+ validate_6host $host, 1;
+ push @hosts, "$interface;$host";
+ }
+
+ unless ( $options eq '-' ) {
+ for my $option (split /,/, $options ) {
+ if ( $option eq 'routeback' ) {
+ if ( $routeback ) {
+ warning_message "Duplicate 'routeback' option ignored";
+ } else {
+ $routeback = 1;
+
+ for my $host ( split /,/, $hosts ) {
+ my $source = match_source_6net $host;
+ my $dest = match_dest_6net $host;
+
+ emit "run_ip6tables -A FORWARD -i $interface -o $interface $source $dest -j ACCEPT";
+ clearrule;
+ }
+ }
+ } elsif ( $option eq 'source' ) {
+ for my $host ( split /,/, $hosts ) {
+ $source{"$interface;$host"} = 1;
+ }
+ } elsif ( $option eq 'dest' ) {
+ for my $host ( split /,/, $hosts ) {
+ $dest{"$interface;$host"} = 1;
+ }
+ } else {
+ warning_message "Unknown 6routestopped option ( $option ) ignored" unless $option eq 'critical';
+ }
+ }
+ }
+
+ push @allhosts, @hosts;
+ }
+
+ for my $host ( @allhosts ) {
+ my ( $interface, $h ) = split /;/, $host;
+ my $source = match_source_6net $h;
+ my $dest = match_dest_6net $h;
+ my $sourcei = match_source_6dev $interface;
+ my $desti = match_dest_6dev $interface;
+
+ emit "\$IP6TABLES -A INPUT $sourcei $source -j ACCEPT";
+ emit "\$IP6TABLES -A OUTPUT $desti $dest -j ACCEPT" unless $config{ADMINISABSENTMINDED};
+
+ my $matched = 0;
+
+ if ( $source{$host} ) {
+ emit "\$IP6TABLES -A FORWARD $sourcei $source -j ACCEPT";
+ $matched = 1;
+ }
+
+ if ( $dest{$host} ) {
+ emit "\$IP6TABLES -A FORWARD $desti $dest -j ACCEPT";
+ $matched = 1;
+ }
+
+ unless ( $matched ) {
+ for my $host1 ( @allhosts ) {
+ unless ( $host eq $host1 ) {
+ my ( $interface1, $h1 ) = split /;/, $host1;
+ my $dest1 = match_dest_6net $h1;
+ my $desti1 = match_dest_6dev $interface1;
+ emit "\$IP6TABLES -A FORWARD $sourcei $desti1 $source $dest1 -j ACCEPT";
+ clearrule;
+ }
+ }
+ }
+ }
+}
+
sub setup_mss();
sub add_common_rules() {
@@ -1447,6 +1662,642 @@ sub process_rules() {
$section = 'DONE';
}
+sub process_6rule1 ( $$$$$$$$$$$$ );
+
+#
+# Expand a macro rule from the rules file
+#
+sub process_6macro ( $$$$$$$$$$$$$$ ) {
+ my ($macro, $target, $param, $source, $dest, $proto, $ports, $sports, $rate, $user, $mark, $connlimit, $time, $wildcard ) = @_;
+
+ my $nocomment = no_comment;
+
+ my $format = 1;
+
+ macro_comment $macro;
+
+ my $macrofile = $macros{$macro};
+
+ progress_message "..Expanding Macro $macrofile...";
+
+ push_open $macrofile;
+
+ while ( read_a_line ) {
+
+ my ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $morigdest, $mrate, $muser );
+
+ if ( $format == 1 ) {
+ ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $mrate, $muser, $morigdest ) = split_line1 1, 9, 'macro file', $macro_commands;
+ } else {
+ ( $mtarget, $msource, $mdest, $mproto, $mports, $msports, $morigdest, $mrate, $muser ) = split_line1 1, 9, 'macro file', $macro_commands;
+ }
+
+ if ( $mtarget eq 'COMMENT' ) {
+ process_comment unless $nocomment;
+ next;
+ }
+
+ if ( $mtarget eq 'FORMAT' ) {
+ fatal_error "Invalid FORMAT ($msource)" unless $msource =~ /^[12]$/;
+ $format = $msource;
+ next;
+ }
+
+ if ( $morigdest ne '-' ) {
+ fatal_error "Invalid macro file entry (too many columns)" if $format == 1;
+ fatal_error "A macro with ORIGINAL DEST cannot be used with IPv6";
+ }
+
+ $mtarget = merge_levels $target, $mtarget;
+
+ if ( $mtarget =~ /^PARAM(:.*)?$/ ) {
+ fatal_error 'PARAM requires a parameter to be supplied in macro invocation' unless $param ne '';
+ $mtarget = substitute_param $param, $mtarget;
+ }
+
+ my $action = isolate_basic_target $mtarget;
+
+ fatal_error "Invalid or missing ACTION ($mtarget)" unless defined $action;
+
+ my $actiontype = $targets6{$action} || find_6macro( $action );
+
+ fatal_error "Invalid Action ($mtarget) in macro" unless $actiontype & ( ACTION + STANDARD + NATRULE + MACRO );
+
+ if ( $msource ) {
+ if ( $msource eq '-' ) {
+ $msource = $source || '';
+ } elsif ( $msource =~ s/^DEST:?// ) {
+ $msource = merge_6macro_source_dest $msource, $dest;
+ } else {
+ $msource =~ s/^SOURCE:?//;
+ $msource = merge_6macro_source_dest $msource, $source;
+ }
+ } else {
+ $msource = '';
+ }
+
+ if ( $mdest ) {
+ if ( $mdest eq '-' ) {
+ $mdest = $dest || '';
+ } elsif ( $mdest =~ s/^SOURCE:?// ) {
+ $mdest = merge_6macro_source_dest $mdest , $source;
+ } else {
+ $mdest =~ s/DEST:?//;
+ $mdest = merge_6macro_source_dest $mdest, $dest;
+ }
+ } else {
+ $mdest = '';
+ }
+
+ process_6rule1(
+ $mtarget,
+ $msource,
+ $mdest,
+ merge_macro_column( $mproto, $proto ) ,
+ merge_macro_column( $mports, $ports ) ,
+ merge_macro_column( $msports, $sports ) ,
+ merge_macro_column( $mrate, $rate ) ,
+ merge_macro_column( $muser, $user ) ,
+ $mark,
+ $connlimit,
+ $time,
+ $wildcard
+ );
+
+ progress_message " IPv6 Rule \"$currentline\" $done";
+ }
+
+ pop_open;
+
+ progress_message "..End Macro $macrofile";
+
+ clear_comment unless $nocomment;
+
+}
+#
+# Once a rule has been expanded via wildcards (source and/or dest zone == 'all'), it is processed by this function. If
+# the target is a macro, the macro is expanded and this function is called recursively for each rule in the expansion.
+#
+sub process_rule1 ( $$$$$$$$$$$$$ ) {
+ my ( $target, $source, $dest, $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time, $wildcard ) = @_;
+ my ( $action, $loglevel) = split_action $target;
+ my ( $basictarget, $param ) = get_target_param $action;
+ my $rule = '';
+ my $actionchainref;
+ my $optimize = $wildcard ? ( $basictarget =~ /!$/ ? 0 : $config{OPTIMIZE} ) : 0;
+
+ unless ( defined $param ) {
+ ( $basictarget, $param ) = ( $1, $2 ) if $action =~ /^(\w+)[(](.*)[)]$/;
+ }
+
+ $param = '' unless defined $param;
+
+ #
+ # Determine the validity of the action
+ #
+ my $actiontype = $targets{$basictarget} || find_macro( $basictarget );
+
+ fatal_error "Unknown action ($action)" unless $actiontype;
+
+ if ( $actiontype == MACRO ) {
+ #
+ # process_macro() will call process_rule1() recursively for each rule in the macro body
+ #
+ fatal_error "Macro invocations nested too deeply" if ++$macro_nest_level > MAX_MACRO_NEST_LEVEL;
+
+ if ( $param ne '' ) {
+ push @param_stack, $current_param;
+ $current_param = $param;
+ }
+
+ process_macro( $basictarget,
+ $target ,
+ $current_param,
+ $source,
+ $dest,
+ $proto,
+ $ports,
+ $sports,
+ $origdest,
+ $ratelimit,
+ $user,
+ $mark,
+ $connlimit,
+ $time,
+ $wildcard );
+
+ $macro_nest_level--;
+
+ $current_param = pop @param_stack if $param ne '';
+
+ return;
+
+ } elsif ( $actiontype & NFQ ) {
+ require_capability( 'NFQUEUE_TARGET', 'NFQUEUE Rules', '' );
+ my $paramval = $param eq '' ? 0 : numeric_value( $param );
+ fatal_error "Invalid value ($param) for NFQUEUE queue number" unless defined($paramval) && $paramval <= 65535;
+ $action = "NFQUEUE --queue-num $paramval";
+ } else {
+ fatal_error "The $basictarget TARGET does not accept a parameter" unless $param eq '';
+ }
+ #
+ # We can now dispense with the postfix character
+ #
+ $action =~ s/[\+\-!]$//;
+ #
+ # Mark target as used
+ #
+ if ( $actiontype & ACTION ) {
+ unless ( $usedactions{$target} ) {
+ $usedactions{$target} = 1;
+ createactionchain $target;
+ }
+ }
+ #
+ # Take care of irregular syntax and targets
+ #
+ if ( $actiontype & REDIRECT ) {
+ my $z = $actiontype & NATONLY ? '' : firewall_zone;
+ if ( $dest eq '-' ) {
+ $dest = join( '', $z, '::' , $ports =~ /[:,]/ ? '' : $ports );
+ } else {
+ $dest = join( '', $z, '::', $dest ) unless $dest =~ /:/;
+ }
+ } elsif ( $action eq 'REJECT' ) {
+ $action = 'reject';
+ } elsif ( $action eq 'CONTINUE' ) {
+ $action = 'RETURN';
+ } elsif ( $actiontype & LOGRULE ) {
+ fatal_error 'LOG requires a log level' unless defined $loglevel and $loglevel ne '';
+ }
+ #
+ # Isolate and validate source and destination zones
+ #
+ my $sourcezone;
+ my $destzone;
+ my $sourceref;
+ my $destref;
+ my $origdstports;
+
+ if ( $source =~ /^(.+?):(.*)/ ) {
+ fatal_error "Missing SOURCE Qualifier ($source)" if $2 eq '';
+ $sourcezone = $1;
+ $source = $2;
+ } else {
+ $sourcezone = $source;
+ $source = ALLIPv4;
+ }
+
+ if ( $dest =~ /^(.*?):(.*)/ ) {
+ fatal_error "Missing DEST Qualifier ($dest)" if $2 eq '';
+ $destzone = $1;
+ $dest = $2;
+ } else {
+ $destzone = $dest;
+ $dest = ALLIPv4;
+ }
+
+ fatal_error "Missing source zone" if $sourcezone eq '-' || $sourcezone =~ /^:/;
+ fatal_error "Unknown source zone ($sourcezone)" unless $sourceref = defined_zone( $sourcezone );
+
+ if ( $actiontype & NATONLY ) {
+ warning_message "Destination zone ($destzone) ignored" unless $destzone eq '-' || $destzone eq '';
+ } else {
+ fatal_error "Missing destination zone" if $destzone eq '-' || $destzone eq '';
+ fatal_error "Unknown destination zone ($destzone)" unless $destref = defined_zone( $destzone );
+ }
+
+ my $restriction = NO_RESTRICT;
+
+ if ( $sourcezone eq firewall_zone ) {
+ $restriction = $destzone eq firewall_zone ? ALL_RESTRICT : OUTPUT_RESTRICT;
+ } else {
+ $restriction = INPUT_RESTRICT if $destzone eq firewall_zone;
+ }
+
+ my ( $chain, $chainref, $policy );
+ #
+ # For compatibility with older Shorewall versions
+ #
+ $origdest = ALLIPv4 if $origdest eq 'all';
+
+ #
+ # Take care of chain
+ #
+
+ unless ( $actiontype & NATONLY ) {
+ #
+ # Check for illegal bridge port rule
+ #
+ if ( $destref->{type} eq 'bport4' ) {
+ unless ( $sourceref->{bridge} eq $destref->{bridge} || single_interface( $sourcezone ) eq $destref->{bridge} ) {
+ return 1 if $wildcard;
+ fatal_error "Rules with a DESTINATION Bridge Port zone must have a SOURCE zone on the same bridge";
+ }
+ }
+
+ $chain = "${sourcezone}2${destzone}";
+ $chainref = ensure_chain 'filter', $chain;
+ $policy = $chainref->{policy};
+
+ if ( $policy eq 'NONE' ) {
+ return 1 if $wildcard;
+ fatal_error "Rules may not override a NONE policy";
+ }
+ #
+ # Handle Optimization
+ #
+ if ( $optimize > 0 ) {
+ my $loglevel = $filter_table->{$chainref->{policychain}}{loglevel};
+ if ( $loglevel ne '' ) {
+ return 1 if $target eq "${policy}:$loglevel}";
+ } else {
+ return 1 if $basictarget eq $policy;
+ }
+ }
+ #
+ # Mark the chain as referenced and add appropriate rules from earlier sections.
+ #
+ $chainref = ensure_filter_chain $chain, 1;
+ }
+
+ #
+ # Generate Fixed part of the rule
+ #
+ $rule = join( '', do_proto($proto, $ports, $sports), do_ratelimit( $ratelimit, $basictarget ) , do_user( $user ) , do_test( $mark , 0xFF ) , do_connlimit( $connlimit ), do_time( $time ) );
+
+ unless ( $section eq 'NEW' ) {
+ fatal_error "Entries in the $section SECTION of the rules file not permitted with FASTACCEPT=Yes" if $config{FASTACCEPT};
+ fatal_error "$basictarget rules are not allowed in the $section SECTION" if $actiontype & ( NATRULE | NONAT );
+ $rule .= "-m state --state $section "
+ }
+
+ #
+ # Generate NAT rule(s), if any
+ #
+ if ( $actiontype & NATRULE ) {
+ my ( $server, $serverport );
+ my $randomize = $dest =~ s/:random$// ? '--random ' : '';
+
+ require_capability( 'NAT_ENABLED' , "$basictarget rules", '' );
+ #
+ # Isolate server port
+ #
+ if ( $dest =~ /^(.*)(:(.+))$/ ) {
+ #
+ # Server IP and Port
+ #
+ $server = $1; # May be empty
+ $serverport = $3; # Not Empty due to RE
+ $origdstports = $ports;
+ if ( $serverport =~ /^(\d+)-(\d+)$/ ) {
+ #
+ # Server Port Range
+ #
+ fatal_error "Invalid port range ($serverport)" unless $1 < $2;
+ my @ports = ( $1, $2 );
+ $_ = validate_port( proto_name( $proto ), $_) for ( @ports );
+ ( $ports = $serverport ) =~ tr/-/:/;
+ } else {
+ $serverport = $ports = validate_port( proto_name( $proto ), $serverport );
+ }
+ } elsif ( $dest eq ':' ) {
+ #
+ # Rule with no server IP or port ( zone:: )
+ #
+ $server = $serverport = '';
+ } else {
+ #
+ # Simple server IP address (may be empty or "-")
+ #
+ $server = $dest;
+ $serverport = '';
+ }
+
+ #
+ # Generate the target
+ #
+ my $target = '';
+
+ if ( $actiontype & REDIRECT ) {
+ fatal_error "A server IP address may not be specified in a REDIRECT rule" if $server;
+ $target = '-j REDIRECT ';
+ $target .= "--to-port $serverport " if $serverport;
+ if ( $origdest eq '' || $origdest eq '-' ) {
+ $origdest = ALLIPv4;
+ } elsif ( $origdest eq 'detect' ) {
+ if ( $config{DETECT_DNAT_IPADDRS} && $sourcezone ne firewall_zone ) {
+ my $interfacesref = $sourceref->{interfaces};
+ my @interfaces = keys %$interfacesref;
+ $origdest = @interfaces ? "detect:@interfaces" : ALLIPv4;
+ } else {
+ $origdest = ALLIPv4;
+ }
+ }
+ } else {
+ fatal_error "A server must be specified in the DEST column in $action rules" if $server eq '';
+
+ if ( $server =~ /^(.+)-(.+)$/ ) {
+ validate_range( $1, $2 );
+ } else {
+ $server = validate_address $server, 1;
+ }
+
+ if ( $action eq 'SAME' ) {
+ fatal_error 'Port mapping not allowed in SAME rules' if $serverport;
+ fatal_error 'SAME not allowed with SOURCE=$FW' if $sourcezone eq firewall_zone;
+ fatal_error "':random' is not supported by the SAME target" if $randomize;
+ warning_message 'Netfilter support for SAME is being dropped in early 2008';
+ $target = '-j SAME ';
+ for my $serv ( split /,/, $server ) {
+ $target .= "--to $serv ";
+ }
+ } elsif ( $action eq 'DNAT' ) {
+ $target = '-j DNAT ';
+ $serverport = ":$serverport" if $serverport;
+ for my $serv ( split /,/, $server ) {
+ $target .= "--to-destination ${serv}${serverport} ";
+ }
+ }
+
+ unless ( $origdest && $origdest ne '-' && $origdest ne 'detect' ) {
+ if ( $config{DETECT_DNAT_IPADDRS} && $sourcezone ne firewall_zone ) {
+ my $interfacesref = $sourceref->{interfaces};
+ my @interfaces = keys %$interfacesref;
+ $origdest = @interfaces ? "detect:@interfaces" : ALLIPv4;
+ } else {
+ $origdest = ALLIPv4;
+ }
+ }
+ }
+
+ $target .= $randomize;
+
+ #
+ # And generate the nat table rule(s)
+ #
+ expand_rule ( ensure_chain ('nat' , $sourceref->{type} eq 'firewall' ? 'OUTPUT' : dnat_chain $sourcezone ),
+ PREROUTE_RESTRICT ,
+ $rule ,
+ $source ,
+ $origdest ,
+ '' ,
+ '' ,
+ $target ,
+ $loglevel ,
+ $action ,
+ $serverport ? do_proto( $proto, '', '' ) : '' );
+ #
+ # After NAT:
+ # - the destination port will be the server port ($ports) -- we did that above
+ # - the destination IP will be the server IP ($dest)
+ # - there will be no log level (we log NAT rules in the nat table rather than in the filter table).
+ # - the target will be ACCEPT.
+ #
+ unless ( $actiontype & NATONLY ) {
+ $rule = join( '', do_proto( $proto, $ports, $sports ), do_ratelimit( $ratelimit, 'ACCEPT' ), do_user $user , do_test( $mark , 0xFF ) );
+ $loglevel = '';
+ $dest = $server;
+ $action = 'ACCEPT';
+ }
+ } elsif ( $actiontype & NONAT ) {
+ #
+ # NONAT or ACCEPT+ -- May not specify a destination interface
+ #
+ fatal_error "Invalid DEST ($dest) in $action rule" if $dest =~ /:/;
+
+ $origdest = '' unless $origdest and $origdest ne '-';
+
+ if ( $origdest eq 'detect' ) {
+ my $interfacesref = $sourceref->{interfaces};
+ my $interfaces = "@$interfacesref";
+ $origdest = $interfaces ? "detect:$interfaces" : ALLIPv4;
+ }
+
+ expand_rule( ensure_chain ('nat' , $sourceref->{type} eq 'firewall' ? 'OUTPUT' : dnat_chain $sourcezone) ,
+ PREROUTE_RESTRICT ,
+ $rule ,
+ $source ,
+ $dest ,
+ $origdest ,
+ '',
+ '-j RETURN ' ,
+ $loglevel ,
+ $action ,
+ '' );
+ }
+
+ #
+ # Add filter table rule, unless this is a NATONLY rule type
+ #
+ unless ( $actiontype & NATONLY ) {
+
+ if ( $actiontype & ACTION ) {
+ $action = (find_logactionchain $target)->{name};
+ $loglevel = '';
+ }
+
+ unless ( $origdest eq '-' ) {
+ require_capability( 'CONNTRACK_MATCH', 'ORIGINAL DEST in a non-NAT rule', 's' ) unless $actiontype & NATRULE;
+ } else {
+ $origdest = '';
+ }
+
+ expand_rule( ensure_chain( 'filter', $chain ) ,
+ $restriction ,
+ $rule ,
+ $source ,
+ $dest ,
+ $origdest ,
+ $origdstports ,
+ "-j $action " ,
+ $loglevel ,
+ $action ,
+ '' );
+ }
+}
+
+#
+# Process a Record in the rules file
+#
+# Deals with the ugliness of wildcard zones ('all' in SOURCE and/or DEST column).
+#
+sub process_rule ( $$$$$$$$$$$$ ) {
+ my ( $target, $source, $dest, $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit , $time ) = @_;
+ my $intrazone = 0;
+ my $includesrcfw = 1;
+ my $includedstfw = 1;
+ my $thisline = $currentline;
+ #
+ # Section Names are optional so once we get to an actual rule, we need to be sure that
+ # we close off any missing sections.
+ #
+ unless ( $sectioned ) {
+ finish_section 'ESTABLISHED,RELATED';
+ $sections{$section = 'NEW'} = 1;
+ $sectioned = 1;
+ }
+
+ #
+ # Handle Wildcards
+ #
+ if ( $source =~ /^all[-+]/ ) {
+ if ( $source eq 'all+' ) {
+ $source = 'all';
+ $intrazone = 1;
+ } elsif ( ( $source eq 'all+-' ) || ( $source eq 'all-+' ) ) {
+ $source = 'all';
+ $intrazone = 1;
+ $includesrcfw = 0;
+ } elsif ( $source eq 'all-' ) {
+ $source = 'all';
+ $includesrcfw = 0;
+ } else {
+ fatal_error "Invalid SOURCE ($source)";
+ }
+ }
+
+ if ( $dest =~ /^all[-+]/ ) {
+ if ( $dest eq 'all+' ) {
+ $dest = 'all';
+ $intrazone = 1;
+ } elsif ( ( $dest eq 'all+-' ) || ( $dest eq 'all-+' ) ) {
+ $dest = 'all';
+ $intrazone = 1;
+ $includedstfw = 0;
+ } elsif ( $dest eq 'all-' ) {
+ $dest = 'all';
+ $includedstfw = 0;
+ } else {
+ fatal_error "Invalid DEST ($dest)";
+ }
+
+ }
+
+ my $action = isolate_basic_target $target;
+
+ fatal_error "Invalid or missing ACTION ($target)" unless defined $action;
+
+ if ( $source eq 'all' ) {
+ for my $zone ( all_zones ) {
+ if ( $includesrcfw || ( zone_type( $zone ) ne 'firewall' ) ) {
+ if ( $dest eq 'all' ) {
+ for my $zone1 ( all_zones ) {
+ if ( $includedstfw || ( zone_type( $zone1 ) ne 'firewall' ) ) {
+ if ( $intrazone || ( $zone ne $zone1 ) ) {
+ process_rule1 $target, $zone, $zone1 , $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time, 1;
+ }
+ }
+ }
+ } else {
+ my $destzone = (split( /:/, $dest, 2 ) )[0];
+ $destzone = firewall_zone unless defined_zone( $destzone ); # We do this to allow 'REDIRECT all ...'; process_rule1 will catch the case where the dest zone is invalid
+ if ( $intrazone || ( $zone ne $destzone ) ) {
+ process_rule1 $target, $zone, $dest , $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time, 1;
+ }
+ }
+ }
+ }
+ } elsif ( $dest eq 'all' ) {
+ for my $zone ( all_zones ) {
+ my $sourcezone = ( split( /:/, $source, 2 ) )[0];
+ if ( ( $includedstfw || ( zone_type( $zone ) ne 'firewall') ) && ( ( $sourcezone ne $zone ) || $intrazone) ) {
+ process_rule1 $target, $source, $zone , $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time, 1;
+ }
+ }
+ } else {
+ process_rule1 $target, $source, $dest, $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time, 0;
+ }
+
+ progress_message " Rule \"$thisline\" $done";
+}
+
+#
+# Process the Rules File
+#
+sub process_rules() {
+
+ my $fn = open_file 'rules';
+
+ first_entry "$doing $fn...";
+
+ while ( read_a_line ) {
+
+ my ( $target, $source, $dest, $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time ) = split_line1 1, 12, 'rules file', \%rules_commands;
+
+ if ( $target eq 'COMMENT' ) {
+ process_comment;
+ } elsif ( $target eq 'SECTION' ) {
+ #
+ # read_a_line has already verified that there are exactly two tokens on the line
+ #
+ fatal_error "Invalid SECTION ($source)" unless defined $sections{$source};
+ fatal_error "Duplicate or out of order SECTION $source" if $sections{$source};
+ $sectioned = 1;
+ $sections{$source} = 1;
+
+ if ( $source eq 'RELATED' ) {
+ $sections{ESTABLISHED} = 1;
+ finish_section 'ESTABLISHED';
+ } elsif ( $source eq 'NEW' ) {
+ @sections{'ESTABLISHED','RELATED'} = ( 1, 1 );
+ finish_section ( ( $section eq 'RELATED' ) ? 'RELATED' : 'ESTABLISHED,RELATED' );
+ }
+
+ $section = $source;
+ } else {
+ if ( "\L$source" =~ /^none(:.*)?$/ || "\L$dest" =~ /^none(:.*)?$/ ) {
+ progress_message "Rule \"$currentline\" ignored."
+ } else {
+ process_rule $target, $source, $dest, $proto, $ports, $sports, $origdest, $ratelimit, $user, $mark, $connlimit, $time;
+ }
+ }
+ }
+
+ clear_comment;
+ $section = 'DONE';
+}
+
#
# To quote an old comment, "generate_matrix makes a sow's ear out of a silk purse".
#
diff --git a/Shorewall-perl/Shorewall/Zones.pm b/Shorewall-perl/Shorewall/Zones.pm
index ea4a8f902..b607cc3ea 100644
--- a/Shorewall-perl/Shorewall/Zones.pm
+++ b/Shorewall-perl/Shorewall/Zones.pm
@@ -44,28 +44,44 @@ our @EXPORT = qw( NOTHING
firewall_zone
defined_zone
zone_type
+ zone_family
all_zones
+ all_6zones
complex_zones
non_firewall_zones
+ non_firewall_6zones
single_interface
validate_interfaces_file
+ validate_6interfaces_file
all_interfaces
+ all_6interfaces
interface_number
find_interface
+ find_6interface
known_interface
+ known_6interface
have_bridges
+ have_6bridges
port_to_bridge
+ port_to_6bridge
source_port_to_bridge
+ source_port_to_6bridge
interface_is_optional
+ interface6_is_optional
find_interfaces_by_option
+ find_6interfaces_by_option
get_interface_option
+ get_6interface_option
set_interface_option
+ set_6interface_option
validate_hosts_file
+ validate_6hosts_file
find_hosts_by_option
+ find_6hosts_by_option
);
our @EXPORT_OK = qw( initialize );
-our $VERSION = 4.1.5;
+our $VERSION = 4.3.0;
#
# IPSEC Option types
@@ -82,7 +98,7 @@ use constant { NOTHING => 'NOTHING',
#
# @zones contains the ordered list of zones with sub-zones appearing before their parents.
#
-# %zones{ => {type = > 'firewall', 'ipv4', 'ipsec4', 'bport4';
+# %zones{ => {type = > 'firewall', 'ipv4', 'ipsec4', 'bport4', 'ipv6', 'ipsec6', 'bport6';
# options => { complex => 0|1
# nested => 0|1
# in_out => < policy match string >
@@ -93,6 +109,7 @@ use constant { NOTHING => 'NOTHING',
# children => [ ]
# interfaces => [ ]
# bridge =>
+# family => 1 = IPv4, 2 = IPv6, 3 = firewall
# hosts { } => [ { => { ipsec => 'ipsec'|'none'
# options => { =>
# ...
@@ -120,14 +137,15 @@ our %reservedName = ( all => 1,
#
# Interface Table.
#
-# @interfaces lists the interface names in the order that they appear in the interfaces file.
+# @interfaces lists the interface names in the order that they appear in the interfaces file.
+# @interfaces6 lists the interface names in the order that they appear in the interfaces6 file.
#
# %interfaces { => { name =>
# root =>
# options => { = ,
# ...
# }
-# zone4 =>
+# zone =>
# nets =>
# bridge =>
# broadcasts => 'none', 'detect' or [ , , ... ]
@@ -135,9 +153,25 @@ our %reservedName = ( all => 1,
# }
# }
#
+# %interfaces6 { => { name =>
+# root =>
+# options => { = ,
+# ...
+# }
+# zone =>
+# nets =>
+# bridge =>
+# broadcasts => 'none', 'detect' or [ , , ... ]
+# number =>
+# }
+# }
+#
our @interfaces;
our %interfaces;
+our @interfaces6;
+our %interfaces6;
our @bport_zones;
+our @bport_6zones;
#
# Initialize globals -- we take this novel approach to globals initialization to allow
@@ -156,6 +190,9 @@ sub initialize() {
@interfaces = ();
%interfaces = ();
@bport_zones = ();
+ @interfaces6 = ();
+ %interfaces6 = ();
+ @bport_6zones = ();
}
INIT {
@@ -219,7 +256,7 @@ sub parse_zone_option_list($$)
if ( $key{$e} ) {
$h{$e} = $val;
} else {
- fatal_error "The \"$e\" option may only be specified for ipsec zones" unless $zonetype eq 'ipsec4';
+ fatal_error "The \"$e\" option may only be specified for ipsec zones" unless $zonetype =~ /^ipsec/;
$options .= $invert;
$options .= "--$e ";
$options .= "$val "if defined $val;
@@ -240,6 +277,7 @@ sub determine_zones()
my @z;
my $ipv4 = 0;
+ my $ipv6 = 0;
my $fn = open_file 'zones';
@@ -262,28 +300,43 @@ sub determine_zones()
push @{$zones{$p}{children}}, $zone;
}
}
-
+
fatal_error "Invalid zone name ($zone)" unless "\L$zone" =~ /^[a-z]\w*$/ && length $zone <= $globals{MAXZONENAMELENGTH};
fatal_error "Invalid zone name ($zone)" if $reservedName{$zone} || $zone =~ /^all2|2all$/;
fatal_error( "Duplicate zone name ($zone)" ) if $zones{$zone};
$type = "ipv4" unless $type;
+ my $family = F_INET;
+
if ( $type =~ /ipv4/i ) {
$type = 'ipv4';
$ipv4 = 1;
+ } elsif ( $type =~ /ipv6/i ) {
+ $type = 'ipv6';
+ $ipv6 = 1;
+ $family = F_INET6;
} elsif ( $type =~ /^ipsec4?$/i ) {
$type = 'ipsec4';
+ } elsif ( $type =~ /^ipsec6?$/i ) {
+ $type = 'ipsec6';
+ $family = F_INET6;
} elsif ( $type =~ /^bport4?$/i ) {
warning_message "Bridge Port zones should have a parent zone" unless @parents;
$type = 'bport4';
push @bport_zones, $zone;
+ } elsif ( $type =~ /^bport6?$/i ) {
+ warning_message "Bridge Port zones should have a parent zone" unless @parents;
+ $type = 'bport6';
+ $family = F_INET6;
+ push @bport_6zones, $zone;
} elsif ( $type eq 'firewall' ) {
fatal_error 'Firewall zone may not be nested' if @parents;
fatal_error "Only one firewall zone may be defined ($zone)" if $firewall_zone;
$firewall_zone = $zone;
$ENV{FW} = $zone;
$type = "firewall";
+ $family = F_INET | F_INET6;
} elsif ( $type eq '-' ) {
$type = 'ipv4';
$ipv4 = 1;
@@ -291,6 +344,10 @@ sub determine_zones()
fatal_error "Invalid zone type ($type)" ;
}
+ for ( @parents ) {
+ fatal_error "Incompatible Parent/Child Zones Types ($_)" unless $zones{$_}{family} == $family
+ }
+
for ( $options, $in_options, $out_options ) {
$_ = '' if $_ eq '-';
}
@@ -299,10 +356,11 @@ sub determine_zones()
parents => \@parents,
exclusions => [],
bridge => '',
+ family => $family,
options => { in_out => parse_zone_option_list( $options || '', $type ) ,
in => parse_zone_option_list( $in_options || '', $type ) ,
out => parse_zone_option_list( $out_options || '', $type ) ,
- complex => ($type eq 'ipsec4' || $options || $in_options || $out_options ? 1 : 0) ,
+ complex => ($type =~ /^ipsec/ || $options || $in_options || $out_options ? 1 : 0) ,
nested => @parents > 0 } ,
interfaces => {} ,
children => [] ,
@@ -311,8 +369,8 @@ sub determine_zones()
push @z, $zone;
}
- fatal_error "No firewall zone defined" unless $firewall_zone;
- fatal_error "No IPv4 zones defined" unless $ipv4;
+ fatal_error "No firewall zone defined" unless $firewall_zone;
+ fatal_error "No IPv4 or IPv6 zones defined" unless $ipv4 || $ipv6;
my %ordered;
@@ -336,7 +394,7 @@ sub determine_zones()
}
#
-# Return true of we have any ipsec zones
+# Return true of we have any ipse4c zones
#
sub haveipseczones() {
for my $zoneref ( values %zones ) {
@@ -346,6 +404,17 @@ sub haveipseczones() {
0;
}
+#
+# Return true of we have any ipse4c zones
+#
+sub haveipsec6zones() {
+ for my $zoneref ( values %zones ) {
+ return 1 if $zoneref->{type} eq 'ipsec6';
+ }
+
+ 0;
+}
+
#
# Report about zones.
#
@@ -374,7 +443,7 @@ sub zone_report()
my $hosts = $groupref->{hosts};
if ( $hosts ) {
my $grouplist = join ',', ( @$hosts );
- progress_message " $interface:$grouplist";
+ progress_message " $interface $grouplist";
$printed = 1;
}
}
@@ -384,7 +453,7 @@ sub zone_report()
}
unless ( $printed ) {
- fatal_error "No bridge has been associated with zone $zone" if $type eq 'bport4' && ! $zoneref->{bridge};
+ fatal_error "No bridge has been associated with zone $zone" if $type =~ /^bport/ && ! $zoneref->{bridge};
warning_message "*** $zone is an EMPTY ZONE ***" unless $type eq 'firewall';
}
@@ -402,7 +471,7 @@ sub dump_zone_contents()
my $exclusions = $zoneref->{exclusions};
my $entry = "$zone $type";
- $entry .= ":$zoneref->{bridge}" if $type eq 'bport4';
+ $entry .= ":$zoneref->{bridge}" if $type =~ /^bport/;
if ( $hostref ) {
for my $type ( sort keys %$hostref ) {
@@ -414,7 +483,7 @@ sub dump_zone_contents()
my $hosts = $groupref->{hosts};
if ( $hosts ) {
my $grouplist = join ',', ( @$hosts );
- $entry .= " $interface:$grouplist";
+ $entry .= " $interface\($grouplist\)";
}
}
}
@@ -455,7 +524,7 @@ sub add_group_to_zone($$$$$)
my $arrayref;
my $zoneref = $zones{$zone};
my $zonetype = $zoneref->{type};
- my $ifacezone = $interfaces{$interface}{zone4};
+ my $ifacezone = $interfaces{$interface}{zone};
$zoneref->{interfaces}{$interface} = 1;
@@ -481,7 +550,7 @@ sub add_group_to_zone($$$$$)
unless ( $switched ) {
if ( $type eq $zonetype ) {
fatal_error "Duplicate Host Group ($interface:$host) in zone $zone" if $ifacezone eq $zone;
- $ifacezone = $zone if $host eq ALLIPv4;
+ $ifacezone = $zone if $host eq ALLIPv4 || $host eq ALLIPv6;
}
}
@@ -506,7 +575,7 @@ sub add_group_to_zone($$$$$)
push @{$arrayref}, { options => $options,
hosts => \@newnetworks,
- ipsec => $type eq 'ipsec4' ? 'ipsec' : 'none' };
+ ipsec => $type =~ /^ipsec/ ? 'ipsec' : 'none' };
}
#
@@ -527,20 +596,36 @@ sub zone_type( $ ) {
find_zone( $_[0] )->{type};
}
+sub zone_family( $ ) {
+ find_zone( $_[0] )->{family};
+}
+
sub defined_zone( $ ) {
$zones{$_[0]};
}
sub all_zones() {
- @zones;
+ grep ( ! $zones{$_}{family} & F_INET , @zones );
+}
+
+sub all_6zones() {
+ grep ( ! $zones{$_}{family} & F_INET6 , @zones );
}
sub non_firewall_zones() {
- grep ( $zones{$_}{type} ne 'firewall' , @zones );
+ grep ( $zones{$_}{family} == F_INET , @zones );
+}
+
+sub non_firewall_6zones() {
+ grep ( $zones{$_}{family} == F_INET6 , @zones );
}
sub complex_zones() {
- grep( $zones{$_}{options}{complex} , @zones );
+ grep( $zones{$_}{options}{complex} && $zones{$_}{family} == F_INET , @zones );
+}
+
+sub complex_6zones() {
+ grep( $zones{$_}{options}{complex} && $zones{$_}{family} == F_INET6 , @zones );
}
sub firewall_zone() {
@@ -551,20 +636,19 @@ sub firewall_zone() {
# Parse the interfaces file.
#
+use constant { SIMPLE_IF_OPTION => 1,
+ BINARY_IF_OPTION => 2,
+ ENUM_IF_OPTION => 3,
+ NUMERIC_IF_OPTION => 4,
+ OBSOLETE_IF_OPTION => 5,
+ MASK_IF_OPTION => 7,
+ IF_OPTION_ZONEONLY => 8 };
+
sub validate_interfaces_file( $ )
{
my $export = shift;
my $num = 0;
- use constant { SIMPLE_IF_OPTION => 1,
- BINARY_IF_OPTION => 2,
- ENUM_IF_OPTION => 3,
- NUMERIC_IF_OPTION => 4,
- OBSOLETE_IF_OPTION => 5,
- MASK_IF_OPTION => 7,
-
- IF_OPTION_ZONEONLY => 8 };
-
my %validoptions = (arp_filter => BINARY_IF_OPTION,
arp_ignore => ENUM_IF_OPTION,
blacklist => SIMPLE_IF_OPTION,
@@ -609,6 +693,7 @@ sub validate_interfaces_file( $ )
fatal_error "Unknown zone ($zone)" unless $zoneref;
fatal_error "Firewall zone not allowed in ZONE column of interface record" if $zoneref->{type} eq 'firewall';
+ fatal_error "IPv6 Zones not allowed in the interfaces file ($zone}" if $zoneref->{type} =~ /6/;
}
$networks = '' if $networks eq '-';
@@ -752,7 +837,7 @@ sub validate_interfaces_file( $ )
add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone;
- $interfaces{$interface}{zone4} = $zone; #Must follow the call to add_group_to_zone()
+ $interfaces{$interface}{zone} = $zone; #Must follow the call to add_group_to_zone()
progress_message " Interface \"$currentline\" Validated";
@@ -782,6 +867,225 @@ sub validate_interfaces_file( $ )
fatal_error "No network interfaces defined" unless @interfaces;
}
+#
+# Parse the interfaces file.
+#
+
+sub validate_6interfaces_file( $ )
+{
+ my $export = shift;
+ my $num = 0;
+
+ my %validoptions = (blacklist => SIMPLE_IF_OPTION,
+ bridge => SIMPLE_IF_OPTION,
+ maclist => SIMPLE_IF_OPTION,
+ nosmurfs => SIMPLE_IF_OPTION,
+ optional => SIMPLE_IF_OPTION,
+ proxyndp => BINARY_IF_OPTION,
+ routeback => SIMPLE_IF_OPTION + IF_OPTION_ZONEONLY,
+ sourceroute => BINARY_IF_OPTION,
+ tcpflags => SIMPLE_IF_OPTION,
+ mss => NUMERIC_IF_OPTION,
+ );
+
+ my $fn = open_file '6interfaces';
+
+ my $first_entry = 1;
+
+ my @ifaces;
+
+ while ( read_a_line ) {
+
+ if ( $first_entry ) {
+ progress_message2 "$doing $fn...";
+ $first_entry = 0;
+ }
+
+ my ($zone, $originalinterface, $networks, $options ) = split_line 2, 4, '6interfaces file';
+ my $zoneref;
+ my $bridge = '';
+
+ if ( $zone eq '-' ) {
+ $zone = '';
+ } else {
+ $zoneref = $zones{$zone};
+
+ fatal_error "Unknown zone ($zone)" unless $zoneref;
+ fatal_error "Firewall zone not allowed in ZONE column of interface record" if $zoneref->{type} eq 'firewall';
+ fatal_error "IPv4 Zones not allowed in the 6interfaces file ($zone}" if $zoneref->{type} =~ /4/;
+ }
+
+ $networks = '' if $networks eq '-';
+ $options = '' if $options eq '-';
+
+ my ($interface, $port, $extra) = split /:/ , $originalinterface, 3;
+
+ fatal_error "Invalid INTERFACE ($originalinterface)" if ! $interface || defined $extra;
+
+ fatal_error "Invalid Interface Name (+)" if $interface eq '+';
+
+ if ( defined $port ) {
+ fatal_error qq("Virtual" interfaces are not supported -- see http://www.shorewall.net/Shorewall_and_Aliased_Interfaces.html) if $port =~ /^\d+$/;
+ require_capability( 'PHYSDEV_MATCH', 'Bridge Ports', '');
+ fatal_error "Your iptables is not recent enough to support bridge ports" unless $capabilities{KLUDGEFREE};
+ fatal_error "Duplicate Interface ($port)" if $interfaces{$port};
+ fatal_error "$interface is not a defined bridge" unless $interfaces{$interface} && $interfaces{$interface}{options}{bridge};
+ fatal_error "Bridge Ports may only be associated with 'bport' zones" if $zone && $zoneref->{type} ne 'bport4';
+
+ if ( $zone ) {
+ if ( $zoneref->{bridge} ) {
+ fatal_error "Bridge Port zones may only be associated with a single bridge" if $zoneref->{bridge} ne $interface;
+ } else {
+ $zoneref->{bridge} = $interface;
+ }
+ }
+
+ fatal_error "Bridge Ports may not have options" if $options && $options ne '-';
+
+ next if $port eq '';
+
+ fatal_error "Invalid Interface Name ($interface:$port)" unless $port =~ /^[\w.@%-]+\+?$/;
+
+ $bridge = $interface;
+ $interface = $port;
+ } else {
+ fatal_error "Duplicate Interface ($interface)" if $interfaces{$interface};
+ fatal_error "Zones of type 'bport' may only be associated with bridge ports" if $zone && $zoneref->{type} eq 'bport4';
+ $bridge = $interface;
+ }
+
+ my $wildcard = 0;
+ my $root;
+
+ if ( $interface =~ /\+$/ ) {
+ $wildcard = 1;
+ $root = substr( $interface, 0, -1 );
+ } else {
+ $root = $interface;
+ }
+
+ my $broadcasts;
+
+ unless ( $networks eq '' || $networks eq 'detect' ) {
+ my @broadcasts = split $networks, 'address';
+
+ for my $address ( @broadcasts ) {
+ fatal_error 'Invalid BROADCAST address' unless $address =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/;
+ }
+
+ if ( $capabilities{ADDRTYPE} ) {
+ warning_message 'Shorewall no longer uses broadcast addresses in rule generation when Address Type Match is available';
+ } else {
+ $broadcasts = \@broadcasts;
+ }
+ }
+
+ my $optionsref = {};
+
+ my %options;
+
+ if ( $options ) {
+
+ for my $option (split_list $options, 'option' ) {
+ next if $option eq '-';
+
+ ( $option, my $value ) = split /=/, $option;
+
+ fatal_error "Invalid Interface option ($option)" unless my $type = $validoptions{$option};
+
+ fatal_error "The \"$option\" option may not be specified on a multi-zone interface" if $type & IF_OPTION_ZONEONLY && ! $zone;
+
+ $type &= MASK_IF_OPTION;
+
+ if ( $type == SIMPLE_IF_OPTION ) {
+ fatal_error "Option $option does not take a value" if defined $value;
+ $options{$option} = 1;
+ } elsif ( $type == BINARY_IF_OPTION ) {
+ $value = 1 unless defined $value;
+ fatal_error "Option value for $option must be 0 or 1" unless ( $value eq '0' || $value eq '1' );
+ fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard;
+ $options{$option} = $value;
+ } elsif ( $type == ENUM_IF_OPTION ) {
+ fatal_error "The $option option may not be used with a wild-card interface name" if $wildcard;
+ if ( $option eq 'arp_ignore' ) {
+ if ( defined $value ) {
+ if ( $value =~ /^[1-3,8]$/ ) {
+ $options{arp_ignore} = $value;
+ } else {
+ fatal_error "Invalid value ($value) for arp_ignore";
+ }
+ } else {
+ $options{arp_ignore} = 1;
+ }
+ } else {
+ fatal_error "Internal Error in validate_interfaces_file";
+ }
+ } elsif ( $type == NUMERIC_IF_OPTION ) {
+ fatal_error "The $option option requires a value" unless defined $value;
+ my $numval = numeric_value $value;
+ fatal_error "Invalid value ($value) for option $option" unless defined $numval;
+ $options{$option} = $numval;
+ } else {
+ warning_message "Support for the $option interface option has been removed from Shorewall-perl";
+ }
+ }
+
+ $zoneref->{options}{in_out}{routeback} = 1 if $zoneref && $options{routeback};
+
+ if ( $options{bridge} ) {
+ require_capability( 'PHYSDEV_MATCH', 'The "bridge" option', 's');
+ fatal_error "Bridges may not have wildcard names" if $wildcard;
+ }
+ } elsif ( $port ) {
+ $options{port} = 1;
+ }
+
+ $optionsref = \%options;
+
+ $interfaces6{$interface} = { name => $interface ,
+ bridge => $bridge ,
+ nets => 0 ,
+ number => ++$num ,
+ root => $root ,
+ broadcasts => $broadcasts ,
+ options => $optionsref };
+
+ push @ifaces, $interface;
+
+ my @networks = allipv4;
+
+ add_group_to_zone( $zone, $zoneref->{type}, $interface, \@networks, $optionsref ) if $zone;
+
+ $interfaces6{$interface}{zone6} = $zone; #Must follow the call to add_group_to_zone()
+
+ progress_message " Interface \"$currentline\" Validated";
+
+ }
+
+ #
+ # We now assemble the @interfaces6 array such that bridge ports immediately precede their associated bridge
+ #
+ for my $interface ( @ifaces ) {
+ my $interfaceref = $interfaces6{$interface};
+
+ if ( $interfaceref->{options}{bridge} ) {
+ my @ports = grep $interfaces{$_}{options}{port} && $interfaces{$_}{bridge} eq $interface, @ifaces;
+
+ if ( @ports ) {
+ push @interfaces, @ports;
+ } else {
+ $interfaceref->{options}{routeback} = 1; #so the bridge will work properly
+ }
+ }
+
+ push @interfaces, $interface unless $interfaceref->{options}{port};
+ }
+ #
+ # Be sure that we have at least one interface
+ #
+ fatal_error "No network interfaces defined" unless @interfaces;
+}
+
#
# Returns true if passed interface matches an entry in /etc/shorewall/interfaces
#
@@ -809,6 +1113,33 @@ sub known_interface($)
0;
}
+#
+# Returns true if passed interface matches an entry in /etc/shorewall/interfaces
+#
+# If the passed name matches a wildcard, a entry for the name is added in %interfaces to speed up validation of other references to that name.
+#
+sub known_6interface($)
+{
+ my $interface = $_[0];
+ my $interfaceref = $interfaces6{$interface};
+
+ return $interfaceref if $interfaceref;
+
+ for my $i ( @interfaces6 ) {
+ $interfaceref = $interfaces6{$i};
+ my $val = $interfaceref->{root};
+ next if $val eq $i;
+ if ( substr( $interface, 0, length $val ) eq $val ) {
+ #
+ # Cache this result for future reference. We set the 'name' to the name of the entry that appears in /etc/shorewall/interfaces.
+ #
+ return $interfaces6{$interface} = { options => $interfaceref->{options}, bridge => $interfaceref->{bridge} , name => $i , number => $interfaceref->{number} };
+ }
+ }
+
+ 0;
+}
+
#
# Return interface number
#
@@ -823,12 +1154,38 @@ sub all_interfaces() {
@interfaces;
}
+#
+# Return 6interface number
+#
+sub interface6_number( $ ) {
+ $interfaces6{$_[0]}{number} || 256;
+}
+
+#
+# Return the 6interfaces list
+#
+sub all_interfaces6() {
+ @interfaces6;
+}
+
#
# Return a reference to the interfaces table entry for an interface
#
sub find_interface( $ ) {
my $interface = $_[0];
- my $interfaceref = $interfaces{ $interface };
+ my $interfaceref = $interfaces6{ $interface };
+
+ fatal_error "Unknown Interface ($interface)" unless $interfaceref;
+
+ $interfaceref;
+}
+
+#
+# Return a reference to the interfaces6 table entry for an interface
+#
+sub find_interface6( $ ) {
+ my $interface = $_[0];
+ my $interfaceref = $interfaces6{ $interface };
fatal_error "Unknown Interface ($interface)" unless $interfaceref;
@@ -842,6 +1199,13 @@ sub have_bridges() {
@bport_zones > 0;
}
+#
+# Returns true if there are bridge port zones defined in the config
+#
+sub have_6bridges() {
+ @bport_6zones > 0;
+}
+
#
# Return the bridge associated with the passed interface. If the interface is not a bridge port,
# return ''
@@ -851,6 +1215,15 @@ sub port_to_bridge( $ ) {
return $portref && $portref->{options}{port} ? $portref->{bridge} : '';
}
+#
+# Return the bridge associated with the passed interface. If the interface is not a bridge port,
+# return ''
+#
+sub port_to_6bridge( $ ) {
+ my $portref = $interfaces6{$_[0]};
+ return $portref && $portref->{options}{port} ? $portref->{bridge} : '';
+}
+
#
# Return the bridge associated with the passed interface.
#
@@ -859,6 +1232,14 @@ sub source_port_to_bridge( $ ) {
return $portref ? $portref->{bridge} : '';
}
+#
+# Return the bridge associated with the passed 6interface.
+#
+sub source_port_to_6bridge( $ ) {
+ my $portref = $interfaces6{$_[0]};
+ return $portref ? $portref->{bridge} : '';
+}
+
#
# Return the 'optional' setting of the passed interface
#
@@ -867,6 +1248,14 @@ sub interface_is_optional($) {
$optionsref && $optionsref->{optional};
}
+#
+# Return the 'optional' setting of the passed interface
+#
+sub interface6_is_optional($) {
+ my $optionsref = $interfaces6{$_[0]}{options};
+ $optionsref && $optionsref->{optional};
+}
+
#
# Returns reference to array of interfaces with the passed option
#
@@ -884,6 +1273,23 @@ sub find_interfaces_by_option( $ ) {
\@ints;
}
+#
+# Returns reference to array of interfaces6 with the passed option
+#
+sub find_interfaces6_by_option( $ ) {
+ my $option = $_[0];
+ my @ints = ();
+
+ for my $interface ( @interfaces ) {
+ my $optionsref = $interfaces{$interface}{options};
+ if ( $optionsref && defined $optionsref->{$option} ) {
+ push @ints , $interface
+ }
+ }
+
+ \@ints;
+}
+
#
# Return the value of an option for an interface
#
@@ -902,6 +1308,24 @@ sub set_interface_option( $$$ ) {
$interfaces{$interface}{options}{$option} = $value;
}
+#
+# Return the value of an option for an interface6
+#
+sub get_interface6_option( $$ ) {
+ my ( $interface, $option ) = @_;
+
+ $interfaces6{$interface}{options}{$option};
+}
+
+#
+# Set an option for an interface6
+#
+sub set_interface6_option( $$$ ) {
+ my ( $interface, $option, $value ) = @_;
+
+ $interfaces6{$interface}{options}{$option} = $value;
+}
+
#
# Validates the hosts file. Generates entries in %zone{..}{hosts}
#
@@ -910,10 +1334,8 @@ sub validate_hosts_file()
my %validoptions = (
blacklist => 1,
maclist => 1,
- norfc1918 => 1,
nosmurfs => 1,
routeback => 1,
- routefilter => 1,
tcpflags => 1,
broadcast => 1,
destonly => 1,
@@ -939,6 +1361,7 @@ sub validate_hosts_file()
fatal_error "Unknown ZONE ($zone)" unless $type;
fatal_error 'Firewall zone not allowed in ZONE column of hosts record' if $type eq 'firewall';
+ fatal_error 'IPv6 zones not allowed in ZONE column of hosts record' if $type =~ /6/;
my $interface;
@@ -951,7 +1374,7 @@ sub validate_hosts_file()
fatal_error "Invalid HOST(S) column contents: $hosts";
}
- if ( $type eq 'bport4' ) {
+ if ( $type eq 'bport6' ) {
if ( $zoneref->{bridge} eq '' ) {
fatal_error 'Bridge Port Zones may only be associated with bridge ports' unless $interfaces{$interface}{options}{port};
$zoneref->{bridge} = $interfaces{$interface}{bridge};
@@ -969,7 +1392,7 @@ sub validate_hosts_file()
for my $option ( @options )
{
if ( $option eq 'ipsec' ) {
- $type = 'ipsec4';
+ $type = 'ipsec6';
$zoneref->{options}{complex} = 1;
$ipsec = 1;
} elsif ( $validoptions{$option}) {
@@ -995,7 +1418,7 @@ sub validate_hosts_file()
#
# Take care of case where the hosts list begins with '!'
#
- $hosts = join( '', ALLIPv4 , $hosts ) if substr($hosts, 0, 2 ) eq ',!';
+ $hosts = join( '', ALLIPv6 , $hosts ) if substr($hosts, 0, 2 ) eq ',!';
add_group_to_zone( $zone, $type , $interface, [ split_list( $hosts, 'host' ) ] , $optionsref);
@@ -1005,6 +1428,108 @@ sub validate_hosts_file()
$capabilities{POLICY_MATCH} = '' unless $ipsec || haveipseczones;
}
+#
+# Validates the 6hosts file. Generates entries in %zone{..}{hosts}
+#
+sub validate_6hosts_file()
+{
+ my %validoptions = (
+ blacklist => 1,
+ maclist => 1,
+ nosmurfs => 1,
+ routeback => 1,
+ tcpflags => 1,
+ broadcast => 1,
+ destonly => 1,
+ sourceonly => 1,
+ );
+
+ my $ipsec = 0;
+ my $first_entry = 1;
+
+ my $fn = open_file '6hosts';
+
+ while ( read_a_line ) {
+
+ if ( $first_entry ) {
+ progress_message2 "$doing $fn...";
+ $first_entry = 0;
+ }
+
+ my ($zone, $hosts, $options ) = split_line 2, 3, '6hosts file';
+
+ my $zoneref = $zones{$zone};
+ my $type = $zoneref->{type};
+
+ fatal_error "Unknown ZONE ($zone)" unless $type;
+ fatal_error 'Firewall zone not allowed in ZONE column of hosts record' if $type eq 'firewall';
+ fatal_error 'IPv4 zonea not allowed in ZONE column of 6hosts record' if $type =~ /4/;
+
+ my $interface;
+
+ if ( $hosts =~ /^([\w.@%-]+\+?)\[(.*)\]$/ ) {
+ $interface = $1;
+ $hosts = $2;
+ $zoneref->{options}{complex} = 1 if $hosts =~ /^\+/;
+ fatal_error "Unknown 6interface ($interface)" unless $interfaces6{$interface}{root};
+ } else {
+ fatal_error "Invalid HOST(S) column contents: $hosts";
+ }
+
+ if ( $type eq 'bport6' ) {
+ if ( $zoneref->{bridge} eq '' ) {
+ fatal_error 'Bridge Port Zones may only be associated with bridge ports' unless $interfaces6{$interface}{options}{port};
+ $zoneref->{bridge} = $interfaces6{$interface}{bridge};
+ } elsif ( $zoneref->{bridge} ne $interfaces6{$interface}{bridge} ) {
+ fatal_error "Interface $interface is not a port on bridge $zoneref->{bridge}";
+ }
+ }
+
+ my $optionsref = {};
+
+ if ( $options ne '-' ) {
+ my @options = split_list $options, 'option';
+ my %options;
+
+ for my $option ( @options )
+ {
+ if ( $option eq 'ipsec' ) {
+ $type = 'ipsec6';
+ $zoneref->{options}{complex} = 1;
+ $ipsec = 1;
+ } elsif ( $validoptions{$option}) {
+ $options{$option} = 1;
+ } else {
+ fatal_error "Invalid option ($option)";
+ }
+ }
+
+ $optionsref = \%options;
+ }
+
+ #
+ # Looking for the '!' at the beginning of a list element is more straight-foward than looking for it in the middle.
+ #
+ # Be sure we don't have a ',!' in the original
+ #
+ fatal_error "Invalid hosts list" if $hosts =~ /,!/;
+ #
+ # Now add a comma before '!'. Do it globally - add_group_to_zone() correctly checks for multiple exclusions
+ #
+ $hosts =~ s/!/,!/g;
+ #
+ # Take care of case where the hosts list begins with '!'
+ #
+ $hosts = join( '', ALLIPv6 , $hosts ) if substr($hosts, 0, 2 ) eq ',!';
+
+ add_group_to_zone( $zone, $type , $interface, [ split_list( $hosts, 'host' ) ] , $optionsref);
+
+ progress_message " Host \"$currentline\" validated";
+ }
+
+ $capabilities{POLICY_MATCH} = 'Yes' if $ipsec || haveipseczones;
+}
+
#
# Returns a reference to a array of host entries. Each entry is a
# reference to an array containing ( interface , polciy match type {ipsec|none} , network );
@@ -1013,7 +1538,7 @@ sub find_hosts_by_option( $ ) {
my $option = $_[0];
my @hosts;
- for my $zone ( grep $zones{$_}{type} ne 'firewall' , @zones ) {
+ for my $zone ( grep $zones{$_}{family} == F_INET , @zones ) {
while ( my ($type, $interfaceref) = each %{$zones{$zone}{hosts}} ) {
while ( my ( $interface, $arrayref) = ( each %{$interfaceref} ) ) {
for my $host ( @{$arrayref} ) {
@@ -1028,7 +1553,7 @@ sub find_hosts_by_option( $ ) {
}
for my $interface ( @interfaces ) {
- if ( ! $interfaces{$interface}{zone4} && $interfaces{$interface}{options}{$option} ) {
+ if ( ! $interfaces{$interface}{zone} && $interfaces{$interface}{options}{$option} ) {
push @hosts, [ $interface, 'none', ALLIPv4 ];
}
}
@@ -1036,4 +1561,35 @@ sub find_hosts_by_option( $ ) {
\@hosts;
}
+#
+# Returns a reference to a array of host entries. Each entry is a
+# reference to an array containing ( interface , polciy match type {ipsec|none} , network );
+#
+sub find_6hosts_by_option( $ ) {
+ my $option = $_[0];
+ my @hosts;
+
+ for my $zone ( grep $zones{$_}{family} == F_INET6 , @zones ) {
+ while ( my ($type, $interfaceref) = each %{$zones{$zone}{hosts}} ) {
+ while ( my ( $interface, $arrayref) = ( each %{$interfaceref} ) ) {
+ for my $host ( @{$arrayref} ) {
+ if ( $host->{options}{$option} ) {
+ for my $net ( @{$host->{hosts}} ) {
+ push @hosts, [ $interface, $host->{ipsec} , $net ];
+ }
+ }
+ }
+ }
+ }
+ }
+
+ for my $interface ( @interfaces6 ) {
+ if ( ! $interfaces6{$interface}{zone} && $interfaces6{$interface}{options}{$option} ) {
+ push @hosts, [ $interface, 'none', ALLIPv6 ];
+ }
+ }
+
+ \@hosts;
+}
+
1;
diff --git a/manpages/shorewall-interfaces.xml b/manpages/shorewall-interfaces.xml
index 555c794dc..98a5376c7 100644
--- a/manpages/shorewall-interfaces.xml
+++ b/manpages/shorewall-interfaces.xml
@@ -1,4 +1,6 @@
+
shorewall-interfaces
@@ -95,9 +97,9 @@ loc eth2 -
The broadcast address(es) for the network(s) to which the
- interface belongs. For P-T-P interfaces, this column is left
- blank. If the interface has multiple addresses on multiple subnets
- then list the broadcast addresses as a comma-separated list.
+ interface belongs. For P-T-P interfaces, this column is left blank.
+ If the interface has multiple addresses on multiple subnets then
+ list the broadcast addresses as a comma-separated list.
If you use the special value detect, Shorewall will detect the broadcast
@@ -257,8 +259,8 @@ loc eth2 -
- you have a static IP but are on a LAN segment with
- lots of DHCP clients.
+ the interface has a static IP but is on a LAN
+ segment with lots of DHCP clients.
@@ -266,6 +268,9 @@ loc eth2 -
port and DHCP clients on another port.
+
+ This option allows DHCP datagrams to enter and leave the
+ interface.
diff --git a/docs/images/100_0269.jpg b/web/images/100_0269.jpg
similarity index 100%
rename from docs/images/100_0269.jpg
rename to web/images/100_0269.jpg