Files
FTL/src
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-15 10:03:46 +02:00
2026-03-28 08:05:57 +01:00