Disable NAT between VLANS

Hello,

I have the following setup:

Edge router with VLAN 1 w/ IP 192.168.2.0/24 for LAN and WiFi and VLAN 3 w/ IP 192.168.3.0/24 for guest WiFi, on which the clients receive their IPs and DNS both in the 192.168.3.0/24 subnet via DHCP. On VLAN 1 resides my Pi-Hole, configured to answer on eth0.
The problem is that all the DSN queries from the guest WiFi clients show up with the router's local IP from the 192.168.2.x subnet, but i would like them to show up with their own IP from the 192.168.3.x subnet.
I have tried forwarding incoming traffic destined to the DHCP allocated DNS to the Pi-Hole, i tried creating routes, i tried masquerading as described at https://jeff.vtkellers.com/posts/technology/force-all-dns-queries-through-pihole-with-openwrt/ but no matter what i do they still show up with an IP from the 192.168.2.x subnet.
I have a feeling it has something to do with NAT since the router obviously rewrites the source IP to its own but i can't figure out how to stop this from happening. I am also aware of this NAT - LAN to LAN IP Rewrite - #2 by pavelgl
Thank you!

Are you sure this is a NAT issue?

OpenWrt is configured by default to act as a caching nameserver, so the DHCP announces the router as a DNS server, that forwards the requests to an upstream DNS.

If all this is happening on the same router, you don't need (and shouldn't have) NAT. This also could be a function of how you've setup the config that allows DNS to your pihole.

Let's see the following:

Please connect to your OpenWrt device using ssh and copy the output of the following commands and post it here using the "Preformatted text </> " button:
grafik
Remember to redact passwords, MAC addresses and any public IP addresses you may have:

ubus call system board
cat /etc/config/network
cat /etc/config/dhcp
cat /etc/config/firewall

The nat-masquerade checkbox should be ticket only on internet facing interface.

Thanks for the fast reply!
Caching is not an issue, i specifically directed the query to the Pi-Hole, i also used several existent and nonexistent domains, PiHole logs them.

For further clarification, VLAN7 is required by my ISP, the edge router's WAN port is connected to an 802.1q VLAN aware switch which has the ISP connection going into one of its ports, where VLAN3 is also properly tagged, the Pi-Hole is actually connected to another OpenWRT machine which also hosts an OpenVPN Server, firewall is active there because of the VPN server but it has no active dnsmasq.
The edge router also runs unbound on 5353.
'/etc/firewall.user' does not exist

ubus call system board
{
        "kernel": "6.6.73",
        "hostname": "XXX",
        "system": "Qualcomm Atheros QCA956X ver 1 rev 0",
        "model": "TP-Link Archer C7 v5",
        "board_name": "tplink,archer-c7-v5",
        "rootfs_type": "squashfs",
        "release": {
                "distribution": "OpenWrt",
                "version": "24.10.0",
                "revision": "r28427-6df0e3d02a",
                "target": "ath79/generic",
                "description": "OpenWrt 24.10.0 r28427-6df0e3d02a",
                "builddate": "1738624177"
        }
}
cat /etc/config/networkconfig 
interface 'loopback'
        option device 'lo'
        option proto 'static'
        option ipaddr '127.0.0.1'
        option netmask '255.0.0.0'

config globals 'globals'
        option ula_prefix 'XXXX'
        option packet_steering '1'

config device
        option name 'br-lan'
        option type 'bridge'
        option ipv6 '0'
        list ports 'eth0.1'

config interface 'lan'
        option device 'br-lan'
        option proto 'static'
        option netmask '255.255.255.0'
        option ip6assign '60'
        option ipaddr '192.168.2.1'
        option delegate '0'
        list dns '192.168.2.6'
        list dns '192.168.2.1'

config switch
        option name 'switch0'
        option reset '1'
        option enable_vlan '1'

config switch_vlan
        option device 'switch0'
        option vlan '1'
        option vid '1'
        option ports '0t 1t 2 3 4 5'

config switch_vlan
        option device 'switch0'
        option vlan '2'
        option ports '0t 1t'
        option vid '7'

config device
        option name 'eth0'
        option ipv6 '0'

