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

4

The question is simple but I think the answer might not be as I went through countless related topics with no concrete reply.

I wish to forward a port 1234 from x.x.x.x to y.y.y.y (both on internet at different locations i.e. y.y.y.y is not an internal IP like 10.a.b.c etc) in a way that y.y.y.y is able to get the original source IP connecting to x.x.x.x.

Right now it sees x.x.x.x as the source IP using the usual SNAT or MASQUERADE rules. If y.y.y.y is some internal IP (like of a lxc container running on x.x.x.x) the same rules work and the container is able to see the actual source IP but not if y.y.y.y is external.

Can this be achieved in any way?

Sam

Posted 2018-09-12T06:00:26.753

Reputation: 41

Well, you could put the IP in the payload. It's ugly and I'd recommend finding an alternative first, but it works. For some values of works. If it's just an application requiring the info and doesn't need to have it in an exact location. – Mast – 2018-09-12T11:56:54.217

Answers

4

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.)

user1686

Posted 2018-09-12T06:00:26.753

Reputation: 283 655

3

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.

RalfFriedl

Posted 2018-09-12T06:00:26.753

Reputation: 1 370

2

You probably can't achieve this because the source address is used for the return packet - thus if the source address is behind NAT, it can't be reached from the wider Internet.

There are at least 2 workarounds (which require additional software and configuration) commonly in use - The first is to use a VPN to bypass the router, or alternatively to use between the router and destination so the destination can know how to reach the source.

In the case of HTTP and HTTPS, don't use IPTABLES firewall rules, use a (transparent or regular) proxy on the router instead, and have the proxy server add an X-FORWARDED-FOR header which the external application can access and process.

Depending on your exact application and firewall, it might also be possible to mark a "relative" internal IP address by modifying the source port the request is coming from (it will still come from the routers address, but you might be able to get a hint of which system behind the router sent it based on the source port). I've not seen this used in practice.

davidgo

Posted 2018-09-12T06:00:26.753

Reputation: 49 152