Henrik Stoerner
2004-09-15 13:39:59 UTC
I have a setup where I use a Linux box with netfilter to forward
tcp connections between a "client" and a "server.
The Linux box has a default gateway defined. However, there are
multiple other routers on the same network, and the default
gateway router sends ICMP redirects to inform the Linux box
which router should be used to reach some destination.
In ascii art (somewhat simplified):
|----| firewall |---| server |
|
|
|----| routerA |-------
| | def.gw |
| Linux NAT | ------------|
|
|----| routerB |----------
|
|----| routerC |----+-----
| |
| |
| |
| client |
This works fine for traffic that originates on the Linux box.
If I connect from the Linux NAT box to the client, the default
gateway sends an ICMP redirect telling the Linux box to use
routerC, and all further traffic then goes via that router.
A lookup with "ip route get <client-IP>" also tells me that
traffic goes via routerC.
If the client connects to a service on the Linux NAT box, the
packets sent from the Linux NAT box to the client also obey
the routing defined by the ICMP redirect, and go via routerC.
But when the connection is initiated by the client for a port that
get's NAT'ed and forwarded to the server, then the packets going
from the Linux NAT box to the client are sent to the default
gateway, completely ignoring the route generated by the ICMP
redirect.
So:
* client sends a SYN to Linux-NAT on port 1984
* Linux-NAT does SNAT+DNAT and forwards packet to server
* server sends SYN+ACK to Linux-NAT box
* Linux-NAT box does the necessary DNAT/SNAT and forwards
SYN+ACK to client using routerA instead of via routerC
Even though routerA sends an ICMP redirect telling Linux-NAT
to use routerC, all of the packets from Linux-NAT to the client
for this connection are sent via routerA.
The iptables rules are simple. I use this in a script:
/sbin/modprobe iptable_nat
/sbin/iptables -t nat -A PREROUTING -p tcp --dport 1984 -j DNAT --to-destination IP.ADDR.OF.SERVER:1984
/sbin/iptables -t nat -A POSTROUTING -p tcp --dport 1984 -j SNAT --to-source IP.ADDR.OF.LINUX-NAT
/sbin/sysctl -w net.ipv4.ip_forward=1
for f in /proc/sys/net/ipv4/conf/*/accept_redirects; do
echo "1" >$f
done
giving me:
# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:1984 to:IP.ADDR.OF.SERVER:1984
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
SNAT tcp -- anywhere anywhere tcp dpt:1984 to:IP.ADDR.OF.LINUX-NAT
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
# lsmod
Module Size Used by Not tainted
iptable_nat 15816 1
ip_conntrack 16488 1 [iptable_nat]
ip_tables 14784 3 [iptable_nat]
I've tried kernels 2.4.18 and 2.4.27. The Linux NAT box is running Debian/testing
on an UltraSparc. The 2.4.18 kernel is the default Debian kernel; the 2.4.27 was
compiled locally. The configuration is available if you need it.
tcp connections between a "client" and a "server.
The Linux box has a default gateway defined. However, there are
multiple other routers on the same network, and the default
gateway router sends ICMP redirects to inform the Linux box
which router should be used to reach some destination.
In ascii art (somewhat simplified):
|----| firewall |---| server |
|
|
|----| routerA |-------
| | def.gw |
| Linux NAT | ------------|
|
|----| routerB |----------
|
|----| routerC |----+-----
| |
| |
| |
| client |
This works fine for traffic that originates on the Linux box.
If I connect from the Linux NAT box to the client, the default
gateway sends an ICMP redirect telling the Linux box to use
routerC, and all further traffic then goes via that router.
A lookup with "ip route get <client-IP>" also tells me that
traffic goes via routerC.
If the client connects to a service on the Linux NAT box, the
packets sent from the Linux NAT box to the client also obey
the routing defined by the ICMP redirect, and go via routerC.
But when the connection is initiated by the client for a port that
get's NAT'ed and forwarded to the server, then the packets going
from the Linux NAT box to the client are sent to the default
gateway, completely ignoring the route generated by the ICMP
redirect.
So:
* client sends a SYN to Linux-NAT on port 1984
* Linux-NAT does SNAT+DNAT and forwards packet to server
* server sends SYN+ACK to Linux-NAT box
* Linux-NAT box does the necessary DNAT/SNAT and forwards
SYN+ACK to client using routerA instead of via routerC
Even though routerA sends an ICMP redirect telling Linux-NAT
to use routerC, all of the packets from Linux-NAT to the client
for this connection are sent via routerA.
The iptables rules are simple. I use this in a script:
/sbin/modprobe iptable_nat
/sbin/iptables -t nat -A PREROUTING -p tcp --dport 1984 -j DNAT --to-destination IP.ADDR.OF.SERVER:1984
/sbin/iptables -t nat -A POSTROUTING -p tcp --dport 1984 -j SNAT --to-source IP.ADDR.OF.LINUX-NAT
/sbin/sysctl -w net.ipv4.ip_forward=1
for f in /proc/sys/net/ipv4/conf/*/accept_redirects; do
echo "1" >$f
done
giving me:
# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DNAT tcp -- anywhere anywhere tcp dpt:1984 to:IP.ADDR.OF.SERVER:1984
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
SNAT tcp -- anywhere anywhere tcp dpt:1984 to:IP.ADDR.OF.LINUX-NAT
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
# lsmod
Module Size Used by Not tainted
iptable_nat 15816 1
ip_conntrack 16488 1 [iptable_nat]
ip_tables 14784 3 [iptable_nat]
I've tried kernels 2.4.18 and 2.4.27. The Linux NAT box is running Debian/testing
on an UltraSparc. The 2.4.18 kernel is the default Debian kernel; the 2.4.27 was
compiled locally. The configuration is available if you need it.
--
Henrik Storner
Henrik Storner