config device
        option name 'eth0.1'
        option type '8021q'
        option ifname 'eth0'
        option vid '1'
        option ipv6 '0'

config device
        option name 'eth0.7'
        option type '8021q'
        option ifname 'eth0'
        option vid '7'
        option ipv6 '0'

config interface 'WAN'
        option proto 'pppoe'
        option device 'eth0.7'
        option username 'XXXXXX'
        option password 'XXXXX'
        option ipv6 '0'
        option peerdns '0'
        option delegate '0'
        list dns '192.168.2.6'
        list dns '192.168.2.1'

config device
        option name 'pppoe-WAN'
        option ipv6 '0'

config interface 'VPNSERVER'
        option proto 'none'
        option device 'tun0'
        option peerdns '0'
        list dns '192.168.2.6'
        list dns '192.168.2.1'
        option delegate '0'

config device
        option name 'tun0'
        option ipv6 '0'

config device
        option name 'WiFi5'
        option ipv6 '0'

config switch_vlan
        option device 'switch0'
        option vlan '3'
        option ports '0t 1t'
        option vid '3'

config device
        option type 'bridge'
        option name 'GUEST'
        list ports 'eth0.3'
        option ipv6 '0'

config device
        option name 'eth0.3'
        option type '8021q'
        option ifname 'eth0'
        option vid '3'
        option ipv6 '0'

config interface 'GUEST'
        option proto 'static'
        option device 'GUEST'
        option ipaddr '192.168.3.1'
        option netmask '255.255.255.0'
        option delegate '0'
        option defaultroute '0'

config route
        option interface 'lan'
        option target 'VPN server subnet Class B/24'
        option gateway 'VPN Server Class B/24IP'

cat /etc/config/dhcp
config dnsmasq
        option domainneeded '1'
        option localise_queries '1'
        option rebind_protection '1'
        option rebind_localhost '1'
        option local '/lan/'
        option domain 'lan'
        option expandhosts '1'
        option authoritative '1'
        option readethers '1'
        option leasefile '/tmp/dhcp.leases'
        option resolvfile '/tmp/resolv.conf.d/resolv.conf.auto'
        option ednspacket_max '1232'
        option strictorder '1'
        option localservice '1'
        list notinterface 'WAN'
        option sequential_ip '1'
        option port '53'
        list server '192.168.2.6'
        list server '192.168.2.1#5353'
        option cachesize '5'

config dhcp 'lan'
        option interface 'lan'
        option start '100'
        option dhcpv4 'server'
        option limit '99'
        option leasetime '24h'
        list dhcp_option '6,192.168.2.6,192.168.2.1'

config odhcpd 'odhcpd'
        option maindhcp '0'
        option leasefile '/tmp/hosts/odhcpd'
        option leasetrigger '/usr/sbin/odhcpd-update'
        option loglevel '4'

config dhcp 'GUEST'
        option interface GUEST'
        option start '100'
        option limit '150'
        option leasetime '12h'
cat /etc/config/firewall


config defaults
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'REJECT'
        option synflood_protect '1'
        option flow_offloading '1'
        option flow_offloading_hw '1'

config zone
        option name 'lan'
        option input 'ACCEPT'
        option output 'ACCEPT'
        option forward 'ACCEPT'
        list network 'lan'

config zone
        option name 'wan'
        option input 'REJECT'
        option output 'ACCEPT'
        option masq '1'
        option mtu_fix '1'
        list network 'WAN'
        option forward 'REJECT'

config forwarding
        option src 'lan'
        option dest 'wan'

config rule
        option name 'Allow-DHCP-Renew'
        option src 'wan'
        option proto 'udp'
        option dest_port '68'
        option target 'ACCEPT'
        option family 'ipv4'

config rule
        option name 'Allow-Ping'
        option src 'wan'
        option proto 'icmp'
        option icmp_type 'echo-request'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-IGMP'
        option src 'wan'
        option proto 'igmp'
        option family 'ipv4'
        option target 'ACCEPT'

config rule
        option name 'Allow-DHCPv6'
        option src 'wan'
        option proto 'udp'
        option src_ip 'fc00::/6'
        option dest_ip 'fc00::/6'
        option dest_port '546'
        option family 'ipv6'
        option target 'ACCEPT'
        option enabled '0'

