The main goal of this page is to understand why a simple NAT (DNAT/SNAT) fails to build a real dmz (behind a home router, for example), why it doesn't work with iproute2+MARK, and how to make it work with the (depreacated yet handy) netfilter ROUTE target.
I assume you know what a DMZ is about and have some experience with NAT under Linux. If you don't, you can first read the wikipedia article and this NAT Howto from Rusty Russell.
Let's assume we have :
This works well with a standard NAT setup for the traffic coming from the outside, but won't work that easily for the traffic from the inside (LAN) : to a DNATed packet coming from a neighbour, the server would directly answer to the client, without going through the router again.
A solution to this issue would be to have a second subnet for the DMZ.
One might want to try the policy routing (iproute2+MARK) solution, but this one fails because of the local table : when the packet is processed by the routing decision system, it first goes through the local table, in which the local addresses are stored (as soon as the associated interface is up).
To make it work, we would have to either remove the public address from the local table and add it in another table (the main table, for example), but the kernel would add the entry to the local table the next time the interface is up.
One could also remove the address from the interface and setup an ARP proxy on the router.
The netfilter/iptables ROUTE target is a netfilter extension originally written by Cédric de Launois.
It has been considered depreacated by the Netfilter team for some time now, and isn't even included by default in the sources of the recent pom versions.
It is still available using the netfilter patch-o-matic by adding the following repository in the sources :
To enable it, you need to retrieve the kernel and iptables sources, patch it with the pom, and build the kernel with ROUTE target support. More information can be found in the netfilter extensions howto.
The current version from the repository (2010/08/31) is not compatible with iptables > 1.4.6 (since the iptables git commit bf97128 actually), thus we need to apply this patch to the iptables sources (after patching it with pom). Then you can build and install it (make/make install).
The ROUTE target should be available now. You can test it with :
iptables -j ROUTE --helpThis should display the ROUTE help (at the bottom). If it doesn't, something went wrong, or the iptables target isn't installed :
ROUTE target v1.11 options: --oif ifname Route packet through `ifname' network interface --iif ifname Change packet's incoming interface to `ifname' --gw ip Route packet via this gateway `ip' --continue Route packet and continue traversing the rules. Not valid with --iif or --tee. --tee Duplicate packet, route the duplicate, continue traversing with original packet. Not valid with --iif or --continue.
The ROUTE target can be used instead of iproute2/MARK (which doesn't work here) because it forces the packet route selection without going through the whole routing decision process (thus not going through the local table). Here is a working iptables script sample:
iptables -t mangle -A PREROUTING -i $LAN -d 10.0.0.2 -j ROUTE --gw 192.168.0.2 iptables -t nat -A PREROUTING -i $WAN -d 10.0.0.2 -j DNAT --to 192.168.0.2 iptables -A FORWARD -i $WAN -o $LAN -m state --state RELATED,ESTABLISHED -j ACCEPT iptables -A FORWARD -i $LAN -o $WAN -s 192.168.0.0/24 ! -d 192.168.0.0/24 -j ACCEPT iptables -t nat -A POSTROUTING -s 192.168.0.0/24 ! -d 192.168.0.0/24 -o $WAN -j MASQUERADEIt is a simple NAT setup with one ROUTE rule on top of it to redirect packets coming from the LAN to the DMZ server.
In order to avoid tracking connection from the LAN to the DMZ (since we won't see the answer from the server going through the router anyway) we can use the raw table like this:
iptables -t raw -A PREROUTING -i $LAN -d 10.0.0.2 -j NOTRACK
We could also route the outcoming traffic to the DMZ without NATing it, with something like:
iptables -t mangle -A PREROUTING -i $WAN -d 10.0.0.2 -m state ! --state RELATED,ESTABLISHED -j ROUTE --gw 192.168.0.2