MACVLAN interface disrupts dnsmasq DHCP clients

I'm trying to tamp down an issue with dnsmasq's handling of DHCP while running a macvlan interface. DHCP is handled on the router. DNS is via pihole, currently on an ubuntu VM on my homelab server, but I'm trying to make it a docker container on the router.

In my current network, I have
10.19.76.1 gateway (APU2) on openwrt 21.02.1, has the docker bits and kmod-macvlan installed.
10.19.76.2 Linksys EA6350 as an AP (no firewall/dnsmasq/odhcpd/etc) on openwrt 21.02.1
10.19.76.3 Linksys EA8300 as an AP (no firewall/dnsmasq/odhcpd/etc) on openwrt 21.02.1
10.19.76.4 macvlan interface for docker containers
10.19.76.5 docker pihole container
10.19.76.6 managed PoE switch
10.19.76.13 an ubuntu VM with pihole/unbound as the current DNS server for the lan.

The APU2 router is running dnsmasq for DHCP, with DNS listening disabled, handing out DHCP addresses from 10.19.76.128/25. Everything below 10.19.76.128 is via static IP or DHCP reservation configuration.

The pihole VM IP is advertised via DHCP options, with the br-lan/WAN interfaces using it, and firewall rules to keep hardcoded devices from using alternate DNS servers.

I'm trying to consolidate and get pihole running as a docker container on my primary router and decommission the VM since that's much heavier than a container. I've tried following along with two guides:

I can get the 10.19.76.5 docker container up and running, and pingable from the host (aka the gateway router). Routing works, and staticly-configured devices run fine. The issue is DHCP clients, whether they are random-assigned or have a DHCP reservation, cannot renew or acquire an IP from the router.

The phone below has a DHCP reservation IP of 10.19.76.98 in the router, but dnsmasq starts churning through DHCP pool IPs as it and the phone keep offering and declining. The br-lan.20 device is a macvlan interface with a static 10.19.76.4 IP, br-lan as the parent. br-lan has two devices it is bridging: eth1 and eth2 (eth0 is WAN). Nothing is connected to the eth2 port, the core switch is connected to eth1. The AP devices are connected to the switch. In past experiences, devices that were connected to the eth2 interface showed up in pihole queries with the router hostname as the originator, rather than the device's hostname. So everything got moved to a bigger, single switch on eth1.
A snippet of the system log while the br-lan.20 interface was up:

Mon Nov  7 18:46:28 2022 daemon.info dnsmasq-dhcp[31526]: DHCPDISCOVER(br-lan) f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:28 2022 daemon.info dnsmasq-dhcp[31526]: DHCPOFFER(br-lan) 10.19.76.220 f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:28 2022 daemon.warn dnsmasq-dhcp[31526]: not using configured address 10.19.76.98 because it was previously declined
Mon Nov  7 18:46:28 2022 daemon.info dnsmasq-dhcp[31526]: DHCPDISCOVER(br-lan.20) f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:28 2022 daemon.info dnsmasq-dhcp[31526]: DHCPOFFER(br-lan.20) 10.19.76.220 f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:29 2022 daemon.info dnsmasq-dhcp[31526]: DHCPREQUEST(br-lan) 10.19.76.220 f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:29 2022 daemon.info dnsmasq-dhcp[31526]: DHCPACK(br-lan) 10.19.76.220 f8:87:f1:cb:e9:2a iPhone11
Mon Nov  7 18:46:29 2022 daemon.info dnsmasq-dhcp[31526]: DHCPREQUEST(br-lan.20) 10.19.76.220 f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:29 2022 daemon.info dnsmasq-dhcp[31526]: DHCPNAK(br-lan.20) 10.19.76.220 f8:87:f1:cb:e9:2a wrong server-ID
Mon Nov  7 18:46:31 2022 daemon.info dnsmasq-dhcp[31526]: DHCPDECLINE(br-lan) 10.19.76.220 f8:87:f1:cb:e9:2a
Mon Nov  7 18:46:54 2022 daemon.warn dnsmasq-dhcp[31526]: not using configured address 10.19.76.98 because it was previously declined

