Imagine running a local web server at 192.168.1.10:80 at your LAN. To expose this service to the internet, you create a DNAT rule to map your router’s public address 1.2.3.4:80 to 192.168.1.10:80.

When a client in the same LAN at 192.168.1.20 tries to access the service, without hairpin NAT:

  1. Client sends a packet with src=192.168.1.20, dst=1.2.3.4:80
  2. The router receives the packet
  3. Router’s DNAT rule maps dst to 192.168.1.10:80
  4. Web server at 192.168.1.10:80 receives the packet, and sends a response back to the client at 192.168.1.20
  5. Client at 192.168.1.20 receives the packet directly, but it is expecting a response from 1.2.3.4:80, which breaks the connection

With hairpin NAT (SNAT + DNAT):

  1. Client sends a packet with src=192.168.1.20, dst=1.2.3.4:80
  2. The router receives the packet
  3. Router’s DNAT rule maps dst to 192.168.1.10:80
  4. Router’s SNAT rule maps src to router’s LAN IP, e.g., 192.168.1.1
  5. Web server at 192.168.1.10:80 receives the packet, and sends a response back to the router at 192.168.1.1
  6. Router untranslates (with conntrack), forwards reply back to 192.168.1.20
  7. Client sees reply from 1.2.3.4 - connection works