config rule
        option name 'Allow-MLD'
        option src 'wan'
        option proto 'icmp'
        option src_ip 'fe80::/10'
        list icmp_type '130/0'
        list icmp_type '131/0'
        list icmp_type '132/0'
        list icmp_type '143/0'
        option family 'ipv6'
        option target 'ACCEPT'
        option enabled '0'

config rule
        option name 'Allow-ICMPv6-Input'
        option src 'wan'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        list icmp_type 'router-solicitation'
        list icmp_type 'neighbour-solicitation'
        list icmp_type 'router-advertisement'
        list icmp_type 'neighbour-advertisement'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'
        option enabled '0'

config rule
        option name 'Allow-ICMPv6-Forward'
        option src 'wan'
        option dest '*'
        option proto 'icmp'
        list icmp_type 'echo-request'
        list icmp_type 'echo-reply'
        list icmp_type 'destination-unreachable'
        list icmp_type 'packet-too-big'
        list icmp_type 'time-exceeded'
        list icmp_type 'bad-header'
        list icmp_type 'unknown-header-type'
        option limit '1000/sec'
        option family 'ipv6'
        option target 'ACCEPT'
        option enabled '0'

config rule
        option name 'Allow-IPSec-ESP'
        option src 'wan'
        option dest 'lan'
        option proto 'esp'
        option target 'ACCEPT'

config rule
        option name 'Allow-ISAKMP'
        option src 'wan'
        option dest 'lan'
        option dest_port '500'
        option proto 'udp'
        option target 'ACCEPT'
        option enabled '0'

config rule
        option name 'Support-UDP-Traceroute'
        option src 'wan'
        option dest_port '33434:33689'
        option proto 'udp'
        option family 'ipv4'
        option target 'REJECT'
        option enabled '0'

config include
        option path '/etc/firewall.user'

config rule
        option name 'GUEST allow DHCP DNS'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53 67 68'
        option target 'ACCEPT'

config rule
        option name 'GUEST allow DHCP DNS'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53'
        option target 'ACCEPT'
        option family 'ipv4'
        option dest 'lan'
        list dest_ip '192.168.2.6'


        list proto 'all'


config redirect
        option target 'DNAT'
        option name 'Redirect DNS to Pi-hole'
        option src 'lan'
        option src_dport '53'
        option dest_ip '192.168.2.6'
        option dest_port '53'
        option dest 'lan'
        option src_ip '!192.168.2.6'
        list src_mac '!ROUTER's OWN MAC'
        option src_dip '!192.168.2.1'
        option reflection '0'
        list proto 'udp'

config nat
        option name 'prevent unexpected DNS'
        option src 'lan'
        option dest_ip '192.168.2.6'
        option target 'MASQUERADE'
        list proto 'udp'
        option dest_port '53'

config rule
        option name 'block 853'
        option src '*'
        option dest 'wan'
        option dest_port '853'
        option target 'REJECT'
        list src_ip '!192.168.2.6'
        list src_ip '!192.168.2.1'
        list src_ip '192.168.3.1'


config rule
        option name 'block hardcoded DNS IP'
        option src '*'
        option dest '*'
        option target 'REJECT'
        list proto 'all'
        list dest_ip '8.8.8.8'
        list dest_ip '4.4.4.4'
        list dest_ip '1.1.1.1'
        list dest_ip '9.9.9.9'
        list dest_ip '8.8.4.4'

config zone
        option name 'GUEST'
        option input 'REJECT'
        option output 'ACCEPT'
        option forward 'REJECT'
        list network 'GUEST'

config forwarding
        option src 'GUEST'
        option dest 'wan'

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.6'
        option dest 'lan'
        option family 'ipv4'

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '!192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.1'

config nat
        option name 'prevent unexpected DNS GUEST'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53'
        option target 'MASQUERADE'
        option dest_ip '192.168.2.6'

It is indeed as you say it should be.

DNAT works that way that router forwards connection and response. If you want pihole to see query sources you have to set DNS server via DHCP option.

I configured the DHCP to serve the Pi-Hole Ip address and i also disabled the

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.6'
        option dest 'lan'
        option family 'ipv4'

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '!192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.1'

