The Edge Engineer Who Wrote a DNS Resolver C
May 29, 2026 By Yusuke Tanaka

In early 2025, a senior engineer at a large content delivery network grew frustrated with a familiar pain point: DNS lookups were adding unpredictable latency to edge requests. Standard resolvers, including the widely used getaddrinfo from glibc, carried decades of backward compatibility overhead. For a CDN point-of-presence handling thousands of concurrent connections per node, even a few milliseconds of tail latency could degrade user experience. So the engineer did what many in the embedded world would consider natural: they wrote their own DNS resolver from scratch, in C, with no external dependencies. The result was a 40% reduction in CPU cycles per query and a P50 lookup time that fell from 2.1 ms to 0.3 ms. This is the story of that resolver and what it reveals about building for the edge in 2026.

Why a Single Engineer Rewrote DNS from Scratch

Standard DNS resolvers have accumulated features over decades. They handle DNSSEC, EDNS0, multiple transport protocols, and a range of record types that most edge services never use. Each feature adds code paths, memory allocations, and conditional branches. For a CDN that only needs A and AAAA records, this bloat becomes a liability. The engineer, whose background is in firmware for constrained devices, saw an opportunity to strip away everything unnecessary.

The primary goal was deterministic behavior. In embedded systems, you cannot afford unpredictable garbage collection or dynamic memory fragmentation. The same principle applies at the edge, where tail-latency spikes can cascade across thousands of requests. By writing in pure C and avoiding any runtime library beyond the kernel's socket interface, the resolver could control every allocation. No malloc in the hot path. No hidden context switches.

A secondary motivation was maintainability. The engineer wanted a codebase that a single person could hold in their head. The final resolver is about 2,000 lines of C, with a single header file documenting every wire-format assumption. This is a stark contrast to the hundreds of thousands of lines in a full-featured resolver like BIND or even the simpler c-ares. The engineer argued that when your infrastructure depends on a piece of software, you should be able to understand it completely.

The project took roughly three months of part-time work, with the first production deployment swapping in over a weekend. There was no downtime during the cutover, thanks to a careful canary rollout across a subset of edge nodes. The engineer presented the results at an internal tech talk, which quickly circulated to other teams. Within two quarters, the custom resolver was running on every node in the CDN.

What the Custom Resolver Changes on the Wire

The most visible change is in the DNS query format. The custom resolver supports only EDNS0 for extension mechanisms and a minimal set of record types: A, AAAA, and CNAME. It deliberately ignores NSEC, RRSIG, and other DNSSEC records. For a CDN that terminates TLS at the edge and validates certificates via other means, DNSSEC is unnecessary overhead. The resolver sends queries with the EDNS0 UDP buffer size set to 1232 bytes, the maximum that avoids IP fragmentation on most paths.

Connection coalescing is another key optimization. When multiple requests need the same upstream resolver, the custom resolver reuses a single UDP socket per destination. This reduces the number of system calls and avoids the TCP handshake overhead that some resolvers fall back to for large responses. The engineer measured a 30% reduction in kernel context switches per query.

Cache eviction uses a simple LRU policy with TTL pinning. Entries with short TTLs (under 60 seconds) are never evicted before expiry, preventing premature cache misses for frequently changing records. The cache is a fixed-size array of 64,000 entries, allocated at startup. There is no dynamic resizing, which eliminates fragmentation and ensures predictable memory usage. The engineer found that a 64,000-entry cache covered roughly 99% of unique hostnames seen by a typical edge node over a 24-hour period.

Response parsing skips optional sections entirely. Most DNS responses include additional section records that the resolver does not need. The parser reads only the question and answer sections, then discards the rest. This reduces parsing time by roughly 20% per response. The trade-off is that the resolver cannot participate in some advanced DNS features like CNAME chasing beyond the first level, but for the CDN's workload, that was acceptable.

The Edge Environment That Demanded This

The CDN's point-of-presence nodes sit in colocation facilities with 10 Gbps uplinks. Each node runs a custom HTTP server written in Go, which handles incoming requests and routes them to origin servers or cache. The DNS resolver is called before every cache miss to resolve the origin hostname. In a typical node, that means thousands of DNS queries per second, each competing for CPU time with the HTTP server and other system processes.

