Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
225 commits
Select commit Hold shift + click to select a range
5ce92ab
Preparing for v2.0.0 branch merge
cuonglm Oct 9, 2025
6286a71
all: unify code to handle static DNS file path
cuonglm May 5, 2025
47c04bf
all: unify handling user home directory logic
cuonglm May 5, 2025
0e66697
all: eliminate usage of global ProxyLogger
cuonglm Apr 3, 2025
aaf31b6
cmd/cli: avoid accessing mainLog when possible
cuonglm Jun 17, 2025
95699fa
cmd/cli: use resolvconffile lib for parsing
cuonglm Jun 17, 2025
29b8b42
all: move nameserver resolution to public API
cuonglm Jun 18, 2025
abad9ef
test: improve DNS resolver tests reliability and thread safety
cuonglm Jun 18, 2025
7ec4353
refactor: move client info handling to desktop-specific files
cuonglm Jun 18, 2025
627eb23
docs: improve test resolv.conf handling documentation
cuonglm Jun 18, 2025
eaa6ccc
refactor: improve network interface validation
cuonglm Jun 19, 2025
2030025
refactor: move getDNS type to os_linux.go
cuonglm Jun 19, 2025
719d76f
refactor(dns): improve DNS proxy code structure and readability
cuonglm Jun 19, 2025
38ae916
internal/router: support Merlin Guest Network Pro VLAN
cuonglm Jun 30, 2025
af1a6e9
internal/router: support Ubios 4.3+
cuonglm Jul 8, 2025
ba9057e
Removing router platforms support
cuonglm Jun 30, 2025
975b465
Removing Windows Server support
cuonglm Jul 3, 2025
a63fa31
refactor: break down proxy method into smaller focused functions
cuonglm Jul 7, 2025
f573de8
Correct debug logging in DNS-over-HTTP transport
cuonglm Jul 7, 2025
7552f1c
refactor: split selfUpgradeCheck into version check and upgrade execu…
cuonglm Jul 14, 2025
02032c8
cmd/cli: ignore empty positional argument for start command
cuonglm Jul 15, 2025
6e1e942
refactor: extract empty string filtering to reusable function
cuonglm Jul 15, 2025
48d0558
Refactor handleRecovery method and improve tests
cuonglm Jul 16, 2025
016c566
Fix tautological condition in findWorkingInterface
cuonglm Jul 16, 2025
d41334c
refactor: migrate from zerolog to zap logging library
cuonglm Jul 21, 2025
f933664
fix: improve listener configuration and error logging
cuonglm Jul 22, 2025
f73a17f
feat: add custom NOTICE log level between INFO and WARN
cuonglm Jul 22, 2025
ddb81b6
feat: add interfaces and types for command refactoring
cuonglm Jul 28, 2025
d0830a7
feat: create commands_log.go and add LogCommand
cuonglm Jul 28, 2025
f0d7cfa
feat: create commands_service.go and add ServiceCommand
cuonglm Jul 28, 2025
1cd034d
feat: create commands_upgrade.go and add UpgradeCommand with complete…
cuonglm Jul 28, 2025
2d6fea1
feat: create commands_clients.go and add ClientsCommand with complete…
cuonglm Jul 28, 2025
efee5b6
feat: create commands_interfaces.go and add InterfacesCommand
cuonglm Jul 28, 2025
d81eef9
feat: port complete alias command logic from original implementation
cuonglm Jul 28, 2025
aa8af67
refactor: remove old initLogCmd and integrate new log command structure
cuonglm Jul 28, 2025
35cc8ad
refactor: consolidate service commands into modular structure with co…
cuonglm Jul 28, 2025
b510fe1
cleanup: remove unused service command functions from commands.go
cuonglm Jul 29, 2025
7677c2f
refactor: move initRunCmd to dedicated commands_run.go file
cuonglm Jul 29, 2025
d5281d5
refactor: rename service_manager.go and remove unused CommandRunner i…
cuonglm Jul 29, 2025
8cc5b71
fix: complete porting of initUninstallCmd logic to ServiceCommand.Uni…
cuonglm Jul 29, 2025
d810420
refactor: split ServiceCommand methods into dedicated files
cuonglm Jul 29, 2025
dd930a3
refactor: fix createStartCommands to follow single responsibility pri…
cuonglm Jul 29, 2025
76e602a
fix: register uninstall command before interfaces command
cuonglm Jul 29, 2025
33dd720
refactor: improve ServiceManager initialization with cleaner API
cuonglm Jul 30, 2025
a61cb1f
refactor: replace direct newService calls with ServiceCommand pattern
cuonglm Jul 30, 2025
bfe6060
refactor: pass rootCmd as parameter to Init*Cmd functions
cuonglm Jul 30, 2025
8959319
fix: reorder service command additions for consistency
cuonglm Jul 30, 2025
f3a5fff
fix: add missing flags to uninstall command
cuonglm Jul 30, 2025
0b4dc51
fix: restore missing logic from refactoring
cuonglm Jul 30, 2025
38f0b84
test: add comprehensive CLI command tests
cuonglm Jul 31, 2025
ed147a3
refactor: move network monitoring to separate goroutine
cuonglm Aug 1, 2025
4d8e10c
fix: correct Windows API constants to fix domain join detection
cuonglm Aug 1, 2025
b187ec9
refactor: convert rootCmd from global to local variable
cuonglm Aug 5, 2025
5bc8da6
Add explanatory comments for variable overwrites and code flow decisions
cuonglm Aug 6, 2025
54be78f
Add comprehensive documentation to CLI components and core functionality
cuonglm Aug 7, 2025
134561c
refactor(prog): move network monitoring outside listener loop
cuonglm Aug 11, 2025
36afb16
fix: ensure upstream health checks can handle large DNS responses
cuonglm Aug 15, 2025
c8477fe
start mobile library with provision id and custom hostname.
Ginder-Singh Aug 20, 2025
64393b7
feat: enhance logging in service commands with consistent logger usage
cuonglm Sep 3, 2025
7778c96
fix: use background context for DNS listeners to survive reloads
cuonglm Sep 3, 2025
082f5a0
feat: enhance DNS proxy logging with comprehensive flow tracking
cuonglm Sep 4, 2025
7040c20
feat: enhance configuration and network management logging
cuonglm Sep 4, 2025
88a297a
feat: enhance CLI commands and service management logging
cuonglm Sep 4, 2025
166b7f3
feat: enhance internal components and utilities logging
cuonglm Sep 4, 2025
d3b01dc
feat: capitalize all log messages for better readability
cuonglm Sep 4, 2025
37ddbd9
docs: add known issues documentation for Darwin 15.5 upgrade issue
cuonglm Sep 5, 2025
4be2621
feat: enhance log reading with ANSI color stripping and comprehensive…
cuonglm Sep 8, 2025
00c1e0f
Upgrade quic-go to v0.54.0
cuonglm Sep 9, 2025
6cf7548
refactor: replace Unix socket log communication with HTTP-based system
cuonglm Sep 12, 2025
52cfb4c
Change download url for v2
cuonglm Sep 23, 2025
f240598
feat: add --rfc1918 flag for explicit LAN client support
cuonglm Sep 24, 2025
650e47a
refactor: consolidate network interface detection logic
cuonglm Oct 1, 2025
e17a538
Fix staticcheck linter
cuonglm Oct 9, 2025
261f948
refactor: extract rule matching logic into internal/rulematcher package
cuonglm Sep 16, 2025
6294ba4
feat: add configurable rule matching engine
cuonglm Sep 16, 2025
c365051
feat: add configurable rule matching with improved code structure
cuonglm Sep 16, 2025
9b1f102
refactor: remove unused StopOnFirstMatch field from MatchingConfig
cuonglm Sep 16, 2025
6aedc2b
docs: add comprehensive package documentation for rulematcher
cuonglm Sep 22, 2025
1489245
cmd/cli: ensure error message ends with newline
cuonglm Oct 3, 2025
aacbcad
cmd/cli: workaround TB.TemdDir path too long for Unix socket path
cuonglm Oct 9, 2025
9dd4183
Upgrade quic-go to v0.56.0
cuonglm Nov 11, 2025
2cb0456
.github/workflows: upgrade staticcheck-action to v1.4.0
cuonglm Nov 12, 2025
673308a
docs: add v2.0.0 breaking changes documentation
cuonglm Oct 2, 2025
9a38409
Upgrade quic-go to v0.57.0
cuonglm Dec 16, 2025
c3c9e1a
.github/workflows: temporary use actions/setup-go
cuonglm Dec 17, 2025
aacba92
docs: add documentation for runtime internal logging
cuonglm Dec 16, 2025
e4e6554
perf(doq): implement connection pooling for improved performance
cuonglm Jan 6, 2026
2d96036
refactor(config): consolidate transport setup and eliminate duplication
cuonglm Jan 6, 2026
9158cd7
fix(config): use three-state atomic for rebootstrap to prevent data race
cuonglm Jan 7, 2026
8b92dc9
perf(dot): implement connection pooling for improved performance
cuonglm Jan 8, 2026
ba3dd3a
Including system metadata when posting to utility API
cuonglm Dec 10, 2025
43d4e19
fix: remove incorrect transport close on DoH3 error
cuonglm Dec 12, 2025
e23451d
fix(system): disable ghw warnings to reduce log noise
cuonglm Jan 9, 2026
8117084
fix(windows): improve DNS server discovery for domain-joined machines
cuonglm Jan 15, 2026
e4ca728
refactor(network): consolidate network change monitoring
cuonglm Jan 20, 2026
6f331f1
fix(dns): handle empty and invalid IP addresses gracefully
cuonglm Jan 27, 2026
e45e56c
fix(dot): validate connections before reuse to prevent io.EOF errors
cuonglm Jan 28, 2026
60dd366
refactor(dot): simplify DoT connection pool implementation
cuonglm Jan 28, 2026
f1b93c8
refactor(doq): simplify DoQ connection pool implementation
cuonglm Jan 28, 2026
878b3d7
fix(cli): avoid warning when HTTP log server is not yet available
cuonglm Feb 3, 2026
f1e49a7
fix(darwin): use scutil for provisioning hostname (#485)
Feb 12, 2026
846aaac
fix: include hostname hints in metadata for API-side fallback
Feb 12, 2026
bc71622
Use go1.25 for CI
cuonglm Mar 5, 2026
ea48186
Fix dnsFromResolvConf not filtering loopback IPs
Mar 3, 2026
c56d477
docs: add DNS Intercept Mode section to README
Mar 5, 2026
3953351
feat: introduce DNS intercept mode infrastructure
Mar 5, 2026
a99dcca
feat: add macOS pf DNS interception
Mar 5, 2026
402771b
feat: add Windows NRPT and WFP DNS interception
Mar 5, 2026
5a61631
feat: add VPN DNS split routing
Mar 5, 2026
0f2a930
feat: robust username detection and CI updates
Mar 3, 2026
2aaa78e
fix(windows): make staticcheck happy
cuonglm Mar 3, 2026
ca8d07d
fix(darwin): correct pf rules tests
cuonglm Mar 3, 2026
86dafc4
Add `log tail` command for live log streaming
Mar 17, 2026
d712499
fix: bracket IPv6 addresses in VPN DNS upstream config
Apr 1, 2026
a3880be
docs: port IPv6 learnings and comment fixes to master
Apr 1, 2026
a8821e6
fix(darwin): support non-standard listener port in intercept mode
Apr 1, 2026
a92d20c
doq: configure QUIC keep-alive and retry on idle timeout
Apr 2, 2026
a767ebd
doq: use OpenStreamSync and retry on StreamLimitReachedError
cuonglm Apr 10, 2026
2742669
fix: prevent panic on network change during SetSelfIP
cuonglm Apr 20, 2026
70b4571
docs: improve common gotchas and non-obvious patterns
cuonglm Apr 21, 2026
b3c670b
dns: fix recovery race condition during rapid network transitions
Apr 21, 2026
8abeeea
log: persist internal runtime logs to disk
Apr 29, 2026
81aa6b2
dns_intercept: add WFP loopback protect for VPN block-outside-dns
Apr 29, 2026
1735d3d
cmd/cli: skip upstream.os healthcheck when WFP loopback protect enabled
May 6, 2026
8e2ef7c
all: explicit TLS MinVersion in tls.Config
cuonglm May 8, 2026
a61677b
cmd/cli: use os.CreateTemp for symlink-safe temp file creation
cuonglm May 8, 2026
0149043
cmd/cli: rate-limit PIN brute-force on control socket
cuonglm May 8, 2026
65d3d46
Bump golang.org/x/net to v0.53.0
cuonglm May 11, 2026
7b36028
doq: validate DNS-over-QUIC response framing
cuonglm May 11, 2026
98ca633
fix(doq): share QUIC transport, close send side before read (RFC 9250)
cuonglm May 14, 2026
e15ca5c
docker: update Dockerfile to use bookworm
cuonglm May 26, 2026
0d183fe
Bump golang.org/x/net to v0.55.0
cuonglm May 29, 2026
3c740b9
fix: port Windows VPN DNS settling fix to master
May 27, 2026
f281466
fix(doh,doq): port oversized response caps to master
cuonglm May 25, 2026
6c54898
fix(windows): include domainless VPN DNS adapters in discovery
May 27, 2026
5ccbf63
test: isolate VPN DNS settling tests from host adapters
cuonglm Jun 8, 2026
7a8450c
fix: flush pf states after forced DNS intercept reload
Jun 4, 2026
a4bc23d
fix: allow intercept fallback for default listener
Jun 4, 2026
9399f45
fix: refresh macOS VPN DNS after pf stabilization
Jun 12, 2026
c1d3686
fix: stop self-upgrade tests from fork-bombing the windows test runner
cuonglm Jun 15, 2026
9eb7067
Upgrade quic-go to v0.59.1
cuonglm Jun 22, 2026
8330049
feat: add firewall mode DNS-resolved IP allowlist
Jun 24, 2026
8948fa4
fix: back off macOS pf watchdog exec storms
Jun 30, 2026
ff14fc8
fix: back off unroutable IPv6 DoH upstream health-check spam
cuonglm Jun 30, 2026
eb8756b
fix: treat 464XLAT CLAT source as local, not WAN
cuonglm Jul 9, 2026
3226c2d
Port NRPT recovery limits to master
Jul 10, 2026
5ef0a59
cmd/cli: preserve fallback listener port across reload in DNS intercept
cuonglm Jul 8, 2026
265573c
fix: restore DNS during invalid-device uninstall
Jul 4, 2026
9cf8bc3
fix: wrong DoQ resolver rewriting upstream responses with SetReply
cuonglm Jul 13, 2026
7f3d332
cmd/cli: discard upstream answers whose question mismatches the request
cuonglm Jul 13, 2026
c43739e
fix: restore v1 intercept parity on master
Jul 15, 2026
19b222f
fix: count DoQ/UDP test server call before writing reply
cuonglm Jul 15, 2026
8ffe8d4
fix: harden DoH oversized-body tests against server write timing
cuonglm Jul 16, 2026
06238e5
Bump golang.org/x/text to v0.40.0
cuonglm Jul 22, 2026
cbef8b9
fix: partition DNS cache by EDNS Client Subnet
cuonglm Jul 21, 2026
171dd0a
fix(firewall): support VM/container egress under macOS Firewall Mode
cuonglm Jul 30, 2026
58a5ea5
fix: validate pf state before stabilization
Aug 6, 2026
e4e1c0a
windows: adopt GP-managed NRPT catch-all
Aug 10, 2026
24fc908
fix: retry macOS OS resolver with route-selected source
Aug 10, 2026
0c8281c
fix: ensure log and cache flags are processed during reload
cuonglm Nov 4, 2025
e5f2506
cmd/cli: fix linter
cuonglm Aug 12, 2026
52b7aaa
cmd/cli, internal/controld: bound API preflight by service lifetime
cuonglm Jul 30, 2026
7de6298
cmd/cli: scope ctrld's WFP objects to a dynamic session on Windows
cuonglm Jul 30, 2026
891d5d9
cmd/cli: drop stale intercept enforcement at startup, before API boot…
cuonglm Jul 30, 2026
052b057
all: keep the first-attempt error when the direct-ip fallback also fails
cuonglm Jul 30, 2026
88e076a
cmd/cli: stop the replacement before rolling its binary back
cuonglm Jul 30, 2026
062dcbf
cmd/cli: report whether ctrld finished starting up, not just what SCM…
cuonglm Jul 30, 2026
0db5ee4
cmd/cli: log the config error that rejected a custom config
cuonglm Aug 12, 2026
0f31059
Redact provision token in logs
Mar 29, 2026
fffa621
security: default metrics server to loopback
Mar 29, 2026
0dde645
Bump bump insomniacslk/dhcp to c76316d
cuonglm Aug 17, 2026
a40eb70
feat(cli): add stable provisioning failure codes
awmwong Aug 20, 2026
5eb2db5
pkg: apply managed DNS mode in the macOS package
Aug 21, 2026
47dd64b
fix(cli): treat explicit intercept-mode off as final in listener setup
awmwong Aug 21, 2026
f0b60f3
pkg: address managed DNS mode review feedback
Aug 21, 2026
4113064
all: apply the organization's allowed destination IPs in Firewall Mode
cuonglm Aug 17, 2026
f96868c
all: permit ctrld's own endpoints in Firewall Mode
cuonglm Aug 24, 2026
80d1acd
dns intercept: port DNS-less network recovery to master
Sep 1, 2026
d8994cc
Rename ctrld v2.0.0 to ctrld-client
cuonglm Aug 27, 2026
dd77d86
dns proxy: port DNS64 synthesis to master
Sep 2, 2026
0f821a7
feat(cli): classify every provisioning failure with stable codes
awmwong Sep 3, 2026
e8e04ae
feat(cli): add ctrld diag for provisioning support
awmwong Sep 3, 2026
dc39073
feat(pkg): classify postinstall failures and test them in ci
awmwong Sep 3, 2026
c48220a
docs: complete the provisioning failure runbook and canary matrix
awmwong Sep 3, 2026
5e1ebfa
darwin: route Zscaler's DNS echo name through the OS resolver
Sep 15, 2026
02d4e8e
fix(darwin): probe interception when stabilization finishes
cuonglm Aug 10, 2026
246a540
fix(darwin): probe interception after the host wakes
cuonglm Aug 19, 2026
13471ba
macOS: repair network recovery and improve diagnostics
Sep 16, 2026
25b79d5
cli: route organization Internal Domains from managed config
cuonglm Sep 17, 2026
2ff70ac
dns_intercept: don't write a GP sibling on failed NRPT handback
cuonglm Sep 11, 2026
488065f
fix: skip internal logging in silent mode
cuonglm Jul 14, 2026
37b835c
feat(cli): port the log journal and network journal events to master
awmwong Sep 21, 2026
4879574
fix(cli): skip DNS restoration quietly when the interface is gone
cuonglm Sep 22, 2026
682c6c8
feat: recognize every NextDNS endpoint under nextdns.io
cuonglm Sep 22, 2026
163c8c7
fix: filter root domain from search domains on Linux
cuonglm Oct 21, 2025
b8c6ca8
fix(net): port transport resource cleanup
Sep 22, 2026
4fb8e50
fix(cli): port runtime lifecycle cleanup
Sep 22, 2026
14d8e20
fix(mobile): port synchronized controller teardown
Sep 22, 2026
7f768e6
fix(cli): bound the DNS loop check probe by upstream timeout
cuonglm Sep 23, 2026
c9c829a
test(cli): scope probe journal assertions to their own log lines
cuonglm Sep 23, 2026
6e66ea8
Port scope OS recovery and retain DNS-target diagnostics
Sep 24, 2026
55190fa
macOS: port native CLAT DNS-target fallback to master
Sep 23, 2026
f5a4cb6
macOS: use the networksetup service name for USB CLAT targets
Sep 24, 2026
a9382c3
macOS: share network service-order parsing
Sep 24, 2026
c7b3881
Port DNS64, VPN routing and DNS recovery ownership to master
Sep 25, 2026
36d4167
cmd/cli: add missing function from cherry-pick
cuonglm Sep 28, 2026
6edce5f
fix(cli): keep Active Directory lookups off Control D's public resolver
alexelisenko Sep 30, 2026
3b4d96e
fix(cli): port the log journal fixes and Windows race tests
awmwong Oct 1, 2026
f914105
fix(cli): mark AD lookups on the bypass and OS retry paths
cuonglm Oct 2, 2026
8d8fc54
docs: port startup DNS stall documentation
Oct 5, 2026
7c1184a
feat(api): classify Control D API maintenance responses
cuonglm Sep 25, 2026
c7b2134
feat(cli): keep serving DNS through Control D API maintenance
cuonglm Sep 25, 2026
cee5bbe
fix(cli): journal that allowed destinations fail closed on a maintena…
Oct 6, 2026
bfc46ef
feat(cli): add opt-in macOS unprivileged log-send socket
Oct 7, 2026
b0408e4
cli: add network fallback and explicit-only Internal Domain modes
Oct 6, 2026
be8c068
Send full host metadata on managed startup
Oct 8, 2026
f391318
fix(cli): roll back an upgrade whose new build never becomes ready
Oct 8, 2026
fc9ad82
fix(cli): redact the provision code in the intercept upgrade check line
Oct 8, 2026
1f875af
test(cli): keep the indefinite recovery wait test off the host's DNS
cuonglm Oct 8, 2026
9562717
Bump golang.org/x/net to v0.60.0
Oct 9, 2026
1dd94eb
fix(cli): set the Intercept DNS target on IPv6-only networks before C…
Oct 9, 2026
4792b0e
feat(cli): route Tailscale Split DNS and App Connector domains in mac…
Oct 9, 2026
d47e6fa
cli: default to dns intercept mode on the supported desktop platforms
Oct 9, 2026
cfbb873
.github/workflows: bump go version to 1.26
cuonglm Oct 12, 2026
3b849bf
fix: recover upstreams from a partial bootstrap lookup
Oct 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ jobs:
fail-fast: false
matrix:
os: ["windows-latest", "ubuntu-latest", "macOS-latest"]
go: ["1.24.x"]
go: ["1.26.x"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 1
- uses: WillAbides/setup-go-faster@v1.8.0
- uses: actions/setup-go@v6
with:
go-version: ${{ matrix.go }}
- run: "go test -race ./..."
- uses: dominikh/staticcheck-action@v1.3.1
- uses: dominikh/staticcheck-action@v1.4.0
with:
version: "2025.1"
version: "2025.1.1"
install-go: false
cache-key: ${{ matrix.go }}
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,10 @@ ctrld-*

# generated file
cmd/cli/rsrc_*.syso
ctrld
ctrld.exe

# Local planning artifacts - never commit
/SPEC.md
/tasks/
/SPEC-*.md
147 changes: 95 additions & 52 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ A highly configurable DNS forwarding proxy with support for:
- Multiple upstreams with fallbacks
- Multiple network policy driven DNS query steering (via network cidr, MAC address or FQDN)
- Policy driven domain based "split horizon" DNS with wildcard support
- Integrations with common router vendors and firmware
- LAN client discovery via DHCP, mDNS, ARP, NDP, hosts file parsing
- Prometheus metrics exporter

Expand All @@ -25,50 +24,32 @@ All DNS protocols are supported, including:
- `DNS-over-HTTP/3` (DOH3)
- `DNS-over-QUIC`

# Use Cases
1. Use secure DNS protocols on networks and devices that don't natively support them (legacy routers, legacy OSes, TVs, smart toasters).
## Use Cases
1. Use secure DNS protocols on networks and devices that don't natively support them (legacy OSes, TVs, smart toasters).
2. Create source IP based DNS routing policies with variable secure DNS upstreams. Subnet 1 (admin) uses upstream resolver A, while Subnet 2 (employee) uses upstream resolver B.
3. Create destination IP based DNS routing policies with variable secure DNS upstreams. Listener 1 uses upstream resolver C, while Listener 2 uses upstream resolver D.
4. Create domain level "split horizon" DNS routing policies to send internal domains (*.company.int) to a local DNS server, while everything else goes to another upstream.
5. Deploy on a router and create LAN client specific DNS routing policies from a web GUI (When using ControlD.com).


## OS Support
- Windows (386, amd64, arm)
- Windows Server (386, amd64)
- Windows Desktop (386, amd64, arm64)
- MacOS (amd64, arm64)
- Linux (386, amd64, arm, mips)
- FreeBSD (386, amd64, arm)
- Common routers (See below)


### Supported Routers
You can run `ctrld` on any supported router. The list of supported routers and firmware includes:
- Asus Merlin
- DD-WRT
- Firewalla
- FreshTomato
- GL.iNet
- OpenWRT
- pfSense / OPNsense
- Synology
- Ubiquiti (UniFi, EdgeOS)

`ctrld` will attempt to interface with dnsmasq (or Windows Server) whenever possible and set itself as the upstream, while running on port 5354. On FreeBSD based OSes, `ctrld` will terminate dnsmasq and unbound in order to be able to listen on port 53 directly.

# Install
- Linux (386, amd64, arm, arm64, mips, mipsle, mips64)
- FreeBSD (386, amd64, arm, arm64)

## Install
There are several ways to download and install `ctrld`.

## Quick Install
The simplest way to download and install `ctrld` is to use the following installer command on any UNIX-like platform:

```shell
sh -c 'sh -c "$(curl -sL https://api.controld.com/dl)"'
sh -c 'sh -c "$(curl -sL https://api.controld.com/dl?version=client)"'
```

Windows user and prefer Powershell (who doesn't)? No problem, execute this command instead in administrative PowerShell:
```shell
(Invoke-WebRequest -Uri 'https://api.controld.com/dl/ps1' -UseBasicParsing).Content | Set-Content "$env:TEMPctrld_install.ps1"; Invoke-Expression "& '$env:TEMPctrld_install.ps1'"
(Invoke-WebRequest -Uri 'https://api.controld.com/dl/ps1?version=client' -UseBasicParsing).Content | Set-Content "$env:TEMP\ctrld_install.ps1"; Invoke-Expression "& '$env:TEMP\ctrld_install.ps1'"
```

Or you can pull and run a Docker container from [Docker Hub](https://hub.docker.com/r/controldns/ctrld)
Expand All @@ -80,7 +61,7 @@ docker run -d --name=ctrld -p 127.0.0.1:53:53/tcp -p 127.0.0.1:53:53/udp control
Alternatively, if you know what you're doing you can download pre-compiled binaries from the [Releases](https://github.com/Control-D-Inc/ctrld/releases) section for the appropriate platform.

## Build
Lastly, you can build `ctrld` from source which requires `go1.21+`:
Lastly, you can build `ctrld` from source which requires `go1.24+`:

```shell
go build ./cmd/ctrld
Expand All @@ -99,7 +80,7 @@ docker build -t controldns/ctrld . -f docker/Dockerfile
```


# Usage
## Usage
The cli is self documenting, so feel free to run `--help` on any sub-command to get specific usages.

## Arguments
Expand All @@ -112,7 +93,7 @@ _/ ___\ __\_ __ \ | / __ |
\/ dns forwarding proxy \/

Usage:
ctrld [command]
ctrld-client [command]

Available Commands:
run Run the DNS proxy server
Expand All @@ -128,12 +109,12 @@ Available Commands:
log Manage runtime debug logs

Flags:
-h, --help help for ctrld
-h, --help help for ctrld-client
-s, --silent do not write any log output
-v, --verbose count verbose log output, "-v" basic logging, "-vv" debug level logging
--version version for ctrld
-v, --verbose count verbose log output, "-v" basic logging, "-vv" debug logging
--version version for ctrld-client

Use "ctrld [command] --help" for more information about a command.
Use "ctrld-client [command] --help" for more information about a command.
```

## Basic Run Mode
Expand All @@ -143,12 +124,12 @@ This is the most basic way to run `ctrld`, in foreground mode. Unless you alread

Windows (Admin Shell)
```shell
ctrld.exe run
ctrld-client.exe run
```

Linux or Macos
```shell
sudo ctrld run
sudo ctrld-client run
```

You can then run a test query using a DNS client, for example, `dig`:
Expand All @@ -161,25 +142,23 @@ You can then run a test query using a DNS client, for example, `dig`:
If `verify.controld.com` resolves, you're successfully using the default Control D upstream. From here, you can start editing the config file that was generated. To enforce a new config, restart the server.

## Service Mode
This mode will run the application as a background system service on any Windows, MacOS, Linux, FreeBSD distribution or supported router. This will create a generic `ctrld.toml` file in the **C:\ControlD** directory (on Windows) or `/etc/controld/` (almost everywhere else), start the system service, and **configure the listener on all physical network interface**. Service will start on OS boot.

When Control D upstreams are used on a router type device, `ctrld` will [relay your network topology](https://docs.controld.com/docs/device-clients) to Control D (LAN IPs, MAC addresses, and hostnames), and you will be able to see your LAN devices in the web panel, view analytics and apply unique profiles to them.
This mode will run the application as a background system service on any Windows, MacOS, Linux or FreeBSD distribution. This will create a generic `ctrld.toml` file in the **C:\ControlD** directory (on Windows) or `/etc/controld/` (almost everywhere else), start the system service, and **configure the listener on all physical network interface**. Service will start on OS boot.

### Command

Windows (Admin Shell)
```shell
ctrld.exe start
ctrld-client.exe start
```

Linux or Macos
```
sudo ctrld start
sudo ctrld-client start
```

If `ctrld` is not in your system path (you installed it manually), you will need to run the above commands from the directory where you installed `ctrld`.
If `ctrld-client` is not in your system path (you installed it manually), you will need to run the above commands from the directory where you installed it.

In order to stop the service, and restore your DNS to original state, simply run `ctrld stop`. If you wish to stop and uninstall the service permanently, run `ctrld uninstall`.
In order to stop the service, and restore your DNS to original state, simply run `ctrld-client stop`. If you wish to stop and uninstall the service permanently, run `ctrld-client uninstall`.

## Unmanaged Service Mode
This mode functions similarly to the "Service Mode" above except it will simply start a system service and the config defined listeners, but **will not make any changes to any network interfaces**. You can then set the `ctrld` listener(s) IP on the desired network interfaces manually.
Expand All @@ -188,36 +167,36 @@ This mode functions similarly to the "Service Mode" above except it will simply

Windows (Admin Shell)
```shell
ctrld.exe service start
ctrld-client.exe service start
```

Linux or Macos
```shell
sudo ctrld service start
sudo ctrld-client service start
```

# Configuration
## Configuration
`ctrld` can be configured in variety of different ways, which include: API, local config file or via cli launch args.

## API Based Auto Configuration
Application can be started with a specific Control D resolver config, instead of the default one. Simply supply your Resolver ID with a `--cd` flag, when using the `start` (service) mode. In this mode, the application will automatically choose a non-conflicting IP and/or port and configure itself as the upstream to whatever process is running on port 53 (like dnsmasq or Windows DNS Server). This mode is used when the 1 liner installer command from the Control D onboarding guide is executed.
Application can be started with a specific Control D resolver config, instead of the default one. Simply supply your Resolver ID with a `--cd` flag, when using the `start` (service) mode. This mode is used when the 1 liner installer command from the Control D onboarding guide is executed.

The following command will use your own personal Control D Device resolver, and start the application in service mode. Your resolver ID is displayed on the "Show Resolvers" screen for the relevant Control D Endpoint.

Windows (Admin Shell)
```shell
ctrld.exe start --cd abcd1234
ctrld-client.exe start --cd abcd1234
```

Linux or Macos
```shell
sudo ctrld start --cd abcd1234
sudo ctrld-client start --cd abcd1234
```

Once you run the above command, the following things will happen:
- You resolver configuration will be fetched from the API, and config file templated with the resolver data
- Application will start as a service, and keep running (even after reboot) until you run the `stop` or `uninstall` sub-commands
- All physical network interface will be updated to use the listener started by the service or dnsmasq upstream will be switched to `ctrld`
- All physical network interface will be updated to use the listener started by the service
- All DNS queries will be sent to the listener

## Manual Configuration
Expand Down Expand Up @@ -257,7 +236,7 @@ If you're unable to use a config file, `ctrld` can be be supplied with basic con

### Example
```
ctrld run --listen=127.0.0.1:53 --primary_upstream=https://freedns.controld.com/p2 --secondary_upstream=10.0.10.1:53 --domains=*.company.int,very-secure.local --log /path/to/log.log
ctrld-client run --listen=127.0.0.1:53 --primary_upstream=https://freedns.controld.com/p2 --secondary_upstream=10.0.10.1:53 --domains=*.company.int,very-secure.local --log /path/to/log.log
```

The above will start a foreground process and:
Expand All @@ -266,5 +245,69 @@ The above will start a foreground process and:
- Excluding `*.company.int` and `very-secure.local` matching queries, that are forwarded to `10.0.10.1:53`
- Write a debug log to `/path/to/log.log`

## DNS Intercept Mode
When running `ctrld` alongside VPN software, DNS conflicts can cause intermittent failures, bypassed filtering, or configuration loops. DNS Intercept Mode prevents these issues by transparently capturing all DNS traffic on the system and routing it through `ctrld`, without modifying network adapter DNS settings.

DNS Intercept Mode (`dns`) is the default for new installations on macOS and on Windows desktop editions when no mode is selected with `--intercept-mode` or `service.intercept_mode`; existing Standard installations keep interface DNS mode across upgrades. Windows Server and other platforms keep interface DNS mode unless intercept is selected explicitly. See [Intercept Mode Default](docs/intercept-mode-default.md).

### When to Use
Enable DNS Intercept Mode if you:
- Use corporate VPN software (F5, Cisco AnyConnect, Palo Alto GlobalProtect, Zscaler)
- Run overlay networks like Tailscale or WireGuard
- Experience random DNS failures when VPN connects/disconnects
- See gaps in your Control D analytics when VPN is active
- Have endpoint security software that also manages DNS

### Command

Windows (Admin Shell)
```shell
ctrld-client.exe start --intercept-mode dns --cd RESOLVER_ID_HERE
```

macOS
```shell
sudo ctrld-client start --intercept-mode dns --cd RESOLVER_ID_HERE
```

`--intercept-mode dns` automatically detects VPN internal domains and routes them to the VPN's DNS server, while Control D handles everything else. On macOS and Windows desktop editions the flag can be left out: `dns` is the default. On Windows Server it must be given.

To use interface DNS mode instead, on a new or an existing service:

Windows (Admin Shell)
```shell
ctrld-client.exe start --intercept-mode off
```

macOS
```shell
sudo ctrld-client start --intercept-mode off
```

This removes the intercept rules and reverts to standard interface-based DNS configuration. The selection is persisted (`intercept_mode = "off"` in `ctrld.toml`), so it survives restarts and upgrades.

### Platform Support
| Platform | Supported | Mechanism |
|----------|-----------|-----------|
| Windows | ✅ | NRPT (Name Resolution Policy Table) |
| macOS | ✅ | pf (packet filter) redirect |
| Linux | ❌ | Not currently supported |

### Features
- **VPN split routing** — VPN-specific domains are automatically detected and forwarded to the VPN's DNS server
- **Captive portal recovery** — Wi-Fi login pages (hotels, airports, coffee shops) work automatically
- **No network adapter changes** — DNS settings stay untouched, eliminating conflicts entirely
- **Automatic port 53 conflict resolution** — if another process (e.g., `mDNSResponder` on macOS) is already using port 53, `ctrld` automatically listens on a different port. OS-level packet interception redirects all DNS traffic to `ctrld` transparently, so no manual configuration is needed. This only applies to intercept mode.

### Tested VPN Software
- F5 BIG-IP APM
- Cisco AnyConnect
- Palo Alto GlobalProtect
- Tailscale (including Exit Nodes)
- Windscribe
- WireGuard

For more details, see the [DNS Intercept Mode documentation](https://docs.controld.com/docs/dns-intercept).

## Contributing
See [Contribution Guideline](./docs/contributing.md)
4 changes: 0 additions & 4 deletions client_info_darwin.go

This file was deleted.

6 changes: 0 additions & 6 deletions client_info_others.go

This file was deleted.

18 changes: 0 additions & 18 deletions client_info_windows.go

This file was deleted.

36 changes: 36 additions & 0 deletions cmd/cli/ad_domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cli

import (
"strings"
"sync/atomic"
)

// activeDirectoryDomain is the machine's Active Directory domain, recorded when
// addExtraSplitDnsRule detects it (Windows only; empty elsewhere).
//
// Names in it are resolved by the domain controller through upstream.os. The
// OS resolver races the LAN servers against Control D's public resolver unless
// the query is marked as a LAN query, and a split-rule match returns before
// that marking, so without this AD names (DC hostnames, DC-locator SRV
// records carrying the machine name, WPAD) were sent to 76.76.2.0 in plaintext.
var activeDirectoryDomain atomic.Pointer[string]

func setActiveDirectoryDomain(domain string) {
d := strings.TrimSuffix(strings.ToLower(strings.TrimSpace(domain)), ".")
activeDirectoryDomain.Store(&d)
}

// inActiveDirectoryDomain reports whether name is the AD domain or below it.
func inActiveDirectoryDomain(name string) bool {
d := activeDirectoryDomain.Load()
if d == nil || *d == "" {
return false
}
name = strings.TrimSuffix(strings.ToLower(name), ".")
return name == *d || strings.HasSuffix(name, "."+*d)
}

// osUpstreamOnly reports whether upstreams is exactly the OS resolver.
func osUpstreamOnly(upstreams []string) bool {
return len(upstreams) == 1 && upstreams[0] == upstreamOS
}
Loading
Loading