8.2 KiB
8.2 KiB
CLI Contract: dns-helper (002 — Server Resolution Modes)
Feature Branch: 002-server-resolution-modes
Date: 2026-03-04
Extends: 001 CLI Contract
Changes from 001
- The
-serverflag onaddbecomes optional (was required). - The
-serverflag gains keyword values:local,gateway. - New flags:
-timeout,-verboseon every command that performs DNS resolution. - Error messages gain context-aware hints for private TLDs.
- Split-horizon conflict detection: in default mode, cross-checks authoritative answer against local resolvers.
Subcommand: add (aliases: a)
Synopsis
dns-helper add -host <hostnames> [-server <mode>] [-timeout <seconds>] [-verbose]
Flags
| Flag | Required | Type | Default | Description |
|---|---|---|---|---|
-host |
Yes | string | — | Comma-separated list of hostnames to resolve and add |
-server |
No | string | (smart default) | Resolution mode: omit for smart default, local, gateway, or IP/IP:port |
-timeout |
No | int | 3 | Per-query DNS timeout in seconds |
-verbose |
No | bool | false | Emit per-stage resolution trace to stderr |
-server Flag Values
| Value | Behavior |
|---|---|
| (omitted) | Smart default: parallel NS fan-out across local + public resolvers, authoritative query, parallel A fallback. See FR-002. |
local |
Query only locally configured DNS resolvers in priority order. No public fallback. See FR-003. |
gateway |
Query the default gateway IP as DNS server. No fallback. See FR-004. |
<ip> |
Query the specified IP on port 53. Existing behavior, unchanged. See FR-005. |
<ip>:<port> |
Query the specified IP on the given port. Port must be 1–65535. See FR-005. |
-server Validation
<ip>:<port>where port is 0 or >65535: Immediate usage error (no DNS queries issued).<ip>where IP is not a valid IPv4 address and not a recognized keyword: Immediate usage error.- Recognized keywords are case-insensitive:
local,LOCAL,Localall accepted.
-timeout Validation
- Must be a positive integer. Zero or negative: Immediate usage error.
- Applied to every individual DNS query (NS fan-out, authoritative, fallback, local, gateway).
Behavior — Smart Default (no -server)
- Discover local DNS resolvers from OS network configuration.
- Build resolver pool: local resolvers + hardcoded bootstrap set (deduplicated).
- Extract all label levels from hostname (e.g.,
www.example.com→["www.example.com", "example.com"]). - Stage 1: Parallel NS fan-out — query NS for every label level across every resolver in the pool simultaneously.
- Select the most-specific NS delegation (longest label level with NS records).
- Stage 2: Resolve one of the NS hostnames to an IP, then query that NS directly for the A record with recursion disabled.
- If Stage 2 returns a CNAME, restart from Stage 1 for the CNAME target domain (max 10 hops).
- Stage 2.5: If Stage 2 returns A records and local resolvers are available, cross-check by querying local resolvers for the same hostname. If local resolvers return a different IP → conflict error (neither IP written). If same IP, NXDOMAIN, or failure → proceed normally.
- If Stage 1 returns no NS records at any level: Stage 3 — parallel A query across all resolvers, take first success.
- If all stages fail, report error with private TLD hint if applicable.
Behavior — Local Mode (-server local)
- Discover local DNS resolvers from OS network configuration.
- Query each resolver in priority order with standard A query.
- Return first successful result.
- If all fail: error. No fallback to public resolvers (FR-026).
Behavior — Gateway Mode (-server gateway)
- Discover default gateway IP from OS network configuration.
- Send DNS query to gateway.
- If gateway doesn't respond: error. No fallback (FR-027).
Output — Verbose Mode (stderr only)
When -verbose is provided, emit to stderr (FR-030/FR-031):
[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.
[dns] Stage 2: Querying ns1.example.com. (93.184.216.34) for www.example.com A (RD=0)
[dns] Result: 93.184.216.34
Verbose output for fallback:
[dns] Stage 1: No NS records found at any level
[dns] Stage 3: Parallel A fallback for myservice.client.local (7 resolvers)
[dns] 10.26.1.1 → 10.0.5.100
[dns] 1.1.1.1 → NXDOMAIN
[dns] 8.8.8.8 → NXDOMAIN
[dns] Result: 10.0.5.100 (via 10.26.1.1)
Verbose output for split-horizon cross-check:
[dns] Stage 2.5: Split-horizon cross-check against local resolvers [10.26.1.1]
[dns] 10.26.1.1 → 10.0.5.100 (differs from authoritative 203.0.113.50)
[dns] CONFLICT: authoritative and local resolvers disagree
Verbose output when cross-check passes:
[dns] Stage 2.5: Split-horizon cross-check against local resolvers [10.26.1.1]
[dns] 10.26.1.1 → 93.184.216.34 (matches authoritative)
[dns] No conflict detected
Output — Error Messages
Private TLD with total failure (FR-025):
Error: failed to resolve myservice.client.local: no DNS server could resolve this hostname
Hint: the hostname uses a private TLD (.local). Try specifying an internal DNS server:
dns-helper add -host myservice.client.local -server <internal-dns-ip>
Local mode, all resolvers unreachable (FR-026):
Error: failed to resolve myhost.example.com using local resolvers: all local DNS servers are unreachable
Local resolvers tried: 10.26.1.1, 10.26.1.2
Gateway mode, no response (FR-027):
Error: failed to resolve myhost.example.com using gateway: gateway 192.168.1.1 did not respond to DNS query
CNAME loop (FR-019):
Error: CNAME chain depth exceeded for www.example.com (max 10 hops): probable CNAME loop or misconfigured zone
Invalid port:
Error: invalid -server value "10.0.0.53:0": port must be between 1 and 65535
Split-horizon conflict (FR-034/FR-035):
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.
Exit Codes
Unchanged from 001:
| Code | Meaning |
|---|---|
| 0 | All hostnames resolved and written successfully |
| 1 | Any error: validation failure, DNS failure, file write failure, partial resolution |
Subcommand: delete
No changes from 001. The delete subcommand does not perform DNS resolution and is unaffected by this feature.
Usage Help
Updated to reflect new flag options:
This utility will update the local hosts file with DNS entries obtained by the specified DNS server.
Usage: dns-helper [add|delete] -host hostname [-server dns.example.com] [-timeout seconds] [-verbose]
Example:
dns-helper add -host xyz.acme.com -server dns.example.com
This will use dns.example.com to find and add all IP addresses for xyz.acme.com to the local hosts file.
dns-helper add -host xyz.acme.com
This will use smart resolution to find the authoritative IP for xyz.acme.com.
dns-helper add -host internal.corp -server local
This will use only locally configured DNS servers to resolve internal.corp.
dns-helper add -host www.example.com -server gateway
This will use the default gateway as the DNS server.
dns-helper delete -host hostname.example.com
This will delete all entries for hostname.example.com from the local hosts file.
dns-helper delete all
This will delete all entries from the local hosts file that were added by this utility.
Note: Adding a hostname will first remove all entries in the hosts file that match the same hostname.
This utility will only remove entries from the hosts file that it added.