mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-05-02 12:45:10 +02:00
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:
committed by
Cuong Manh Le
parent
afed925404
commit
8cb383d87e
@@ -56,7 +56,7 @@ ctrld run --intercept-mode hard --cd <resolver-uid>
|
||||
|
||||
Windows DNS intercept uses a two-tier architecture with mode-dependent enforcement:
|
||||
|
||||
- **`dns` mode**: NRPT only — graceful DNS routing through the Windows DNS Client service. At worst, a VPN overwrites NRPT and queries bypass ctrld temporarily. DNS never breaks.
|
||||
- **`dns` mode**: NRPT + loopback WFP protect — graceful DNS routing through the Windows DNS Client service, with proactive WFP permit filters that protect the NRPT → localhost path from third-party DNS block filters (e.g., OpenVPN's `block-outside-dns`).
|
||||
- **`hard` mode**: NRPT + WFP — same NRPT routing, plus WFP kernel-level block filters that prevent any outbound DNS bypass. Equivalent enforcement to macOS pf.
|
||||
|
||||
#### Why This Design?
|
||||
@@ -70,8 +70,9 @@ Separating them into modes means most users get `dns` mode (safe, can never brea
|
||||
1. Creates NRPT catch-all registry rule (`.` → `127.0.0.1`) under `HKLM\...\DnsPolicyConfig\CtrldCatchAll`
|
||||
2. Triggers Group Policy refresh via `RefreshPolicyEx` (userenv.dll) so DNS Client loads NRPT immediately
|
||||
3. Flushes DNS cache to clear stale entries
|
||||
4. Starts NRPT health monitor (30s periodic check)
|
||||
5. Launches async NRPT probe-and-heal to verify NRPT is actually routing queries
|
||||
4. **Activates loopback WFP protect** — adds 4 permit filters (IPv4/IPv6 × UDP/TCP) for DNS to localhost with `FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT`. These prevent third-party WFP block filters from blocking the NRPT → `127.0.0.1` path (see [Loopback WFP Protect](#loopback-wfp-protect) below). Non-fatal if this fails.
|
||||
5. Starts NRPT health monitor (30s periodic check)
|
||||
6. Launches async NRPT probe-and-heal to verify NRPT is actually routing queries
|
||||
|
||||
#### Startup Sequence (hard mode)
|
||||
|
||||
@@ -112,6 +113,32 @@ The **Name Resolution Policy Table** is a Windows feature (originally for Direct
|
||||
|
||||
**VPN coexistence**: VPN software can set DNS to whatever it wants on the interface — for public IPs, the WFP block filter prevents those servers from being reached on port 53. For private IPs, the subnet permits allow it. ctrld handles all DNS routing through NRPT and can forward VPN-specific domains to VPN DNS servers through its own upstream mechanism.
|
||||
|
||||
#### Loopback WFP Protect (dns mode)
|
||||
|
||||
Third-party VPN software (e.g., OpenVPN, Securepoint SSL VPN) can install WFP block filters via `block-outside-dns` that block **all** DNS traffic to non-tunnel interfaces — including loopback. This breaks the NRPT → `127.0.0.1:53` path that ctrld depends on, causing DNS resolution to time out.
|
||||
|
||||
ctrld proactively adds 4 WFP "hard permit" filters at startup:
|
||||
|
||||
| Filter | Layer | Protocol |
|
||||
|---|---|---|
|
||||
| Permit DNS to localhost (IPv4/UDP) | ALE_AUTH_CONNECT_V4 | UDP |
|
||||
| Permit DNS to localhost (IPv4/TCP) | ALE_AUTH_CONNECT_V4 | TCP |
|
||||
| Permit DNS to localhost (IPv6/UDP) | ALE_AUTH_CONNECT_V6 | UDP |
|
||||
| Permit DNS to localhost (IPv6/TCP) | ALE_AUTH_CONNECT_V6 | TCP |
|
||||
|
||||
**Key properties:**
|
||||
- **Scope**: Port 53 to `127.0.0.1` (or configured listener IP) and `::1` only
|
||||
- **Flag**: `FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT` (0x08) — "hard permit" that overrides BLOCK decisions from other sublayers regardless of weight or insertion order
|
||||
- **Weight**: 15 (above hard mode's permit=10)
|
||||
- **Sublayer**: ctrld's sublayer at maximum priority (0xFFFF)
|
||||
- **Lifetime**: Process lifetime — added at startup, removed on shutdown/uninstall
|
||||
|
||||
Because `CLEAR_ACTION_RIGHT` is a cross-sublayer override, the order of filter installation doesn't matter — even if a VPN connects hours later and adds its own WFP block filters, ctrld's hard permit for loopback DNS is never overridden.
|
||||
|
||||
The reactive fallback in `nrptProbeAndHeal()` is preserved as defense-in-depth for edge cases where proactive activation fails at startup.
|
||||
|
||||
See: [Issue #526](https://gitlab.int.windscribe.com/controld/clients/ctrld/-/issues/526)
|
||||
|
||||
#### NRPT Probe and Auto-Heal
|
||||
|
||||
`RefreshPolicyEx` returns immediately — it does NOT wait for the DNS Client service to actually load the NRPT rule. On cold machines (first boot, fresh install), the DNS Client may take several seconds to process the policy refresh. During this window, the NRPT rule exists in the registry but isn't active.
|
||||
|
||||
@@ -50,6 +50,29 @@ To isolate the signals, avoid running the log viewer in the same window as the d
|
||||
* **Window B:** Open a new SSH connection to run `ctrld log tail`.
|
||||
Because Window B has a different **Session ID** and **Process Group ID**, pressing `Ctrl+C` in Window B will not affect the process in Window A.
|
||||
|
||||
## Windows Issues
|
||||
|
||||
### VPN `block-outside-dns` Breaks DNS When Using ctrld in DNS Mode
|
||||
|
||||
**Issue**: VPN software that uses OpenVPN's `block-outside-dns` directive installs WFP (Windows Filtering Platform) block filters that prevent DNS queries from reaching ctrld's loopback listener.
|
||||
|
||||
**Status**: Fixed in v1.5.1
|
||||
|
||||
**Description**: When a VPN connects with `block-outside-dns` enabled, OpenVPN adds WFP filters that block all DNS traffic to non-tunnel interfaces — including loopback (`127.0.0.1`). Since ctrld's NRPT catch-all rule routes DNS through the Windows DNS Client to `127.0.0.1:53`, the WFP block filters prevent DNS Client from reaching ctrld, causing all DNS queries to time out.
|
||||
|
||||
This affects any VPN client that implements `block-outside-dns` via WFP, including:
|
||||
- OpenVPN GUI (community)
|
||||
- Securepoint SSL VPN
|
||||
- Any OpenVPN-based client that honors the `block-outside-dns` push directive
|
||||
|
||||
**Fix**: ctrld now proactively adds WFP "hard permit" filters for DNS to localhost at startup. These use `FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT` to override block decisions from any other WFP sublayer, ensuring the NRPT → loopback path is always available regardless of VPN state. See `docs/dns-intercept-mode.md` for technical details.
|
||||
|
||||
**Affected Versions**: ctrld ≤ v1.5.0 in `dns` intercept mode on Windows
|
||||
|
||||
**Last Updated**: 04/28/2026
|
||||
|
||||
---
|
||||
|
||||
## Contributing to Known Issues
|
||||
|
||||
If you encounter an issue not listed here, please:
|
||||
|
||||
@@ -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 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user