Minimal caching DNS proxy for macOS.
It listens on port 53, forwards a small allowlist of DNS record types to an upstream resolver, keeps replies in an in-memory LRU cache, serves cached data immediately, and refreshes cached entries in the background.
The project was written for a local MacBook setup with:
- Apple Silicon / macOS
- upstream DNS
192.168.3.1 - cache size
8 MB - local bind on
:53
- fast local DNS proxy on
udp4,udp6,tcp4,tcp6 - in-memory LRU cache limited by RAM usage
- stale-while-refresh behavior
- TTL-aware cache expiration
- negative caching for empty and
NXDOMAIN-like replies - local filtering of unsupported record types
- periodic request statistics in terminal
- auto-kill of the process already holding port
53
Forwarded upstream:
ACNAMENSSOASRVHTTPS
Answered locally with empty NOERROR:
- everything else, including common types such as
AAAA,PTR,MX,TXT,SVCB,CAA,DS,DNSKEY,RRSIG,NAPTR,TLSA
- If the query type is not in the allowlist, the server returns an empty
NOERRORreply immediately. - If the query is in cache, the cached reply is returned right away.
- Cached replies are refreshed in the background, one refresh per cache key at a time.
- If the query is not in cache, it is forwarded to the upstream DNS server.
- Replies are cached with TTL based on the shortest TTL found in the DNS message.
- If TTL has expired, stale data may still be served once while the cache is refreshed.
- max cache size:
8 MB - eviction policy: LRU
- TTL source: minimum TTL across
Answer,Authority, andAdditional - fallback TTL for empty replies:
30s - zero TTL is normalized to
1s
- macOS
- Go
1.25+ sudoaccess to bind:53- upstream resolver reachable at
192.168.3.1:53
cd "/Users/user/Documents/dns_server_project"
./run.shrun.sh builds the binary locally and starts it with sudo.
go test ./...
go build ./...dig @127.0.0.1 google.com A
dig @::1 -6 google.com A
dig @127.0.0.1 google.com TXT
dig @127.0.0.1 -x 8.8.8.8Expected behavior:
Ais forwarded and cachedTXTreturns empty locallyPTRreturns empty locally
miss name=google.com. type=A rcode=NOERROR answers=6
hit name=google.com. type=A rcode=NOERROR answers=6
hit* name=google.com. type=A rcode=NOERROR answers=6
skip name=google.com. type=TXT rcode=NOERROR answers=0
refresh name=google.com. type=A rcode=NOERROR answers=6
stats total=642 hits=121 negative_hits=12 blocked=88 refresh=35 misses=521 forwarded=519 errors=0 cache_used=55794/8388608
dig -6 @127.0.0.1 ...is not a valid way to test IPv6 transport. Use@::1 -6.- The proxy intentionally does not forward every DNS type.
- This project is intentionally compact and optimized for a local machine, not for public recursive DNS service.
MIT