Iptables forwarding port/s to a external IP transparently (remote end should see the actual source IP)

With only NAT – no. IP packets only have one 'source' field.

  • If you make iptables keep the original client address (DNAT only), then y.y.y.y will attempt to send replies directly to that original client (who thinks it's talking to x.x.x.x and doesn't expect any packets from y.y.y.y).

  • If you make iptables put your address as the new source (DNAT+SNAT), then y.y.y.y won't know the original source address.

(This is actually the same problem as with trying to port-forward from LAN to the same LAN, and the 2nd method is called 'NAT hairpinning' or 'NAT reflection'.)


To make this work, you need a tunnel/VPN between x.x.x.x and y.y.y.y – wrap the original packets you receive, without any changes, inside another IP packet that is sent to y.y.y.y (which will then unwrap the tunnelled packet and see the original source).

Of course, this means that you need root privileges on both systems in order to configure the tunnel. Additionally, the destination (y.y.y.y) needs to support "policy routing" – Linux and FreeBSD pf are capable of this. It needs a rule that'll route everything through the VPN if the source address is a VPN address.

You still need the iptables DNAT rule, but its 'to-destination' will be y's VPN address, not the public address. You can use any tunnel/VPN type for this, from basic "IP-in-IP" to GRE to OpenVPN to WireGuard. For example:

  • x.x.x.x

    Bring up the tunnel:

    ip link add gre-y type gre local x.x.x.x remote y.y.y.y ttl 64
    ip link set gre-y up
    ip addr add 192.168.47.1/24 dev gre-y
    

    Add port-forwarding rule, just like on a LAN:

    iptables -t nat -I PREROUTING -p tcp --dport 1234 -j DNAT --to-destination 192.168.47.2
    
  • y.y.y.y

    Bring up the tunnel:

    ip link add gre-x type gre local y.y.y.y remote x.x.x.x ttl 64
    ip link set gre-x up
    ip addr add 192.168.47.2/24 dev gre-x
    

    Make sure it works:

    ping 192.168.47.1
    

    Set up policy routing, so that replies (and only replies) will go over the tunnel:

    ip route add default via 192.168.47.1 dev gre-x table 1111
    ip rule add pref 1000 from 192.168.47.2 lookup 1111
    

(To use another tunnel/VPN type, replace only the "Bring up the tunnel" part.)


No, you can't achieve that. The problem is that the routing won't work.

Host E (external) sends a packet to Host F (forwarder). F sends that packet with the same source to host D (destination). This part works, whether D is internal or external.

Now D must return an answer, and sends it to the source address in the packet, which is E.

  • If D is an internal host and F is the default gateway for D, then the packet passes F, where the source D in the answer packet is changed to F. Host E sees a packet coming from F, and everything is fine.
  • If D is an external host, then F will not be the default gateway for D, and therefor D will send the answer directly to E. Host E will get the answer from D, but expects an answer from F, and therefor discards the packet from D. Host E will retry a few times, will again discard the answer from the wrong source address, and eventually time out.