DL6ER 685d46bbfb dnsmasq: back off MAC lookups for clients that have no ARP entry
`_FTL_new_query()` asks `find_mac()` for a client's hardware address whenever
`client->hwlen` is still unset. For a client that has no ARP entry the lookup
returns nothing, `hwlen` stays unset, and the next query asks again - so the
lookup runs on *every* query for the lifetime of that client. That is the normal
case for anything behind a router and for every loopback client, including the
internal handoff FTL's own encrypted-DNS listeners use.

It is not cheap. `find_mac()` may trigger a netlink call to refresh the ARP
table, and the SHM lock is deliberately dropped around it so the I/O does not
block other threads. Measured over 20 000 queries from a loopback client, with
the per-component counters behind `debug.performance`:

```
new_query -> find_mac (netlink)                20000 calls, avg  66.8 us
new_query -> lock_shm() wait after find_mac    20000 calls, avg 107.8 us
new_query (total under lock)                   20002 calls, avg 541.6 us
```

The lookup fired on all 20 000 queries, and re-acquiring the lock afterwards
cost more than the work the unlock was protecting. Together the two account for
some 27% of FTL's per-query cost, spent re-learning that the answer is still no.

Remember when a lookup came back empty and skip it for a minute
(`MAC_LOOKUP_BACKOFF`), rather than never: an ARP entry that appears later is
still picked up on the next attempt. A client whose MAC is already known is
unaffected, as `hwlen` short-circuits the check before the new one.

Loopback clients are skipped outright: 127.0.0.0/8 and ::1 never appear in the
neighbour table, so unlike a client that may gain an ARP entry later there is
nothing to come back for, and one lookup per backoff interval would still be
one too many.

This adds `hwaddr_next_try` to `clientsData`, so the shared-memory version goes
to 18. The field is a `uint32_t` of monotonic seconds placed in the alignment
padding between `hash` and `groupspos`, so it costs nothing: measured with and
without it, `sizeof(clientsData)` is 688 either way and `ippos` stays at offset
64, the cache-line start that padding exists to guarantee. `clientsData` is a
per-client object held in shared memory, so a `time_t` appended to it would have
cost 16 bytes each and pushed `ippos` across the boundary.

Signed-off-by: DL6ER <dl6er@dl6er.de>
2026-08-05 23:31:08 +02:00
2026-06-08 19:59:53 +02:00
2025-03-06 15:04:02 +02:00

Pi-hole logo
Network-wide ad blocking via your own Linux hardware

FTLDNS logo

FTLDNS (pihole-FTL) provides an interactive API and also generates statistics for Pi-hole®'s Web interface.

  • Fast: stats are read directly from memory by coupling our codebase closely with dnsmasq
  • Versatile: upstream changes to dnsmasq can quickly be merged in without much conflict
  • Lightweight: runs smoothly with minimal hardware and software requirements such as Raspberry Pi Zero
  • Interactive: our API can be used to interface with your projects
  • Insightful: stats normally reserved inside of dnsmasq are made available so you can see what's really happening on your network

Official documentation

The official FTLDNS documentation can be found here.

Installation

FTLDNS (pihole-FTL) is automatically installed when installing Pi-hole.

IMPORTANT

FTLDNS will disable any existing installations of dnsmasq. This is because FTLDNS is dnsmasq + Pi-hole's code, so both cannot run simultaneously.

S
Description
The Pi-hole FTL engine
Readme EUPL-1.2
256 MiB
Languages
C 96.8%
C++ 1.1%
Python 1%
Shell 0.7%
CMake 0.3%