config nat
        option name 'prevent unexpected DNS GUEST'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53'
        option target 'MASQUERADE'
        option dest_ip '192.168.2.6'

i restarted the firewall and reconnected to the guest network, it changed nothing.

Observe conntrack -E to figure out what happens to DNS packets. Any DNS ipv4 packet crossing your router will gain its source address.

As others said by default an OpenWrt router advertises itself as the DNS server with the IP of the interface (192.168.3.1 for guests). The internal DNS process then recurses requests for names that it does not know to an upstream server. The router uses its own IP for these requests. This is not a NATing of the original request, it is generating a new request from the internal service. The reply will then be cached and returned to the original requestor.

If you want to bypass this process so that the Pi-Hole receives DNS queries directly from guests, retaining their .3 source IP:

  • block input to port 53 (local DNS) from src guest. Typically a guest network has input generally blocked with an "Allow-DNS" rule as an exception. Remove that rule.
  • configure dhcp server option 6 to advertise Pi-Hole IP to guests (this also removes the default advertise router IP 192.168.3.1)
  • Allow port 53 forward from guest to lan 192.168.2.6.
1 Like

the VPN clients get served 2 class B adresses, say X an d Y from the VPN server pool, the DNS request directed at X get redirected to the pihole through port forwarding on the OpenWRT that runs the VPN Server and in this case they show up with their respective class B IPs in the Pi-Hole log; the ones directed at Y are redirected to my edge router which himself then tries to ask the Pi-Hole or the locally running unbound which serves as a backup should the Pi-Hole be nonreachable for whatever reason.
There is a route to the VPN class B pool configured on the edge router such that it sends the traffic to the OpenWRT that the VPN runs on so that connections from the WAN to the VPN can be initiated.
So the fact that the VPN clients show up with their class B Address in the Pi-Hole when there is some DNAT involved makes me believe that the guest network might also work like that.

Alright, thank you all for your input, it made me think about stuff i hadn't considered before.

I got it working.

The guest clients now get served 192.168.3.1 by the router as the resolver, which gets forwarded to the PiHole, where they appear with their individual IP. Guest clients also get served the actually not assigned to anything IP 192.168.33.3 as a resolver as a backup should the Pi-Hole not be reachable. If a guest client uses any resolver except 192.168.3.1, it gets forwarded to the router which in turn queries what it can and knows; in this case, to prevent unexpected source, i set up the masquerade rule as below.
Somehow, assigning 192.168.3.3 as a resolver failed, but i can live with that, at least for now. I did turn AP isolation off to test it.
Here is an excerpt of the configfile that made it possible

cat /etc/config/firewall

config zone
        option name 'GUEST'
        option input 'REJECT'
        option output 'ACCEPT'
        option forward 'REJECT'
        list network 'GUEST'

config forwarding
        option src 'GUEST'
        option dest 'wan'

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.6'
        option dest 'lan'
        option family 'ipv4'

config redirect
        option target 'DNAT'
        option name 'catch GUEST DNS'
        list proto 'udp'
        option src 'GUEST'
        option src_dip '!192.168.3.1'
        option src_dport '53'
        option dest_port '53'
        option dest_ip '192.168.2.1'

config rule
        option name 'GUEST allow DHCP DNS'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53 67 68'
        option target 'ACCEPT'

config rule
        option name 'GUEST allow DNS'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53'
        option target 'ACCEPT'
        option family 'ipv4'
        option dest 'lan'
        list dest_ip '192.168.2.6'
        option dest_port '53'

config nat
        option name 'prevent unexpected DNS GUEST'
        list proto 'udp'
        option src 'GUEST'
        option dest_port '53'
        option target 'MASQUERADE'
        option dest_ip '!192.168.2.6'

config rule
        option name 'block hardcoded DNS IP'
        option src '*'
        option dest '*'
        option target 'REJECT'
        list proto 'all'
        list dest_ip '8.8.8.8'
        list dest_ip '4.4.4.4'
        list dest_ip '1.1.1.1'
        list dest_ip '9.9.9.9'
        list dest_ip '8.8.4.4'

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.