Skip to main content
Back to Blog
Cybersecurity12 min read

Your DNS Resolver Is Already a Network Sensor: Shipping Pi-hole Query Logs into a Detection Pipeline

By Quinn Vidal|July 11, 2026

In our last post on passive AI-service detection with Zeek, we ended the coverage story with an admission: wireless devices on a different aggregation switch were a documented gap. The mirror port saw everything on the wired office segment — and nothing anywhere else.

This post is about closing that gap without buying hardware, and it starts from an observation that feels obvious the moment you say it out loud: the one component that already sees every device on every segment is your DNS resolver.

If your DHCP hands out a resolver you control — Pi-hole, in our case — then every workstation, phone, tablet, TV, and IoT gadget on every VLAN announces the services it's about to use, at DNS level, to a box you administer. No mirror port. No agent. No traffic in the path. The query log is sitting right there.

The engineering problem isn't getting the data. It's getting it reliably — without dropping events during outages, without double-counting devices, and without building another one of the silent failure modes we spent the last post warning about. The forwarder we built to do this is open source at github.com/AIQSO/pihole-dns-forwarder, and this post covers the parts that took actual thought.

The Mirroring Boundary

Port mirroring gives you the deepest passive signal available — TLS SNI, JA3/JA4 fingerprints, flow shapes — but only where you can physically insert a mirror. In most small and mid-size networks, that means the wired core. What it structurally cannot reach:

  • WiFi VLANs. Client traffic terminates on access points; unless every AP uplinks through a switch you can mirror (and you can afford to mirror them all), wireless devices are invisible.
  • IoT and camera segments hanging off their own switches.
  • Anything added after you built the mirror. Networks grow toward your blind spots.

DNS inverts the geometry. Instead of copying traffic in flight, you read intentions at the point every device already reports to voluntarily. The trade is depth for coverage — DNS tells you a device asked about a service, not that it connected or how much it sent. That trade is exactly why the two sources compose so well:

Two telemetry lanes: every device resolves through Pi-hole whose query log a forwarder ships to bulk ingest; a mirrored office segment feeds a Zeek sensor into the same pipeline

One strategy line summarizes it: DNS coverage everywhere, deep passive inspection where mirrored.

Reading Pi-hole v6: The API Mechanics

Pi-hole v6 replaced the old PHP admin API with a REST API served by FTL itself, and three of its behaviors shape any serious integration.

Session auth with finite slots. You authenticate with POST /api/auth and get back a short-lived session id (sid) sent as a header on every request. Two practical notes: first, create a dedicated app password (Settings → Web interface/API) instead of using the admin password — it survives admin-password rotations and keeps the real credential out of your config. Second, sessions get evicted: max_sessions is finite, and a crashed client that never called DELETE /api/auth leaves a slot occupied until expiry. Your client needs to re-authenticate exactly once on a 401 and free its session on shutdown, or you'll starve yourself of slots across restarts.

A time-windowed query log with a snapshot cursor. GET /api/queries takes a from timestamp and returns newest-first pages, plus a cursor that pins the result set — pagination walks a consistent snapshot instead of a moving target. Every query record carries a monotonically increasing id, which is the only thing you should trust for "have I seen this?" — timestamps repeat at busy moments.

A 24-hour in-memory buffer. FTL keeps the last 24 hours of queries available to the API regardless of what you do. This is a gift for reliability engineering: it means the resolver itself can be your retry buffer, and your forwarder never needs to persist undelivered events on disk.

The Reliability Engineering

Passive detection systems fail toward silence — that was the thesis of the Zeek post, learned the hard way. So the forwarder was built assuming every component around it will misbehave: the ingest endpoint will go down mid-batch, the daemon will be restarted at the worst moment, and one of these failures will eventually happen during the other.

Watermark reads, not log tailing. Each poll asks for queries since the last seen timestamp (minus a small overlap window for boundary records) and filters on the monotonic id. Crucially, there are two watermarks: an in-memory one that advances as records are fetched, and a persisted one that advances only when a batch is confirmed delivered. A restart during a send outage re-polls everything queued-but-unsent from Pi-hole's 24h buffer. The failure mode this kills: the classic tail-and-forward design that acknowledges reads on fetch and silently loses whatever was in flight when the process died.

Backpressure, never eviction. If the send queue fills because the ingest side is down, the forwarder simply stops polling. The watermark stays put; Pi-hole holds the backlog. When the endpoint recovers, the backlog drains oldest-first through the snapshot cursor — paged from the tail of the result set, so a backlog deeper than one poll's page budget advances the watermark only past records actually fetched. Nothing is skipped behind a jumped watermark, and nothing is dropped from a full queue.

