Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

  1. Service management: Installing, uninstalling, starting, and stopping the DNS server as a persistent background system service (via systemd on Linux, launchd on macOS, or SCM on Windows).
  2. Server execution: The actual server boot loop — binding to port 53, registering sockets with the hickory server, 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 Commands subcommand: one of install, uninstall, start, stop, or run. -> See: kinetic-dns/src/bin/kinetic-dns.rs — Lines 21-50

Install flow

When the user runs kinetic-dns-server install:

  1. It calls install_service() which reads the KineticConfig to get the configured dns_port.
  2. It reads the current executable path using env::current_exe().
  3. It creates a ServiceLabel like kinetic-dns using the NETWORK_ID constant from kinetic-core.
  4. It calls <dyn ServiceManager>::native() to automatically detect the host OS’s service manager (systemd, launchd, SCM).
  5. It issues a manager.install(ServiceInstallCtx { ... }) call. This registers the binary with the OS service manager so it auto-starts on reboot.
  6. 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 ~kin prefix is the key. It tells systemd-resolved to only send .kin queries to this specific DNS server, leaving all other traffic untouched.
  • It then restarts systemd-resolved via systemctl 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/kin with the nameserver IP and port.
  • The macOS network stack reads this directory and automatically routes .kin queries to the specified server.
  • Additionally, because macOS’s loopback interface only listens on 127.0.0.1 by default, the binary calls setup_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, restarts systemd-resolved to flush the override.
  • On macOS, teardown_macos_alias() removes the loopback alias by calling ifconfig 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 5354 for 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]:53 for IPv6 and registers both sockets with hickory’s ServerFuture.
  • 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 use sudo for 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 nobody shell, 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 (typically 127.0.0.2). Shared constant to ensure consistency across all Kinetic tools.
  • kinetic_core::constants::NETWORK_ID: Used to construct the ServiceLabel (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 configured dns_port.
  • kinetic_core::shutdown::shutdown_signal(): A shared future that resolves when SIGINT or SIGTERM is received by the process.
  • kinetic_dns::KineticDnsHandler: The struct defined in lib.rs that implements the actual DNS resolution logic.

6. Quick Reference

CommandWhat Happens
kinetic-dns-server installRegisters service + injects OS DNS rules for .kin
kinetic-dns-server startStarts the registered background service
kinetic-dns-server stopStops the background service
kinetic-dns-server uninstallRemoves service + removes OS DNS override rules
kinetic-dns-server runRuns the server in the foreground (no service)
No argumentSame as run
PlatformDNS Override Mechanism
Linux/etc/systemd/resolved.conf.d/kinetic.conf with Domains=~kin
macOS/etc/resolver/kin + lo0 loopback alias
WindowsNRPT 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-specific ServiceManager (systemd/launchd/SCM) without the caller knowing the concrete type.
  • privdrop crate 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.