{"id":42363204,"url":"https://github.com/bschaatsbergen/dnsdialer","last_synced_at":"2026-01-27T17:15:13.778Z","repository":{"id":319489573,"uuid":"1078764882","full_name":"bschaatsbergen/dnsdialer","owner":"bschaatsbergen","description":"Multi-resolver DNS dialer for Go with race, fallback, and consensus strategies","archived":false,"fork":false,"pushed_at":"2026-01-12T01:48:57.000Z","size":126,"stargazers_count":42,"open_issues_count":8,"forks_count":5,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-14T17:27:13.210Z","etag":null,"topics":["dns","golang","multiplexer"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/bschaatsbergen/dnsdialer","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bschaatsbergen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-18T11:26:33.000Z","updated_at":"2026-01-12T20:33:03.000Z","dependencies_parsed_at":null,"dependency_job_id":"763d0aaa-d88a-4666-8292-879032a16463","html_url":"https://github.com/bschaatsbergen/dnsdialer","commit_stats":null,"previous_names":["bschaatsbergen/dnsmux"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bschaatsbergen/dnsdialer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bschaatsbergen%2Fdnsdialer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bschaatsbergen%2Fdnsdialer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bschaatsbergen%2Fdnsdialer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bschaatsbergen%2Fdnsdialer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bschaatsbergen","download_url":"https://codeload.github.com/bschaatsbergen/dnsdialer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bschaatsbergen%2Fdnsdialer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28816649,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-27T12:25:15.069Z","status":"ssl_error","status_checked_at":"2026-01-27T12:25:05.297Z","response_time":168,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dns","golang","multiplexer"],"created_at":"2026-01-27T17:15:13.018Z","updated_at":"2026-01-27T17:15:13.766Z","avatar_url":"https://github.com/bschaatsbergen.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# dnsdialer\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/bschaatsbergen/dnsdialer.svg)](https://pkg.go.dev/github.com/bschaatsbergen/dnsdialer)\n[![Go Report Card](https://goreportcard.com/badge/github.com/bschaatsbergen/dnsdialer)](https://goreportcard.com/report/bschaatsbergen/dnsdialer)\n\nThis package allows you to take control of DNS resolution behavior through configurable multi-resolver strategies.\n\nWhy you'd want multiple resolvers: Redundancy (primary resolver failure doesn't cascade into total DNS outage). Performance (concurrent queries across resolvers, returning fastest response). Security (consensus validation across independent resolvers mitigates poisoning and MITM attacks). Integrity (cross-resolver validation detects poisoning, cache corruption, and configuration drift before propagation).\n\nMost OS-level DNS stacks already support multiple resolvers, but they don't use them in parallel, they typically try the first, then fail over in sequence (which can be slow if the first resolver hangs). In high-throughput systems where single-digit millisecond DNS latency affects tail latencies and resolver failures propagate into cascading outages, you need deterministic multi-resolver behavior.\n\nWhile OS-level DNS caching (mDNSResponder on macOS, systemd-resolved on Linux) provides sub-millisecond lookups, this package bypasses it by default to ensure fresh results for redundancy and consensus validation. Optional LRU caching with TTL-aware expiration is available via `WithCache()` to reduce latency on repeated lookups while maintaining explicit control over cache size and TTL bounds.\n\nThis package provides a `DialContext` implementation that plugs directly into HTTP transports, gRPC clients, or any custom connection pools expecting [net.Dialer](https://pkg.go.dev/net#Dialer).\n\n## How it works\n\nBuilt on [miekg/dns](https://pkg.go.dev/github.com/miekg/dns), dnsdialer implements the same `DialContext` signature as [net.Dialer](https://pkg.go.dev/net#Dialer), making it a drop-in replacement for any Go code that accepts a custom dialer (HTTP clients, gRPC, etc.).\n\nThe only difference: instead of using your system DNS resolver, it queries multiple DNS resolvers using your chosen strategy.\n\n## Performance\n\nThe standard library's [net.Dialer](https://pkg.go.dev/net#Dialer) relies on OS-level DNS caching (mDNSResponder on macOS, systemd-resolved on Linux), which provides sub-millisecond lookups once cached. dnsdialer has its own in-process LRU cache to avoid shared global state and maintain explicit control over TTL bounds. By caching parsed [net.IP](https://pkg.go.dev/net#IP) slices instead of raw DNS strings, you get similar dial latency with reduced per-lookup allocations.\n\n```console\ngo test -bench='^BenchmarkStdLib_DialContext$|^BenchmarkDNSDialer_DialContext_Cache_Single_Race$' -run=^$ -benchtime=5s -benchmem\ngoos: darwin\ngoarch: arm64\npkg: github.com/bschaatsbergen/dnsdialer\ncpu: Apple M4\nBenchmarkStdLib_DialContext-10                               360          16735385 ns/op            3549 B/op         57 allocs/op\nBenchmarkDNSDialer_DialContext_Cache_Single_Race-10          354          16519391 ns/op             936 B/op         21 allocs/op\nPASS\nok      github.com/bschaatsbergen/dnsdialer     12.114s\n```\n\nThe standard library's DNS resolver implementation varies by CGO status: with CGO enabled (default), it uses [getaddrinfo()](https://man7.org/linux/man-pages/man3/getaddrinfo.3.html) which requires a system call and inter-process communication to the OS DNS cache (mDNSResponder on macOS, systemd-resolved on Linux) for every lookup. With CGO disabled, it uses a [pure Go](https://github.com/golang/go/blob/master/src/net/lookup_unix.go#L58) DNS implementation that sends queries directly to DNS resolvers for every lookup. dnsdialer maintains deterministic in-memory caching regardless of build configuration, providing much faster lookups by eliminating external communication overhead (system calls, inter-process communication, or network round-trips):\n\n```console\nCGO_ENABLED=0 go test -bench=CGO -benchtime=5s -run=^ -benchmem\ngoos: darwin\ngoarch: arm64\npkg: github.com/bschaatsbergen/dnsdialer\ncpu: Apple M4\nBenchmarkCGO_StdLib_LookupHost-10                  26244            226596 ns/op             348 B/op         11 allocs/op\nBenchmarkCGO_DNSDialer_LookupHost-10            58951063            103.6 ns/op              128 B/op          3 allocs/op\nPASS\nok      github.com/bschaatsbergen/dnsdialer     12.401s\n```\n\n## Usage\n\n### HTTP Client\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"8.8.8.8:53\", \"1.1.1.1:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Race{}),\n    dnsdialer.WithCache(1000, 1*time.Second, 5*time.Minute),\n)\n\nclient := \u0026http.Client{\n    Transport: \u0026http.Transport{\n        DialContext: dialer.DialContext,\n    },\n}\n\nresp, err := client.Get(\"https://api.github.com\")\n```\n\n### gRPC\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"8.8.8.8:53\", \"1.1.1.1:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Race{}),\n    dnsdialer.WithCache(1000, 1*time.Second, 5*time.Minute),\n)\n\nconn, err := grpc.Dial(\n    \"api.example.com:443\",\n    grpc.WithContextDialer(func(ctx context.Context, addr string) (net.Conn, error) {\n        return dialer.DialContext(ctx, \"tcp\", addr)\n    }),\n)\n```\n\n## Strategies\n\n### Race\n\nQueries all servers simultaneously and returns the first successful response.\nMinimizes latency by leveraging the fastest available server.\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"8.8.8.8:53\", \"1.1.1.1:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Race{}),\n)\n```\n\n### Fallback\n\nQueries servers sequentially in order, providing reliability through ordered failover.\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"primary.dns:53\", \"backup.dns:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Fallback{}),\n)\n```\n\n### Consensus\n\nRequires a minimum number of servers to agree on the response.\nImproves security by detecting inconsistencies or DNS poisoning.\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"8.8.8.8:53\", \"1.1.1.1:53\", \"9.9.9.9:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Consensus{\n        MinAgreement: 2,    // Require 2 servers to agree\n        IgnoreTTL:    true, // Ignore TTL differences when comparing\n    }),\n)\n```\n\n### Compare\n\nQueries all servers and detects discrepancies, calling a user-provided callback when differences are found.\nUseful for monitoring DNS resolver integrity.\n\n```go\ndialer := dnsdialer.New(\n    dnsdialer.WithResolvers(\"8.8.8.8:53\", \"1.1.1.1:53\"),\n    dnsdialer.WithStrategy(dnsdialer.Compare{\n        OnDiscrepancy: func(host string, qtype dnsdialer.RecordType, results map[string][]dnsdialer.Record) {\n            fmt.Printf(\"Discrepancy detected for %s (%s)\\n\", host, qtype)\n        },\n        IgnoreTTL: true,\n    }),\n)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbschaatsbergen%2Fdnsdialer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbschaatsbergen%2Fdnsdialer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbschaatsbergen%2Fdnsdialer/lists"}