Iptables port forwarding with internal SNAT

iptablesport-forwarding

I would like to forward an external port to a different port on a machine on a private network. However, I need to SNAT the traffic to appear to be from the gateway's internal IP, since the destination machine has a totally different outbound route. I'm combining two questions here, but I'm not sure if I can merge the results of asking them separately.

Gateway = 1.2.3.4/192.168.2.5, internal server = 192.168.2.10

  1. Forwarding to different port

I'm used to doing this, and it seems to be pretty much the only answer Google finds for me:

iptables -A PREROUTING -t nat -p tcp -d 1.2.3.4 --dport 12345 -j DNAT --to-destination 192.168.2.10:12345
iptables -A FORWARD -p tcp -d 192.168.2.10 --dport 12345 -j ACCEPT
iptables -A POSTROUTING -t nat -d 192.168.2.10 -s 192.168.2.0/24 -p tcp --dport 12345 -j SNAT --to 1.2.3.4

which will forward port 12345 from my external IP of 1.2.3.4 to 192.168.2.10:12345. What if I want to forward to port 54321? I get confused about which port reference matches which machine; I've solved this by trial and error in the past only to find later that it didn't really work.

  1. Forwarding from the LAN address

This part I'm not sure is even possible. What I would like to have is for the traffic incoming to 192.168.2.10:54321 originate from the gateway machine's LAN address, not from the internet. (I'm trying to forward port 443 to a ssh server, but that server is already visible externally on a different IP address, so reply traffic would take a different route.)

I've modified my standard rules above:

iptables -A PREROUTING -t nat -p tcp -d 1.2.3.4 --dport 12345 -j DNAT --to-destination 192.168.2.10:12345
iptables -A POSTROUTING -t nat -p tcp -d 192.168.2.10 --dport 12345 -j SNAT --to-source 192.168.2.5
iptables -A FORWARD -p tcp -d 192.168.2.10 --dport 12345 -j ACCEPT
iptables -A POSTROUTING -t nat -d 192.168.2.10 -s 192.168.2.0/24 -p tcp --dport 12345 -j SNAT --to 1.2.3.4

tcpdump shows me that the traffic is now from the gateway's LAN address, 192.168.2.5, and the server is replying, but it isn't then being forwarded on to the external address. I'm suspecting that this isn't even possible, that the reply traffic needs to have the client's address as its destination, but if iptables can redirect the replies correctly, I'd appreciate a clue as to how.

Plus, if possible, including the correct syntax for changing the destination port, as for part 1.

Best Answer

After some experimenting, I think I've answered my question, so I should post it here in case anyone else finds it useful. Yes, it is possible, and it's fairly simple, just a matter of getting the right combination of addresses and ports.

Note: the examples below require that kernel IP forwarding is enabled using

echo 1 > /proc/sys/net/ipv4/ip_forward

before any of these fragments.

Comments from my script:

# summary:
# allow forwarding *to* destination ip:port
# allow forwarding *from* destination ip:port
# nat packets identified by arrival at external IP / port to have
#  *destination* internal ip:port
# nat packets identified by arrival at internal IP / port to have
#  *source* internal network IP of gateway machine

For the example in the question:

# allow inbound and outbound forwarding
iptables -A FORWARD -p tcp -d 192.168.2.10 --dport 54321 -j ACCEPT
iptables -A FORWARD -p tcp -s 192.168.2.10 --sport 54321 -j ACCEPT

# route packets arriving at external IP/port to LAN machine
iptables -A PREROUTING -t nat -p tcp -d 1.2.3.4 --dport 12345 -j DNAT --to-destination 192.168.2.10:54321
# rewrite packets going to LAN machine (identified by address/port)
# to originate from gateway's internal address
iptables -A POSTROUTING -t nat -p tcp -d 192.168.2.10 --dport 54321 -j SNAT --to-source 192.168.2.5

The actual script, since this should be easier to apply directly:

# EXTIP = external IP of gateway (1.2.3.4)
# EPORT = external port (12345)
# DIP   = destination IP in local network (192.168.2.10)
# DPORT = destination port (54321)
# INTIP = internal IP of gateway (192.168.2.5)

iptables -A FORWARD -p tcp -d $DIP --dport $DPORT -j ACCEPT
iptables -A FORWARD -p tcp -s $DIP --sport $DPORT -j ACCEPT

iptables -A PREROUTING -t nat -p tcp -d $EXTIP --dport $EPORT -j DNAT --to-destination $DIP:$DPORT
iptables -A POSTROUTING -t nat -p tcp -d $DIP --dport $DPORT -j SNAT --to-source $INTIP

I hope the answer's useful to someone else and that I'm not cluttering up the site with something that's been handled many times.

Related Question