Here's what the syslog looks like as the macvlan interface is brought down and clients connect again:

Wed Nov  9 12:13:22 2022 daemon.warn dnsmasq-dhcp[27040]: not using configured address 10.19.76.98 because it was previously declined
Wed Nov  9 12:13:22 2022 daemon.info dnsmasq-dhcp[27040]: DHCPDISCOVER(br-lan) f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:22 2022 daemon.info dnsmasq-dhcp[27040]: DHCPOFFER(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:23 2022 daemon.info dnsmasq-dhcp[27040]: DHCPREQUEST(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:23 2022 daemon.info dnsmasq-dhcp[27040]: DHCPACK(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a iPhone11
Wed Nov  9 12:13:48 2022 daemon.warn dnsmasq-dhcp[27040]: not using configured address 10.19.76.98 because it was previously declined
Wed Nov  9 12:13:48 2022 daemon.info dnsmasq-dhcp[27040]: DHCPDISCOVER(br-lan) f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:48 2022 daemon.info dnsmasq-dhcp[27040]: DHCPOFFER(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:49 2022 daemon.info dnsmasq-dhcp[27040]: DHCPREQUEST(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a
Wed Nov  9 12:13:49 2022 daemon.info dnsmasq-dhcp[27040]: DHCPACK(br-lan) 10.19.76.146 f8:87:f1:cb:e9:2a iPhone11
Wed Nov  9 12:13:52 2022 daemon.notice netifd: Interface 'macvlan' is now down
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Withdrawing address record for 10.19.76.4 on br-lan.20.
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Leaving mDNS multicast group on interface br-lan.20.IPv4 with address 10.19.76.4.
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Interface br-lan.20.IPv4 no longer relevant for mDNS.
Wed Nov  9 12:13:52 2022 kern.info kernel: [17089.618720] device br-lan left promiscuous mode
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Interface br-lan.20.IPv6 no longer relevant for mDNS.
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Leaving mDNS multicast group on interface br-lan.20.IPv6 with address fe80::20d:b9ff:fe4a:c397.
Wed Nov  9 12:13:52 2022 daemon.info avahi-daemon[3483]: Withdrawing address record for fe80::20d:b9ff:fe4a:c397 on br-lan.20.
Wed Nov  9 12:13:52 2022 daemon.notice netifd: Interface 'macvlan' is disabled
Wed Nov  9 12:13:52 2022 daemon.notice netifd: macvlan 'br-lan.20' link is down
Wed Nov  9 12:13:52 2022 daemon.notice netifd: Interface 'macvlan' has link connectivity loss
Wed Nov  9 12:13:54 2022 daemon.info dnsmasq-dhcp[27040]: DHCPDISCOVER(br-lan) b6:dc:80:62:c5:04
Wed Nov  9 12:13:54 2022 daemon.info dnsmasq-dhcp[27040]: DHCPOFFER(br-lan) 10.19.76.214 b6:dc:80:62:c5:04

I read a thread here that the EA6350/EA8300 ipq40xx devices have issues with VLANs, but the DHCP issue strikes all clients including hardwired ethernet devices.

So long as a DHCP lease is active, the devices have no connectivity issues. It's when they renew or join fresh that DHCP negotiation fails. In the DHCP server settings, I've tried setting the Listen Interfaces and Exclude Interfaces, but that fails. I've tried removing eth2 from br-lan and making it it's own interface with the macvlan attached to it, but that fails too. Tried setting a manual MAC address for the macvlan interface, same thing. Devices can't negotiate a DHCP lease. As soon as I disable the macvlan interface, devices can connect successfully again.

The host router needs to be able to communicate with the pihole docker container as I do a great deal of ssh SOCKS proxy tunneling from a remote site, and if the router can't talk to the container, DNS resolution for that remote device fails.

spicy bits of /etc/config/network

config device
        option name 'br-lan'
        option type 'bridge'
        list ports 'eth1'
        list ports 'eth2'

config interface 'lan'
        option device 'br-lan'
        option proto 'static'
        list dns '10.19.76.13'
        list ipaddr '10.19.76.1/24'

config interface 'macvlan'
        option proto 'static'
        option defaultroute '0'
        option netmask '255.255.255.255'
        option device 'br-lan.20'
        option ipaddr '10.19.76.4'

config device
        option type 'macvlan'
        option ifname 'br-lan'
        option mode 'bridge'
        option name 'br-lan.20'
        option acceptlocal '1'
        option macaddr '00:0D:B9:4A:C3:97'

config route
        option interface 'macvlan'
        option target '10.19.76.5'
        option netmask '255.255.255.255'

relevant /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 localservice '1'
        option ednspacket_max '1232'
        option confdir '/tmp/dnsmasq.d'
        list server '10.19.76.13'
        option port '0'
        list addnhosts '/etc/ethers'
        list interface 'br-lan'
        list notinterface 'br-lan.20'

config dhcp 'lan'
        option interface 'lan'
        option leasetime '12h'
        option dhcpv4 'server'
        list dhcp_option '6,10.19.76.13'
        list dhcp_option '4,10.19.76.1'
        list dhcp_option '42,10.19.76.1'
        option start '128'
        option limit '127'
        list ra_flags 'none'
...
config host
        option name 'iPhone11'
        option dns '1'
        option mac 'F8:87:F1:CB:E9:2A'
        option ip '10.19.76.98'
        option leasetime '12:00:00'

the docker-compose.yml for creating the pihole container:

root@Gateway:~# cat docker-compose.yml
version: "3.3"

# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    hostname: gatewaypihole.lan
    environment:
       TZ: 'America/New York'
       WEBPASSWORD: '123456'
    # Volumes store your data between container upgrades
    volumes:
      - './pihole/etc-pihole/:/etc/pihole/'
      - './pihole/etc-dnsmasq.d/:/etc/dnsmasq.d/'
      - './pihole/var-log/:/var/log'
      - './pihole/etc-cont-init.d/10-fixroutes.sh:/etc/cont-init.d/10-fixroutes.sh'
    # Recommended but not required (DHCP needs NET_ADMIN)
    #   https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
    cap_add:
      - NET_ADMIN
    restart: unless-stopped
    networks:
      internal:
      lan:
        ipv4_address: 10.19.76.5

networks:
  internal:
  lan:
    name: lan
    driver: macvlan
    driver_opts:
      parent: br-lan.20
    ipam:
      config:
        - subnet: 10.19.76.0/24

Adding the /etc/config/network from my two AP devices if that's relevant:
EA8300 AP:

root@EA8300:~# cat /etc/config/network

config interface 'loopback'
        option proto 'static'
        option ipaddr '127.0.0.1'
        option netmask '255.0.0.0'
        option device 'lo'

config globals 'globals'
        option ula_prefix 'fdde:dbb5:8067::/48'

config interface 'lan'
        option proto 'static'
        option ip6assign '60'
        option device 'br-lan'
        option netmask '255.255.255.0'
        option gateway '10.19.76.1'
        list dns '10.19.76.13'
        option ipaddr '10.19.76.3'

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

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

config device
        option name 'br-lan'
        option type 'bridge'
        list ports 'eth0'
        list ports 'eth1'

EA6350 AP:

root@EA6350:~# cat /etc/config/network

config interface 'loopback'
        option proto 'static'
        option ipaddr '127.0.0.1'
        option netmask '255.0.0.0'
        option device 'lo'

config globals 'globals'
        option ula_prefix 'fd15:81b4:12ec::/48'

config interface 'lan'
        option proto 'static'
        option ip6assign '60'
        option ipaddr '10.19.76.2'
        option gateway '10.19.76.1'
        option netmask '255.255.255.0'
        list dns '10.19.76.13'
        option device 'br-lan'

config device 'lan_eth0_dev'
        option name 'eth0'
        option macaddr '60:38:e0:93:34:4b'

config device 'wan_eth1_dev'
        option name 'eth1'
        option macaddr '60:38:e0:93:34:4a'

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

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

config device
        option name 'br-lan'
        option type 'bridge'
        list ports 'eth0'
        list ports 'eth1'

Finally found a system log from a client that's showing more of what's going on. This is from my iMac that otherwise is connected via ethernet, but here is trying to get a DHCP address for it's wifi interface:

|default|18:35:42.749213-0500|configd|DHCP en1: setting 10.19.76.227 netmask 255.255.255.0 broadcast 10.19.76.255|
|default|18:35:42.778990-0500|configd|DHCP en1: publish success|
|default|18:35:42.784635-0500|configd|DHCP en1: 10.19.76.227 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:35:42.785145-0500|configd|DHCP en1: status = 'address in use'|
|default|18:35:42.788678-0500|configd|DHCP en1: removing 10.19.76.227|
|default|18:36:04.060393-0500|configd|DHCP en1: 10.19.76.228 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:36:04.060508-0500|configd|DHCP en1: status = 'address in use'|
|default|18:36:23.889610-0500|configd|DHCP en1: 10.19.76.231 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:36:23.889712-0500|configd|DHCP en1: status = 'address in use'|
|default|18:36:43.540858-0500|configd|DHCP en1: 10.19.76.234 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:36:43.540936-0500|configd|DHCP en1: status = 'address in use'|
|default|18:36:59.187850-0500|configd|DHCP en1: setting 10.19.76.235 netmask 255.255.255.0 broadcast 10.19.76.255|
|default|18:36:59.209098-0500|configd|DHCP en1: publish success|
|default|18:36:59.271372-0500|configd|DHCP en1: 10.19.76.235 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:36:59.271985-0500|configd|DHCP en1: status = 'address in use'|
|default|18:36:59.272670-0500|configd|DHCP en1: removing 10.19.76.235|
|default|18:37:16.981010-0500|configd|DHCP en1: setting 10.19.76.237 netmask 255.255.255.0 broadcast 10.19.76.255|
|default|18:37:16.993586-0500|configd|DHCP en1: publish success|
|default|18:37:16.998008-0500|configd|DHCP en1: 10.19.76.237 in use by de:67:60:37:96:c7, DHCP Server 10.19.76.1|
|default|18:37:16.998384-0500|configd|DHCP en1: status = 'address in use'|
|default|18:37:17.999582-0500|configd|DHCP en1: removing 10.19.76.237|

The de:67:60:37:96:c7 MAC address is the macvlan interface. The MAC will change unless set staticly, but the result is the same. I tried changing the macvlan interface from a static protocol to a DHCP client (with a DHCP reservation set up for it by MAC), no luck.

Hi @pp6000v2 and welcome to the forum! That's a nice setup, the only thing I think is missing is the below in your /etc/config/dhcp. One thing I learned in past experiences is that interfaces should have a config section in /etc/config/dhcp, even if ignore '1', this makes dnsmasq behave better.

config dhcp 'macvlan'
        option interface 'macvlan'
        option start '100'
        option limit '150'
        option leasetime '12h'
        option ignore '1'
        list ra_flags 'none'

I realized that the hostname for the pihole container is set to gatewaypihole.lan, I suggest you change it to gatewaypihole or just pihole, without the .lan suffix, otherwise pihole will reply NXDOMAIN if you lookup gatewaypihole.lan but will give an answer if you lookup gatewaypihole.lan.lan (took me a while to figure this one out :joy:).

Hope this helps!

Tried that one already because being a virtual interface running on top of the br-lan interface (with and without Listen/Exclude interfaces configured), I thought maybe explicitly setting it up with 'ignore' would help dnsmasq, you know, ignore it. Eventually took it one option box tick further, unchecking the dynamic DHCP box as the description for it

Dynamically allocate DHCP addresses for clients. If disabled, only clients having static leases will be served.

let me think just for a second that maybe it'd work since all of my devices in the house have either static IP's outside of the DHCP range, or a DHCP reservation configured in the server.

config dhcp 'macvlan'
     option interface 'macvlan'
     option start '100'
     option limit '150'
     option leasetime '12h'
     option ignore '1'
     option dynamicdhcp '0'
     list ra_flags 'none'

It did not haha.

Can't edit the lead post anymore, but ETA:

root@Gateway:~# 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 localservice '1'
        option ednspacket_max '1232'
        option confdir '/tmp/dnsmasq.d'
        list server '10.19.76.13'
        option port '0'
        list addnhosts '/etc/ethers'
        list notinterface 'macvlan'
        list notinterface 'veth5'

config dhcp 'lan'
        option interface 'lan'
        option leasetime '12h'
        option dhcpv4 'server'
        option start '128'
        option limit '127'
        option force '1'
        list dhcp_option '6,10.19.76.13'
        list dhcp_option '4,10.19.76.1'
        list dhcp_option '42,10.19.76.1'
        list ra_flags 'none'

config dhcp 'wan'
        option interface 'wan'
        option ignore '1'
        list ra_flags 'none'

config dhcp 'wg_lan'
        option interface 'wg_lan'
        option ignore '1'
        list ra_flags 'none'

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

config dhcp 'macvlan'
        option interface 'macvlan'
        option start '100'
        option limit '150'
        option leasetime '12h'
        option ignore '1'
        option dynamicdhcp '0'
        list ra_flags 'none'

config host
        option name 'macvlan'
        option dns '1'
        option ip '10.19.76.4'
        option mac 'DE:67:60:37:96:C7'
        option leasetime '12:00:00'

config host
        option name 'EA6350-AP'
        option dns '1'
        option mac '60:38:E0:93:34:4B'
        option ip '10.19.76.2'

config host
        option name 'EA8300-AP'
        option dns '1'
        option mac 'C4:41:1E:23:16:41'
        option ip '10.19.76.3'

config host
        option name 'Core-Switch'
        option dns '1'
        option mac '8C:3B:AD:C9:DB:69'
        option ip '10.19.76.6'
        option leasetime '12:00:00'
...
config host
        option name 'iPhone11'
        option dns '1'
        option mac 'F8:87:F1:CB:E9:2A'
        option ip '10.19.76.98'
        option leasetime '12:00:00'

Kinda have it figured out. There was a thread on https://bugzilla.kernel.org/show_bug.cgi?id=206885
that very closely shadowed my experience. I have net.ipv4.conf.all.proxy_arp=1 set in /etc/sysctl.conf due to a wireguard interface setup (so that remote clients can access internal lan resources). This proxy_arp setting has the (buggy or expected) result of ARP replies coming from the macvlan interface for many/all IP's on the network. This led to DHCP clients seeing their leased IP in use by someone else and declining the lease.

It took a minute since I was refreshing and not rebooting (thought commenting out a line would restore to defaults on refresh, but apparently you must change a 1 to 0 to make the change = 0). Changing sysctl.conf to

net.ipv4.conf.all.proxy_arp = 0
net.ipv4.conf.eth2/20.proxy_arp = 0
net.ipv4.conf.br-lan.proxy_arp = 1
net.ipv4.conf.wg_lan.proxy_arp = 1

and running sysctl -p got everything working properly by doing a per-interface proxy_arp. Now my wireguard clients get everything, and DHCP clients connect and don't have IP conflicts.

Nope, can't ping the gateway router (aka the docker host) from inside the container. Watching tcpdump output, the macvlan interface is just asking over and over who has 10.19.76.1 with no response.

I can ping from the host to the container, and it works. Just can't get the container to be able to ping the host/gateway router. Which rather breaks pihole's ability to update gravity or work.