Configuring NAT loopback via web UI

Hello all,

I have a TP-Link Archer AX23 which I'm setting up as a subnet. My ISP router is dog water for firewall.

My network is configured as such:
ISP Router forwarding 443, and a few other ports, to the OpenWrt router, and the OpenWrt router is further forwarding those ports to the system hosting the relevant service. OpenWrt will also manage the NAT loopback.

The reason I'm writing this post is I haven't been able to find a straight forward answer on how to configure this using the LuCl UI and with that said someone else might find this helpful in the future, including myself.

My Issue
I'm rustier than a mining museum's outdoor equipment exhibit. I have Let's Encrypt certificates on the service in question, so just using local routing isn't an option - wrong IP for the certificate.

For the NAT loopback, I think these are the settings I need to provide in the LuCI UI under Network -> Firewall -> Port Forwards. Regular port forward is simple and I haven't set it up yet. Trying to get the rule for the subnet done first. Technically there's a subnet off of this one via virtual machines.

General Settings Tab:
Source Zone: LAN
External Port: 443
Destination Zone: WAN
Internal Address: 192.168.1.220
Internal Port: 443

Advanced Tab:
I only want to redirect traffic from one of my "cloud" VMs so I put the subnet they are all apart of.
Source IP Address: 192.168.1.0/24
External IP address:
Enable NAT Loopback: Checked
Loopback Source IP: Use External IP address
Reflection Zones: LAN

Everything else should be default.

I have tested this and it seems to be working as expected on my Windows laptop connected to the router via WiFi, however, the systems I intended this for are not able to use the NAT loopback. I took a pcap/tcpdump while attempting and I noticed that there were issues with some of the packets. Essentially it went like this:

client to openwrt A record
client to openwrt AAAA record
openwrt to client A record with public IP
openwrt to client AAAA record with public IP
client (private IP) SYN to server( public IP )
server( private IP) SYN, ACK to client (private IP.)
client ( private IP ) ICMP to server ( private IP ) destination unreachable ( host administratively prohibited )
client (Private IP) SYN to server ( public IP ) tcp retransmission
server( private IP ) SYN, ACK to client ( private IP ) tcp retransmission

So I started looking around and found that OpenWrt should be rewriting the IP in the Source from the Private IP in the SYN ACK to the Private IP that the request was sent to. So I checked the port forward and there was nowhere to set a rewrite address. I looked around and found: Network -> Firewall -> NAT Rules. Clicked the Add button.

I created the following NAT rule

General Tab
Protocol: TCP
Outbound Zone: LAN
Source Address: 192.168.1.220
Source Port: 443
Destination Address: 192.168.1.0/24
Action: SNAT - rewrite source ip
Rewrite IP Address:
Rewrite Port: do not rewrite

I've tried putting br-lan into promiscuous mode. I tried following this post ( NAT Loopback (Hairpin) isn't working: No rewriting Occuring OpenWrt 23.05.2 - #8 by securecryptomining ) without any success. I didn't bother with the script to run tcpdump on boot since this seemed to only work as a side effect of tcpdump putting the device into promiscuous mode. I did try tcpdump incase promiscuous mode wasn't enough out of sheer desperation. I'm out of ideas.

/etc/config/firewall

config redirect
        option dest 'wan'
        option target 'DNAT'
        option name 'pkg NAT loopback'
        option src 'lan'
        option src_ip '192.168.1.0/24'
        option src_dip '<public ip>'
        option src_dport '443'
        option dest_ip '192.168.1.220'
        option dest_port '443'
        option reflection_src 'external'
        list reflection_zone 'lan'

config nat
        list proto 'tcp'
        option src '*'
        option src_ip '192.168.1.220'
        option src_port '443'
        option dest_ip '192.168.1.0/24'
        option target 'SNAT'
        option snat_ip '<public ip>'

I overwrote the pcap with the DNS query in it, so it just uses cache now.

14	2.987014	192.168.1.222	<Public IP>	TCP	74	49561 → 443 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=533513 TSecr=0 WS=128
15	2.987678	192.168.1.220	192.168.1.222	TCP	74	443 → 49561 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=64 SACK_PERM TSval=2236033172 TSecr=533513
16	2.987758	192.168.1.222	192.168.1.220	ICMP	102	Destination unreachable (Host administratively prohibited)
18	3.989998	192.168.1.222	<Public IP>	TCP	74	[TCP Retransmission] 49561 → 443 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=534516 TSecr=0 WS=128
19	3.990690	192.168.1.220	192.168.1.222	TCP	74	[TCP Retransmission] 443 → 49561 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=64 SACK_PERM TSval=2236033172 TSecr=534516
20	3.990744	192.168.1.222	192.168.1.220	ICMP	102	Destination unreachable (Host administratively prohibited)
24	5.994064	192.168.1.222	<Public IP>	TCP	74	[TCP Retransmission] 49561 → 443 [SYN] Seq=0 Win=29200 Len=0 MSS=1460 SACK_PERM TSval=536520 TSecr=0 WS=128
25	5.994772	192.168.1.220	192.168.1.222	TCP	74	[TCP Retransmission] 443 → 49561 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=64 SACK_PERM TSval=2236033172 TSecr=536520

That's not how HTTPS certificates work. They relate to the domain name, not IP. You can use the same Let's Encrypt certificate on a web server to serve HTTPS to both internal and external IPs.

222 receives the SYN-ACK response directly from 200, while expecting it to be returned from the public IP where the request was originally sent. That's why it rejects the packet with icmp-host-prohibited and sends a new request.

You need to make all traffic go through the router.

