Add: dns leak detection
This commit is contained in:
69
docs/COMMANDS.md
Normal file
69
docs/COMMANDS.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# WTFnet Commands
|
||||
|
||||
This document lists CLI commands and supported flags. Output defaults to text; use `--json` for structured output.
|
||||
|
||||
## Global flags
|
||||
- `--json` / `--pretty`
|
||||
- `--no-color` / `--quiet`
|
||||
- `-v` / `-vv` / `--verbose`
|
||||
- `--log-level <error|warn|info|debug|trace>`
|
||||
- `--log-format <text|json>`
|
||||
- `--log-file <path>`
|
||||
- `NETTOOL_LOG_FILTER` or `RUST_LOG` can override log filters (ex: `maxminddb::decoder=debug`)
|
||||
|
||||
## sys
|
||||
- `sys ifaces`
|
||||
- `sys ip` flags: `--all`, `--iface <name>`
|
||||
- `sys route` flags: `--ipv4`, `--ipv6`, `--to <ip>`
|
||||
- `sys dns`
|
||||
|
||||
## ports
|
||||
- `ports listen` flags: `--tcp`, `--udp`, `--port <n>`
|
||||
- `ports who <port>`
|
||||
- `ports conns` flags: `--top <n>`, `--by-process`
|
||||
|
||||
## neigh
|
||||
- `neigh list` flags: `--ipv4`, `--ipv6`, `--iface <name>`
|
||||
|
||||
## cert
|
||||
- `cert roots`
|
||||
- `cert baseline <path>`
|
||||
- `cert diff <path>`
|
||||
|
||||
## geoip
|
||||
- `geoip lookup <ip>`
|
||||
- `geoip status`
|
||||
|
||||
## probe
|
||||
- `probe ping <host>` flags: `--count <n>`, `--timeout-ms <n>`, `--interval-ms <n>`, `--no-geoip`
|
||||
- `probe tcping <host:port>` flags: `--count <n>`, `--timeout-ms <n>`, `--socks5 <url>`, `--prefer-ipv4`, `--no-geoip`
|
||||
- `probe trace <host>` flags: `--max-hops <n>`, `--per-hop <n>`, `--timeout-ms <n>`, `--udp`, `--port <n>`, `--rdns`, `--no-geoip`
|
||||
|
||||
## dns
|
||||
- `dns query <domain> <type>` flags: `--server <ip[:port]>`, `--transport <udp|tcp|dot|doh>`, `--tls-name <name>`, `--socks5 <url>`, `--prefer-ipv4`, `--timeout-ms <n>`
|
||||
- `dns detect <domain>` flags: `--servers <csv>`, `--transport <udp|tcp|dot|doh>`, `--tls-name <name>`, `--socks5 <url>`, `--prefer-ipv4`, `--repeat <n>`, `--timeout-ms <n>`
|
||||
- `dns watch` flags: `--duration <Ns|Nms>`, `--iface <name>`, `--filter <pattern>`
|
||||
- `dns leak status` flags: `--profile <full-tunnel|proxy-stub|split>`, `--policy <path>`
|
||||
- `dns leak watch` flags: `--duration <Ns|Nms>`, `--iface <name>`, `--profile <full-tunnel|proxy-stub|split>`, `--policy <path>`, `--privacy <full|redacted|minimal>`, `--out <path>`, `--summary-only`, `--iface-diag`
|
||||
- `dns leak report` flags: `<path>`, `--privacy <full|redacted|minimal>`
|
||||
|
||||
## http
|
||||
- `http head|get <url>` flags: `--timeout-ms <n>`, `--follow-redirects <n>`, `--show-headers`, `--show-body`, `--max-body-bytes <n>`, `--http1-only`, `--http2-only`, `--http3` (feature `http3`), `--http3-only` (feature `http3`), `--geoip`, `--socks5 <url>`
|
||||
|
||||
## tls
|
||||
- `tls handshake|cert|verify|alpn <host:port>` flags: `--sni <name>`, `--alpn <csv>`, `--timeout-ms <n>`, `--insecure`, `--socks5 <url>`, `--prefer-ipv4`, `--show-extensions`, `--ocsp`
|
||||
|
||||
## discover
|
||||
- `discover mdns` flags: `--duration <Ns|Nms>`, `--service <type>`
|
||||
- `discover ssdp` flags: `--duration <Ns|Nms>`
|
||||
- `discover llmnr` flags: `--duration <Ns|Nms>`, `--name <host>`
|
||||
- `discover nbns` flags: `--duration <Ns|Nms>`
|
||||
|
||||
## diag
|
||||
- `diag` flags: `--out <path>`, `--bundle <path>`, `--dns-detect <domain>`, `--dns-timeout-ms <n>`, `--dns-repeat <n>`
|
||||
|
||||
## calc
|
||||
- `calc subnet <cidr>|<ip> <mask>`
|
||||
- `calc contains <a> <b>`
|
||||
- `calc overlap <a> <b>`
|
||||
- `calc summarize <cidr...>`
|
||||
176
docs/DNS_LEAK_DETECTOR_IMPLEMENTATION.md
Normal file
176
docs/DNS_LEAK_DETECTOR_IMPLEMENTATION.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# DNS Leak Detector - Implementation Guide (v0.4)
|
||||
|
||||
This document explains how to implement the DNS leak detector as a new subcrate in WTFnet.
|
||||
|
||||
## 1) New crate: `wtfnet-dnsleak`
|
||||
|
||||
### 1.1 Module layout
|
||||
|
||||
crates/wtfnet-dnsleak/src/
|
||||
- lib.rs
|
||||
- policy.rs # safe path constraints + presets
|
||||
- sensor.rs # passive capture -> normalized TrafficEvent stream
|
||||
- classify.rs # transport classification + confidence
|
||||
- route.rs # interface/route classification (tunnel/physical/loopback)
|
||||
- rules.rs # Leak-A/B/C/D evaluation
|
||||
- report.rs # LeakEvent + SummaryReport builders
|
||||
- privacy.rs # full/redacted/minimal redaction logic
|
||||
|
||||
## 2) Core data types
|
||||
|
||||
### 2.1 TrafficEvent (raw from sensor)
|
||||
Fields:
|
||||
- ts: timestamp
|
||||
- proto: udp/tcp
|
||||
- src_ip, src_port
|
||||
- dst_ip, dst_port
|
||||
- iface_name (capture interface if known)
|
||||
- payload: optional bytes (only for plaintext DNS parsing)
|
||||
|
||||
### 2.2 ClassifiedEvent
|
||||
Adds:
|
||||
- transport: udp53/tcp53/dot/doh/unknown
|
||||
- doh_confidence: HIGH/MEDIUM/LOW (only if doh)
|
||||
- qname/qtype: nullable
|
||||
|
||||
### 2.3 EnrichedEvent
|
||||
Adds:
|
||||
- route_class: loopback/tunnel/physical/unknown
|
||||
- process info: pid/ppid/name (nullable)
|
||||
- attribution_confidence: HIGH/MEDIUM/LOW/NONE
|
||||
- attrib_failure_reason: optional string
|
||||
|
||||
### 2.4 LeakEvent (final output)
|
||||
Adds:
|
||||
- leak_type: A/B/C/D
|
||||
- severity: P0..P3
|
||||
- policy_rule_id
|
||||
- evidence: minimal structured evidence
|
||||
|
||||
## 3) Platform integration: Process Attribution Engine (PAE)
|
||||
|
||||
### 3.1 Trait addition (wtfnet-platform)
|
||||
|
||||
Add:
|
||||
trait FlowOwnerProvider {
|
||||
fn owner_of(
|
||||
&self,
|
||||
proto: Proto,
|
||||
src_ip: IpAddr,
|
||||
src_port: u16,
|
||||
dst_ip: IpAddr,
|
||||
dst_port: u16,
|
||||
) -> FlowOwnerResult;
|
||||
}
|
||||
|
||||
FlowOwnerResult:
|
||||
- pid, ppid, process_name (optional)
|
||||
- confidence: HIGH/MEDIUM/LOW/NONE
|
||||
- failure_reason: optional string
|
||||
|
||||
Design rule: attribution is best-effort and never blocks leak detection.
|
||||
|
||||
## 4) Transport classification logic
|
||||
|
||||
### 4.1 Plain DNS
|
||||
Match:
|
||||
- UDP dst port 53 OR TCP dst port 53
|
||||
Parse QNAME/QTYPE from payload.
|
||||
|
||||
### 4.2 DoT
|
||||
Match:
|
||||
- TCP dst port 853
|
||||
|
||||
### 4.3 DoH (heuristic)
|
||||
Match candidates:
|
||||
- TCP dst port 443 AND (one of):
|
||||
- dst IP in configured DoH resolver list
|
||||
- dst SNI matches known DoH provider list (if available)
|
||||
- frequent small HTTPS bursts pattern (weak)
|
||||
|
||||
Attach confidence:
|
||||
- MEDIUM: known endpoint match
|
||||
- LOW: traffic-shape heuristic only
|
||||
|
||||
## 5) Policy model
|
||||
|
||||
Policy defines "safe DNS path" constraints:
|
||||
- allowed interfaces
|
||||
- allowed destinations (IP/CIDR)
|
||||
- allowed processes
|
||||
- allowed ports
|
||||
|
||||
A DNS event is a leak if it violates safe-path constraints.
|
||||
|
||||
### 5.1 Built-in profiles
|
||||
|
||||
full-tunnel:
|
||||
- allow DNS only via tunnel iface or loopback stub
|
||||
- any UDP/TCP 53 on physical iface => Leak-A
|
||||
|
||||
proxy-stub (default):
|
||||
- allow DNS only to loopback stub
|
||||
- allow stub upstream only to proxy destinations
|
||||
- flag direct DoH/DoT outside proxy path => Leak-C
|
||||
|
||||
split:
|
||||
- allow plaintext DNS only for allowlist
|
||||
- enforce unknown => proxy resolve (Leak-B)
|
||||
|
||||
## 6) Leak rules (A/B/C/D)
|
||||
|
||||
Leak-A (plaintext escape):
|
||||
- transport udp53/tcp53
|
||||
- route_class != allowed
|
||||
- dst not in allowed destination set
|
||||
|
||||
Leak-B (split policy intent leak):
|
||||
- qname matches proxy-required set or "unknown"
|
||||
- query observed going to ISP/domicile resolver or non-tunnel iface
|
||||
|
||||
Leak-C (encrypted bypass):
|
||||
- DoT or DoH flow exists
|
||||
- not via approved egress path (iface/destination)
|
||||
|
||||
Leak-D (mismatch indicator):
|
||||
- correlate qname to later TCP/TLS flows (optional v0.4 NICE)
|
||||
|
||||
## 7) Privacy modes
|
||||
|
||||
Because domains and cmdlines are sensitive, support:
|
||||
- Full: store full qname and cmdline
|
||||
- Redacted (default): hash qname or keep eTLD+1 only; truncate cmdline
|
||||
- Minimal: no domains/cmdline; keep leak counts + resolver IPs + process name
|
||||
|
||||
Privacy mode applies in report builder, not in sensor.
|
||||
|
||||
## 8) CLI integration
|
||||
|
||||
Add under `dns` command group:
|
||||
|
||||
- `dns leak status`
|
||||
- `dns leak watch`
|
||||
- `dns leak report`
|
||||
|
||||
`watch` returns:
|
||||
- summary report (human) by default
|
||||
- `--json` returns structured report with events list
|
||||
|
||||
## 9) Recommended incremental build plan
|
||||
|
||||
Phase 1 (core passive detection):
|
||||
- sensor: udp/tcp capture
|
||||
- classify: udp53/tcp53/dot
|
||||
- parse plaintext qname/qtype
|
||||
- policy: allowlist + allowed interfaces/dests
|
||||
- leak rules: Leak-A + Leak-C (DoT)
|
||||
- report: events + summary
|
||||
|
||||
Phase 2 (process attribution + DoH heuristics):
|
||||
- platform FlowOwnerProvider impls
|
||||
- DoH heuristic classification + confidence
|
||||
- privacy modes
|
||||
|
||||
Phase 3 (optional correlation / Leak-D):
|
||||
- flow tracker correlating DNS -> TCP/TLS connect events
|
||||
- mismatch indicator output
|
||||
154
docs/RELEASE_v0.4.0.md
Normal file
154
docs/RELEASE_v0.4.0.md
Normal file
@@ -0,0 +1,154 @@
|
||||
# WTFnet v0.4.0 - DNS Leak Detection
|
||||
|
||||
v0.4.0 introduces a client-side DNS leak detector aimed at censorship-resistance threat models:
|
||||
detect when DNS behavior escapes the intended safe path. The detector focuses on evidence:
|
||||
transport, interface, destination, and (best-effort) process attribution.
|
||||
|
||||
This release does NOT include HTTP/3 or OS-native TLS verification.
|
||||
|
||||
## 0) Summary
|
||||
|
||||
New major capability: `dns leak` command group.
|
||||
|
||||
Core idea:
|
||||
Passive monitor captures outbound DNS-like traffic -> classify (Plain DNS / DoT / DoH) ->
|
||||
enrich with interface/route/process metadata -> evaluate leak definitions (A/B/C/D) ->
|
||||
emit events + summary report.
|
||||
|
||||
Leak definitions are explicit:
|
||||
- Leak-A: plaintext DNS outside safe path
|
||||
- Leak-B: split-policy intent leak (proxy-required domains resolved via ISP/local path)
|
||||
- Leak-C: encrypted DNS escape/bypass (DoH/DoT outside approved egress)
|
||||
- Leak-D: mismatch risk indicator (DNS egress differs from TCP/TLS egress)
|
||||
|
||||
## 1) Goals
|
||||
|
||||
### G1. Detect DNS leaks without needing special test domains
|
||||
Passive detection must work continuously and produce evidence.
|
||||
|
||||
### G2. Support censorship-resistance leak definitions
|
||||
Include both classic VPN-bypass leaks and split-policy intent leaks.
|
||||
|
||||
### G3. Best-effort process attribution
|
||||
Attach PID/PPID/process name when OS allows; degrade gracefully with confidence.
|
||||
|
||||
### G4. Privacy-aware by default
|
||||
Support privacy modes: Full / Redacted / Minimal.
|
||||
|
||||
## 2) Non-goals (v0.4.0)
|
||||
|
||||
- No "doctor" / smart one-shot diagnosis command
|
||||
- No shell completions / man pages
|
||||
- No HTTP/3 support
|
||||
- No OS-native TLS verifier integration
|
||||
- No firewall modification / kill switch management (detection only)
|
||||
|
||||
## 3) New crates / architecture changes
|
||||
|
||||
### 3.1 New subcrate: `wtfnet-dnsleak`
|
||||
Responsibilities:
|
||||
- passive sensor (pcap/pnet feature-gated)
|
||||
- DNS parser (plaintext only)
|
||||
- transport classifier: udp53/tcp53/dot/doh (confidence)
|
||||
- flow tracker + metadata enrichment
|
||||
- process attribution integration
|
||||
- leak rules engine (A/B/C/D)
|
||||
- structured event + summary report builder
|
||||
|
||||
### 3.2 `wtfnet-platform` extension: flow ownership lookup
|
||||
Add a new trait:
|
||||
- FlowOwnerProvider: map observed traffic 5-tuple -> process info (best-effort)
|
||||
|
||||
Return process attribution confidence:
|
||||
HIGH/MEDIUM/LOW/NONE plus failure reason.
|
||||
|
||||
## 4) CLI scope
|
||||
|
||||
### 4.1 Commands
|
||||
New command group:
|
||||
|
||||
#### `wtfn dns leak watch`
|
||||
Start passive monitoring for a bounded duration (default 10s):
|
||||
- classify transports (udp53/tcp53/dot/doh)
|
||||
- apply leak rules and emit events + summary
|
||||
|
||||
#### `wtfn dns leak status`
|
||||
Print baseline snapshot:
|
||||
- interfaces + routes
|
||||
- system DNS configuration
|
||||
- active policy summary
|
||||
|
||||
#### `wtfn dns leak report`
|
||||
Parse a saved events file and produce a human summary.
|
||||
|
||||
### 4.2 Flags (proposed)
|
||||
Common:
|
||||
- `--duration <Ns|Nms>` (default 10s)
|
||||
- `--iface <name>` (optional capture interface)
|
||||
- `--policy <path>` (JSON policy file)
|
||||
- `--profile <full-tunnel|proxy-stub|split>` (built-in presets)
|
||||
- `--privacy <full|redacted|minimal>` (default redacted)
|
||||
- `--out <path>` (write JSON report/events)
|
||||
|
||||
## 5) Policy model (v0.4.0)
|
||||
|
||||
Safe DNS path constraints can be defined by:
|
||||
- allowed interfaces: loopback/tunnel
|
||||
- allowed destination set: proxy IPs, internal resolvers
|
||||
- allowed processes: only local stub/proxy can resolve upstream
|
||||
- allowed ports: e.g. only 443 to proxy server
|
||||
|
||||
A DNS event is a leak if it violates safe-path constraints.
|
||||
|
||||
Built-in profiles:
|
||||
1) full-tunnel VPN style
|
||||
2) proxy + local stub (default, censorship model)
|
||||
3) split policy
|
||||
|
||||
## 6) Outputs
|
||||
|
||||
### 6.1 Leak events (structured)
|
||||
Each LeakEvent includes:
|
||||
- timestamp
|
||||
- transport: udp53/tcp53/dot/doh/unknown
|
||||
- qname/qtype (nullable)
|
||||
- interface + route_class
|
||||
- dst ip:port
|
||||
- process info (nullable) + attribution confidence
|
||||
- leak_type: A/B/C/D
|
||||
- severity: P0..P3
|
||||
- evidence fields + optional geoip
|
||||
|
||||
### 6.2 Summary report
|
||||
- leak counts by type
|
||||
- top leaking processes (if available)
|
||||
- top resolver destinations
|
||||
- timeline/burst hints
|
||||
|
||||
## 7) Deliverables checklist
|
||||
|
||||
MUST:
|
||||
- new `wtfnet-dnsleak` crate integrated into workspace + CLI
|
||||
- passive capture for UDP/TCP 53 and TCP 853
|
||||
- DoH heuristic classification (confidence-based)
|
||||
- policy engine + Leak-A/B/C/D rules
|
||||
- structured events + human summary
|
||||
- privacy modes full/redacted/minimal
|
||||
- best-effort process attribution with confidence and failure reason
|
||||
|
||||
SHOULD:
|
||||
- saved report file support (`--out report.json`)
|
||||
- route_class inference with policy hints + heuristics
|
||||
|
||||
NICE:
|
||||
- correlation_id (DNS -> subsequent TCP/TLS connection) for Leak-D mismatch indicator
|
||||
|
||||
## 8) Definition of Done
|
||||
|
||||
- v0.4.0 builds on Linux (Debian/Ubuntu) and Windows
|
||||
- `wtfn dns leak watch` detects:
|
||||
- plaintext DNS leaving physical interface (Leak-A)
|
||||
- DoT traffic leaving outside approved egress (Leak-C)
|
||||
- DoH-ish encrypted resolver traffic outside policy (Leak-C)
|
||||
- events include interface + dst + (best-effort) PID/process info
|
||||
- output remains stable and additive; no breaking change to v0.3 commands
|
||||
@@ -29,17 +29,17 @@ This is a practical checklist to execute v0.3.0.
|
||||
- [x] diff categories: add/remove/expired/changed
|
||||
|
||||
## 6) optional LLMNR/NBNS
|
||||
- [ ] implement `discover llmnr`
|
||||
- [ ] implement `discover nbns`
|
||||
- [ ] bounded collection, low-noise
|
||||
- [x] implement `discover llmnr`
|
||||
- [x] implement `discover nbns`
|
||||
- [x] bounded collection, low-noise
|
||||
|
||||
## 7) docs updates
|
||||
- [x] update README roadmap
|
||||
- [ ] update COMMANDS.md with new flags/commands
|
||||
- [ ] add RELEASE_v0.3.0.md
|
||||
- [x] update COMMANDS.md with new flags/commands
|
||||
- [x] add RELEASE_v0.3.0.md
|
||||
|
||||
## 8) optional HTTP/3 (last)
|
||||
- [x] add `http3` cargo feature + deps
|
||||
- [x] implement `--http3` / `--http3-only`
|
||||
- [ ] define error classification for QUIC failures
|
||||
- [ ] keep feature disabled in default builds until stabilized
|
||||
- [x] define error classification for QUIC failures
|
||||
- [x] keep feature disabled in default builds until stabilized
|
||||
|
||||
33
docs/WORK_ITEMS_v0.4.0.md
Normal file
33
docs/WORK_ITEMS_v0.4.0.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# WTFnet v0.4.0 - Work Items
|
||||
|
||||
This is a practical checklist to execute v0.4.0.
|
||||
|
||||
## 1) platform flow ownership
|
||||
- [x] add FlowOwnerProvider trait + data types
|
||||
- [x] implement Linux best-effort lookup
|
||||
- [x] implement Windows best-effort lookup
|
||||
|
||||
## 2) new wtfnet-dnsleak crate
|
||||
- [x] crate scaffold + pcap feature
|
||||
- [x] UDP/TCP 53 capture + classify
|
||||
- [x] DoT detection (TCP 853)
|
||||
- [x] policy model + profiles
|
||||
- [x] leak rules A/B/C (partial)
|
||||
- [x] privacy modes
|
||||
- [x] report + summary builder
|
||||
|
||||
## 3) CLI wiring
|
||||
- [x] add `dns leak status`
|
||||
- [x] add `dns leak watch`
|
||||
- [x] add `dns leak report`
|
||||
|
||||
## 4) docs updates
|
||||
- [x] add `docs/RELEASE_v0.4.0.md`
|
||||
- [x] add `docs/DNS_LEAK_DETECTOR_IMPLEMENTATION.md`
|
||||
- [x] update README roadmap + flags
|
||||
- [x] update COMMANDS.md
|
||||
- [x] update status/implementation docs
|
||||
|
||||
## 5) follow-ups
|
||||
- [ ] add DoH heuristic classification (optional)
|
||||
- [ ] add Leak-D mismatch correlation (optional)
|
||||
723
docs/dns_leak_detection_design.md
Normal file
723
docs/dns_leak_detection_design.md
Normal file
@@ -0,0 +1,723 @@
|
||||
Below is a **high-level (language-agnostic)** design for a **client-side DNS leak detector** aimed at *censorship-resistance threat models*, i.e.:
|
||||
|
||||
> “Censor/ISP can observe/log DNS intent or infer proxy usage; we want to detect when DNS behavior escapes the intended protection path.”
|
||||
|
||||
I’ll cover: **definitions**, **detection standards**, **workflow**, **modules**, **passive+active detection**, **outputs**, and **test methodology**.
|
||||
|
||||
---
|
||||
|
||||
# 1) Scope and goals
|
||||
|
||||
## Goals
|
||||
|
||||
Your detector should answer, with evidence:
|
||||
|
||||
1. **Did any DNS query leave the device outside the intended safe path?**
|
||||
2. **Which domains leaked?** (when visible)
|
||||
3. **Which transport leaked?** (UDP/53, TCP/53, DoT/853, DoH)
|
||||
4. **Which interface leaked?** (Wi-Fi/Ethernet vs tunnel)
|
||||
5. **Which process/app triggered it?** (if your OS allows attribution)
|
||||
|
||||
And in your censorship model, it should also detect:
|
||||
|
||||
6. **Split-policy intent leakage**: “unknown/sensitive domains were resolved using domestic/ISP-facing DNS.”
|
||||
|
||||
## Non-goals (be explicit)
|
||||
|
||||
* Not a censorship circumvention tool itself
|
||||
* Not a full firewall manager (can suggest fixes, but detection is the core)
|
||||
* Not perfect attribution on every OS (process mapping may be partial)
|
||||
|
||||
---
|
||||
|
||||
# 2) Define “DNS leak” precisely (your program’s standard)
|
||||
|
||||
You need a **formal definition** because “DNS leak” is overloaded.
|
||||
|
||||
## Standard definition A (classic VPN / tunnel bypass)
|
||||
|
||||
A leak occurs if:
|
||||
|
||||
> **An unencrypted DNS query is sent outside the secure tunnel path**
|
||||
> This is essentially how popular leak test sites define it (“unencrypted DNS query sent OUTSIDE the established VPN tunnel”). ([IP Leak][1])
|
||||
|
||||
Your detector should implement it in a machine-checkable way:
|
||||
|
||||
**Leak-A condition**
|
||||
|
||||
* DNS over **UDP/53 or TCP/53**
|
||||
* Destination is **not** a “trusted resolver path” (e.g., not the tunnel interface, not loopback stub, not proxy channel)
|
||||
* Interface is **not** the intended egress
|
||||
|
||||
✅ Strong for censorship: plaintext DNS exposes intent.
|
||||
|
||||
---
|
||||
|
||||
## Standard definition B (split-policy intent leak)
|
||||
|
||||
A leak occurs if:
|
||||
|
||||
> **A domain that should be “proxied / remote-resolved” was queried via local/ISP-facing DNS.**
|
||||
|
||||
This is the “proxy split rules still leak intent” case.
|
||||
|
||||
**Leak-B condition**
|
||||
|
||||
* Query name matches either:
|
||||
|
||||
* a “proxy-required set” (sensitive list, non-allowlist, unknown), or
|
||||
* a policy rule (“everything except allowlist must resolve via proxy DNS”)
|
||||
* And the query was observed going to:
|
||||
|
||||
* ISP resolver(s) / domestic resolver(s) / non-tunnel interface
|
||||
|
||||
✅ This is the leak most users in censorship settings care about.
|
||||
|
||||
---
|
||||
|
||||
## Standard definition C (encrypted DNS escape / bypass)
|
||||
|
||||
A leak occurs if:
|
||||
|
||||
> DNS was encrypted, but escaped the intended channel (e.g., app uses its own DoH directly to the Internet).
|
||||
|
||||
This matters because DoH hides the QNAME but still creates **observable behavior** and breaks your “DNS must follow proxy” invariant.
|
||||
|
||||
**Leak-C condition**
|
||||
|
||||
* DoH (RFC 8484) ([IETF Datatracker][2]) or DoT (RFC 7858) ([IETF Datatracker][3]) flow exists
|
||||
* And it does **not** go through your approved egress path (tunnel/proxy)
|
||||
|
||||
✅ Detects “Firefox/Chrome built-in DoH bypass” style cases.
|
||||
|
||||
---
|
||||
|
||||
## Standard definition D (mismatch risk indicator)
|
||||
|
||||
Not a “leak” by itself, but a **proxy inference amplifier**:
|
||||
|
||||
> DNS egress region/path differs from traffic egress region/path.
|
||||
|
||||
This is a *censorship-resistance hygiene metric*, not a binary leak.
|
||||
|
||||
**Mismatch condition**
|
||||
|
||||
* Same domain produces:
|
||||
|
||||
* DNS resolution via path X
|
||||
* TCP/TLS connection via path Y
|
||||
* Where X ≠ Y (interface, ASN region, etc.)
|
||||
|
||||
✅ Helps catch “DNS direct, traffic proxy” or “DNS proxy, traffic direct” weirdness.
|
||||
|
||||
---
|
||||
|
||||
# 3) High-level architecture
|
||||
|
||||
## Core components
|
||||
|
||||
1. **Policy & Configuration**
|
||||
|
||||
* What counts as “safe DNS path”
|
||||
* Which interfaces are “protected” (tunnel) vs “physical”
|
||||
* Allowlist / proxy-required sets (optional)
|
||||
* Known resolver lists (optional)
|
||||
* Severity thresholds
|
||||
|
||||
2. **Traffic Sensor (Passive Monitor)**
|
||||
|
||||
* Captures outbound traffic metadata (and optionally payload for DNS parsing)
|
||||
* Must cover:
|
||||
|
||||
* UDP/53, TCP/53
|
||||
* TCP/853 (DoT)
|
||||
* HTTPS flows that look like DoH (see below)
|
||||
* Emits normalized events into a pipeline
|
||||
|
||||
3. **Classifier**
|
||||
|
||||
* Recognize DNS protocol types:
|
||||
|
||||
* Plain DNS
|
||||
* DoT
|
||||
* DoH
|
||||
* Attach confidence scores (especially for DoH)
|
||||
|
||||
4. **DNS Parser (for plaintext DNS only)**
|
||||
|
||||
* Extract: QNAME, QTYPE, transaction IDs, response codes (optional)
|
||||
* Store minimally (privacy-aware)
|
||||
|
||||
5. **Flow Tracker**
|
||||
|
||||
* Correlate packets into “flows”
|
||||
* Map flow → interface → destination → process (if possible)
|
||||
* Track timing correlation: DNS → connection attempts
|
||||
|
||||
6. **Leak Detector (Rules Engine)**
|
||||
|
||||
* Apply Leak-A/B/C/D definitions
|
||||
* Produce leak events + severity + evidence chain
|
||||
|
||||
7. **Active Prober**
|
||||
|
||||
* Generates controlled DNS lookups to test behavior
|
||||
* Can test fail-closed, bypasses, multi-interface behavior, etc.
|
||||
|
||||
8. **Report Generator**
|
||||
|
||||
* Human-readable summary
|
||||
* Machine-readable logs (JSON)
|
||||
* Recommendations (non-invasive)
|
||||
|
||||
---
|
||||
|
||||
# 4) Workflow (end-to-end)
|
||||
|
||||
## Workflow 0: Setup & baseline
|
||||
|
||||
1. Enumerate interfaces and routes
|
||||
|
||||
* Identify physical NICs
|
||||
* Identify tunnel / proxy interface (or “expected egress destinations”)
|
||||
2. Identify system DNS configuration
|
||||
|
||||
* Default resolvers per interface
|
||||
* Local stub presence (127.0.0.1, etc.)
|
||||
3. Load policy profile
|
||||
|
||||
* Full-tunnel, split-tunnel, or proxy-based
|
||||
4. Start passive monitor
|
||||
|
||||
**Output:** “Current state snapshot” (useful even before testing).
|
||||
|
||||
---
|
||||
|
||||
## Workflow 1: Passive detection loop (always-on)
|
||||
|
||||
Continuously:
|
||||
|
||||
1. Capture outbound packets/flows
|
||||
2. Classify as DNS-like (plain DNS / DoT / DoH / unknown)
|
||||
3. If plaintext DNS → parse QNAME/QTYPE
|
||||
4. Assign metadata:
|
||||
|
||||
* interface
|
||||
* dst IP/port
|
||||
* process (if possible)
|
||||
* timestamp
|
||||
5. Evaluate leak rules:
|
||||
|
||||
* Leak-A/B/C/D
|
||||
6. Write event log + optional real-time alert
|
||||
|
||||
**Key design point:** passive mode should be able to detect leaks **without requiring any special test domain**.
|
||||
|
||||
---
|
||||
|
||||
## Workflow 2: Active test suite (on-demand)
|
||||
|
||||
Active tests exist because some leaks are intermittent or only happen under stress.
|
||||
|
||||
### Active Test A: “No plaintext DNS escape”
|
||||
|
||||
* Trigger a set of DNS queries (unique random domains)
|
||||
* Verify **zero UDP/53 & TCP/53** leaves physical interfaces
|
||||
|
||||
### Active Test B: “Fail-closed test”
|
||||
|
||||
* Temporarily disrupt the “protected path” (e.g., tunnel down)
|
||||
* Trigger lookups again
|
||||
* Expected: DNS fails (no fallback to ISP DNS)
|
||||
|
||||
### Active Test C: “App bypass test”
|
||||
|
||||
* Launch test scenarios that mimic real apps
|
||||
* Confirm no direct DoH/DoT flows go to public Internet outside the proxy path
|
||||
|
||||
### Active Test D: “Split-policy correctness”
|
||||
|
||||
* Query domains that should be:
|
||||
|
||||
* direct-allowed
|
||||
* proxy-required
|
||||
* unknown
|
||||
* Confirm resolution path matches policy
|
||||
|
||||
---
|
||||
|
||||
# 5) How to recognize DNS transports (detection mechanics)
|
||||
|
||||
## Plain DNS (strongest signal)
|
||||
|
||||
**Match conditions**
|
||||
|
||||
* UDP dst port 53 OR TCP dst port 53
|
||||
* Parse DNS header
|
||||
* Extract QNAME/QTYPE
|
||||
|
||||
**Evidence strength:** high
|
||||
**Intent visibility:** yes (domain visible)
|
||||
|
||||
---
|
||||
|
||||
## DoT (port-based, easy)
|
||||
|
||||
DoT is defined over TLS, typically port **853**. ([IETF Datatracker][3])
|
||||
|
||||
**Match conditions**
|
||||
|
||||
* TCP dst port 853
|
||||
* Optionally confirm TLS handshake exists
|
||||
|
||||
**Evidence strength:** high
|
||||
**Intent visibility:** no (domain hidden)
|
||||
|
||||
---
|
||||
|
||||
## DoH (harder; heuristic + optional allowlists)
|
||||
|
||||
DoH is DNS over HTTPS (RFC 8484). ([IETF Datatracker][2])
|
||||
|
||||
**Recognizers (from strongest to weakest):**
|
||||
|
||||
1. HTTP request with `Content-Type: application/dns-message`
|
||||
2. Path/pattern common to DoH endpoints (optional list)
|
||||
3. SNI matches known DoH providers (optional list)
|
||||
4. Traffic resembles frequent small HTTPS POST/GET bursts typical of DoH (weak)
|
||||
|
||||
**Evidence strength:** medium
|
||||
**Intent visibility:** no (domain hidden)
|
||||
|
||||
**Important for your use-case:** you may not need to *prove* it’s DoH; you mostly need to detect “DNS-like encrypted resolver traffic bypassing the proxy channel.”
|
||||
|
||||
---
|
||||
|
||||
# 6) Policy model: define “safe DNS path”
|
||||
|
||||
You need a simple abstraction users can configure:
|
||||
|
||||
### Safe DNS path can be defined by one or more of:
|
||||
|
||||
* **Allowed interfaces**
|
||||
|
||||
* loopback (local stub)
|
||||
* tunnel interface
|
||||
* **Allowed destination set**
|
||||
|
||||
* proxy server IP(s)
|
||||
* internal resolver IP(s)
|
||||
* **Allowed process**
|
||||
|
||||
* only your local stub + proxy allowed to resolve externally
|
||||
* **Allowed port set**
|
||||
|
||||
* maybe only permit 443 to proxy server (if DNS rides inside it)
|
||||
|
||||
Then implement:
|
||||
|
||||
**A DNS event is a “leak” if it violates safe-path constraints.**
|
||||
|
||||
---
|
||||
|
||||
# 7) Leak severity model (useful for real-world debugging)
|
||||
|
||||
### Severity P0 (critical)
|
||||
|
||||
* Plaintext DNS (UDP/TCP 53) on physical interface to ISP/public resolver
|
||||
* Especially if QNAME matches proxy-required/sensitive list
|
||||
|
||||
### Severity P1 (high)
|
||||
|
||||
* DoH/DoT bypassing proxy channel directly to public Internet
|
||||
|
||||
### Severity P2 (medium)
|
||||
|
||||
* Policy mismatch: domain resolved locally but connection later proxied (or vice versa)
|
||||
|
||||
### Severity P3 (low / info)
|
||||
|
||||
* Authoritative-side “resolver egress exposure” (less relevant for client-side leak detector)
|
||||
* CDN performance mismatch indicators
|
||||
|
||||
---
|
||||
|
||||
# 8) Outputs and reporting
|
||||
|
||||
## Real-time console output (for debugging)
|
||||
|
||||
* “DNS leak detected: Plain DNS”
|
||||
* domain (if visible)
|
||||
* destination resolver IP
|
||||
* interface
|
||||
* process name (if available)
|
||||
* policy rule violated
|
||||
* suggested fix category (e.g., “force stub + block port 53”)
|
||||
|
||||
## Forensics log (machine-readable)
|
||||
|
||||
A single **LeakEvent** record could include:
|
||||
|
||||
* timestamp
|
||||
* leak_type (A/B/C/D)
|
||||
* transport (UDP53, TCP53, DoT, DoH)
|
||||
* qname/qtype (nullable)
|
||||
* src_iface / dst_ip / dst_port
|
||||
* process_id/process_name (nullable)
|
||||
* correlation_id (link DNS → subsequent connection attempt)
|
||||
* confidence score (esp. DoH)
|
||||
* raw evidence pointers (pcap offsets / event IDs)
|
||||
|
||||
## Summary report
|
||||
|
||||
* Leak counts by type
|
||||
* Top leaking processes
|
||||
* Top leaking resolver destinations
|
||||
* Timeline view (bursts often indicate OS fallback behavior)
|
||||
* “Pass/Fail” per policy definition
|
||||
|
||||
---
|
||||
|
||||
# 9) Validation strategy (“how do I know my detector is correct?”)
|
||||
|
||||
## Ground truth tests
|
||||
|
||||
1. **Known-leak scenario**
|
||||
|
||||
* intentionally set OS DNS to ISP DNS, no tunnel
|
||||
* detector must catch plaintext DNS
|
||||
|
||||
2. **Known-safe scenario**
|
||||
|
||||
* local stub only + blocked outbound 53/853
|
||||
* detector should show zero leaks
|
||||
|
||||
3. **Bypass scenario**
|
||||
|
||||
* enable browser built-in DoH directly
|
||||
* detector should catch encrypted resolver bypass (Leak-C)
|
||||
|
||||
4. **Split-policy scenario**
|
||||
|
||||
* allowlist CN direct, everything else proxy-resolve
|
||||
* detector should show:
|
||||
|
||||
* allowlist resolved direct
|
||||
* unknown resolved via proxy path
|
||||
|
||||
---
|
||||
|
||||
# 10) Recommended “profiles” (makes tool usable)
|
||||
|
||||
Provide built-in presets:
|
||||
|
||||
### Profile 1: Full-tunnel VPN
|
||||
|
||||
* allow DNS only via tunnel interface or loopback stub
|
||||
* any UDP/TCP 53 on physical NIC = leak
|
||||
|
||||
### Profile 2: Proxy + local stub (your case)
|
||||
|
||||
* allow DNS only to loopback stub
|
||||
* allow stub upstream only via proxy server destinations
|
||||
* flag any direct DoH/DoT to public endpoints
|
||||
|
||||
### Profile 3: Split tunnel (geoip + allowlist)
|
||||
|
||||
* allow plaintext DNS **only** for allowlisted domains (if user accepts risk)
|
||||
* enforce “unknown → proxy-resolve”
|
||||
* emphasize Leak-B correctness
|
||||
|
||||
---
|
||||
|
||||
Below is an updated **high-level design** (still language-agnostic) that integrates **process attribution** cleanly, including how it fits into the workflow and what to log.
|
||||
|
||||
---
|
||||
|
||||
# 1) New component: Process Attribution Engine (PAE)
|
||||
|
||||
## Purpose
|
||||
|
||||
When a DNS-like event is observed, the PAE tries to attach:
|
||||
|
||||
* **PID**
|
||||
* **PPID**
|
||||
* **process name**
|
||||
* *(optional but extremely useful)* full command line, executable path, user, container/app package, etc.
|
||||
|
||||
This lets your logs answer:
|
||||
|
||||
> “Which program generated the leaked DNS request?”
|
||||
> “Was it a browser, OS service, updater, antivirus, proxy itself, or some library?”
|
||||
|
||||
## Position in the pipeline
|
||||
|
||||
It sits between **Traffic Sensor** and **Leak Detector** as an “event enricher”:
|
||||
|
||||
**Traffic Event → (Classifier) → (Process Attribution) → Enriched Event → Leak Rules → Report**
|
||||
|
||||
---
|
||||
|
||||
# 2) Updated architecture (with process attribution)
|
||||
|
||||
### Existing modules (from earlier design)
|
||||
|
||||
1. Policy & Configuration
|
||||
2. Traffic Sensor (packet/flow monitor)
|
||||
3. Classifier (Plain DNS / DoT / DoH / Unknown)
|
||||
4. DNS Parser (plaintext only)
|
||||
5. Flow Tracker
|
||||
6. Leak Detector (rules engine)
|
||||
7. Active Prober
|
||||
8. Report Generator
|
||||
|
||||
### New module
|
||||
|
||||
9. **Process Attribution Engine (PAE)**
|
||||
|
||||
* resolves “who owns this flow / packet”
|
||||
* emits PID/PPID/name
|
||||
* handles platform-specific differences and fallbacks
|
||||
|
||||
---
|
||||
|
||||
# 3) Workflow changes (what happens when a potential leak is seen)
|
||||
|
||||
## Passive detection loop (updated)
|
||||
|
||||
1. Capture outbound traffic event
|
||||
2. Classify transport type:
|
||||
|
||||
* UDP/53, TCP/53 → plaintext DNS
|
||||
* TCP/853 → DoT
|
||||
* HTTPS patterns → DoH (heuristic)
|
||||
3. Extract the **5-tuple**
|
||||
|
||||
* src IP:port, dst IP:port, protocol
|
||||
4. **PAE lookup**
|
||||
|
||||
* resolve the owner process for this traffic
|
||||
* attach PID/PPID/name (+ optional metadata)
|
||||
5. Apply leak rules (A/B/C/D)
|
||||
6. Emit:
|
||||
|
||||
* realtime log line (human readable)
|
||||
* structured record (JSON/event log)
|
||||
|
||||
---
|
||||
|
||||
# 4) Process attribution: what to detect and how (high-level)
|
||||
|
||||
Process attribution always works on one core concept:
|
||||
|
||||
> **Map observed traffic (socket/flow) → owning process**
|
||||
|
||||
### Inputs PAE needs
|
||||
|
||||
* protocol (UDP/TCP)
|
||||
* local src port
|
||||
* local address
|
||||
* timestamp
|
||||
* optionally: connection state / flow ID
|
||||
|
||||
### Output from PAE
|
||||
|
||||
* `pid`, `ppid`, `process_name`
|
||||
* optional enrichment:
|
||||
|
||||
* `exe_path`
|
||||
* `cmdline`
|
||||
* `user`
|
||||
* “process tree chain” (for debugging: parent → child → …)
|
||||
|
||||
---
|
||||
|
||||
# 5) Platform support strategy (without implementation detail)
|
||||
|
||||
Process attribution is **OS-specific**, so structure it as:
|
||||
|
||||
## “Attribution Provider” interface
|
||||
|
||||
* Provider A: “kernel-level flow owner”
|
||||
* Provider B: “socket table owner lookup”
|
||||
* Provider C: “event tracing feed”
|
||||
* Provider D: fallback “unknown / not supported”
|
||||
|
||||
Your main design goal is:
|
||||
|
||||
### Design rule
|
||||
|
||||
**Attribution must be best-effort + gracefully degrading**, never blocking detection.
|
||||
|
||||
So you always log the leak even if PID is unavailable:
|
||||
|
||||
* `pid=null, attribution_confidence=LOW`
|
||||
|
||||
---
|
||||
|
||||
# 6) Attribution confidence + race handling (important!)
|
||||
|
||||
Attribution can be tricky because:
|
||||
|
||||
* a process may exit quickly (“short-lived resolver helper”)
|
||||
* ports can be reused
|
||||
* NAT or local proxies may obscure the real origin
|
||||
|
||||
So log **confidence**:
|
||||
|
||||
* **HIGH**: direct mapping from kernel/socket owner at time of event
|
||||
* **MEDIUM**: mapping by lookup shortly after event (possible race)
|
||||
* **LOW**: inferred / uncertain
|
||||
* **NONE**: not resolved
|
||||
|
||||
Also record *why* attribution failed:
|
||||
|
||||
* “permission denied”
|
||||
* “flow already gone”
|
||||
* “unsupported transport”
|
||||
* “ambiguous mapping”
|
||||
|
||||
This makes debugging much easier.
|
||||
|
||||
---
|
||||
|
||||
# 7) What PID/PPID adds to your leak definitions
|
||||
|
||||
### Leak-A (plaintext DNS outside safe path)
|
||||
|
||||
Now you can say:
|
||||
|
||||
> “`svchost.exe (PID 1234)` sent UDP/53 to ISP resolver on Wi-Fi interface”
|
||||
|
||||
### Leak-B (split-policy intent leak)
|
||||
|
||||
You can catch:
|
||||
|
||||
* “game launcher looked up blocked domain”
|
||||
* “system service triggered a sensitive name unexpectedly”
|
||||
* “your proxy itself isn’t actually resolving via its own channel”
|
||||
|
||||
### Leak-C (encrypted DNS bypass)
|
||||
|
||||
This becomes *very actionable*:
|
||||
|
||||
> “`firefox.exe` started direct DoH to resolver outside tunnel”
|
||||
|
||||
### Leak-D (mismatch indicator)
|
||||
|
||||
You can also correlate:
|
||||
|
||||
* DNS resolved by one process
|
||||
* connection made by another process
|
||||
(e.g., local stub vs app)
|
||||
|
||||
---
|
||||
|
||||
# 8) Reporting / realtime logging format (updated)
|
||||
|
||||
## Realtime log line (human readable)
|
||||
|
||||
Example (conceptual):
|
||||
|
||||
* **[P0][Leak-A] Plain DNS leaked**
|
||||
|
||||
* Domain: `example-sensitive.com` (A)
|
||||
* From: `Wi-Fi` → To: `1.2.3.4:53`
|
||||
* Process: `browser.exe` **PID=4321 PPID=1200**
|
||||
* Policy violated: “No UDP/53 on physical NIC”
|
||||
|
||||
## Structured event (JSON-style fields)
|
||||
|
||||
Minimum recommended fields:
|
||||
|
||||
### Event identity
|
||||
|
||||
* `event_id`
|
||||
* `timestamp`
|
||||
|
||||
### DNS identity
|
||||
|
||||
* `transport` (udp53/tcp53/dot/doh/unknown)
|
||||
* `qname` (nullable)
|
||||
* `qtype` (nullable)
|
||||
|
||||
### Network path
|
||||
|
||||
* `interface_name`
|
||||
* `src_ip`, `src_port`
|
||||
* `dst_ip`, `dst_port`
|
||||
* `route_class` (tunnel / physical / loopback)
|
||||
|
||||
### Process identity (your requested additions)
|
||||
|
||||
* `pid`
|
||||
* `ppid`
|
||||
* `process_name`
|
||||
* optional:
|
||||
|
||||
* `exe_path`
|
||||
* `cmdline`
|
||||
* `user`
|
||||
|
||||
### Detection result
|
||||
|
||||
* `leak_type` (A/B/C/D)
|
||||
* `severity` (P0..P3)
|
||||
* `policy_rule_id`
|
||||
* `attribution_confidence`
|
||||
|
||||
---
|
||||
|
||||
# 9) Privacy and safety notes (important in a DNS tool)
|
||||
|
||||
Because you’re logging **domains** and **process command lines**, this becomes sensitive.
|
||||
|
||||
Add a “privacy mode” policy:
|
||||
|
||||
* **Full**: store full domain + cmdline
|
||||
* **Redacted**: hash domain; keep TLD only; truncate cmdline
|
||||
* **Minimal**: only keep leak counts + resolver IPs + process name
|
||||
|
||||
Also allow “capture window” (rotate logs, avoid giant histories).
|
||||
|
||||
---
|
||||
|
||||
# 10) UX feature: “Show me the process tree”
|
||||
|
||||
When a leak happens, a good debugger view is:
|
||||
|
||||
* `PID: foo (pid 1000)`
|
||||
|
||||
* `PPID: bar (pid 900)`
|
||||
|
||||
* `PPID: systemd/svchost/etc`
|
||||
|
||||
This is extremely useful to identify:
|
||||
|
||||
* browsers spawning helpers
|
||||
* OS DNS services
|
||||
* containerized processes
|
||||
* update agents / telemetry daemons
|
||||
|
||||
So your report generator should support:
|
||||
|
||||
✅ **Process chain rendering** (where possible)
|
||||
|
||||
---
|
||||
|
||||
# 11) Practical edge cases you should detect (with PID helping)
|
||||
|
||||
1. **Local stub is fine, upstream isn’t**
|
||||
|
||||
* Your local resolver process leaks upstream plaintext DNS
|
||||
2. **Browser uses its own DoH**
|
||||
|
||||
* process attribution immediately reveals it
|
||||
3. **Multiple interfaces**
|
||||
|
||||
* a leak only happens on Wi-Fi but not Ethernet
|
||||
4. **Kill-switch failure**
|
||||
|
||||
* when tunnel drops, PID shows which app starts leaking first
|
||||
|
||||
---
|
||||
42
docs/dns_leak_implementation_status.md
Normal file
42
docs/dns_leak_implementation_status.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# DNS Leak Detection - Implementation Status
|
||||
|
||||
This document tracks the current DNS leak detector implementation against the design in
|
||||
`docs/dns_leak_detection_design.md` and `docs/requirement_docs_v0.4.md`.
|
||||
|
||||
## Implemented
|
||||
- New `wtfnet-dnsleak` crate with passive capture (pcap feature).
|
||||
- Transport classification:
|
||||
- Plain DNS (UDP/53, TCP/53) with qname/qtype parsing.
|
||||
- DoT (TCP/853) detection.
|
||||
- DoH detection is not implemented (skipped for now).
|
||||
- Leak rules:
|
||||
- Leak-A (plaintext DNS outside safe path).
|
||||
- Leak-B (split-policy intent leak based on proxy-required/allowlist domains).
|
||||
- Leak-C (encrypted DNS bypass for DoT).
|
||||
- Policy profiles: `full-tunnel`, `proxy-stub`, `split`.
|
||||
- Privacy modes: full/redacted/minimal (redacts qname).
|
||||
- Process attribution:
|
||||
- Best-effort `FlowOwnerProvider` with Linux `/proc` and Windows `netstat` lookups.
|
||||
- Confidence levels and failure reasons exposed in events.
|
||||
- CLI commands:
|
||||
- `dns leak status`
|
||||
- `dns leak watch`
|
||||
- `dns leak report`
|
||||
- `dns leak watch --iface-diag` (diagnostics for capture-capable interfaces).
|
||||
- Interface selection:
|
||||
- per-interface open timeout to avoid capture hangs
|
||||
- stable default pick (up, non-loopback, named ethernet/wlan) before fallback scan
|
||||
|
||||
## Partially implemented
|
||||
- Route/interface classification: heuristic only (loopback/tunnel/physical by iface name).
|
||||
- Safe path matching: allowed ifaces/dests/ports/processes; no route-based policy.
|
||||
|
||||
## Not implemented (v0.4 backlog)
|
||||
- DoH heuristic detection (SNI/endpoint list/traffic shape).
|
||||
- Leak-D mismatch correlation (DNS -> TCP/TLS flows).
|
||||
- GeoIP enrichment of leak events.
|
||||
- Process tree reporting (PPID chain).
|
||||
|
||||
## Known limitations
|
||||
- On Windows, pcap capture may require selecting a specific NPF interface; use
|
||||
`dns leak watch --iface-diag` to list interfaces that can be opened.
|
||||
@@ -9,9 +9,10 @@ This document tracks current implementation status against the original design i
|
||||
- GeoIP: local GeoLite2 Country + ASN support.
|
||||
- Probe: ping/tcping/trace with GeoIP enrichment.
|
||||
- DNS: Hickory-based query/detect with best-effort heuristics.
|
||||
- DNS leak detection: new passive monitor with policy and privacy modes (best-effort).
|
||||
- HTTP: head/get via reqwest with best-effort timing breakdown and optional HTTP/3 (feature-gated).
|
||||
- TLS: rustls-based handshake/verify/cert/alpn.
|
||||
- Discover: mDNS/SSDP bounded collection.
|
||||
- Discover: mDNS/SSDP bounded collection plus LLMNR/NBNS (best-effort).
|
||||
- Diag: bundle export in zip.
|
||||
|
||||
## Deviations or refinements
|
||||
@@ -23,7 +24,7 @@ This document tracks current implementation status against the original design i
|
||||
## Gaps vs design (as of now)
|
||||
- HTTP/3 is feature-gated and incomplete; not enabled in default builds.
|
||||
- TLS verification is rustls-based (no OS-native verifier).
|
||||
- Discover does not include LLMNR/NBNS.
|
||||
- DNS leak DoH detection is heuristic and currently optional.
|
||||
|
||||
## Current stage summary
|
||||
- v0.1 scope is complete.
|
||||
|
||||
484
docs/requirement_docs_v0.4.md
Normal file
484
docs/requirement_docs_v0.4.md
Normal file
@@ -0,0 +1,484 @@
|
||||
Yep — your DNS leak detector design **fits your current architecture cleanly**, and **making it a dedicated subcrate is the right move**.
|
||||
|
||||
Your existing workspace already follows “thin CLI + feature crates + platform traits + OS dispatch”, so the detector drops in naturally as **one new feature crate** plus a small extension to `wtfnet-platform` for **best-effort process attribution**. This matches the design’s pipeline idea: **Traffic Sensor → Classifier → (Process Attribution) → Leak Rules → Report**
|
||||
|
||||
Below I’ll do two things:
|
||||
|
||||
1. **Evaluate fit + integration plan** (what needs changing / what can be reused)
|
||||
2. Provide **repo-ready v0.4.0 documents**, focused on implementing this detector.
|
||||
|
||||
---
|
||||
|
||||
# 1) Can this design fit your current architecture?
|
||||
|
||||
## ✅ Yes — it matches your crate boundaries almost 1:1
|
||||
|
||||
Your detector design already breaks down into modules that map directly into a new subcrate:
|
||||
|
||||
* Policy & Configuration
|
||||
* Passive Traffic Sensor (UDP53/TCP53/853/“DoH-ish”)
|
||||
* Classifier (DNS / DoT / DoH + confidence)
|
||||
* DNS Parser (plaintext only)
|
||||
* Flow Tracker (interface/route/process correlation)
|
||||
* Leak Detector rules A/B/C/D
|
||||
* Report generator + structured events
|
||||
|
||||
So: **new crate = `wtfnet-dnsleak`**, and keep `wtfnet-dns` for **active query/detect/watch** (already exists).
|
||||
|
||||
## ✅ What you can reuse immediately
|
||||
|
||||
### Reuse from your current codebase
|
||||
|
||||
* `wtfnet-dns watch` capture plumbing (you already have passive-ish capture)
|
||||
* existing DNS parsing logic (for UDP/TCP 53)
|
||||
* existing GeoIP enrichment pipeline (optional)
|
||||
* `wtfnet-platform` interface snapshot + routing info (for “which interface leaked?”)
|
||||
* your JSON output envelope + logging style
|
||||
|
||||
### Reuse from the detector design directly
|
||||
|
||||
* Leak definitions A/B/C/D (this is already precise and CLI-tool friendly)
|
||||
* DoH recognition levels + confidence model (strong → weak)
|
||||
* “safe DNS path” abstraction (interfaces/dests/process/ports)
|
||||
* process attribution confidence levels and failure reasons
|
||||
* privacy modes (Full/Redacted/Minimal)
|
||||
|
||||
## ✅ What you’ll need to add (small + contained)
|
||||
|
||||
### 1) New platform trait for flow → process owner (best effort)
|
||||
|
||||
Your design explicitly wants PID/PPID/process name to show “who leaked DNS” , and it recommends an OS-specific provider interface .
|
||||
|
||||
So extend `wtfnet-platform` with something like:
|
||||
|
||||
* `FlowOwnerProvider::owner_of(tuple, timestamp) -> ProcessInfo + confidence`
|
||||
|
||||
This stays consistent with your existing “platform traits + OS dispatch” architecture.
|
||||
|
||||
### 2) Route/interface classification (“tunnel vs physical vs loopback”)
|
||||
|
||||
Your event schema wants `route_class` (tunnel/physical/loopback) .
|
||||
Implement this via **policy-configured interface labels** + a small heuristic fallback.
|
||||
|
||||
### 3) DoH detection will be heuristic (and that’s OK)
|
||||
|
||||
Your design already plans for this: DoH classification is “hard”, confidence-based .
|
||||
In v0.4, keep it simple:
|
||||
|
||||
* DoT = TCP/853
|
||||
* Plain DNS = UDP/TCP 53 (+ parse QNAME)
|
||||
* “DoH-ish” = TCP/443 to known resolver host/IP OR “small HTTPS bursts” pattern
|
||||
…and attach confidence.
|
||||
|
||||
---
|
||||
|
||||
# 2) v0.4.0 documents (repo-ready)
|
||||
|
||||
Below are two docs you can drop into `docs/`.
|
||||
|
||||
---
|
||||
|
||||
## `docs/RELEASE_v0.4.0.md`
|
||||
|
||||
```markdown
|
||||
# WTFnet v0.4.0 — DNS Leak Detection
|
||||
|
||||
v0.4.0 introduces a client-side DNS leak detector aimed at censorship-resistance threat models:
|
||||
detect when DNS behavior escapes the intended safe path. The detector focuses on evidence:
|
||||
transport, interface, destination, and (best-effort) process attribution.
|
||||
|
||||
This release does NOT include HTTP/3 or OS-native TLS verification.
|
||||
|
||||
---
|
||||
|
||||
## 0) Summary
|
||||
|
||||
New major capability: `dns leak` command group.
|
||||
|
||||
Core idea:
|
||||
Passive monitor captures outbound DNS-like traffic → classify (Plain DNS / DoT / DoH) →
|
||||
enrich with interface/route/process metadata → evaluate leak definitions (A/B/C/D) →
|
||||
emit events + summary report.
|
||||
|
||||
Leak definitions are explicit:
|
||||
- Leak-A: plaintext DNS outside safe path
|
||||
- Leak-B: split-policy intent leak (proxy-required domains resolved via ISP/local path)
|
||||
- Leak-C: encrypted DNS escape/bypass (DoH/DoT outside approved egress)
|
||||
- Leak-D: mismatch risk indicator (DNS egress differs from TCP/TLS egress)
|
||||
|
||||
---
|
||||
|
||||
## 1) Goals
|
||||
|
||||
### G1. Detect DNS leaks without needing special test domains
|
||||
Passive detection must work continuously and produce evidence.
|
||||
|
||||
### G2. Support censorship-resistance leak definitions
|
||||
Include both classic VPN-bypass leaks and split-policy intent leaks.
|
||||
|
||||
### G3. Best-effort process attribution
|
||||
Attach PID/PPID/process name when OS allows; degrade gracefully with confidence.
|
||||
|
||||
### G4. Privacy-aware by default
|
||||
Support privacy modes: Full / Redacted / Minimal.
|
||||
|
||||
---
|
||||
|
||||
## 2) Non-goals (v0.4.0)
|
||||
|
||||
- No "doctor" / smart one-shot diagnosis command
|
||||
- No shell completions / man pages
|
||||
- No HTTP/3 support
|
||||
- No OS-native TLS verifier integration
|
||||
- No firewall modification / "kill switch" management (detection only)
|
||||
|
||||
---
|
||||
|
||||
## 3) New crates / architecture changes
|
||||
|
||||
### 3.1 New subcrate: `wtfnet-dnsleak`
|
||||
Responsibilities:
|
||||
- passive sensor (pcap/pnet feature-gated)
|
||||
- DNS parser (plaintext only)
|
||||
- transport classifier: udp53/tcp53/dot/doh (confidence)
|
||||
- flow tracker + metadata enrichment
|
||||
- process attribution integration
|
||||
- leak rules engine (A/B/C/D)
|
||||
- structured event + summary report builder
|
||||
|
||||
### 3.2 `wtfnet-platform` extension: flow ownership lookup
|
||||
Add a new trait:
|
||||
- FlowOwnerProvider: map observed traffic 5-tuple → process info (best-effort)
|
||||
|
||||
Return process attribution confidence:
|
||||
HIGH/MEDIUM/LOW/NONE plus failure reason.
|
||||
|
||||
---
|
||||
|
||||
## 4) CLI scope
|
||||
|
||||
### 4.1 Commands
|
||||
New command group:
|
||||
|
||||
#### `wtfn dns leak watch`
|
||||
Start passive monitoring for a bounded duration (default 10s):
|
||||
- classify transports (udp53/tcp53/dot/doh)
|
||||
- apply leak rules and emit events + summary
|
||||
|
||||
#### `wtfn dns leak status`
|
||||
Print baseline snapshot:
|
||||
- interfaces + routes
|
||||
- system DNS configuration
|
||||
- active policy summary
|
||||
|
||||
#### `wtfn dns leak report`
|
||||
Parse a saved events file and produce a human summary.
|
||||
|
||||
### 4.2 Flags (proposed)
|
||||
Common:
|
||||
- `--duration <Ns|Nms>` (default 10s)
|
||||
- `--iface <name>` (optional capture interface)
|
||||
- `--policy <path>` (JSON policy file)
|
||||
- `--profile <full-tunnel|proxy-stub|split>` (built-in presets)
|
||||
- `--privacy <full|redacted|minimal>` (default redacted)
|
||||
- `--geoip` (include GeoIP in event outputs)
|
||||
- `--out <path>` (write JSON report/events)
|
||||
|
||||
---
|
||||
|
||||
## 5) Policy model (v0.4.0)
|
||||
|
||||
Safe DNS path constraints can be defined by:
|
||||
- allowed interfaces: loopback/tunnel
|
||||
- allowed destination set: proxy IPs, internal resolvers
|
||||
- allowed processes: only local stub/proxy can resolve upstream
|
||||
- allowed ports: e.g. only 443 to proxy server
|
||||
|
||||
A DNS event is a leak if it violates safe-path constraints.
|
||||
|
||||
Built-in profiles:
|
||||
1) full-tunnel VPN style
|
||||
2) proxy + local stub (default, censorship model)
|
||||
3) split policy
|
||||
|
||||
---
|
||||
|
||||
## 6) Outputs
|
||||
|
||||
### 6.1 Leak events (structured)
|
||||
Each LeakEvent includes:
|
||||
- timestamp
|
||||
- transport: udp53/tcp53/dot/doh/unknown
|
||||
- qname/qtype (nullable)
|
||||
- interface + route_class
|
||||
- dst ip:port
|
||||
- process info (nullable) + attribution confidence
|
||||
- leak_type: A/B/C/D
|
||||
- severity: P0..P3
|
||||
- evidence fields + optional geoip
|
||||
|
||||
### 6.2 Summary report
|
||||
- leak counts by type
|
||||
- top leaking processes (if available)
|
||||
- top resolver destinations
|
||||
- timeline/burst hints
|
||||
|
||||
---
|
||||
|
||||
## 7) Deliverables checklist
|
||||
|
||||
MUST:
|
||||
- new `wtfnet-dnsleak` crate integrated into workspace + CLI
|
||||
- passive capture for UDP/TCP 53 and TCP 853
|
||||
- DoH heuristic classification (confidence-based)
|
||||
- policy engine + Leak-A/B/C/D rules
|
||||
- structured events + human summary
|
||||
- privacy modes full/redacted/minimal
|
||||
- best-effort process attribution with confidence and failure reason
|
||||
|
||||
SHOULD:
|
||||
- saved report file support (`--out report.json`)
|
||||
- route_class inference with policy hints + heuristics
|
||||
|
||||
NICE:
|
||||
- correlation_id (DNS → subsequent TCP/TLS connection) for Leak-D mismatch indicator
|
||||
|
||||
---
|
||||
|
||||
## 8) Definition of Done
|
||||
|
||||
- v0.4.0 builds on Linux (Debian/Ubuntu) and Windows
|
||||
- `wtfn dns leak watch` detects:
|
||||
- plaintext DNS leaving physical interface (Leak-A)
|
||||
- DoT traffic leaving outside approved egress (Leak-C)
|
||||
- DoH-ish encrypted resolver traffic outside policy (Leak-C)
|
||||
- events include interface + dst + (best-effort) PID/process info
|
||||
- output remains stable and additive; no breaking change to v0.3 commands
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `docs/DNS_LEAK_DETECTOR_IMPLEMENTATION.md`
|
||||
|
||||
```markdown
|
||||
# DNS Leak Detector — Implementation Guide (v0.4)
|
||||
|
||||
This document explains how to implement the DNS leak detector as a new subcrate in WTFnet.
|
||||
|
||||
---
|
||||
|
||||
## 1) New crate: `wtfnet-dnsleak`
|
||||
|
||||
### 1.1 Module layout
|
||||
|
||||
crates/wtfnet-dnsleak/src/
|
||||
- lib.rs
|
||||
- policy.rs # safe path constraints + presets
|
||||
- sensor.rs # passive capture -> normalized TrafficEvent stream
|
||||
- classify.rs # transport classification + confidence
|
||||
- parse_dns.rs # plaintext DNS parser: qname/qtype
|
||||
- attrib.rs # process attribution integration (platform provider)
|
||||
- route.rs # interface/route classification (tunnel/physical/loopback)
|
||||
- rules.rs # Leak-A/B/C/D evaluation
|
||||
- report.rs # LeakEvent + SummaryReport builders
|
||||
- privacy.rs # full/redacted/minimal redaction logic
|
||||
|
||||
---
|
||||
|
||||
## 2) Core data types
|
||||
|
||||
### 2.1 TrafficEvent (raw from sensor)
|
||||
Fields:
|
||||
- ts: timestamp
|
||||
- proto: udp/tcp
|
||||
- src_ip, src_port
|
||||
- dst_ip, dst_port
|
||||
- iface_name (capture interface if known)
|
||||
- payload: optional bytes (only for plaintext DNS parsing)
|
||||
|
||||
### 2.2 ClassifiedEvent
|
||||
Adds:
|
||||
- transport: udp53/tcp53/dot/doh/unknown
|
||||
- doh_confidence: HIGH/MEDIUM/LOW (only if doh)
|
||||
- qname/qtype: nullable
|
||||
|
||||
### 2.3 EnrichedEvent
|
||||
Adds:
|
||||
- route_class: loopback/tunnel/physical/unknown
|
||||
- process info: pid/ppid/name (nullable)
|
||||
- attribution_confidence: HIGH/MEDIUM/LOW/NONE
|
||||
- attrib_failure_reason: optional string
|
||||
- geoip: optional
|
||||
|
||||
### 2.4 LeakEvent (final output)
|
||||
Adds:
|
||||
- leak_type: A/B/C/D
|
||||
- severity: P0..P3
|
||||
- policy_rule_id
|
||||
- evidence: minimal structured evidence
|
||||
|
||||
---
|
||||
|
||||
## 3) Platform integration: Process Attribution Engine (PAE)
|
||||
|
||||
### 3.1 Trait addition (wtfnet-platform)
|
||||
|
||||
Add:
|
||||
trait FlowOwnerProvider {
|
||||
fn owner_of(
|
||||
&self,
|
||||
proto: Proto,
|
||||
src_ip: IpAddr,
|
||||
src_port: u16,
|
||||
dst_ip: IpAddr,
|
||||
dst_port: u16,
|
||||
ts: SystemTime,
|
||||
) -> FlowOwnerResult;
|
||||
}
|
||||
|
||||
FlowOwnerResult:
|
||||
- pid, ppid, process_name (optional)
|
||||
- confidence: HIGH/MEDIUM/LOW/NONE
|
||||
- failure_reason: optional string
|
||||
|
||||
Design rule: attribution is best-effort and never blocks leak detection.
|
||||
|
||||
---
|
||||
|
||||
## 4) Transport classification logic
|
||||
|
||||
### 4.1 Plain DNS
|
||||
Match:
|
||||
- UDP dst port 53 OR TCP dst port 53
|
||||
Parse QNAME/QTYPE from payload.
|
||||
|
||||
### 4.2 DoT
|
||||
Match:
|
||||
- TCP dst port 853
|
||||
|
||||
### 4.3 DoH (heuristic)
|
||||
Match candidates:
|
||||
- TCP dst port 443 AND (one of):
|
||||
- dst IP in configured DoH resolver list
|
||||
- dst SNI matches known DoH provider list (if available)
|
||||
- frequent small HTTPS bursts pattern (weak)
|
||||
|
||||
Attach confidence:
|
||||
- MEDIUM: known endpoint match
|
||||
- LOW: traffic-shape heuristic only
|
||||
|
||||
Important: you mostly need to detect encrypted resolver traffic bypassing the proxy channel,
|
||||
not to fully prove DoH with payload inspection.
|
||||
|
||||
---
|
||||
|
||||
## 5) Policy model
|
||||
|
||||
Policy defines "safe DNS path" constraints:
|
||||
- allowed interfaces
|
||||
- allowed destinations (IP/CIDR)
|
||||
- allowed processes
|
||||
- allowed ports
|
||||
|
||||
A DNS event is a leak if it violates safe-path constraints.
|
||||
|
||||
### 5.1 Built-in profiles
|
||||
|
||||
full-tunnel:
|
||||
- allow DNS only via tunnel iface or loopback stub
|
||||
- any UDP/TCP 53 on physical iface => Leak-A
|
||||
|
||||
proxy-stub (default):
|
||||
- allow DNS only to loopback stub
|
||||
- allow stub upstream only to proxy destinations
|
||||
- flag direct DoH/DoT outside proxy path => Leak-C
|
||||
|
||||
split:
|
||||
- allow plaintext DNS only for allowlist
|
||||
- enforce unknown => proxy resolve (Leak-B)
|
||||
|
||||
---
|
||||
|
||||
## 6) Leak rules (A/B/C/D)
|
||||
|
||||
Leak-A (plaintext escape):
|
||||
- transport udp53/tcp53
|
||||
- route_class != allowed
|
||||
- dst not in allowed destination set
|
||||
|
||||
Leak-B (split policy intent leak):
|
||||
- qname matches proxy-required set or "unknown"
|
||||
- query observed going to ISP/domicile resolver or non-tunnel iface
|
||||
|
||||
Leak-C (encrypted bypass):
|
||||
- DoT or DoH flow exists
|
||||
- not via approved egress path (iface/destination)
|
||||
|
||||
Leak-D (mismatch indicator):
|
||||
- correlate qname to later TCP/TLS flows (optional v0.4 NICE)
|
||||
|
||||
---
|
||||
|
||||
## 7) Privacy modes
|
||||
|
||||
Because domains and cmdlines are sensitive, support:
|
||||
- Full: store full qname and cmdline
|
||||
- Redacted (default): hash qname or keep eTLD+1 only; truncate cmdline
|
||||
- Minimal: no domains/cmdline; keep leak counts + resolver IPs + process name
|
||||
|
||||
Privacy mode applies in report builder, not in sensor.
|
||||
|
||||
---
|
||||
|
||||
## 8) CLI integration
|
||||
|
||||
Add under `dns` command group:
|
||||
|
||||
- `dns leak status`
|
||||
- `dns leak watch`
|
||||
- `dns leak report`
|
||||
|
||||
`watch` returns:
|
||||
- summary report (human) by default
|
||||
- `--json` returns structured report with events list
|
||||
|
||||
---
|
||||
|
||||
## 9) Recommended incremental build plan
|
||||
|
||||
Phase 1 (core passive detection):
|
||||
- sensor: udp/tcp capture
|
||||
- classify: udp53/tcp53/dot
|
||||
- parse plaintext qname/qtype
|
||||
- policy: allowlist + allowed interfaces/dests
|
||||
- leak rules: Leak-A + Leak-C (DoT)
|
||||
- report: events + summary
|
||||
|
||||
Phase 2 (process attribution + DoH heuristics):
|
||||
- platform FlowOwnerProvider impls
|
||||
- DoH heuristic classification + confidence
|
||||
- privacy modes
|
||||
|
||||
Phase 3 (optional correlation / Leak-D):
|
||||
- flow tracker correlating DNS -> TCP/TLS connect events
|
||||
- mismatch indicator output
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# Small note about *where* it lives in your architecture
|
||||
|
||||
This design slots in with **minimal churn**:
|
||||
|
||||
* ✅ Add `crates/wtfnet-dnsleak`
|
||||
* ✅ Add `dns leak ...` subcommands in CLI
|
||||
* ✅ Add a **single** new platform trait for process attribution (best-effort)
|
||||
* ✅ Reuse your existing `dns watch` capture approach as the sensor
|
||||
|
||||
…which is exactly what your design describes: passive monitoring + classification + rules + evidence output and the PAE “event enricher” location in the pipeline .
|
||||
|
||||
**If it's too hard to detect DoH traffic, skip it.**
|
||||
|
||||
---
|
||||
@@ -29,6 +29,11 @@ This document tracks the planned roadmap alongside the current implementation st
|
||||
- optional LLMNR/NBNS discovery
|
||||
- optional HTTP/3 (feature-gated; experimental, incomplete)
|
||||
|
||||
### v0.4 (dns leak detection)
|
||||
- dns leak detector (passive watch + report)
|
||||
- process attribution (best-effort)
|
||||
- policy profiles + privacy modes
|
||||
|
||||
## Current stage
|
||||
|
||||
### Implemented
|
||||
@@ -69,13 +74,20 @@ This document tracks the planned roadmap alongside the current implementation st
|
||||
- v0.3: TLS extras (OCSP flag + richer cert parsing).
|
||||
- v0.3: cert baseline/diff improvements.
|
||||
- v0.3: HTTP/3 request path (feature-gated; experimental, incomplete).
|
||||
- v0.3: HTTP/3 error classification (feature-gated).
|
||||
- v0.4: platform flow-owner lookup (best-effort).
|
||||
- v0.4: dns leak detector crate + CLI commands (status/watch/report).
|
||||
- Discover crate with mdns/ssdp commands.
|
||||
- Discover llmnr/nbns (best-effort).
|
||||
- Diag crate with report and bundle export.
|
||||
- Basic unit tests for calc and TLS parsing.
|
||||
|
||||
### In progress
|
||||
- v0.4: DoH heuristic classification (optional).
|
||||
- v0.4: Leak-D mismatch correlation (optional).
|
||||
- v0.3: optional HTTP/3 (feature-gated; keep disabled until stabilized).
|
||||
|
||||
### Next
|
||||
- Complete v0.3 trace upgrades and update CLI output.
|
||||
- Update docs/README/COMMANDS for v0.4.
|
||||
- Add v0.2 tests (dns detect, basic http/tls smoke).
|
||||
- Track DNS leak design status in `docs/dns_leak_implementation_status.md`.
|
||||
|
||||
Reference in New Issue
Block a user