dns_intercept: add WFP loopback protect for VPN block-outside-dns

When third-party VPN software (e.g., OpenVPN) installs WFP block filters via
block-outside-dns, all DNS traffic to non-tunnel interfaces is blocked —
including DNS to 127.0.0.1 (ctrld's NRPT target). This breaks DNS mode
interception because the NRPT catch-all rule routes queries to loopback,
but WFP blocks the connection before it reaches ctrld's listener.

Fix: after exhausting all NRPT recovery attempts, activate a minimal WFP
session with "hard permit" filters (FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT)
for DNS to localhost in a max-priority sublayer (weight 0xFFFF). This
overrides the VPN's block for loopback DNS only, while preserving the
VPN's DNS leak protection for all other (non-loopback) DNS traffic.

The loopback protect is:
- Only activated when NRPT probes fail (not preemptively)
- Harmless when no conflicting WFP blocks exist (permit-only, no blocks)
- Persistent until ctrld shutdown (survives VPN reconnect cycles)
- Cleaned up by the existing cleanupWFPFilters path on shutdown
This commit is contained in:
Codescribe
2026-04-29 04:42:12 -04:00
committed by Cuong Manh Le
parent 8abeeea4c3
commit 81aa6b237b
6 changed files with 336 additions and 32 deletions
+13 -7
View File
@@ -15,11 +15,15 @@ the same enforcement guarantees as macOS pf.
```
┌─────────────────────────────────────────────────────────────────┐
dns mode (NRPT only)
│ dns mode (NRPT + loopback WFP protect)
│ │
│ App DNS query → DNS Client service → NRPT lookup │
│ → "." catch-all matches → forward to 127.0.0.1 (ctrld) │
│ │
│ Loopback WFP protect: 4 hard-permit filters (port 53 to │
│ localhost, CLEAR_ACTION_RIGHT) prevent third-party VPN WFP │
│ blocks (e.g., OpenVPN block-outside-dns) from breaking NRPT. │
│ │
│ If VPN clears NRPT: health monitor re-adds within 30s │
│ Worst case: queries go to VPN DNS until NRPT restored │
│ DNS never breaks — graceful degradation │
@@ -182,8 +186,10 @@ When `vpnDNSManager.Refresh()` discovers VPN DNS servers on public IPs:
- Both UDP and TCP for each IP
3. Store new filter IDs for next cleanup cycle
**In `dns` mode, VPN DNS exemptions are skipped** — there are no WFP block
filters to exempt from.
**In `dns` mode, VPN DNS exemptions are skipped** — there are no ctrld WFP block
filters to exempt from. The loopback WFP protect filters only permit localhost
DNS; VPN DNS traffic goes through the tunnel interface and is already permitted
by the VPN's own WFP rules.
### Session Lifecycle
@@ -202,8 +208,8 @@ filters to exempt from.
**Startup (dns mode):**
```
1. Add NRPT catch-all rule + GP refresh + DNS flush
2. Start NRPT health monitor goroutine
3. (No WFP — done)
2. Activate loopback WFP protect (4 hard-permit filters for localhost DNS)
3. Start NRPT health monitor goroutine
```
**Shutdown:**
@@ -338,9 +344,9 @@ breaking DNS.
| Aspect | macOS (pf) | Windows dns mode | Windows hard mode |
|--------|-----------|------------------|-------------------|
| **Routing** | `rdr` redirect | NRPT policy | NRPT policy |
| **Enforcement** | `route-to` + block rules | None (graceful) | WFP block filters |
| **Enforcement** | `route-to` + block rules | Loopback WFP protect | WFP block filters |
| **Can break DNS?** | Yes (pf corruption) | No | Yes (if NRPT lost) |
| **VPN coexistence** | Watchdog + stabilization | NRPT most-specific-match | Same + WFP permits |
| **VPN coexistence** | Watchdog + stabilization | NRPT + loopback hard-permit | Same + WFP permits |
| **Bypass protection** | pf catches all packets | None | WFP catches all connections |
| **Recovery** | Probe + auto-heal | Health monitor re-adds | Full restart on sublayer loss |