cli: default to dns intercept mode on the supported desktop platforms

When no intercept mode is selected by flag or config, a new installation
on macOS or a Windows desktop edition uses "dns". Windows Server, iOS and
other platforms keep interface DNS mode unless intercept is selected
explicitly, and so does a config that cannot run in intercept mode.
Existing Standard installations are not switched on upgrade: ctrld-client
start marks the services it installs fresh, a service with neither a mode
nor the marker keeps interface DNS mode, and the macOS package pins such a
service to "off" explicitly. An explicit "off" stays final and is
persisted.

Master adaptation: the run flag and start install hunk move from
commands.go to commands_run.go and commands_service_start.go and use the
start command's logger and svcConfig; setDNS logs through p.Info(); the
flag test builds the run command with InitRunCmd; user-facing commands,
docs and the pkg test use the ctrld-client binary name.
This commit is contained in:
Dev Scribe authored and Cuong Manh Le committed 2026-10-12 14:42:31 +07:00
1 parent 4792b0eeab
commit d47e6fa173
20 files changed
+1122 -131

No files matched your search

+15
View File
@@ -309,6 +309,21 @@ Specifying the `ip` and `port` of the Prometheus metrics server. The Prometheus
- Required: no
- Default: ""
### intercept_mode
How ctrld captures the host's DNS traffic. `dns` intercepts it in the OS packet filter (WFP on Windows, pf on macOS) and
forwards VPN internal domains to the VPN's DNS server; `hard` intercepts it with no VPN split routing; `off` uses interface
DNS mode, setting ctrld's listener as the DNS server of the network interfaces.
The `--intercept-mode` flag takes precedence over this value. When neither is set, the default applies: `dns` on macOS and
Windows desktop editions, interface DNS mode elsewhere, Windows Server included. An installed service that predates the
default keeps interface DNS mode. `ctrld-client start --intercept-mode off` writes `off` here, because an unset value means the
default. See [Intercept Mode Default](intercept-mode-default.md).
- Type: string
- Required: no
- Valid values: `off`, `dns`, `hard`
- Default: `dns` for new installations on macOS and Windows desktop editions; interface DNS mode elsewhere, Windows Server included, and for services installed before this default
### dns_watchdog_enabled
Watches all physical interfaces for DNS changes and reverts them to ctrld's settings.The DNS watchdog process only runs on Windows and MacOS.
+13 -3
View File
@@ -24,8 +24,15 @@ DNS intercept mode works at a lower level than interface settings:
## Usage
On macOS and Windows desktop editions, `dns` is the default mode for new installations: a fresh
start that selects no mode, by flag or by `service.intercept_mode`, intercepts. An existing
Standard installation keeps interface DNS mode across upgrades. `--intercept-mode off`
selects interface DNS mode and is persisted. See
[Intercept Mode Default](intercept-mode-default.md) for the full precedence.
```bash
# Start ctrld with DNS intercept mode (auto-detects VPN search domains)
# Start ctrld with DNS intercept mode (the default on macOS and Windows desktop editions)
ctrld-client start --cd <resolver-uid>
ctrld-client start --intercept-mode dns --cd <resolver-uid>
# Hard intercept: all DNS through ctrld, no VPN split routing
@@ -37,6 +44,9 @@ ctrld-client start --intercept-mode dns -c /path/to/ctrld.toml
# Run in foreground (debug)
ctrld-client run --intercept-mode dns --cd <resolver-uid>
ctrld-client run --intercept-mode hard --cd <resolver-uid>
# Interface DNS mode instead (persisted as intercept_mode = "off")
ctrld-client start --intercept-mode off --cd <resolver-uid>
```
### Intercept Modes
@@ -202,9 +212,9 @@ This is periodic (not just network-event-driven) because VPN software can clear
**ctrld's own traffic**: ctrld's upstream queries use DoH (HTTPS on port 443), not plain DNS on port 53, so the pf redirect does not create a loop for DoH upstreams. **Warning:** If an "os" upstream is configured (which uses plain DNS on port 53 to external servers), the pf redirect will capture ctrld's own outbound queries and create a loop. ctrld will log a warning at startup if this is detected. Use DoH upstreams when DNS intercept mode is active.
## What Changes vs Default Mode
## What Changes vs Interface DNS Mode
| Behavior | Default Mode | DNS Intercept Mode |
| Behavior | Interface DNS Mode | DNS Intercept Mode |
|----------|-------------|-------------------|
| Interface DNS settings | Set to `127.0.0.1` | **Not modified** |
| DNS watchdog | Active (polls every 20s) | **Disabled** |
+149
View File
@@ -0,0 +1,149 @@
# Intercept Mode Default
On the supported desktop platforms, macOS and Windows desktop (workstation)
editions, a new ctrld installation runs in DNS intercept mode (`dns`) unless
you select a mode explicitly. Everywhere else, Windows Server included, it
keeps interface DNS mode, which sets ctrld's listener as the DNS server of the
network interfaces. An existing installation keeps the mode it had: upgrading
never switches a Standard (interface DNS) install to intercept mode. See
[Upgrades](#upgrades).
Windows Server is excluded on purpose. A server, and a domain controller
running its own DNS server in particular, is not switched to intercept mode by
default. Intercept mode still works there when selected explicitly.
This applies to a service that `ctrld-client start` installs and to `ctrld-client run` in
the foreground alike. See [DNS Intercept Mode](dns-intercept-mode.md) for what
intercept mode does.
## Precedence
The effective mode is the first of these that is set:
1. `--intercept-mode` on the command line, or in the service's arguments.
2. `service.intercept_mode` in the configuration the service runs with:
`ctrld.toml`, a `--base64_config`, or a Control D custom configuration.
3. The default: `dns` on macOS and Windows desktop editions; interface DNS
mode elsewhere, Windows Server included. An installed service that predates
the default is the exception, see [Upgrades](#upgrades).
`off` is a selection like `dns` and `hard`, not an absence of one. At either
explicit level it stops the lookup and selects interface DNS mode, including
over a persisted `intercept_mode = "dns"`.
| `--intercept-mode` | `service.intercept_mode` | macOS / Windows desktop | Windows Server | Linux and others |
|---|---|---|---|---|
| unset | unset | `dns` (default)² | interface DNS mode | interface DNS mode |
| unset | `dns` / `hard` | that mode | that mode | that mode¹ |
| unset | `off` | interface DNS mode | interface DNS mode | interface DNS mode |
| `dns` / `hard` | any | that mode | that mode | that mode¹ |
| `off` | any | interface DNS mode | interface DNS mode | interface DNS mode |
² For a fresh install or a foreground run. An installed service that predates
the default keeps interface DNS mode, see [Upgrades](#upgrades).
¹ Intercept mode is not supported outside Windows and macOS, so an explicit
`dns` or `hard` there fails to start intercept, as before. An unset mode never
tries: those platforms do not log an intercept-start failure just because the
default changed.
Desktop versus server follows `ctrld.IsDesktopPlatform`, which ctrld already
uses elsewhere: on Windows it is the workstation product type
(`VER_NT_WORKSTATION`), so Windows Server and domain controllers are not
desktop platforms.
iOS is not a desktop platform here either, although it builds ctrld's macOS
code: it has no pf to intercept with.
The default also keeps interface DNS mode for a configuration that cannot run
in intercept mode. On macOS that is any `os` or untyped upstream: pf would
redirect ctrld's own port-53 queries to it back to ctrld. Such a configuration
worked in interface DNS mode and keeps doing so; the service log says why the
default was not used. Selecting `dns` or `hard` explicitly for it still fails
to start intercept, as before.
The service decides the default itself, on every start, against the
configuration it runs with. In Control D mode (`--cd`) that is the resolver's
configuration it fetches, so an `os` upstream or an `intercept_mode` in a
custom configuration counts, including one added after installation.
## Opting out
Use interface DNS mode explicitly:
```shell
# Windows (Admin Shell)
ctrld-client.exe start --intercept-mode off --cd RESOLVER_ID_HERE
# macOS
sudo ctrld-client start --intercept-mode off --cd RESOLVER_ID_HERE
```
On an installed service, `ctrld-client start --intercept-mode off` with no other
arguments switches the existing service to interface DNS mode, keeping its
other arguments.
The selection is stored in two places, so it survives restarts and upgrades:
- in the service's arguments, as `--intercept-mode off`;
- in `ctrld.toml`, as `intercept_mode = "off"`.
Earlier builds cleared `intercept_mode` when switching to `off`. That no longer
works as an opt-out: an unset value now means the platform default, so
switching to `off` writes `off`. Older builds read `off` the same way, so a
downgrade keeps interface DNS mode too.
## Upgrades
Only a fresh installation gets the `dns` default. An existing Standard
(interface DNS) installation stays in Standard mode across upgrades, whichever
path upgrades it.
To tell the two apart, `ctrld-client start` marks the services it installs, in the
service's arguments, when no mode is selected:
- A fresh install records `--intercept-mode-default=true`, an internal flag.
It ranks lowest: the service applies the default only if neither
`--intercept-mode` nor `service.intercept_mode` selects a mode, against the
configuration it fetched.
- A reinstall over an existing service keeps what that service recorded, its
mode or the marker. If it recorded neither, it was installed in Standard mode
before this default, and the reinstall records `--intercept-mode off`, unless
`service.intercept_mode` selects a mode.
- Platforms without the `dns` default record nothing, as before.
Older ctrld builds ignore flags they do not know in the service's arguments,
so the marker is safe if a device is downgraded.
An installed service with neither a mode nor the marker therefore predates
the default. It keeps interface DNS mode when the new binary runs it, and the
service log says why. That covers upgrades that rewrite nothing, such as
`ctrld-client upgrade` run by an older binary, which swaps the binary and restarts the
service with its existing arguments. A foreground `ctrld-client run` with no mode
selected uses the default.
The macOS package pins such a service explicitly when it upgrades it with a
profile that has no `InterceptMode`: it runs `ctrld-client start --intercept-mode
off`, the same as for a `standard` profile. See
[macOS MDM Package](macos-pkg-mdm.md).
To move an existing Standard installation to intercept mode, select it
explicitly with `ctrld-client start --intercept-mode dns`.
The Control D installer and dashboard should generate `--intercept-mode off` for
a device where Standard mode is chosen, so a fresh install never gets intercept
mode against that choice.
## Where the mode is reported
- `ctrld-client start` output: `DNS intercept mode active ...` once the service runs
in intercept mode, followed, when the default selected it, by how to switch
to interface DNS mode. A reinstall over a Standard install prints `Existing
service selects no intercept mode; keeping interface DNS mode
(--intercept-mode off)`.
- The service log, at startup: `Intercept mode enabled via config` when the
configuration set the mode, `Intercept mode "dns" is the default on this
platform; ...` when the default applied, or `Intercept mode default not
applied; using interface DNS mode` with the reason, for a service that
predates the default or a configuration that cannot run in intercept mode.
- The log header field `intercept_mode`, which reads `off` for interface DNS
mode.