It's weird to me that event-based DNS using epoll or similar doesn't have a battle-tested implementation. I know it's harder to do in C than in Rust but I'm pretty sure that's what Hickory does internally.
Many async frameworks (e. g. libevent [1]) have a DNS client. But it's not something easy to use unless your program uses this specific framework (say libevent) for all network I/O. The problem is not that it's hard to do in C but that there is no single async framework everyone would use.
I use hickory a lot and have contributed to it. It does have a pretty robust async DNS implementation, and its helpfully split into multiple different crates so you can pick your entry point into the stack. For instance, it offers a recursive resolver, but you can also just import the protocol library and build your own with tokio.
it’s a weird problem, in that (1) DNS is hard, and (2) you really need the upstream vendor to solve the problem, because correct applications want to use the system resolver.
If you don’t use the system resolver, you have to glue into the system’s configuration mechanism for resolvers somehow … which isn’t simple — for example, there’s a lot of complex logic on macOS around handling which resolver to use based on what connections, VPNs, etc, are present.
And the there’s nsswitch and other plugin systems that are meant to allow globally configured hooks plug into the name resolution path.