Skip to content

pi.hole record: address selection for multi-homed hosts is arbitrary and differs between plain and encrypted DNS #2996

Description

@DL6ER

Background

The pi.hole special name (and the local <hostname>) resolves to a locally derived address. With one address per family this is unambiguous, but on a multi-homed interface the selection is arbitrary, and since #2989 added the inbound DoT/DoH server it now differs between plain and encrypted transports. This issue tracks giving both transports one consistent, usable policy.

Surfaced by @darkexplosiveqwx while reviewing #2989; the investigation below is theirs. Full write-up: #2989 (comment)

Reproducer

A dummy interface with several addresses per family (host-only /32 and /128, plus shared /30 and /100 subnets):

sudo ip link add dummy0 type dummy && sudo ip link set dummy0 up
sudo ip addr add 192.168.100.10/32 dev dummy0
sudo ip addr add 192.168.100.11/32 dev dummy0
sudo ip addr add 192.168.101.1/30 dev dummy0
sudo ip addr add 192.168.101.2/30 dev dummy0
sudo ip -6 addr add 2001:db8:100::10/128 dev dummy0
sudo ip -6 addr add 2001:db8:100::11/128 dev dummy0
sudo ip -6 addr add 2001:db8:101::1/100 dev dummy0
sudo ip -6 addr add 2001:db8:101::2/100 dev dummy0

Current behavior

  • Plain DNS (port 53): every query, regardless of source address or family, is answered with the firstmost address in enumeration (here 192.168.100.10 for A, 2001:db8:101::2 for AAAA).
  • Encrypted DNS (DoT/DoH), as of Add inbound DoT/DoH server for clients -> Pi-hole #2989:
    • Same family as the transport: answered with the exact address the client connected to (usable, correct).
    • Cross family: previously returned the loopback (127.0.0.1/::1), which is plain wrong. Mitigated in 2c370df to return NODATA instead - safe, but a stopgap.

So the record hands out different answers over plain vs. encrypted DNS, and over plain DNS an address the client may not be able to reach.

Options (apply to both transports)

  • firstmost address only - current plain behavior; simple and predictable-ish, but a magic pick that may be unusable.
  • all addresses of the interface - simple, includes the usable ones (plus possibly unusable ones).
  • the exact connected address only - only when the family matches; known usable (current encrypted same-family behavior).
  • all addresses matching the client's subnet - only when the family matches; most likely to be usable.
  • all addresses of the same scope (IPv6) - ULA/LL/GUA relative to the connected address; effectively a broader subnet match.

Limitations

  • Cross-family queries are fundamentally under-informed: we do not know the client's address in the other family. A database lookup would be same-segment only and unreliable, so it is not worth it.
  • Returning the loopback is wrong (already fixed for the encrypted path).
  • Returning nothing cross-family hurts discoverability - no client expects to have to query both families.

Proposal

Direction I agree with (per @darkexplosiveqwx's evaluation):

  • Same family: return the addresses on the arriving interface that match the client's subnet - least arbitrary, most likely usable.
  • Cross family: return all of that family's addresses on the arriving interface rather than a single magic pick - gives the client fallbacks, and any leakage is bounded to the one interface the query arrived on.
  • The static, non-localized dns.reply.host.force4/force6 (and friends) keep overriding everything and stay positive, as today.

The encrypted-path loopback -> NODATA mitigation already shipped in #2989 (2c370df) as a stopgap; this issue is about replacing the arbitrary firstmost/magic selection with a consistent, subnet-aware policy across both transports.

References

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions