Best practice for NAT loopback/reflection/hairpinning behind router/fritzbox/etc?

I’m not sure, if I got it right… but I understand a NAT-loopback as a loopback in the same subnet. The special feature of a NAT loopback is (imho) that the source has to be altered. So it is a combination of at least three rules: a DNAT, a FORWARD and a SNAT/masquerading rule (all three chains are involved)

Lets assume the following setup:

  • router with red & green network
  • red has public IP 11.22.33.44
  • green IP is 192.168.0.1 and subnet is 192.168.0.0/24
  • webserver in green network with IP 192.168.0.2, listening on port 80
  • client pc with IP 192.168.0.3 in green network with gateway set to 192.168.0.1

I’m using uif ( Ubuntu Manpage: uif.conf — Tool for generating optimized packet filter rules ) which makes iptable/netfilter rules more human readable but I will try to provide the corresponding iptable commands, as well. But those are not tested, the uif configuration lines are testet.

Just as a reminder: packages pass the kernel in this sequence: Prerouting (DNAT) → Filter (FORWARD) → Postrouting (SNAT/MASQ)

Goal:

  • external access from the internet to the public IP 11.22.33.44 Port 80 should be routed to 192.168.0.2:80
  • access from the green network 192.168.0.0/24 (e.g. the client pc with IP 192.168.0.3) to 11.22.33.44:80 should be routed back to 192.168.0.2:80

Problem:

  • 1st goal is straight forward:
    UIF:
    nat+ i=if_red p=tcp(/80) D=192.168.0.2
    fw+ i=if_red d=192.168.0.2 p=tcp(/80)

    IPTABLES:
    iptables -t nat -A PREROUTING -i if_red -p tcp --dport 80 -j DNAT --to-destination 192.168.0.2
    iptables -A FORWARD -i if_red -p tcp -d 192.168.0.2 --dport 80 -j ACCEPT

    all packets arriving at red interface with destination port 80 will get it’s destination address rewritten to 192.168.0.2 (port remains unaltered) and 2nd rule allows the packet to be forwarded to the subnet which holds 192.168.0.2 (which is green)

  • 2nd goal is NOT achieved by doing
    UIF:
    nat+ i=if_green s=192.168.0.0/24 d=11.22.33.44 p=tcp(/80) D=192.168.0.2
    fw+ i=if_green s=192.168.0.0/24 d=192.168.0.2 p=tcp(/80)

    IPTABLES:
    iptables -t nat -A PREROUTING -i if_green -p tcp -s 192.168.0.0/24 -d 11.22.33.44 --dport 80 -j DNAT --to-destination 192.168.0.2
    iptables -A FORWARD -i if_green -p tcp -s 192.168.0.0/24 -d 192.168.0.2 --dport 80 -j ACCEPT

    the nat line will rewrite the destination address to 192.168.0.2 for packages that enter via green interface with destination 11.22.33.44:80 (and the second allows all packages to be forwarded which arrive through green from green subnet and have the destination 192.168.0.2:80)
    This is not working, because the webserver will arrive an initial package from the router with a source address from the client (192.168.0.3). The answer from the webserver will go to the source address of the package (192.168.0.3) and since this is on the same subnet as the webserver itself, it will be delivered straight to the client without involving the gateway (here:the router). So the packages will go different routes (client → router → webserver) vs. (webserver → client). The firewall will log packages with invalid state. (Hint: imho an answer of a package has to take the same route, as the initial package. There are only very rare conditions, where this is not necessary - e.g. “holepunching”)

Solution:

  • in addition to above you have to rewrite the source address of the package, so that the answer of the webserver will take the same way back (webserver → router → client)
    UIF:
    nat+ i=if_green s=192.168.0.0/24 d=11.22.33.44 p=tcp(/80) D=192.168.0.2
    fw+ i=if_green s=192.168.0.0/24 d=192.168.0.2 p=tcp(/80)
    masq+ o=if_green s=192.168.0.0/24 d=192.168.0.2 p=tcp(/80)

    IPTABLES:
    iptables -t nat -A PREROUTING -i if_green -p tcp -s 192.168.0.0/24 -d 11.22.33.44 --dport 80 -j DNAT --to-destination 192.168.0.2
    iptables -A FORWARD -i if_green -p tcp -s 192.168.0.0/24 -d 192.168.0.2 --dport 80 -j ACCEPT
    iptables -t nat -A POSTROUTING -o if_green -p tcp -s 192.168.0.0/24 -d 192.168.0.2 --dport 80 -j MASQUERADE

    instead of masquerading a snat should work as well (replace last line, not tested):
    UIF:
    nat+ o=if_green s=192.168.0.0/24 d=192.168.0.2 p=tcp(/80) S=192.168.0.1

    IPTABLES:
    iptables -t nat -A POSTROUTING -o if_green -p tcp -s 192.168.0.0/24 -d 192.168.0.2 --dport 80 -j SNAT --to-source 192.168.168.0.1

If your server you would like to reach from the green network is not in the green network but in a different subnet e.g. the orange subnet, then this is a simple NAT + PortForward and not a loopback (in my understanding of NAT loopback/relection/…) But you still need to know the public IP (which is easy, if you have a static one :wink: )

By the way: I don’t use a DMZ for the NAS because it is the most critical device in the network and provides several services like DHCP/DNS/RADIUS for the internal network. On the other hand, I don’t want to miss the conveniences of accessing it from outside. Hence I focus on securing this device… It’s a tradeoff, of course…

I hope this provides clarification an does not cause even more confusion :wink:

Cheers,
Holger

1 Like