Encrypted DNS on Windows, without the config file.
Avalonia · .NET 10 · Nord
Encrypted DNS on Windows normally means hand-editing a TOML file, installing a service from a terminal, and hoping you got the endpoint syntax right. ControlDNS is a small desktop app that does all of that for you: pick a resolver, pick a transport, press Apply.
Resolvers
- 15 built-in public resolvers across six providers, plus any custom endpoint
- Every transport the proxy supports: DoH, DoH3, DoT, DoQ, and plain UDP/TCP
- Optional fallback resolver, written into the listener policy so it is genuinely used
- Custom endpoints you apply are remembered and reappear in the list next time
Latency-aware
- Measures round-trip time to every resolver (ICMP, falling back to a TCP handshake for providers that drop pings) and orders the list fastest-first
- Latency shown per row, refreshed on demand
Safe by construction
- Preview shows the exact config that Apply would write
- Dry run loads the pending config into the engine on a scratch port
(
127.0.0.1:15353) and reports whether it was accepted - your live config and service are untouched - A timestamped backup is taken before every Apply, with one-click restore
- Input is validated before it reaches the engine, so a typo surfaces as a message rather than a service that silently refuses to start
No permanent elevation
- ControlDNS runs as a normal user process
- Service state is read straight from the Windows service manager, which needs no elevation, so the status indicator costs nothing and never nags you
- The handful of commands that do need administrator rights are run through an elevated helper on demand - you approve a single UAC prompt for that one action, and the app itself never holds administrator rights
Self-installing
- No engine on the machine? Press Install engine. It downloads into your user profile, so installing needs no administrator rights either
- A ControlDNS-managed engine is always preferred over any copy already on the machine, so the app is never fighting another tool's install
Desktop niceties
- Close to tray instead of exiting
- Pin to tray: drop the title bar entirely and toggle the window from the tray icon
- Maximise is locked - the layout is a fixed narrow column by design
- Nord dark theme throughout
- Activity log of every command with its raw output; copy or export it
| Overview | Resolvers | Settings |
|---|---|---|
| Status, active resolver, one-press toggle | Latency-sorted picker with transport selection | Engine install, tray behaviour, listener, cache |
- Windows 10 or 11
- .NET 10 runtime to run, .NET 10 SDK to build
- A DNS proxy engine - install one from the Settings page, or point ControlDNS
at an existing
ctrld.exe - For Native AOT release publishing: Microsoft Visual C++ (MSVC) platform linker (installed via Visual Studio's Desktop development with C++ workload or Build Tools).
To build and run in Debug mode:
dotnet builddotnet runTo build a standalone Native AOT executable (single-file without .NET dependency):
dotnet publish -c Release- Launch ControlDNS. If no engine is found, the overview page says so.
- Settings > Install engine. No UAC prompt; it installs into your profile.
- DNS. Pick a resolver - the list is already ordered by measured ping - and pick a transport.
- Dry run to confirm the engine accepts the config. Still no UAC prompt.
- Apply. This is the first point a UAC prompt appears, because installing and starting the Windows service requires it.
| Path | Contents |
|---|---|
%AppData%\ControlDNS\settings.json |
GUI settings - the source of truth |
%AppData%\ControlDNS\backups\ |
Timestamped config copies (last 20) |
%AppData%\ControlDNS\dry-run.toml |
Scratch config used by Dry run |
%LocalAppData%\ControlDNS\engine\ |
Managed engine and its live config |
| Path | Contents |
|---|---|
Models/ |
Config shapes: upstreams, protocols, presets, settings, backups |
Services/ |
Engine wrapper, elevation helper, installer, TOML writer, validation, backups, latency probe, status polling |
ViewModels/ |
One view model per page plus the shell |
Views/ |
Avalonia XAML for the shell and the five pages |
Styles/ |
Nord palette resources and control styles |
Converters/ |
Value converters used from XAML |
The tray icon starts hidden. Windows puts new notification-area icons in the overflow flyout. Drag it onto the taskbar once to keep it visible.
The generated config always writes an explicit listener policy so the fallback upstream is honoured rather than ignored:
[listener.0.policy]
name = 'ControlDNS Policy'
networks = [{ 'network.0' = ['upstream.0', 'upstream.1'] }]Dry runs bake the probe port into the scratch config rather than passing
--listen. The engine requires --primary_upstream alongside --listen and
then ignores the config file, which would defeat the purpose of the test.
Copyright (C) 2026 yagizzg. ControlDNS is open source under the GNU General Public License v3.0.
In plain terms: read it, run it, change it, share it. If you distribute ControlDNS or anything built from its code, that work has to be open source under the GPL too - you cannot take this code closed.
If you want to reuse this, please email me first at yagizzci@proton.me. That is a request rather than a licence condition - the GPL does not allow extra restrictions on top - but I would like to know where the code ends up, and I am happy to help.
ControlDNS is a front end. The DNS proxying itself is done by ctrld (MPL-2.0), which is what the Install engine button downloads. ControlDNS generates its configuration, manages its Windows service, and stays out of the query path entirely.