Before the custom resolver, the CDN used glibc's getaddrinfo with nscd (name service cache daemon). The engineer traced tail-latency spikes to glibc's internal lock contention. When multiple threads called getaddrinfo simultaneously, they would block on a mutex inside the resolver code. This caused P99 lookup times to jump from a baseline of 4 ms to over 12 ms during traffic bursts. The nscd cache helped with repeated lookups, but it introduced its own overhead and occasional cache corruption issues.

The swap-in process took three weeks of careful testing. The engineer deployed the custom resolver on a single node, monitored for two weeks, then expanded to a cluster of ten nodes. After another week with no incidents, the resolver was rolled out to all 200 nodes in the fleet. The engineer wrote a detailed migration guide that other teams later used for similar projects. The guide emphasized the importance of measuring before and after, using real traffic patterns rather than synthetic benchmarks.

Performance Benchmarks from Production

The most dramatic improvement was in P50 lookup time: from 2.1 ms with glibc to 0.3 ms with the custom resolver. This was measured over a 24-hour period on a node handling 15,000 queries per second. The P99 fell from 12 ms to 1.8 ms. The engineer attributed the improvement to three factors: elimination of lock contention, reduced memory allocation, and a more efficient cache design.

Memory per resolver instance dropped from roughly 50 MB to 24 MB. The custom resolver's fixed-size cache and lack of dynamic structures accounted for most of the savings. The engineer also removed the buffer for DNSSEC validation, which glibc allocates even when not used. Across 200 nodes, the total memory savings amounted to over 5 GB, which was redirected to the HTTP cache.

Throughput scaled linearly to 50,000 queries per second per core, limited only by the kernel's UDP receive buffer. Beyond that, the resolver became CPU-bound on packet parsing. The engineer noted that the linear scaling was expected given the lock-free design. Each thread runs its own resolver instance with its own cache partition, so there is no shared state. This is a common pattern in high-performance networking, but it required careful tuning of the kernel's net.core.rmem_default and net.core.rmem_max settings.

No open-source resolver matched these figures in the engineer's tests. They compared against c-ares, libunbound, and getaddrinfo with nscd. The closest was libunbound, which achieved a P50 of 0.8 ms and a P99 of 6 ms, but it used significantly more memory—around 80 MB per instance. The engineer acknowledged that libunbound provides DNSSEC validation and other features that the custom resolver lacks, but for the CDN's use case, the trade-off was clear.

Lessons for Engineers Building at the Edge

The first lesson is to profile before optimizing. The engineer spent two weeks profiling the existing resolver before writing a single line of new code. They used perf to identify lock contention, strace to count system calls, and custom logging to measure cache hit rates. Without that data, the custom resolver might have optimized the wrong thing. Many teams jump straight to building a custom solution without understanding the baseline.

Second, custom network stacks are viable in 2026. The Linux kernel's UDP stack is mature, and with careful configuration, you can achieve near-zero-copy packet processing. The engineer used setsockopt to set SO_RXQ_OVFL and SO_RXQ_BPF to avoid kernel overhead. They also pinned the resolver thread to a dedicated CPU core using pthread_setaffinity_np. These techniques are well-known in the high-frequency trading world but are underused in web infrastructure.

Third, minimal C code reduces the attack surface. The custom resolver has no buffer overflows from complex parsing because it rejects any response that does not match the expected format. The engineer wrote a fuzz tester that ran for weeks without finding a crash. By contrast, glibc's resolver has had multiple CVEs over the years. For a CDN that handles sensitive customer data, reducing the attack surface is a strong argument for custom code.

Finally, document every wire-format assumption. The engineer included a 20-page document explaining why each RFC was interpreted in a particular way. This document became the reference for future maintenance and for other teams considering similar projects. Without it, the custom resolver would be a black box that only the original author could modify.

When to Roll Your Own (and When Not To)

The custom resolver is not for everyone. It only makes sense if DNS latency is on your critical path. For most applications, the standard resolver is fast enough. The engineer's CDN had a specific workload: high throughput, low tolerance for tail latency, and a homogeneous environment where every node runs the same software stack. If your application runs on diverse hardware or uses multiple cloud providers, a custom resolver would be more trouble than it is worth.

Another consideration is maintenance. The engineer committed to maintaining the resolver for the foreseeable future. That means responding to bug reports, updating for kernel changes, and possibly adding features as the CDN's needs evolve. If your team cannot afford that commitment, it is better to use an open-source resolver and tune it. The engineer noted that the open-source landscape improves yearly—projects like c-ares have made significant performance gains in recent versions.

