mirror of
https://github.com/pi-hole/FTL.git
synced 2026-08-20 18:27:33 +01:00
`_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>