To do complete port forwarding with netfilter, the firewall introduced linux 2.4, three things are required, and two are optional.
You need to at least compile support for DNAT and SNAT "Full NAT" and "Connection state match support" (only required for the state matching in the FORWARD-rule) in the kernel or as kernel modules. Just compile everything as modules, and you'll be sure not to miss anything.
The only things i'm sure you won't need are the ipchains and ipfwadm compatibility modules.
Then turn on forwarding with the following line at startup:
echo 1 > /proc/sys/net/ipv4/ip_forward
Your startup scripts already do this if your firewall already is forwarding from the internal network to the external network, so called masquerading or source-nat (SNAT).
This document assumes that this is the case.
First, for the forwarding itself, a rule in the PREROUTING chain of the nat-table is required. To forward tcp-port $FW_EXTERNAL_PORT on ip $FW_EXTERNAL_IP to internal ip:port $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT
iptables -t nat -A PREROUTING -p tcp --dport $FW_EXTERNAL_PORT -d $FW_EXTERNAL_IP -j DNAT --to-destination $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT
"Before deciding where to send packages coming into the machine (PREROUTING), match packets coming in on $FW_EXTERNAL_IP:$FW_EXTERNAL_PORT, and rewrite their destination addresses to $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT".
Now, this is a forwarding, and for forwarding to work, you must also allow this forwarding in the FORWARD chain of the filter table:
iptables -A FORWARD -p tcp --dport $INTERNAL_MACHINE_PORT -d $INTERNAL_MACHINE_IP -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT
"When deciding whether to allow a packet to be forwarded (FORWARD), that is, while routing, match
packages going to $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT that are either part of a new
connection, an established connection, or related to an established connection, and allow them
through."
Then, for this to work on the internal network, a rule in the POSTROUTING chain of the nat-table is required. Without this, the machine forwarded to on the internal network wouldn't know to send
responses on the internal network through the internal ip of the firewall, $FW_INTERNAL_IP.
iptables -t nat -A POSTROUTING -p tcp -d $INTERNAL_MACHINE_IP --dport $INTERNAL_MACHINE_PORT -j SNAT --to-source $FW_INTERNAL_IP
"After deciding where to send packages coming into the machine (POSTROUTING), match packets going
to $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT, and rewrite their source addresses to the internal address of the firewall, $FW_INTERNAL_IP".
To make requests from the firewall to the firewall itself work, yet another rule is needed.
You also have to have support for NAT of locally-generated connections (CONFIG_IP_NF_NAT_LOCAL) in the kernel, which was added in 2.4.19.
iptables -t nat -A OUTPUT -p tcp --dport $FW_EXTERNAL_PORT -d $FW_EXTERNAL_IP -j DNAT --to-destination $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT
"For packages originating from this machine, the firewall (OUTPUT), match packets coming in on $FW_EXTERNAL_IP:$FW_EXTERNAL_PORT, and rewrite their destination addresses to $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT".
So, here are the finished lines, slightly reformatted, with some example values:
FW_EXTERNAL_IP=1.1.1.1 # The IP-address of the external interface of the firewall FW_EXTERNAL_INTERFACE=eth0 # The external interface, if using -i instead of -d. FW_EXTERNAL_PORT=80 # The port to be forwarded FW_INTERNAL_IP=192.168.0.1 # The IP-address of the internal interface of the firewall INTERNAL_MACHINE_IP=192.168.0.2 # The IP-address of the machine on the internal network to be forwarded to. INTERNAL_MACHINE_PORT=80 # The port to be forwarded to
# Activate forwarding echo 1 > /proc/sys/net/ipv4/ip_forward
# Forward packets coming in from the outside iptables -t nat -A PREROUTING -p tcp -d $FW_EXTERNAL_IP --dport $FW_EXTERNAL_PORT -j DNAT --to-destination $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT
# Make it work from the firewall itself iptables -t nat -A OUTPUT -p tcp -d $FW_EXTERNAL_IP --dport $FW_EXTERNAL_PORT -j DNAT --to-destination $INTERNAL_MACHINE_IP:$INTERNAL_MACHINE_PORT
# Make responses on the internal network go through the firewall iptables -t nat -A POSTROUTING -p tcp -d $INTERNAL_MACHINE_IP --dport $INTERNAL_MACHINE_PORT -j SNAT --to-source $FW_INTERNAL_IP
# Allow forwarded packets iptables -A FORWARD -p tcp -d $INTERNAL_MACHINE_IP --dport $INTERNAL_MACHINE_PORT -j ACCEPT -m state --state NEW,ESTABLISHED,RELATED
You can probably replace -d $FW_EXTERNAL_IP with -i $FW_EXTERNAL_INTERFACE, if you want to match incoming packets on the interface instead of the destination address, on all but the OUTPUT DNAT rule.
Replace "-p tcp" with "-p udp" in all rules, and remove the "-m state --state NEW,ESTABLISHED,RELATED"-part of the last rule.
See the iptables howto/tutorial at http://iptables-tutorial.frozentux.net/iptables-tutorial.html#TABLE.DNATTARGET for a more thorough explanation of DNAT, and iptables(8) for information on iptables.