# Quickstart: Smart DNS Server Resolution Modes **Feature Branch**: `002-server-resolution-modes` **Date**: 2026-03-04 --- ## Prerequisites - Go 1.20 toolchain installed and on `PATH` - Repository cloned and on branch `002-server-resolution-modes` - `go mod tidy` run successfully ## Build ```powershell # From repository root go build -o dns-helper.exe . ``` Cross-compile (existing build.ps1 pattern): ```powershell .\build.ps1 ``` ## Run Tests ```powershell go test ./... -v ``` ## TDD Workflow This feature follows the Red-Green-Refactor cycle mandated by Constitution Principle VI. ### Order of Implementation The recommended implementation order (each step follows TDD): 1. **`resolver/parse.go`** — `ParseServerFlag()` and `ExtractLabelLevels()` (pure functions, no I/O) 2. **`platform/network.go`** — `NetworkInfo` type, `NetworkDiscoverer` interface, `FakeNetworkDiscoverer` 3. **`platform/network_windows.go`** — Windows `Discover()` implementation 4. **`platform/network_linux.go`** — Linux `Discover()` implementation 5. **`platform/network_darwin.go`** — macOS `Discover()` implementation 6. **`resolver/pool.go`** — `BuildResolverPool()` and `BootstrapResolvers` 7. **`resolver/transport.go`** — Generalized `udpQuery()` shared transport (extract from existing `resolver.go`) 8. **`resolver/authority.go`** — `ParallelNSFanOut()`, `SelectAuthoritativeNS()`, `QueryAuthoritative()` 9. **`resolver/splithorizon.go`** — `CheckSplitHorizon()` (Stage 2.5 cross-check) 10. **`resolver/fallback.go`** — `ParallelAFallback()` 11. **`resolver/modes.go`** — `Resolve()` mode dispatcher integrating all components 12. **`main.go`** — Update CLI flag parsing, wire new resolver pipeline ### Per-Step TDD Cycle For each item above: 1. **Red**: Write the test in `*_test.go` — it should fail (function doesn't exist yet). 2. **Green**: Implement the minimum code to make the test pass. 3. **Refactor**: Clean up while keeping tests green. Example for `ParseServerFlag`: ```go // resolver/parse_test.go func TestParseServerFlag_Default(t *testing.T) { mode, err := resolver.ParseServerFlag("") if err != nil { t.Fatalf("unexpected error: %v", err) } if mode.Mode != "default" { t.Errorf("expected mode 'default', got %q", mode.Mode) } } func TestParseServerFlag_Local(t *testing.T) { mode, err := resolver.ParseServerFlag("local") if err != nil { t.Fatalf("unexpected error: %v", err) } if mode.Mode != "local" { t.Errorf("expected mode 'local', got %q", mode.Mode) } } func TestParseServerFlag_InvalidPort(t *testing.T) { _, err := resolver.ParseServerFlag("10.0.0.53:0") if err == nil { t.Fatal("expected error for port 0") } } ``` ### Testing Parallel Fan-out Use the existing fake DNS server helpers from `resolver/resolver_test.go`: ```go // Start multiple fake DNS servers, each responding to NS queries differently. // Pass their addresses as the resolver pool to ParallelNSFanOut. ns1 := startFakeDNS(t, handleNSQuery("example.com", []string{"ns1.example.com."})) ns2 := startFakeDNS(t, handleNSQueryNXDOMAIN) results := resolver.ParallelNSFanOut(ctx, []string{ns1, ns2}, []string{"example.com"}, 3*time.Second) // Assert: 2 results, one with NS records, one with nil ``` ### Testing Platform Discovery Use `FakeNetworkDiscoverer` for unit tests — never call real OS commands in unit tests: ```go fake := &platform.FakeNetworkDiscoverer{ Info: platform.NetworkInfo{ DNSServers: []string{"10.0.0.1", "10.0.0.2"}, Gateway: "10.0.0.1", Interface: "eth0", }, } pool := resolver.BuildResolverPool(resolver.ServerMode{Mode: "default"}, fake.Info) // Assert: pool contains local resolvers + bootstrap set ``` Platform-specific integration tests (build-tagged) can test real `os/exec` parsing: ```go //go:build windows func TestWindowsDiscover(t *testing.T) { d := &platform.WindowsNetworkDiscoverer{} info, err := d.Discover() // Assert: no error, DNSServers non-empty on a typical system } ``` ## Manual Verification ### Smart Default Resolution (no -server) ```powershell # Should discover authoritative NS for example.com and query it directly .\dns-helper.exe add -host www.example.com -verbose ``` ### Local Resolver Mode ```powershell .\dns-helper.exe add -host internal.corp.local -server local -verbose ``` ### Gateway Mode ```powershell .\dns-helper.exe add -host www.example.com -server gateway -verbose ``` ### Explicit IP (existing, unchanged) ```powershell .\dns-helper.exe add -host www.example.com -server 8.8.8.8 ``` ### Timeout Override ```powershell .\dns-helper.exe add -host www.example.com -timeout 1 -verbose ``` ## Key Files Changed (Summary) | File | Change | |------|--------| | `main.go` | Updated `runAdd()`: `-server` optional, new `-timeout`/`-verbose` flags, mode dispatch | | `resolver/resolver.go` | Existing code unchanged; generalized UDP transport extracted | | `resolver/parse.go` | **New**: `ParseServerFlag()`, `ExtractLabelLevels()` | | `resolver/pool.go` | **New**: `BuildResolverPool()`, `BootstrapResolvers` | | `resolver/transport.go` | **New**: shared `udpQuery()` transport layer | | `resolver/authority.go` | **New**: `ParallelNSFanOut()`, `SelectAuthoritativeNS()`, `QueryAuthoritative()` | | `resolver/splithorizon.go` | **New**: `CheckSplitHorizon()` — Stage 2.5 cross-check | | `resolver/fallback.go` | **New**: `ParallelAFallback()` | | `resolver/modes.go` | **New**: `Resolve()` mode dispatcher | | `platform/platform.go` | Extended with `NetworkInfo`, `NetworkDiscoverer` interface | | `platform/platform_windows.go` | **Extended**: `Discover()` via `netsh` | | `platform/platform_linux.go` | **Extended**: `Discover()` via `ip route` + `/etc/resolv.conf` | | `platform/platform_darwin.go` | **Extended**: `Discover()` via `route` + `scutil` |