There is also the ecosystem compatibility issue. The custom resolver does not support DNSSEC, and it handles CNAMEs only one level deep. If your infrastructure relies on DNS-based security or complex redirection, you will need a more full-featured solution. The engineer acknowledged that the resolver is a "narrow tool for a narrow job." They recommended that teams evaluate their actual DNS requirements before deciding to build.

Ultimately, the trade-off is flexibility versus ecosystem compatibility. The custom resolver gave the CDN complete control over performance and resource usage, but it also created a dependency that only one person fully understood. The engineer mitigated this by writing thorough documentation and training a backup maintainer. Still, the risk of bus-factor is real. For teams considering a similar path, the engineer advises starting with a small, well-defined scope and expanding only when the benefits are clear.

Counter-Arguments and Alternative Approaches

Not everyone agrees that a custom resolver is the right path. Some engineers argue that the performance gains could be achieved by tuning existing software. For example, glibc's getaddrinfo can be configured with RES_OPTIONS to reduce timeouts and retries. The nscd cache can be replaced with a more modern cache like systemd-resolved, which offers better concurrency. In benchmarks conducted by a separate team at the same CDN, tuning systemd-resolved with a 128 MB cache and enabling EDNS0 reduced P50 lookup times to 1.2 ms and P99 to 5 ms—still higher than the custom resolver, but without the maintenance burden.

Another alternative is to use a kernel-bypass approach with DPDK or XDP. One team at a different CDN implemented a DNS resolver using XDP (eXpress Data Path) and achieved sub-0.1 ms P50 latency. However, that solution required a dedicated CPU core and custom kernel modules, making it harder to deploy across diverse hardware. The engineer's C resolver ran on standard kernel networking, which simplified operations. This trade-off between performance and deployability is a key consideration.

A third counter-argument is that DNS latency is often not the bottleneck. In many CDN architectures, the origin server's response time dominates the total request latency. The engineer acknowledged that for cache hits, DNS is a negligible factor. However, for cache misses—which can account for 10-20% of requests during traffic spikes—the DNS lookup can add significant delay. The custom resolver's impact was most visible during these periods, where it reduced the overall miss latency by up to 15%.

There is also the risk of vendor lock-in. If the CDN later decides to adopt a different resolver for new features (like DNS-over-HTTPS), the custom code would need to be extended or replaced. The engineer planned for this by designing the resolver with a modular front-end that could be swapped out. In fact, a subsequent project added DoH support by wrapping the core resolver with a TLS layer, requiring only 300 additional lines of code.

Measuring Real-World Impact

To quantify the resolver's effect on end-user experience, the CDN ran A/B tests across two regions. In Region A (custom resolver), average page load times for dynamic content improved by 8% compared to Region B (glibc with nscd). The improvement was most pronounced for mobile users on slower networks, where DNS latency can be a larger fraction of total time. The engineer noted that these gains were achieved without any changes to the HTTP server or caching logic.

Another metric was DNS failure rate. The custom resolver had a 0.02% failure rate (timeouts or SERVFAIL responses), compared to 0.05% for glibc. The engineer attributed this to the resolver's strict parsing and immediate retry on truncated responses. In glibc, some truncated responses were not handled correctly, leading to unnecessary failures. The custom resolver's retry logic was simpler: if a UDP response was truncated (TC bit set), it immediately retried with TCP, avoiding the wait for a new UDP query.

Operational overhead also decreased. Before the custom resolver, the operations team spent roughly 10 hours per month troubleshooting DNS-related issues, such as cache corruption or high CPU usage from nscd. After the rollout, that time dropped to near zero. The engineer attributed this to the resolver's deterministic behavior and detailed logging. Each resolver instance wrote structured logs to a dedicated file, making it easy to diagnose any anomalies.

Finally, the engineer measured energy consumption per query. Using RAPL (Running Average Power Limit) counters, they found that the custom resolver consumed 0.8 microjoules per query, compared to 2.1 microjoules for glibc. Over a year, the fleet-wide energy savings were estimated at 1,200 kWh, which translated to a modest reduction in cooling costs. While not the primary motivation, this environmental benefit was a welcome side effect.

The story of this custom DNS resolver is a reminder that sometimes the best tool for the job is the one you build yourself. But it is also a caution that building your own tool carries costs that are easy to underestimate. The engineer's success came from a deep understanding of both the problem domain and the underlying system. For anyone building at the edge in 2026, that combination of domain knowledge and systems thinking is the real takeaway.

Related Articles