DNS Encryption Audit via Passive LAN Tap (OPNsense ↔ ISP Modem)¶
Guide for using a passive Ethernet tap ("ninja star" / Throwing Star LAN Tap style) to verify whether DNS traffic leaving your network toward your ISP is actually encrypted.
1. What the Tap Gives You¶
A passive Ethernet tap splits the TX/RX pairs onto separate monitor ports.
- Passive, one direction per port. No power needed on cheap resistor-based taps, but each monitor port only sees traffic in one direction. Capture from both monitor ports simultaneously to see full-duplex traffic.
- Speed limits. Cheap resistor taps are often 10/100 only. On Gigabit WAN links you may need to force the link down to
100baseTX <full-duplex>to get a reliable passive tap. Skip this if you have an active/gig-capable tap. - Placement. Insert the tap between the OPNsense WAN interface and the ISP modem, so you capture OPNsense's actual egress traffic — after any outbound rules/NAT, i.e., ground truth of what's hitting the wire.
[Modem] <--> [Tap Port A/B] <--> [OPNsense WAN NIC]
| |
Monitor1 Monitor2
| |
[Capture Laptop - 2 NICs, or 2 capture devices]
If your capture machine only has one NIC:
- Add a USB-to-Ethernet adapter for the second monitor port, or
- Capture only the modem-facing (upstream) monitor port — sufficient for the DNS-encryption question, since it shows exactly what leaves toward your ISP.
If you need to force 100Mbps: OPNsense → Interfaces > [WAN] > Speed and Duplex → set to 100baseTX <full-duplex>, run your capture, then set back to autoselect.
2. Capture the Traffic¶
# identify the interface connected to the tap
ip link show
# capture everything, write to file for later analysis in Wireshark
sudo tcpdump -i eth1 -w dns_audit.pcap -s 0
# or filter live to just DNS-relevant ports
sudo tcpdump -i eth1 -w dns_audit.pcap -s 0 'port 53 or port 853 or port 443'
Let it run while generating DNS activity from inside your network (browse a few sites, or trigger manual lookups — see Section 5).
3. What Encrypted DNS Looks Like on the Wire¶
| Protocol | Port | Wireshark signature |
|---|---|---|
| Plain DNS | UDP/TCP 53 | Protocol column shows DNS; query names readable in plaintext (Standard query A example.com) |
| DNS-over-TLS (DoT) | TCP 853 | Protocol shows TLS; handshake visible, then encrypted application data — no query names visible |
| DNS-over-HTTPS (DoH) | TCP 443 | Protocol shows TLS/HTTP2; indistinguishable from normal HTTPS by port alone — check SNI in ClientHello |
The real test: any traffic on port 53 leaving toward the WAN is plaintext DNS, regardless of what your resolver config claims.
4. One-Shot Summary Command (tshark)¶
This scans the whole capture and prints any plaintext DNS query names found, plus a breakdown of DoT/DoH/plain-53 traffic — no manual paging through Wireshark required.
# 1. Any plaintext DNS queries leaving the WAN side (the critical leak check)
tshark -r dns_audit.pcap -Y "dns.flags.response == 0" \
-T fields -e frame.time -e ip.src -e ip.dst -e dns.qry.name \
> plaintext_dns_queries.txt
echo "=== Plaintext DNS queries found: ==="
wc -l < plaintext_dns_queries.txt
cat plaintext_dns_queries.txt
# 2. DoT sessions and their SNI (confirms who you're actually talking to over 853)
echo "=== DoT (port 853) sessions and SNI: ==="
tshark -r dns_audit.pcap -Y "tcp.port == 853 && tls.handshake.type == 1" \
-T fields -e frame.time -e ip.dst -e tls.handshake.extensions_server_name
# 3. DoH candidates on 443 (SNI matching common DoH providers)
echo "=== Possible DoH (port 443) sessions: ==="
tshark -r dns_audit.pcap -Y "tcp.port == 443 && tls.handshake.type == 1 && \
(tls.handshake.extensions_server_name contains \"dns\" || \
tls.handshake.extensions_server_name == \"1.1.1.1\" || \
tls.handshake.extensions_server_name == \"dns.google\" || \
tls.handshake.extensions_server_name == \"cloudflare-dns.com\")" \
-T fields -e frame.time -e ip.dst -e tls.handshake.extensions_server_name
# 4. Raw counts, for a quick pass/fail
echo "=== Packet counts by category: ==="
echo -n "Plain DNS (53): "; tshark -r dns_audit.pcap -Y "udp.port==53 or tcp.port==53" -T fields -e frame.number | wc -l
echo -n "DoT (853): "; tshark -r dns_audit.pcap -Y "tcp.port==853" -T fields -e frame.number | wc -l
echo -n "TLS/443 total: "; tshark -r dns_audit.pcap -Y "tcp.port==443 && tls" -T fields -e frame.number | wc -l
Reading the output:
- plaintext_dns_queries.txt non-empty → you have a leak. The file shows exactly which queries and which source IP sent them, so you can trace it to a specific device/service.
- DoT section shows SNI not matching your configured resolver → misconfigured forwarder or unexpected upstream.
- DoH section populated → some client (often a browser with built-in DoH) is doing encrypted DNS outside your local resolver — not a leak, but worth knowing about since it bypasses local logging.
5. Verification Workflow (Wireshark GUI)¶
Open dns_audit.pcap and apply these filters in sequence:
a. Plaintext leakage (most important):
dns
Any hits with a destination outside your LAN = unencrypted DNS leaving your network.
b. DoT:
tcp.port == 853
Right-click a stream → Follow → TLS Stream. Should show a TLS handshake with no cleartext DNS payload. Check tls.handshake.extensions_server_name in the ClientHello to confirm the SNI matches your intended DoT resolver.
c. DoH:
tls.handshake.extensions_server_name contains "dns"
or target known providers directly:
tls.handshake.extensions_server_name == "cloudflare-dns.com" or tls.handshake.extensions_server_name == "dns.google" or tls.handshake.extensions_server_name == "1.1.1.1"
d. Confirm the fix:
udp.port == 53 or tcp.port == 53
Should return zero packets on the WAN-side capture once DNS is fully encrypted and non-leaking.
6. Generate Known Lookups for Correlation¶
While capturing, trigger deliberate lookups so you have known timestamps to search for:
dig @<your-configured-resolver> example.com
7. Cross-Reference Against OPNsense Config¶
- Services > Unbound DNS > General — confirm "DNS over TLS" is enabled for outgoing queries, and check the forwarding server format.
- Services > Unbound DNS > Query Forwarding — verify forwarding targets use
IP@853(DoT), not the default port 53. This is the most common misconfig: DoT "enabled" in name only, still forwarding on 53. - Confirm no other service (container, IoT device, app with hardcoded resolver) is bypassing Unbound and going straight to
8.8.8.8:53on the WAN — the pcap catches this even when the OPNsense config looks correct.
8. Common Findings¶
| Observation | Meaning |
|---|---|
| Zero UDP/TCP 53 packets on WAN-side capture | DNS is encrypted end-to-end, working as intended |
| Plaintext query names visible on port 53 | Unbound isn't actually forwarding over TLS (check @853 syntax), or a LAN client is bypassing your resolver |
| TLS on 853 but SNI doesn't match configured resolver | Misconfigured forwarder, or worth checking for unexpected MITM/rogue CA |
| Unexpected DoH-shaped TLS on 443 | Likely a browser (Firefox/Chrome ship DoH defaults) doing its own encrypted DNS independent of OPNsense — not a leak, but bypasses local resolver/logging |
Tap type assumed: passive resistor-based ("Throwing Star" style). Adjust the 100Mbps forcing step if using a gig-capable active tap.