config redirect
	    option name 'DNAT-Loopback'
	    option target 'DNAT'
	    option src 'lan'
        option dest 'wan'
        option src_ip '192.168.1.0/24'
	    option src_dip '<public ip>'
	    option dest_ip '192.168.1.200'
        list proto 'tcp'
        option src_dport '443'
        option dest_port '443'
        option reflection '0'
        
config nat
        option name 'SNAT-Loopback'
	    option target 'SNAT'
        option src 'lan'
        option src_ip '192.168.1.0/24'
        option dest_ip '192.168.1.200'
        option snat_ip '192.168.1.1' #<-- Correct router LAN IP
        option proto 'tcp'
        option dest_port '443'

Yes, that makes sense. DDNS and Certificates work, regardless of IP - I'm not using DDNS, just solidifying your point. I can't explain why wget had issues with the certificate. Had to use --no-check-certificate. Might be because these servers are out of date and haven't been turned on since late 2019. I'll check the dates on the CA certificate. Rustier than old mining equipment left out on the front lawn.

Actually, the CA certificates on the server don't matter. The client needs to have updated root certificates. This explains why the old FreeBSD 11.1 ( FreeBSD 11 EOL 2021 ) system that's been off since 2019 has issues with the certificate and the up to date Windows 10 laptop didn't. This is going to be a "fun" project.

Thanks for the response. I appreciate your effort. I did try exactly what you provided, after switching out for the actual public IP. It was not fruitful. I'm going to keep tweaking what you've provided. Hoping for the best. I tried changing the SNAT_IP to the public IP and the destination zone to LAN. Destination WAN with a private IP didn't make any sense to me, even though WAN is technically just another LAN.

My latest config look like this:

config redirect
        option name 'DNAT-Loopback'
        option target 'DNAT'
        option src 'lan'
        option dest 'lan'
        option src_ip '192.168.1.0/24'
        option src_dip '<Public IP>'
        option dest_ip '192.168.1.200'
        list proto 'tcp'
        option src_dport '443'
        option dest_port '443'
        option reflection '0'

config nat
        option name 'SNAT-Loopback'
        option target 'SNAT'
        option src 'lan'
        option src_ip '192.168.1.0/24'
        option dest_ip '192.168.1.200'
        option snat_ip '192.168.1.1'
        option proto 'tcp'
        option dest_port '443'

Pretty sure I have the system hitting the OpenWRT router based on this pcap from br-lan on the OpenWrt. 150 is my laptop, wirelessly connected to the OpenWRT router. So it appears the source IP rewrite rule is working, for which I thank you again.

71	4.652224	192.168.1.150	<Public IP>	TCP	66	60335 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
75	4.652959	192.168.1.150	<Public IP>	TCP	66	60336 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
78	4.655022	192.168.1.150	<Public IP>	TCP	66	60337 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
80	4.658226	<Public IP>	192.168.1.150	TCP	54	80 → 60337 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
114	4.843138	192.168.1.150	<Public IP>	TCP	66	60338 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
144	5.170381	192.168.1.150	<Public IP>	TCP	66	[TCP Port numbers reused] 60337 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
145	5.174786	<Public IP>	192.168.1.150	TCP	54	80 → 60337 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
167	5.657714	192.168.1.150	<Public IP>	TCP	66	[TCP Retransmission] 60335 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
168	5.657715	192.168.1.150	<Public IP>	TCP	66	[TCP Retransmission] 60336 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
169	5.683385	192.168.1.150	<Public IP>	TCP	66	[TCP Port numbers reused] 60337 → 80 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM
170	5.687911	<Public IP>	192.168.1.150	TCP	54	80 → 60337 [RST, ACK] Seq=1 Ack=1 Win=0 Len=0
172	5.855096	192.168.1.150	<Public IP>	TCP	66	[TCP Retransmission] 60338 → 443 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM

I don't think the SNAT_IP needs to be the router LAN IP - or any IP in the private IP space. Be it 10.0.0.0/8 or 192.168.0.0/16 or whatever that 17X.X.X.X/X CIDR is. From everything I can find it needs to be the Public IP on the/an edge device, since the whole point of NAT loopback - I forget what we called it in 2012-2019 - is that regular NAT gets "confused" when you go out to the Internet just to be turned around. So you need to short circuit the request, and tell "some device" to rewrite the source address to the originally requested address. From what I remember this would usually be "the next hop" from your system, but can be any authoritative routing system on the network. Otherwise, a system accepting responses from "just anyone", even it's own gateway, would be vulnerable to a plethora of attacks.

That being said, I'm sure there's use cases for NAT loopback in Intranets.

I don't mean to be argumentative, I'm trying to remember that which I've lost. Please correct anything I've said that's inaccurate, or flat out wrong. If I can remember correctly, this was done by a proper managed fiber switch. This would have been using Cisco's IOS, or whatever Force10 used pre-Dell. Not sure if it was a Force10 or a proper Cisco. May have been post-Dell but pre-rebranding. I digress.

I don't think the systems not having been updated since 2019 is helping me any.

Trying to achieve the rubber duckie effect. It's not going well. Hahaha.

I've solved my issue. I called the ISP to bridge their router so mine was the authoritative device which gets the public IP on it's WAN interface. My end configuration is:

config redirect
        option dest 'lan'
        option target 'DNAT'
        option name 'pkg'
        option src 'wan'
        option src_dip 'PUBLIC IP'
        option src_dport '443'
        option dest_ip '192.168.1.220'
        option dest_port '443'
        option reflection_src 'external'
        list reflection_zone 'lan'