#!/bin/sh # # Shorewall 4.0 -- /usr/share/shorewall/lib.nat # # This program is under GPL [http://www.gnu.org/copyleft/gpl.htm] # # (c) 1999,2000,2001,2002,2003,2004,2005,2006,2007 - Tom Eastep (teastep@shorewall.net) # # Complete documentation is available at http://shorewall.net # # This program is free software; you can redistribute it and/or modify # it under the terms of Version 2 of the GNU General Public License # as published by the Free Software Foundation. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA # # This library is loaded by /usr/share/shorewall/compiler when any of the following # configuration files are non-empty: masq, nat, netmap; or when there are # DNAT/REDIRECT rules in the /etc/shorewall/rules file. # # # Set up Source NAT (including masquerading) # setup_masq() { local comment= do_ipsec_options() { local options="$(separate_list $ipsec)" option [ -n "$ORIGINAL_POLICY_MATCH" ] || \ fatal_error "IPSEC options require policy match support in your kernel and iptables" policy="-m policy --pol ipsec --dir out" for option in $options; do case $option in [Yy]es) ;; strict) policy="$policy --strict" ;; next) policy="$policy --next" ;; reqid=*) policy="$policy --reqid ${option#*=}" ;; spi=*) policy="$policy --spi ${option#*=}" ;; proto=*) policy="$policy --proto ${option#*=}" ;; mode=*) policy="$policy --mode ${option#*=}" ;; tunnel-src=*) policy="$policy --tunnel-src ${option#*=}" ;; tunnel-dst=*) policy="$policy --tunnel-dst ${option#*=}" ;; reqid!=*) policy="$policy ! --reqid ${option#*=}" ;; spi!=*) policy="$policy ! --spi ${option#*=}" ;; proto!=*) policy="$policy ! --proto ${option#*=}" ;; mode!=*) policy="$policy ! --mode ${option#*=}" ;; tunnel-src!=*) policy="$policy ! --tunnel-src ${option#*=}" ;; tunnel-dst!=*) policy="$policy ! --tunnel-dst ${option#*=}" ;; *) fatal_error "Invalid IPSEC option \"$option\"" ;; esac done } setup_one() { local add_snat_aliases=$ADD_SNAT_ALIASES pre_nat= policy= destnets= [ "x$ipsec" = x- ] && ipsec= case $ipsec in Yes|yes) [ -n "$ORIGINAL_POLICY_MATCH" ] || \ fatal_error "IPSEC=Yes requires policy match support in your kernel and iptables" policy="-m policy --pol ipsec --dir out" ;; No|no) [ -n "$ORIGINAL_POLICY_MATCH" ] || \ fatal_error "IPSEC=No requires policy match support in your kernel and iptables" policy="-m policy --pol none --dir out" ;; *) if [ -n "$ipsec" ]; then do_ipsec_options elif [ -n "$POLICY_MATCH" ]; then policy="-m policy --pol none --dir out" fi ;; esac case $fullinterface in +*) pre_nat=Yes fullinterface=${fullinterface#+} ;; esac case $fullinterface in *::*) add_snat_aliases= destnets="${fullinterface##*:}" fullinterface="${fullinterface%:*}" ;; *:*:*) # Both alias name and networks destnets="${fullinterface##*:}" fullinterface="${fullinterface%:*}" ;; *:) add_snat_aliases= fullinterface=${fullinterface%:} ;; *:*) # Alias name OR networks case ${fullinterface#*:} in *.*) # It's a networks destnets="${fullinterface#*:}" fullinterface="${fullinterface%:*}" ;; *) #it's an alias name ;; esac ;; *) ;; esac interface=${fullinterface%:*} if ! list_search $interface $ALL_INTERFACES; then fatal_error "Unknown interface $interface" fi if [ "$networks" = "${networks%!*}" ]; then nomasq= else nomasq="${networks#*!}" networks="${networks%!*}" fi source="${networks:=0.0.0.0/0}" detectinterface= case $source in *.*.*|+*|!+*) ;; *) detectinterface=$networks networks= ;; esac [ "x$proto" = x- ] && proto= [ "x$ports" = x- ] && ports= [ "x$mark" = x- ] && mark= if [ -n "$proto" ]; then displayproto="($proto)" case $proto in tcp|TCP|udp|UDP|6|17) if [ -n "$ports" ]; then displayproto="($proto $ports)" listcount=$(list_count $ports) if [ $listcount -gt 1 ]; then case $ports in *:*) if [ -n "$XMULTIPORT" ]; then if [ $(($listcount + $(list_count1 $(split $ports) ) )) -le 16 ]; then ports="-m multiport --dports $ports" else fatal_error "More than 15 entries in port list ($ports)" fi else fatal_error "Port Range not allowed in list ($ports)" fi ;; *) if [ -n "$MULTIPORT" ]; then [ $listcount -le 15 ] || fatal_error "More than 15 entries in port list ($ports)" ports="-m multiport --dports $ports" else fatal_error "Port Ranges require multiport match support in your kernel ($ports)" fi ;; esac else ports="--dport $ports" fi fi ;; *) [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)" ;; esac proto="-p $proto" else displayproto="(all)" [ -n "$ports" ] && fatal_error "Ports only allowed with UDP or TCP ($ports)" fi if [ -n "$mark" ]; then displaymark="($mark)" if [ "$mark" = "${mark%!*}" ]; then mark="-m mark --mark $mark" else mark="-m mark ! --mark ${mark#*!}" fi fi destination=${destnets:=0.0.0.0/0} [ -z "$pre_nat" ] && chain=$(masq_chain $interface) || chain=$(snat_chain $interface) ensurenatchain $chain case $destnets in !*) destnets=${destnets#!} build_exclusion_chain newchain nat "$nomasq" "$destnets" if [ -n "$networks" ]; then for s in $networks; do addnatrule $chain $(source_ip_range $s) $proto $ports $mark $policy -j $newchain done networks= elif [ -n "$detectinterface" ]; then indent >&3 << __EOF__ networks="\$(get_routed_networks $detectinterface)" [ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\"" for network in \$networks; do run_iptables -t nat -A $chain -s \$network $proto $ports $mark $policy -j $newchain done __EOF__ else addnatrule $chain -j $newchain fi chain=$newchain destnets=0.0.0.0/0 proto= ports= policy= detectinterface= [ -n "$nomasq" ] && source="$source except $nomasq" ;; *) if [ -n "$nomasq" ]; then build_exclusion_chain newchain nat $nomasq if [ -n "$networks" ]; then for s in $networks; do for destnet in $(separate_list $destnets); do addnatrule $chain $(both_ip_ranges $s $destnet) $proto $ports $mark $policy -j $newchain done done elif [ -n "$detectinterface" ]; then indent >&3 << __EOF__ networks="\$(get_routed_networks $detectinterface)" [ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\"" for network in \$networks; do __EOF__ for destnet in $(separate_list $destnets); do indent >&3 << __EOF__ run_iptables -t nat -A $chain -s \$network $(dest_ip_range $destnet) $proto $ports $mark $policy -j $newchain __EOF__ done indent >&3 << __EOF__ done __EOF__ else for destnet in $(separate_list $destnets); do addnatrule $chain $(dest_ip_range $destnet) $proto $ports $mark $policy -j $newchain done fi chain=$newchain networks= destnets=0.0.0.0/0 proto= ports= policy= detectinterface= source="$source except $nomasq" fi ;; esac addrlist= target=MASQUERADE [ "x$addresses" = x- ] && addresses= if [ -n "$addresses" ]; then case "$addresses" in SAME:nodst:*) target="SAME --nodst" addresses=${addresses#SAME:nodst:} if [ "$addresses" = detect ]; then addrlist='$addrlist' else for address in $(separate_list $addresses); do addrlist="$addrlist --to $address"; done fi ;; SAME:*) target="SAME" addresses=${addresses#SAME:} if [ "$addresses" = detect ]; then addrlist='$addrlist' else for address in $(separate_list $addresses); do addrlist="$addrlist --to $address"; done fi ;; detect) target=SNAT addrlist='$addrlist' ;; *) for address in $(separate_list $addresses); do case $address in *.*.*.*) target=SNAT addrlist="$addrlist --to-source $address" ;; *) addrlist="$addrlist --to-ports ${address#:}" ;; esac done ;; esac if [ "$addrlist" = '$addrlist' ]; then addresses='$(combine_list $addresses)' indent >&3 << __EOF__ addrlist= addresses=\$(find_interface_addresses $interface) if [ -n "\$addresses" ]; then for address in \$addresses; do addrlist="$addrlist --to-source \$address" done else fatal_error "Unable to determine the IP address(es) of $interface" fi __EOF__ elif [ -n "$add_snat_aliases" ]; then for address in $(separate_list $addresses); do address=${address%:)} if [ -n "$address" ]; then for addr in $(ip_range_explicit ${address%:*}) ; do if ! list_search $addr $ALIASES_TO_ADD; then [ -n "$RETAIN_ALIASES" ] || save_command del_ip_addr $addr $interface ALIASES_TO_ADD="$ALIASES_TO_ADD $addr $fullinterface" case $fullinterface in *:*) fullinterface=${fullinterface%:*}:$((${fullinterface#*:} + 1 )) ;; esac fi done fi done fi fi if [ -n "$networks" ]; then for network in $networks; do for destnet in $(separate_list $destnets); do addnatrule $chain $(both_ip_ranges $network $destnet) $proto $ports $mark $policy -j $target $addrlist done if [ -n "$addresses" ]; then progress_message_and_save " To $destination $displayproto from $network through ${interface} using $addresses" else progress_message_and_save " To $destination $displayproto from $network through ${interface}" fi done elif [ -n "$detectinterface" ]; then indent >&3 << __EOF__ networks="\$(get_routed_networks $detectinterface)" [ -z "\$networks" ] && fatal_error "Unable to determine the routes through interface \"$detectinterface\"" for network in \$networks; do __EOF__ for destnet in $(separate_list $destnets); do indent >&3 << __EOF__ run_iptables -t nat -A $chain -s \$network $(dest_ip_range $destnet) $proto $ports $mark $policy -j $target $addrlist __EOF__ done if [ -n "$addresses" ]; then message=" To $destination $displayproto from \$network through ${interface} using $addresses" else message=" To $destination $displayproto from \$network through ${interface}" fi indent >&3 << __EOF__ progress_message "$message" done __EOF__ else for destnet in $(separate_list $destnets); do addnatrule $chain $(dest_ip_range $destnet) $proto $ports $mark $policy -j $target $addrlist done if [ -n "$addresses" ]; then progress_message_and_save " To $destination $displayproto from $source through ${interface} using $addresses" else progress_message_and_save " To $destination $displayproto from $source through ${interface}" fi fi } #setup_one() if [ -s $TMP_DIR/masq ]; then progress_message2 "$DOING Masquerading/SNAT" save_progress_message "Setting up Masquerading/SNAT..." while read fullinterface networks addresses proto ports ipsec mark; do if [ -n "$NAT_ENABLED" ]; then if [ "x$fullinterface" = xCOMMENT ]; then if [ -n "$COMMENTS" ]; then comment=$(echo $networks $addresses $proto $ports $ipsec $mark) save_command COMMENT=\"$comment\" else error_message "COMMENT ignored -- requires comment support in iptables/Netfilter" fi else setup_one fi else error_message "WARNING: NAT disabled; masq rule ignored" fi done < $TMP_DIR/masq # # Just in case the file ended with a comment # if [ -n "$COMMENTS" ]; then save_command save_command COMMENT= save_command fi fi } # # Setup Static Network Address Translation (NAT) # setup_nat() { local external= interface= internal= allints= localnat= policyin= policyout= comment= validate_one() #1 = Variable Name, $2 = Column name, $3 = value { case $3 in Yes|yes) ;; No|no) eval ${1}= ;; *) [ -n "$3" ] && \ fatal_error "Invalid value ($3) for $2 in entry \"$external $interface $internal $allints $localnat\"" ;; esac } do_one_nat() { local add_ip_aliases=$ADD_IP_ALIASES iface=${interface%:*} if [ -n "$add_ip_aliases" ]; then case $interface in *:) interface=${interface%:} add_ip_aliases= ;; *) [ -n "$RETAIN_ALIASES" ] || save_command del_ip_addr $external $iface ;; esac else interface=${interface%:} fi validate_one allints "ALL INTERFACES" $allints validate_one localnat "LOCAL" $localnat if [ -n "$allints" ]; then addnatrule nat_in -d $external $policyin -j DNAT --to-destination $internal addnatrule nat_out -s $internal $policyout -j SNAT --to-source $external else addnatrule $(input_chain $iface) -d $external $policyin -j DNAT --to-destination $internal addnatrule $(output_chain $iface) -s $internal $policyout -j SNAT --to-source $external fi [ -n "$localnat" ] && \ run_iptables2 -t nat -A OUTPUT -d $external $policyout -j DNAT --to-destination $internal if [ -n "$add_ip_aliases" ]; then list_search $external $ALIASES_TO_ADD || \ ALIASES_TO_ADD="$ALIASES_TO_ADD $external $interface" fi } # # At this point, we're just interested in the network translation # > $STATEDIR/nat if [ -n "$POLICY_MATCH" ]; then policyin="-m policy --pol none --dir in" policyout="-m policy --pol none --dir out" fi if [ -s $TMP_DIR/nat ]; then save_progress_message "Setting up one-to-one NAT..." while read external interface internal allints localnat; do if [ "x$external" = xCOMMENT ]; then if [ -n "$COMMENTS" ]; then comment=$(echo $interface $internal $allints $localnat) save_command COMMENT=\"$comment\" else error_message "COMMENT ignored -- requires comment support in iptables/Netfilter" fi else do_one_nat fi progress_message_and_save " Host $internal NAT $external on $interface" done < $TMP_DIR/nat if [ -n "$COMMENTS" ]; then save_command save_command COMMENT= save_command fi fi } # # Setup Network Mapping (NETMAP) # setup_netmap() { while read type net1 interface net2 ; do list_search $interface $ALL_INTERFACES || \ fatal_error "Unknown interface $interface in entry \"$type $net1 $interface $net2\"" case $type in DNAT) addnatrule $(input_chain $interface) -d $net1 -j NETMAP --to $net2 ;; SNAT) addnatrule $(output_chain $interface) -s $net1 -j NETMAP --to $net2 ;; *) fatal_error "Invalid type $type in entry \"$type $net1 $interface $net2\"" ;; esac progress_message_and_save " Network $net1 on $interface mapped to $net2 ($type)" done < $TMP_DIR/netmap } # # Add a NAT rule - Helper function for the rules file processor # # The caller has established the following variables: # cli = Source IP, interface or MAC Specification # serv = Destination IP Specification # servport = Port the server is listening on # dest_interface = Destination Interface Specification # proto = Protocol Specification # addr = Original Destination Address # dports = Destination Port Specification. 'dports' may be changed # by this function # cport = Source Port Specification # multiport = String to invoke multiport match if appropriate # ratelimit = Optional rate limiting clause # userandgroup = -m owner match to limit the rule to a particular user and/or group # logtag = Log tag # excludesource = Source Exclusion List # add_nat_rule() { local chain local excludedests= # Be sure we can NAT if [ -z "$NAT_ENABLED" ]; then fatal_error "Rule \"$rule\" requires NAT which is disabled" fi # Parse SNAT address if any if [ "$addr" != "${addr%:*}" ]; then fatal_error "SNAT may no longer be specified in a DNAT rule; use ${CONFDIR}/masq instead" fi # Set original destination address case $addr in all) addr= ;; detect) eval interfaces=\$${source}_interfaces if [ -n "$DETECT_DNAT_IPADDRS" -a "$source" != "$FW" ]; then save_command if [ $(list_count1 $interfaces) -eq 1 ]; then save_command "addr=\$(find_first_interface_address $interfaces)" else save_command "addr=" for interface in $interfaces; do ident >&3 << __EOF__ addr="\$addr \$(find_first_interface_address $interface)" __EOF__ done fi else addr= fi ;; !*) if [ $(list_count $addr) -gt 1 ]; then excludedests="${addr#\!}" addr= fi ;; esac addr=${addr:-0.0.0.0/0} # Select target if [ "$logtarget" = SAME ]; then [ -n "$servport" ] && fatal_error "Port mapping not allowed in SAME rules" serv1= for srv in $(separate_list $serv); do serv1="$serv1 --to ${srv}" done target1="SAME $serv1" elif [ -n "$serv" ]; then servport="${servport:+:$servport}" serv1= for srv in $(separate_list $serv); do serv1="$serv1 --to-destination ${srv}${servport}" done target1="DNAT $serv1" else target1="REDIRECT --to-port $servport" fi # Generate nat table rules if [ "$source" = "$FW" ]; then if [ -n "${excludesource}${excludedests}" ]; then build_exclusion_chain chain nat "$excludesource" $excludedests for adr in $(separate_list $addr); do run_iptables2 -t nat -A OUTPUT $cli $proto $userandgroup $multiport $sports $dports $(dest_ip_range $adr) -j $chain done if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat fi addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection else for adr in $(separate_list $addr); do if [ -n "$loglevel" ]; then log_rule_limit $loglevel OUTPUT OUTPUT $logtarget "$ratelimit" "$logtag" -A -t nat \ $(fix_bang $proto $cli $sports $userandgroup $(dest_ip_range $adr) $multiport $dports) fi run_iptables2 -t nat -A OUTPUT $ratelimit $proto $sports $userandgroup $(dest_ip_range $adr) $multiport $dports -j $target1 done fi else if [ -n "${excludesource}${excludedests}" ]; then build_exclusion_chain chain nat "$excludesource" $excludedests if [ $addr = detect ]; then ensurenatchain $(dnat_chain $source) # # The 'for loops' begun below are completed in add_a_rule() (in the compiler) # indent >&3 << __EOF__ for adr in \$addr; do run_iptables -t nat -A $(fix_bang $(dnat_chain $source) $cli $proto $multiport $sports $dports) -d \$adr -j $chain __EOF__ else for adr in $(separate_list $addr); do addnatrule $(dnat_chain $source) $cli $proto $multiport $sports $dports $(dest_ip_range $adr) -j $chain done fi if [ -n "$loglevel" ]; then log_rule_limit $loglevel $chain $(dnat_chain $source) $logtarget "$ratelimit" "$logtag" -A -t nat fi addnatrule $chain $ratelimit $proto -j $target1 # Protocol is necessary for port redirection else chain=$(dnat_chain $source) if [ $addr = detect ]; then ensurenatchain $chain indent >&3 << __EOF__ for adr in \$addr; do __EOF__ if [ -n "$loglevel" ]; then indent >&3 << __EOF__ log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A -t nat $(fix_bang $proto $cli $sports $multiport $dports) -d \$adr __EOF__ fi indent >&3 << __EOF__ run_iptables -t nat -A $chain $(fix_bang $proto $ratelimit $cli $sports $multiport $dports) -d \$adr -j $target1 __EOF__ else for adr in $(separate_list $addr); do if [ -n "$loglevel" ]; then ensurenatchain $chain log_rule_limit $loglevel $chain $chain $logtarget "$ratelimit" "$logtag" -A -t nat \ $(fix_bang $proto $cli $sports $(dest_ip_range $adr) $multiport $dports) fi addnatrule $chain $proto $ratelimit $cli $sports \ -d $adr $multiport $dports -j $target1 done fi fi fi # Replace destination port by the new destination port if [ -n "$servport" ]; then if [ -z "$multiport" ]; then dports="--dport ${servport#*:}" else dports="--dports ${servport#*:}" fi fi [ "x$addr" = "x0.0.0.0/0" ] && addr= ratelimit= }