Dedup keyed on (client, query) — the key is the point. A resolver-level source exists for one reason: per-device attribution. A naive dedup on query name alone would mean the first laptop to resolve a domain suppresses every other device's first sighting for the window. Keying dedup on the (client, query) pair keeps the event volume sane (repeat lookups from the same device collapse) while guaranteeing that a new device touching a domain is always reported.

Loss accounting you can audit. Every record read from the API lands in exactly one counter — sent, sampled, excluded, malformed, or rejected — and the counters ship in a heartbeat event every 60 seconds. When someone asks "are we seeing everything?", the answer is arithmetic, not vibes.

A watchdog for the failure Restart=always can't see. systemd's Restart=always catches a process that exits. It does nothing for a process that hangs — wedged alive at 0% CPU, holding its PID, forwarding nothing. We've had a forwarder do exactly that for days. The fix is Type=notify plus WatchdogSec: the daemon pings systemd every loop iteration, and a hang stops the pings and triggers a restart. If you run any long-lived forwarder without a watchdog, you are trusting a failure mode you cannot observe.

Fail-loud configuration. Required settings have no defaults. A forwarder that starts with a guessed endpoint and forwards into the void is worse than one that refuses to start — the second one pages you; the first one quietly becomes a coverage lie.

The Identity Trap

One subtle decision matters more than it looks: events identify devices by client IP only, deliberately matching what Zeek's dns.log provides. It's tempting to enrich Pi-hole events with MAC addresses — Pi-hole knows them for local clients. But if two sensors feed one pipeline and they identify the same laptop differently (IP from one, MAC+IP from the other), your device inventory mints duplicates and every per-device count becomes wrong in a way that's maddening to debug. Pick one identity scheme across all sensors and enforce it in the event schema. Divergent identifiers are a data-quality bug you inflict on yourself.

What DNS Telemetry Cannot See

Same rule as last time: the limits belong in writing, before anyone asks.

LimitWhat it meansWhat you can do
Encrypted DNS (DoH/DoT)A client using DNS-over-HTTPS (:443) or DNS-over-TLS (:853) bypasses your resolver entirelyBlock outbound :853 for DoT; blocklist known DoH resolver endpoints (DoH rides :443, so it can't be blanket-blocked); treat encrypted-DNS adoption as its own policy signal
Hardcoded resolversA device pointed at a public resolver over plain :53 never talks to Pi-holeFirewall rules redirecting or blocking outbound :53 that isn't your resolver
OS-level cachingA cached name doesn't re-query — you see first use, not every useAccept it; DNS gives you presence and frequency, not session counts
Query is not connectionResolving a domain proves interest, not contactPair with flow/SNI data where mirrored; treat DNS-only sightings accordingly
IP identity is DHCP-fragileLease churn can reassign an IP between devicesLong leases or reservations for segments where attribution matters

Two non-obvious notes from running this in production. First, blocked queries are still signal — a lookup that Pi-hole's blocklist denied is still a device announcing intent, so the forwarder ships those too, tagged with their blocked status. Second, DNS-as-sensor means DNS-as-dependency: the week we shipped this, we discovered our secondary resolver had been quietly dead — which made the primary a single point of failure for both name resolution and telemetry. If your resolver becomes part of your security architecture, run two of them and test the failover path from the segments that matter.

What This Buys You

With the forwarder live, every device that resolves through your Pi-hole — including every wireless and IoT device no mirror port will ever see — feeds the same event pipeline as your deep-inspection sensors, in the same event shape, with loss accounting you can audit. The coverage gap we documented in the Zeek post is closed at the DNS layer, and the two sources corroborate each other where they overlap.

The forwarder — single file, systemd unit, 31 unit tests, Apache-2.0 — is at github.com/AIQSO/pihole-dns-forwarder, alongside the Zeek detection configs from the companion post. What sits on top of the pipeline — device attribution, the AI-service inventory, risk scoring, and the dashboards that turn events into answers — ships as Faron, our agentless shadow-AI detection product.

If your network has segments a mirror port can't reach — most do — book a call and we'll map what DNS-level visibility would and wouldn't see in your environment.

Q
Quinn Vidal
AIQSO
Share:

Need Help With Your Digital Strategy?

AIQSO helps businesses automate workflows, train custom AI models, and optimize their online presence.

Privacy Settings

We use cookies to enhance your browsing experience, provide personalized content, and analyze our traffic. We respect your privacy and will never sell your data. Read our privacy policy