Expand description
osdns provides transactional, ownership-safe control over host
operating-system DNS configuration on Linux, Windows, and macOS.
It is intended for VPN clients, mesh networks, local DNS proxies, tunnels, security agents, and other software that must modify the host resolver without taking ownership of unrelated system state.
osdns is not a DNS resolver, DNS server, or DNS protocol library. It
configures the operating system’s resolver; it does not implement DNS
itself.
§Ownership and incarnation safety
Refuse mutation when the backend’s available ownership or incarnation evidence no longer establishes a safe target.
DNS configuration is shared mutable state. DHCP clients, NetworkManager, systemd-resolved, other VPN software, administrators, and device-management tooling may change it at any time.
Every mutation belongs to an explicit owner and Lease, is journaled
before it happens, and is restored only while the backend can still
establish that the current state is the one this lease produced. Backends
with OwnershipIdentity::Durable check a generation or version.
Backends with OwnershipIdentity::BestEffort compare DNS values and
then write; another actor can win the gap between those steps.
Capabilities::mutation_guard is separate: it is whether the write
itself can be refused when the expected state has already changed.
Capabilities::resource_binding reports whether resource identity is
native-guarded or only checked immediately before a native API that still
permits a final selector-reuse race.
§Basic usage
use osdns::{DnsConfig, DnsManager, DnsScope, InterfaceSelector};
let manager = DnsManager::builder()
.owner("io.example.agent")
.build()?;
let config = DnsConfig::builder(DnsScope::Interface(InterfaceSelector::Default))
.nameserver("127.0.0.1".parse().unwrap())
.build()?;
manager.validate(&config)?;
let lease = manager.apply(&config)?;
// The configuration stays in effect while the lease is alive.
lease.restore()?;§Leases
DnsManager::apply returns a Lease. The lease owns every OS resource
covered by that operation — including resources where the desired state
was already in effect, which still get journal records, live state, and
reconciliation — and holds the corresponding inter-process locks for its
lifetime. Inter-process locks live in a global system location
independent of journal storage, so custom state directories never create
private ownership universes.
Lease::restore is the canonical way to end a lease. Dropping a lease
performs best-effort restoration, but correctness never depends on Drop:
a crashed process is recovered through
DnsManager::recover_stale.
A live lease can move to a new desired configuration with
Lease::update without releasing ownership. An update cannot silently
change the set of owned resources.
§Safe restoration
Restoration overwrites a resource only while the lease still owns it.
With OwnershipIdentity::Durable, that means a backend-issued identity
still names the applied state. With OwnershipIdentity::BestEffort,
the backend compares DNS values and then writes; that sequence is not
atomic. Capabilities::mutation_guard reports whether a write can be
refused when the expected generation no longer matches. A state that
merely matches the desired configuration proves nothing by itself.
§Crash recovery
Mutations are backed by a durable journal. The transaction order is:
capture -> write Prepared -> fsync -> apply -> read back -> verify
-> write Applied -> fsyncA process crash may release an OS lock without removing its journal.
DnsManager::recover_stale inspects records left behind by crashed or
exited processes and recovers them where it is safe to do so. Recovery
never guesses ownership: only an applied snapshot the backend still
considers ours, or the original state, authorizes action. In particular, an unverified
Prepared record whose current state merely matches the desired
configuration proves nothing — the crash may predate the mutation while
an external actor independently produced that state — so the resource is
reported as RecoveryOutcome::ExternalConflict and left untouched.
Corrupt current-format journals fail closed with Error::JournalCorrupt.
Incompatible format versions fail with
Error::UnsupportedJournalVersion. Pre-v1 journal state is not migrated;
clear the old state directory before upgrading.
§Validation guarantee
DnsManager::validate success means the backend can faithfully
represent every explicitly requested semantic. A backend never silently
ignores a requested field: unsupported semantics fail with
Error::Unsupported before any lock, journal write, or OS mutation.
The pipeline is structural validation, then generic capability checks,
then backend-specific semantic validation.
§Optional fields
None means preserve / leave unspecified, never implicitly false or
empty. In particular, default_route = None preserves the current
default-route value on every backend; only Some(true) / Some(false)
may change it.
§Cooperative vs Enforce
ConflictPolicy::Cooperative (the default) does not intentionally
overwrite state that its backend can identify as externally changed;
conflicts are surfaced to the lease owner. Detection strength and any
remaining native race are described by Capabilities.
ConflictPolicy::Enforce is for active VPN, mesh, and tunnel agents
and actually guarantees active reconciliation without requiring a public
DnsManager::watch subscription. The first active lease starts the
internal native watch and reconciler; the last lease ending stops them.
External changes to resources owned by a live lease are reconciled: the
reconciler waits for stable authoritative state, rebases the lease onto
the new external base, and reapplies the desired overlay
transactionally. Restoring a rebased lease returns to the new external
base, not the pre-lease state. Backends without watch support fail
Enforce lease creation with Error::Unsupported instead of silently
behaving cooperatively. DnsManager::watch remains a pure
observability subscription.
§Update transactions
Lease::update moves every owned resource as one logical transaction:
either all resources reach the new configuration or all are rolled back
to their immediately previous applied state with journals restored. A
valid configuration that resolves to a different resource set than the
lease owns fails with Error::UpdateRequiresRebind; restore or abandon
the lease and apply fresh.
§Split DNS
nameservers are the resolver endpoints owned by the configuration;
routing_domains are the names that should route to those endpoints.
When routing domains are non-empty and default_route != Some(true),
unrelated DNS remains outside the overlay wherever the backend supports
true split DNS. Backends follow ownership minimization: a split-only
configuration owns only the scoped resources needed to express it (on
macOS, only /etc/resolver/<domain> files, leaving the service DNS
state untouched).
Routing domains are part of the platform-neutral configuration model
(see DnsConfigBuilder::routing_domain).
The mechanism depends on the active backend: systemd-resolved routing
domains on Linux, NetworkManager DNS routing where supported (the root
wildcard is the canonical ~.), NRPT rules on Windows, and scoped
/etc/resolver/<domain> files on macOS.
Configurations a backend cannot represent are rejected with
Error::Unsupported before any mutation. Use
DnsManager::capabilities to probe support at runtime.
§Platform and backend differences
Linux selects among systemd-resolved (per-link DNS and routing domains),
NetworkManager (per-interface DNS), resolvconf/openresolv (owner-tagged
global records), and direct /etc/resolv.conf manipulation, based on
which component actually owns DNS state on the host.
Windows uses the modern IP Helper APIs for per-interface IPv4/IPv6 settings and the Name Resolution Policy Table (NRPT) for split DNS, with native IP Helper and registry notifications for watching. Windows has no global DNS scope. Requires Windows 10 build 19041 or later.
macOS uses SystemConfiguration for per-service DNS and scoped
/etc/resolver/<domain> files for split DNS, with SCDynamicStore and
FSEvents notifications for watching.
Capabilities is the authoritative runtime description of what the
active backend guarantees. Never assume two backends behave identically.
§Privileges
Changing system DNS configuration generally requires elevated privileges.
osdns never attempts privilege escalation. Insufficient permissions are
reported as Error::RequiresPrivilege; the caller is responsible for
running with appropriate OS privileges.
§Runtime model
osdns has no async runtime dependency and does not require Tokio or
async-std. Configuration changes are synchronous control-plane operations
using native blocking APIs. Native watcher threads are started for
ConflictPolicy::Enforce leases (first active lease to last lease end)
and for each DnsManager::watch subscription.
§Safety and security limitations
osdnsnever performs privilege escalation.- Filesystem and registry resources are ownership-controlled: files, rules, and records not demonstrably ours are never overwritten or deleted.
- Corrupt or unknown journal state fails closed; no mutation is attempted.
- Unsafe code is isolated to platform FFI modules and justified with
SAFETY:comments. - DNS configuration alone does not enforce packet routing and is not DNS leak prevention. Applications requiring traffic isolation must separately control routing and firewall policy.
Re-exports§
pub use capability::BackendKind;pub use capability::Capabilities;pub use capability::MutationGuard;pub use capability::OwnershipIdentity;pub use capability::ResourceBinding;pub use config::DnsConfig;pub use config::DnsConfigBuilder;pub use config::DnsScope;pub use config::InterfaceSelector;pub use error::ConflictReason;pub use error::Error;pub use error::Result;pub use interface::InterfaceInfo;pub use lease::Lease;pub use lease::RestoreFailure;pub use manager::ConflictPolicy;pub use manager::DnsManager;pub use manager::DnsManagerBuilder;pub use manager::RecoveryOutcome;pub use normalize::DnsSuffix;pub use ownership::ResourceId;pub use watch::DnsEvent;pub use watch::WatchCallback;pub use watch::WatchHandle;
Modules§
- capability
- Backend capability model: what each platform backend can guarantee.
- config
- Platform-neutral DNS configuration model with validated builders.
- error
- The typed error model.
- interface
- Network interface information.
- lease
- Leases: exclusive, transactional ownership over DNS state.
- manager
- The
DnsManagerentry point and builder. - normalize
DnsSuffixnormalization and the normalized configuration form.- ownership
- Resource identifiers and inter-process resource locking.
- testing
- Testing utilities for applications and tests built on
osdns. - watch
- Watch events and handles.