ekDNSHelper

A cross-platform CLI tool that resolves hostnames against DNS servers and writes the resulting IP-to-hostname mappings into the local hosts file. It manages a clearly delimited block within the hosts file so it can cleanly add and remove only its own entries.

By default, ekdns performs smart resolution: it discovers the authoritative nameserver for each hostname through a parallel NS fan-out across local and public resolvers, then queries that NS directly for the freshest answer. Internal hostnames (without zone delegation) fall back to a parallel A-record query across all resolvers.

Built as a single static binary with no runtime dependencies.

Copyright (c) 2026 Emberkom LLC

Features

  • Smart default resolution: parallel NS fan-out → authoritative query → parallel A fallback (no -server flag needed for most use cases)
  • -server local: query only locally configured DNS resolvers (for internal hostnames)
  • -server gateway: query the default gateway as a DNS server
  • -server <ip>[:<port>]: query a specific DNS server (existing explicit mode)
  • Split-horizon conflict detection: warns when the authoritative answer differs from what local resolvers say
  • CNAME chain following (up to 10 hops) with loop detection
  • Context-aware error messages with private TLD hints (.local, .corp, .internal, etc.)
  • Per-query timeout control via -timeout
  • Verbose per-stage resolution trace via -verbose
  • Managed block markers ensure only ekdns's entries are ever modified
  • Automatic backup of the hosts file before every write
  • File-based lock prevents concurrent modifications
  • Cross-platform: Windows, macOS, and Linux

Requirements

  • Go 1.20 toolchain (this project must not use Go 1.21+ features)
  • Administrator / root privileges (required to modify the system hosts file)

Installation

Build from source

# Clone the repository
git clone <repo-url>
cd ekdns

# Build for the current platform
.\build.ps1

# Or build optimized release binaries for all platforms
.\build.ps1 -Release -CrossCompile

The default build output is build\ekdns.exe (Windows). Cross-compiled binaries are named build\ekdns-<os>-<arch>[.exe].

Release packages

# Build release binaries and create distributable archives
.\build.ps1 -Release -CrossCompile -Package

Archives are written to releases\ekdns-<version>-<os>-<arch>.zip (Windows) or .tar.gz (macOS/Linux).

Usage

Note: ekdns must be run with elevated privileges (Run as Administrator on Windows, sudo on macOS/Linux) because it modifies the system hosts file.

Add entries

Resolve hostnames using smart default resolution and add to hosts file:

ekdns add -host www.example.com

Specify a resolution mode:

# Use only local DNS resolvers (no public fallback)
ekdns add -host internal.corp -server local

# Use the default gateway as DNS
ekdns add -host www.example.com -server gateway

# Use a specific DNS server
ekdns add -host www.example.com -server 8.8.8.8

# Use a specific DNS server on a non-standard port
ekdns add -host www.example.com -server 10.0.0.53:5353

Add multiple hostnames at once:

ekdns add -host "host1.example.com,host2.example.com"

Use timeout and verbose options:

ekdns add -host www.example.com -timeout 5 -verbose

Delete entries

Remove a specific hostname:

ekdns delete -host hostname.example.com

Remove multiple hostnames:

ekdns delete -host "host1.example.com,host2.example.com"

Remove all managed entries:

ekdns delete all

Short aliases: a for add, d / del for delete

Command reference

Command Flag Required Default Description
add -host Yes Comma-separated hostnames to resolve and add
add -server No smart default Resolution mode: omit, local, gateway, <ip>, or <ip>:<port>
add -timeout No 3 Per-query DNS timeout in seconds
add -verbose No false Emit per-stage resolution trace to stderr
delete -host Yes (unless all) Comma-separated hostnames to remove
delete all Removes every entry ekdns has written

Exit codes

Code Meaning
0 Success
1 Error or partial failure (e.g. one hostname failed to resolve)

Resolution Modes

Smart Default (no -server flag)

When -server is omitted, ekdns performs multi-stage authoritative resolution:

  1. Discover local DNS resolvers and default gateway from OS network configuration.
  2. Build resolver pool: local resolvers + hardcoded bootstrap set (1.1.1.1, 8.8.8.8, etc.), deduplicated.
  3. Stage 1 — NS Fan-out: Query NS records for every label level of the hostname across all resolvers simultaneously (e.g., www.example.com and example.com × all resolvers in parallel).
  4. Select authority: The most-specific zone with NS records wins.
  5. Stage 2 — Authoritative Query: Resolve the NS hostname to an IP, then send a non-recursive A query directly to that nameserver.
  6. Stage 2.5 — Split-Horizon Check: Concurrently with Stage 2, query local resolvers for the same hostname. If local and authoritative answers differ, report a conflict error — neither IP is written.
  7. CNAME Following: If Stage 2 returns a CNAME, restart from Stage 1 for the target (max 10 hops).
  8. Stage 3 — Parallel A Fallback: If no NS records were found at any level (internal hosts without zone delegation), send A queries to all resolvers simultaneously and take the first successful response.
# Example: smart default resolution with verbose trace
ekdns add -host www.example.com -verbose

Verbose output:

