Binary Entrypoint: Service Management and Boot Loop
File: kinetic-dns/src/bin/kinetic-dns.rs
Crate: kinetic-dns | Stage: 10
Reading Time: 30 minutes
1. What Is This?
This is the actual executable binary of kinetic-dns. When a user runs kinetic-dns-server run or when the OS service manager starts the daemon, this file is what gets executed.
It does two completely separate jobs:
- Service management: Installing, uninstalling, starting, and stopping the DNS server as a persistent background system service (via
systemdon Linux,launchdon macOS, or SCM on Windows). - Server execution: The actual server boot loop — binding to port 53, registering sockets with the
hickoryserver, dropping OS privileges, and then running until a shutdown signal arrives.
It also contains the OS-level DNS configuration injection, which is the critical step that tells the operating system “for .kin domains, use this DNS server instead of your normal one.”
2. Why Kinetic Needs This
A standard OS won’t send .kin queries anywhere useful. By default, if the user’s browser queries for saif.kin, the OS sends it to the configured system DNS resolver (e.g., 8.8.8.8). That resolver has no idea what .kin is and returns NXDOMAIN.
To intercept these queries before they ever leave the machine, the DNS server must be injected as a domain-specific override into the OS resolver configuration. This binary does exactly that — it hooks into systemd-resolved on Linux, /etc/resolver/ on macOS, or the NRPT policy engine on Windows to redirect only .kin queries to itself.
Important
Without this OS-level hook, the entire DNS resolution pipeline is unreachable.
3. How It Works
The CLI structure
The binary uses clap to parse command-line arguments. The Cli struct accepts:
--api-url: URL to the running kinetic-daemon (default:http://127.0.0.1:16000).--dns-port: UDP port to bind (default:53).- A
Commandssubcommand: one ofinstall,uninstall,start,stop, orrun. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 21-50
Install flow
When the user runs kinetic-dns-server install:
- It calls
install_service()which reads theKineticConfigto get the configureddns_port. - It reads the current executable path using
env::current_exe(). - It creates a
ServiceLabellikekinetic-dnsusing theNETWORK_IDconstant fromkinetic-core. - It calls
<dyn ServiceManager>::native()to automatically detect the host OS’s service manager (systemd, launchd, SCM). - It issues a
manager.install(ServiceInstallCtx { ... })call. This registers the binary with the OS service manager so it auto-starts on reboot. - Immediately after installation,
configure_os_dns()is called to inject the DNS override rules into the OS. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 146-178
OS DNS configuration injection
The configure_os_dns() function inspects std::env::consts::OS at runtime to determine the platform and injects the configuration differently for each:
Linux (systemd-resolved):
- Creates a file at
/etc/systemd/resolved.conf.d/<NETWORK_ID>.conf. - The content is:
[Resolve] DNS=127.0.0.2:53 Domains=~kin - The
~kinprefix is the key. It tellssystemd-resolvedto only send.kinqueries to this specific DNS server, leaving all other traffic untouched. - It then restarts
systemd-resolvedviasystemctl restart systemd-resolved. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 80-93
macOS (/etc/resolver/):
- macOS uses a per-TLD resolver directory. Kinetic writes
/etc/resolver/kinwith the nameserver IP and port. - The macOS network stack reads this directory and automatically routes
.kinqueries to the specified server. - Additionally, because macOS’s loopback interface only listens on
127.0.0.1by default, the binary callssetup_macos_alias()to create a loopback alias IP (ifconfig lo0 alias 127.0.0.2 ...), ensuring the DNS server can bind without conflicting with other services. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 52-103
Windows (NRPT):
- Uses PowerShell to call
Add-DnsClientNrptRule -Namespace '.kin' -NameServers '127.0.0.2'. - NRPT (Name Resolution Policy Table) is a Windows feature specifically designed for domain-specific resolver overrides, making it the correct tool for this job.
-> See:
kinetic-dns/src/bin/kinetic-dns.rs— Lines 104-115
Uninstall flow
uninstall_service() reverses the install:
- Calls
manager.uninstall()to deregister from the OS service manager. - Calls
remove_os_dns()which deletes the config files and on Linux, restartssystemd-resolvedto flush the override. - On macOS,
teardown_macos_alias()removes the loopback alias by callingifconfig lo0 -alias <ip>. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 119-144
The actual server run loop (run_server)
When the command is run (or no subcommand is given), the binary enters run_server():
Step 1: Logging setup
It initializes the tracing subscriber with a logging level pulled from the RUST_LOG environment variable, falling back to info.
Step 2: Handler creation
Creates a KineticDnsHandler instance (defined in lib.rs), passing it:
- The daemon API URL (e.g.,
http://127.0.0.1:16000) - A shared
Arc<RwLock<HashSet<String>>>for Atlas TLDs (initially empty) - Port
5354for the Atlas resolver -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 221-225
Step 3: Socket binding (with fallback)
The server attempts to bind a tokio::net::UdpSocket to <LOCAL_BIND_IP>:53.
- If it succeeds, it also tries to bind
[::1]:53for IPv6 and registers both sockets withhickory’sServerFuture. - If binding to port 53 fails (because the process isn’t running as root), it falls back to port 5353 (or
dns_port + 1000), printing a warning to usesudofor native interception. - Both the primary and fallback sockets register on IPv4 and IPv6.
-> See:
kinetic-dns/src/bin/kinetic-dns.rs— Lines 233-337
Step 4: Privilege dropping
After successfully binding to the privileged port 53 (which requires root), the process immediately drops its root privileges using the privdrop crate:
#![allow(unused)]
fn main() {
privdrop::PrivDrop::default().user("nobody").group("nogroup").apply()
}
Important
This is standard security practice: acquire the privileged resource (the port), then surrender the privileges. If the process is subsequently exploited, the attacker only gains a
nobodyshell, not root. -> See:kinetic-dns/src/bin/kinetic-dns.rs— Lines 244-257
Step 5: Run and shutdown
server.block_until_done() runs the Hickory server. The code uses tokio::select! to simultaneously watch for either:
- The server completing (error or normal exit).
- A shutdown signal from
kinetic_core::shutdown::shutdown_signal().
Whichever arrives first stops the other branch. On macOS, teardown_macos_alias() is called after the loop ends to clean up the loopback alias.
-> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 259-271
4. Key Pieces
configure_os_dns(dns_port: u16) -> Result<()>
The most important function for getting .kin to actually work on the host machine.
Does nothing to the internet DNS. Injects a narrow, domain-specific override targeted exclusively at .kin.
Returns an error if writing to system directories fails (e.g., lack of root permission).
-> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 74-117
setup_macos_alias(ip: &str) and teardown_macos_alias(ip: &str)
macOS-only functions wrapped in #[cfg(target_os = "macos")] so they are completely stripped from Linux and Windows builds.
They shell out to ifconfig lo0 alias <ip> and ifconfig lo0 -alias <ip> respectively.
Required because the /etc/resolver/ mechanism needs to reach the DNS server on a specific IP, which must exist on the local network interface.
-> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 52-72
install_service() / uninstall_service()
Wrappers around the service_manager crate that abstract the differences between systemd unit files, launchd plist files, and Windows SCM entries behind a common API.
-> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 146-208
run_server(api_url: String, dns_port: u16) -> Result<()>
The actual async loop that keeps the DNS server alive. This is the function called by both start (background) and run (foreground) commands.
-> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 210-341
5. Cross-Crate Connections
kinetic_core::constants::TLD: The registered Kinetic TLD (.kin). Used when writing DNS override configs so the correct domain suffix is always registered.kinetic_core::constants::LOCAL_BIND_IP: The IP the server binds to (typically127.0.0.2). Shared constant to ensure consistency across all Kinetic tools.kinetic_core::constants::NETWORK_ID: Used to construct theServiceLabel(e.g.,kinetic-dns) and file paths (e.g.,kinetic.conf).kinetic_core::config::KineticConfig::load(): Loads the user’s local TOML config file to get the configureddns_port.kinetic_core::shutdown::shutdown_signal(): A shared future that resolves whenSIGINTorSIGTERMis received by the process.kinetic_dns::KineticDnsHandler: The struct defined inlib.rsthat implements the actual DNS resolution logic.
6. Quick Reference
| Command | What Happens |
|---|---|
kinetic-dns-server install | Registers service + injects OS DNS rules for .kin |
kinetic-dns-server start | Starts the registered background service |
kinetic-dns-server stop | Stops the background service |
kinetic-dns-server uninstall | Removes service + removes OS DNS override rules |
kinetic-dns-server run | Runs the server in the foreground (no service) |
| No argument | Same as run |
| Platform | DNS Override Mechanism |
|---|---|
| Linux | /etc/systemd/resolved.conf.d/kinetic.conf with Domains=~kin |
| macOS | /etc/resolver/kin + lo0 loopback alias |
| Windows | NRPT policy via PowerShell Add-DnsClientNrptRule |
Bind IP: kinetic_core::constants::LOCAL_BIND_IP (typically 127.0.0.2)
Default Port: 53 with automatic fallback to 5353
Privilege drop: Immediately after binding port 53, drops to nobody:nogroup
7. Open Questions
- Windows non-root binding: On Windows, binding to port 53 also requires elevated privileges but there is no equivalent of
privdrop. The current implementation doesn’t explicitly handle this case. - Reload on config change: The service doesn’t hot-reload when the user changes the port in
KineticConfig. It must be reinstalled. - IPv6 loopback alias on macOS: Only IPv4 loopback alias is created. If a user has IPv6-only networking, this may not work.
- Atlas TLD hot-injection: The Atlas TLD set is initialized empty at startup. The mechanism for the daemon to push new TLDs to the running DNS server is not documented here and likely uses an HTTP endpoint or IPC.
8. Rust Concepts
<dyn ServiceManager>::native(): Calls a trait object’s associated function using Fully Qualified Syntax. Returns a boxed platform-specificServiceManager(systemd/launchd/SCM) without the caller knowing the concrete type.privdropcrate and POSIX privilege dropping: A critical security pattern. Bind to a privileged port as root, then immediately drop to an unprivileged user so that any runtime exploit can’t escalate.#[cfg(target_os = "macos")]: Strips macOS-only code from Linux and Windows binaries at compile time, not at runtime.tokio::select!with shutdown signal: Two async futures race — the server loop and the OS signal watcher. Whichever wins cancels the other, ensuring graceful shutdown without zombie threads.