From a9a379c5a52df2f868ed57f9b985917663fcdd4c Mon Sep 17 00:00:00 2001 From: Tom Eastep Date: Mon, 22 Jan 2018 14:38:25 -0800 Subject: [PATCH] Implement INPUT SNAT Signed-off-by: Tom Eastep --- Shorewall-core/lib.cli | 5 +- Shorewall/Perl/Shorewall/Chains.pm | 4 + Shorewall/Perl/Shorewall/Config.pm | 12 ++- Shorewall/Perl/Shorewall/Rules.pm | 111 ++++++++++++++++---------- Shorewall/manpages/shorewall-snat.xml | 16 ++-- 5 files changed, 96 insertions(+), 52 deletions(-) diff --git a/Shorewall-core/lib.cli b/Shorewall-core/lib.cli index 6b8c9262b..29f583ffa 100644 --- a/Shorewall-core/lib.cli +++ b/Shorewall-core/lib.cli @@ -25,7 +25,7 @@ # loaded after this one and replaces some of the functions declared here. # -SHOREWALL_CAPVERSION=50106 +SHOREWALL_CAPVERSION=50112 if [ -z "$g_basedir" ]; then # @@ -2863,6 +2863,7 @@ determine_capabilities() { qt $g_tool -t nat -A $chain -j NETMAP --to 2001:470:B:227::/64 && NETMAP_TARGET=Yes fi qt $g_tool -t nat -A $chain -j MASQUERADE && MASQUERADE_TGT=Yes + qt $g_tool -t nat -L INPUT -n && NAT_INPUT_CHAIN=Yes qt $g_tool -t nat -A $chain -p udplite -m multiport --dport 33 -j REDIRECT --to-port 22 && UDPREDIRECT=Yes qt $g_tool -t nat -F $chain qt $g_tool -t nat -X $chain @@ -3331,6 +3332,7 @@ report_capabilities_unsorted() { report_capability "NFQUEUE CPU Fanout (CPU_FANOUT)" $CPU_FANOUT report_capability "NETMAP Target (NETMAP_TARGET)" $NETMAP_TARGET report_capability "--nflog-size support (NFLOG_SIZE)" $NFLOG_SIZE + report_capability "INPUT chain in nat table (NAT_INPUT_CHAIN)" $NAT_INPUT_CHAIN echo " Kernel Version (KERNELVERSION): $KERNELVERSION" echo " Capabilities Version (CAPVERSION): $CAPVERSION" @@ -3439,6 +3441,7 @@ report_capabilities_unsorted1() { report_capability1 NETMAP_TARGET report_capability1 NFLOG_SIZE report_capability1 RESTORE_WAIT_OPTION + report_capability1 NAT_INPUT_CHAIN report_capability1 AMANDA_HELPER report_capability1 FTP_HELPER diff --git a/Shorewall/Perl/Shorewall/Chains.pm b/Shorewall/Perl/Shorewall/Chains.pm index bf7632b83..223f9f927 100644 --- a/Shorewall/Perl/Shorewall/Chains.pm +++ b/Shorewall/Perl/Shorewall/Chains.pm @@ -3182,6 +3182,8 @@ sub initialize_chain_table($) { new_builtin_chain 'nat', $chain, 'ACCEPT'; } + new_builtin_chain 'nat', 'INPUT', 'ACCEPT' if have_capability('NAT_INPUT_CHAIN'); + for my $chain ( qw(PREROUTING INPUT OUTPUT ) ) { new_builtin_chain 'mangle', $chain, 'ACCEPT'; } @@ -3244,6 +3246,8 @@ sub initialize_chain_table($) { new_builtin_chain 'nat', $chain, 'ACCEPT'; } + new_builtin_chain 'nat', 'INPUT', 'ACCEPT' if have_capability('NAT_INPUT_CHAIN'); + for my $chain ( qw(PREROUTING INPUT OUTPUT FORWARD POSTROUTING ) ) { new_builtin_chain 'mangle', $chain, 'ACCEPT'; } diff --git a/Shorewall/Perl/Shorewall/Config.pm b/Shorewall/Perl/Shorewall/Config.pm index ea2d3077c..ba0728132 100644 --- a/Shorewall/Perl/Shorewall/Config.pm +++ b/Shorewall/Perl/Shorewall/Config.pm @@ -496,6 +496,7 @@ our %capdesc = ( NAT_ENABLED => 'NAT', NFLOG_SIZE => '--nflog-size support', RESTORE_WAIT_OPTION => 'iptables-restore --wait option', + NAT_INPUT_CHAIN => 'INPUT chain in NAT table', AMANDA_HELPER => 'Amanda Helper', FTP_HELPER => 'FTP Helper', FTP0_HELPER => 'FTP-0 Helper', @@ -835,7 +836,7 @@ sub initialize( $;$$$) { EXPORT => 0, KLUDGEFREE => '', VERSION => "5.1.8-Beta1", - CAPVERSION => 50106 , + CAPVERSION => 50112 , BLACKLIST_LOG_TAG => '', RELATED_LOG_TAG => '', MACLIST_LOG_TAG => '', @@ -1129,6 +1130,7 @@ sub initialize( $;$$$) { NETMAP_TARGET => undef, NFLOG_SIZE => undef, RESTORE_WAIT_OPTION => undef, + NAT_INPUT_CHAIN => undef, AMANDA_HELPER => undef, FTP_HELPER => undef, @@ -4446,6 +4448,12 @@ sub Nat_Enabled() { qt1( "$iptables $iptablesw -t nat -L -n" ); } +sub Nat_Input_Chain { + have_capability( 'NAT_ENABLED' ) || return ''; + + qt1( "$iptables $iptablesw -t nat -L INPUT -n" ); +} + sub Persistent_Snat() { have_capability( 'NAT_ENABLED' ) || return ''; @@ -5091,6 +5099,7 @@ our %detect_capability = MASQUERADE_TGT => \&Masquerade_Tgt, MULTIPORT => \&Multiport, NAT_ENABLED => \&Nat_Enabled, + NAT_INPUT_CHAIN => \&Nat_Input_Chain, NETBIOS_NS_HELPER => \&Netbios_ns_Helper, NETMAP_TARGET => \&Netmap_Target, NEW_CONNTRACK_MATCH => \&New_Conntrack_Match, @@ -5190,6 +5199,7 @@ sub determine_capabilities() { # $capabilities{NAT_ENABLED} = detect_capability( 'NAT_ENABLED' ); $capabilities{PERSISTENT_SNAT} = detect_capability( 'PERSISTENT_SNAT' ); + $capabilities{NAT_INPUT_CHAIN} = detect_capability( 'NAT_INPUT_CHAIN' ); $capabilities{MANGLE_ENABLED} = detect_capability( 'MANGLE_ENABLED' ); if ( $capabilities{CONNTRACK_MATCH} = detect_capability( 'CONNTRACK_MATCH' ) ) { diff --git a/Shorewall/Perl/Shorewall/Rules.pm b/Shorewall/Perl/Shorewall/Rules.pm index a4ada5e7a..7baaf7968 100644 --- a/Shorewall/Perl/Shorewall/Rules.pm +++ b/Shorewall/Perl/Shorewall/Rules.pm @@ -5501,22 +5501,6 @@ sub process_snat1( $$$$$$$$$$$$ ) { $interfaces = $dest; } # - # Handle IPSEC options, if any - # - if ( $ipsec ne '-' ) { - fatal_error "Non-empty IPSEC column requires policy match support in your kernel and iptables" unless have_capability( 'POLICY_MATCH' ); - - if ( $ipsec =~ /^yes$/i ) { - $baserule .= do_ipsec_options 'out', 'ipsec', ''; - } elsif ( $ipsec =~ /^no$/i ) { - $baserule .= do_ipsec_options 'out', 'none', ''; - } else { - $baserule .= do_ipsec_options 'out', 'ipsec', $ipsec; - } - } elsif ( have_ipsec ) { - $baserule .= '-m policy --pol none --dir out '; - } - # # Handle Protocol, Ports and Condition # $baserule .= do_proto( $proto, $ports, '' ); @@ -5527,7 +5511,9 @@ sub process_snat1( $$$$$$$$$$$$ ) { $baserule .= do_user( $user ) if $user ne '-'; $baserule .= do_probability( $probability ) if $probability ne '-'; - for my $fullinterface ( split_list( $interfaces, 'interface' ) ) { + my @interfaces = split_list( $interfaces, 'interface' ); + + for my $fullinterface ( @interfaces ) { my $rule = ''; my $saveaddresses = $addresses; @@ -5535,36 +5521,71 @@ sub process_snat1( $$$$$$$$$$$$ ) { my $savebaserule = $baserule; my $interface = $fullinterface; - $interface =~ s/:.*//; #interface name may include 'alias' - - unless ( $inaction ) { - if ( $interface =~ /(.*)[(](\w*)[)]$/ ) { - $interface = $1; - my $provider = $2; - - fatal_error "Missing Provider ($dest)" unless supplied $provider; - - $dest =~ s/[(]\w*[)]//; - my $realm = provider_realm( $provider ); - - fatal_error "$provider is not a shared-interface provider" unless $realm; - - $rule .= "-m realm --realm $realm "; - } - - fatal_error "Unknown interface ($interface)" unless my $interfaceref = known_interface( $interface ); - - if ( $interfaceref->{root} ) { - $interface = $interfaceref->{name} if $interface eq $interfaceref->{physical}; + if ( $inaction ) { + $interface =~ s/:.*// if $family == F_IPV4; #interface name may include 'alias' + } else { + if ( $interface eq firewall_zone ) { + if ( @interfaces == 1 ) { + fatal_error q('+' not valid when the DEST is $FW) if $pre_nat; + fatal_error q('MASQUERADE' not allowed when DEST is $FW) if $action eq 'MASQUERADE'; + require_capability 'NAT_INPUT_CHAIN', '$FW in the DEST column', 's'; + $interface = ''; + } else { + fatal_error q($FW may not appear in a list of interfaces); + } } else { - $rule .= match_dest_dev( $interface ); - $interface = $interfaceref->{name}; + $interface =~ s/:.*// if $family == F_IPV4; #interface name may include 'alias' + + if ( $interface =~ /(.*)[(](\w*)[)]$/ ) { + $interface = $1; + my $provider = $2; + + fatal_error "Missing Provider ($dest)" unless supplied $provider; + + $dest =~ s/[(]\w*[)]//; + my $realm = provider_realm( $provider ); + + fatal_error "$provider is not a shared-interface provider" unless $realm; + + $rule .= "-m realm --realm $realm "; + } + + fatal_error "Unknown interface ($interface)" unless my $interfaceref = known_interface( $interface ); + + if ( $interfaceref->{root} ) { + $interface = $interfaceref->{name} if $interface eq $interfaceref->{physical}; + } else { + $rule .= match_dest_dev( $interface ); + $interface = $interfaceref->{name}; + } } - $chainref = ensure_chain('nat', $pre_nat ? snat_chain $interface : masq_chain $interface); + $chainref = $interface ? ensure_chain('nat', $pre_nat ? snat_chain $interface : masq_chain $interface) : $nat_table->{INPUT}; } $baserule .= do_condition( $condition , $chainref->{name} ); + # + # Handle IPSEC options, if any + # + if ( $ipsec ne '-' ) { + fatal_error "Non-empty IPSEC column requires policy match support in your kernel and iptables" unless have_capability( 'POLICY_MATCH' ); + + my $dir = $interface ? 'out' : 'in'; + + if ( $ipsec =~ /^yes$/i ) { + $baserule .= do_ipsec_options $dir, 'ipsec', ''; + } elsif ( $ipsec =~ /^no$/i ) { + $baserule .= do_ipsec_options $dir, 'none', ''; + } else { + $baserule .= do_ipsec_options $dir, 'ipsec', $ipsec; + } + } elsif ( have_ipsec ) { + if ( $interface ) { + $baserule .= '-m policy --pol none --dir out '; + } else { + $baserule .= '-m policy --pol none --dir in '; + } + } my $detectaddress = 0; my $exceptionrule = ''; @@ -5572,6 +5593,7 @@ sub process_snat1( $$$$$$$$$$$$ ) { if ( $action eq 'SNAT' ) { if ( $addresses eq 'detect' ) { + fatal_error q('detect' not allowed when the destination is $FW) unless $interface; my $variable = get_interface_address $interface; $target .= " --to-source $variable"; @@ -5698,6 +5720,7 @@ sub process_snat1( $$$$$$$$$$$$ ) { $target .= $addrlist; } } elsif ( $action eq 'MASQUERADE' ) { + fatal_error q('MASQUERADE' not allowed when the destination is $FW') unless $interface; if ( supplied $addresses ) { validate_portpair1($proto, $addresses ); $target .= " --to-ports $addresses"; @@ -5715,7 +5738,7 @@ sub process_snat1( $$$$$$$$$$$$ ) { $params, $loglevel, $source, - supplied( $destnets ) && $destnets ne '-' ? $inaction ? $destnets : join( ':', $interface, $destnets ) : $inaction ? '-' : $interface, + supplied( $destnets ) && $destnets ne '-' ? $inaction || $interface ? join( ':', $interface, $destnets ) : $destnets : $inaction ? '-' : $interface, $proto, $ports, $ipsec, @@ -5780,7 +5803,7 @@ sub process_snat1( $$$$$$$$$$$$ ) { $destnets = ALLIP unless supplied $destnets && $destnets ne '-'; expand_rule( $chainref , - POSTROUTE_RESTRICT , + $interface ? POSTROUTE_RESTRICT : INPUT_RESTRICT , $prerule , $baserule . $inlinematches . $rule , $source , @@ -5795,7 +5818,7 @@ sub process_snat1( $$$$$$$$$$$$ ) { conditional_rule_end( $chainref ) if $detectaddress || $conditional; - if ( $add_snat_aliases && $addresses ) { + if ( $interface && $add_snat_aliases && $addresses ) { my ( $interface, $alias , $remainder ) = split( /:/, $fullinterface, 3 ); fatal_error "Invalid alias ($alias:$remainder)" if defined $remainder; for my $address ( split_list $addresses, 'address' ) { diff --git a/Shorewall/manpages/shorewall-snat.xml b/Shorewall/manpages/shorewall-snat.xml index d6182dbd6..9ce9f8113 100644 --- a/Shorewall/manpages/shorewall-snat.xml +++ b/Shorewall/manpages/shorewall-snat.xml @@ -253,10 +253,10 @@ Set of hosts that you wish to masquerade. You can specify this as an address (net or host) or as an - interface (use of an - interface is deprecated). If you give the name - of an interface, the interface must be up before you start the - firewall and the Shorewall rules compiler will warn you of that + interface. Unless you want to perform SNAT in + the INPUT chain (see DEST below), if you give the name of an + interface (deprecated), the interface must be up before you start + the firewall and the Shorewall rules compiler will warn you of that fact. (Shorewall will use your main routing table to determine the appropriate addresses to masquerade). @@ -268,9 +268,9 @@ DEST - - interface[interface[:digit][,interface[:digit]]...[:digit]]...|$FW}[:[dest-address[,dest-address]...[exclusion]] @@ -286,6 +286,10 @@ is the only use for the alias name; it may not appear in any other place in your Shorewall configuration. + Beginning with Shorewall 5.1.12, SNAT may be performed in the + nat table's INPUT chain by specifying $FW rather than one or more + interfaces. + Each interface must match an entry in shorewall-interfaces(5). Shorewall allows loose matches to wildcard entries in