MMotor OS
System

DNS resolver

Name resolution is a standalone service, dns-resolver, reached over bounded synchronous IPC. Its backend is native Rust and depends only on Motor OS networking and filesystem APIs. It runs with the None role.

Request path

Rust application (std::net::ToSocketAddrs, TcpStream::connect("host:port"), ...)
  -> rt.vdso lookup_host
  -> moto-dns: bounded synchronous IPC
  -> dns-resolver service
  -> connected UDP, with TCP fallback
  -> sys-io

Numeric addresses and localhost are answered in the runtime without contacting the service. Other names use a fresh client connection per lookup, so a dead resolver connection is never cached and a restarted service is found by the next call. The service keeps four fixed workers and a versioned, pointer-free request and response format defined in the moto-dns crate (names up to 253 bytes, up to 16 addresses per answer). Because each lookup takes one IPC connection into four workers, more than 32 concurrent lookups are refused with NotConnected; raising this is on the follow-up list.

Configuration

The service reads /system/cfg/libc/hosts first and /system/cfg/libc/resolv.conf second. sys-io owns resolv.conf: before starting services it publishes the DNS servers from the active static device configuration atomically, and it republishes the aggregate whenever a DHCP lease changes. Static servers are declared with dns_servers in sys-net.toml; DHCP-configured devices use the servers from their leases. At most the first two unique nameservers are queried, which keeps one-family lookup latency inside the IPC deadline when both are unavailable.

Transport and validation

Each UDP attempt:

UDP delivery is unreliable, so the backend performs one bounded retransmission, on a new socket with a new transaction ID. NXDOMAIN is final and is not retried. A response with the truncated bit set is repeated over a connected DNS-over-TCP stream using the same query and the same strict parser.

There is no cache. Correct caching requires honoring each record's TTL and a negative-cache policy; an arbitrary service-wide TTL would trade a small speedup for stale answers.

Result contract

The resolver reports one of: success, not found, temporary failure, timeout, out of memory, unsupported family, system failure, resolver failure, invalid request, or busy. The runtime maps these to stable Motor error codes, which surface through std::io::Error. Combined lookups collect IPv4 before IPv6 and then apply the destination-ordering policy: usable addresses first, with an unusable global IPv6 range ranked last so that a VM without IPv6 connectivity does not wait on it.

Testing

dns-resolver --self-test covers numeric and hosts-file lookups, malformed requests, result ordering, restart behavior, live external resolution, and a deterministic loopback DNS fixture that drops the first UDP query, answers the retransmission with a truncated response, and completes the lookup over TCP. The regular test suites never go out to the Internet; only this explicitly separate live check does.