[dns] Resolver pool: [10.26.1.1, 1.1.1.1, 8.8.8.8, 1.0.0.1, 8.8.4.4, 9.9.9.9, 208.67.222.222]
[dns] Stage 1: NS fan-out for www.example.com (2 levels × 7 resolvers = 14 queries)
[dns]   example.com NS: ns1.example.com., ns2.example.com. (via 8.8.8.8, 1.1.1.1)
[dns]   www.example.com NS: (none)
[dns] Selected authority: example.com → ns1.example.com., ns2.example.com.
[dns] Stage 2: Querying ns1.example.com. (93.184.216.34) for www.example.com A (RD=0)
[dns] Result: 93.184.216.34

Local Mode (-server local)

Queries only the locally configured DNS resolvers (from OS network configuration) in priority order. No fallback to public resolvers.

ekdns add -host internal.client.local -server local

Fails with an error if all local resolvers are unreachable or no local resolvers are found.

Gateway Mode (-server gateway)

Discovers the default gateway IP and uses it as the DNS server. No fallback.

ekdns add -host www.example.com -server gateway

Explicit Server (-server <ip>[:<port>])

Queries the specified DNS server directly. Port defaults to 53 if omitted.

ekdns add -host www.example.com -server 8.8.8.8
ekdns add -host www.example.com -server 10.0.0.53:5353

Error Messages

Private TLD hint

When a hostname with a private TLD (.local, .corp, .internal, .lan, .home, .private) cannot be resolved:

Error: failed to resolve myservice.client.local: ...
  Hint: the hostname uses a private TLD (.local). Try specifying an internal DNS server:
        ekdns add -host myservice.client.local -server <internal-dns-ip>

Split-horizon conflict

When the authoritative answer differs from the local resolver's answer:

Error: conflicting DNS answers for app.acme.com
  Authoritative (ns1.acme.com): 203.0.113.50
  Local resolver (10.26.1.1):   10.0.5.100
  The hostname resolves to different IPs depending on the DNS source.
  Use -server local to trust your internal DNS, or -server <ip> to choose explicitly.

CNAME loop

Error: CNAME chain depth exceeded for www.example.com (max 10 hops): probable CNAME loop or misconfigured zone

How it works

ekdns wraps its entries between two marker lines in the hosts file:

# ekDNSHelper <<-> START CONFIG
192.0.2.1	hostname.example.com
# ekDNSHelper <->> END CONFIG

Only lines within this managed block are ever modified or removed. The rest of the hosts file is preserved exactly as-is.

Before every write, a backup of the hosts file is saved to the directory containing the ekdns executable.

Hosts file locations

Platform Path
Windows %SystemRoot%\System32\drivers\etc\hosts
macOS /etc/hosts
Linux /etc/hosts

Network discovery (per platform)

When using smart default, local, or gateway modes, ekdns discovers your network configuration via OS utilities:

Platform Method
Windows netsh interface ipv4 show route + netsh interface ipv4 show dnsservers
Linux ip route show default + /etc/resolv.conf (with resolvectl fallback for systemd-resolved)
macOS route -n get default + scutil --dns

Project structure

main.go              CLI entry point (add / delete commands, flag parsing)
build.ps1            Build & packaging script
go.mod               Module definition (Go 1.20, golang.org/x/net)
hostfile/
  hostfile.go        Hosts file parsing, DNSEntry type, add/remove helpers
  managed.go         Managed block read/write logic
  filesystem.go      FileSystem interface + OSFileSystem implementation
  hostfile_test.go
  managed_test.go
lockfile/
  lockfile.go        File-based mutex to prevent concurrent modifications
  lockfile_test.go
platform/
  platform.go             Package doc — GetHostsFilePath()
  platform_windows.go     Windows: GetHostsFilePath()
  platform_darwin.go      macOS: GetHostsFilePath()
  platform_linux.go       Linux: GetHostsFilePath()
  network.go              NetworkInfo type, NetworkDiscoverer interface, FakeNetworkDiscoverer
  network_windows.go      Windows: WindowsNetworkDiscoverer (netsh)
  network_darwin.go       macOS: DarwinNetworkDiscoverer (route + scutil)
  network_linux.go        Linux: LinuxNetworkDiscoverer (ip route + resolv.conf)
  discoverer_windows.go   Windows: NewNetworkDiscoverer() constructor
  discoverer_darwin.go    macOS: NewNetworkDiscoverer() constructor
  discoverer_linux.go     Linux: NewNetworkDiscoverer() constructor
  platform_test.go
  network_test.go
resolver/
  resolver.go        Existing single-server A-record lookup (used internally)
  parse.go           ServerMode, QueryConfig, ParseServerFlag(), ExtractLabelLevels()
  parse_test.go
  transport.go       Shared UDP query transport (UDPQuery)
  transport_test.go
  pool.go            BuildResolverPool() — constructs resolver list per mode
  pool_test.go
  authority.go       ParallelNSFanOut(), SelectAuthoritativeNS(), QueryAuthoritative()
  authority_test.go
  fallback.go        ParallelAFallback() — Stage 3 parallel A fallback
  fallback_test.go
  splithorizon.go    CheckSplitHorizon() — Stage 2.5 cross-check
  splithorizon_test.go
  modes.go           Resolve() dispatcher and mode implementations
  modes_test.go
  resolver_test.go

Development

Running tests

go1.20 test ./...

Running tests with coverage

go1.20 test -cover ./...

Vetting code

go1.20 vet ./...

Dependencies

No other third-party dependencies. Standard library first.

License

Copyright (c) 2024 Emberkom LLC. All rights reserved.

S
Description
No description provided
Readme
24 MiB
Languages
Go 75.1%
PowerShell 24.9%