Skip to content

dnsmasq fails to start at boot when listen-address is on a late-arriving interface (bind-interfaces should be bind-dynamic) #761

Description

@planitron

Summary

roles/infra/templates/dns/dnsmasq.conf ships bind-interfaces together with listen-address=127.0.0.1,{{ inventory_hostname }}.

When the node address lives on an interface that is not up yet at boot (an overlay/VPN interface such as WireGuard, Tailscale or ZeroTier, or a VIP managed by vip-manager), dnsmasq cannot bind, retries, and gives up permanently — taking the internal *.pigsty DNS down until someone restarts it by hand.

Switching to bind-dynamic fixes it, and dnsmasq itself recommends that option.

Environment

Pigsty v4.4.0 (still present on main)
OS Ubuntu 24.04 LTS
dnsmasq 2.90
systemd 255
Node address on an interface brought up after basic.target

Root cause

bind-interfaces makes dnsmasq bind each configured address explicitly at startup, which requires every interface to already exist. The generated config is:

listen-address=127.0.0.1,10.0.0.1     # 10.0.0.1 lives on a late-arriving interface
bind-interfaces

At boot that interface is not up yet, so:

dnsmasq: failed to create listening socket for 10.0.0.1: Cannot assign requested address
dnsmasq: FAILED to start up
systemd: dnsmasq.service: Scheduled restart job, restart counter is at 5.
systemd: dnsmasq.service: Start request repeated too quickly.

dnsmasq warns about this configuration itself:

dnsmasq: LOUD WARNING: use --bind-dynamic rather than --bind-interfaces to avoid DNS amplification

Why a drop-in in /etc/dnsmasq.d/ cannot fix it

bind-interfaces and bind-dynamic are mutually exclusive. A drop-in adding bind-dynamic conflicts with the bind-interfaces already present in the main config file, so the only possible fix is in the template itself — which ./infra.yml -t dns regenerates on every run.

Suggested fix

- bind-interfaces
+ bind-dynamic

bind-dynamic attaches to interfaces as they appear, and keeps the same anti-amplification property as bind-interfaces (it does not answer on addresses it was not configured for). libvirt adopted bind-dynamic for exactly this class of interface-timing issue.

Verification after the change

systemctl restart dnsmasq
journalctl -u dnsmasq -b | grep -c "LOUD WARNING"    # -> 0
getent hosts <node-name> i.pigsty                     # resolves
# and after a full reboot:
systemctl show dnsmasq -p NRestarts --value           # -> 0 (no failed start, no retry)

Impact

Any deployment where the node address is not on a physical interface present at boot: overlay networks, VIPs, or vip-manager setups. On a static datacenter network the current default is harmless — which is probably why this went unnoticed.

References

  • dnsmasq(8)--bind-dynamic vs --bind-interfaces
  • libvirt switched to bind-dynamic for the same interface-timing problem

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions