Files
firestuff/markdown/2016-03-01-asynchronous-name-resolution-in-c.md
2019-04-15 01:22:25 +00:00

3.9 KiB
Raw Blame History

Down another rabbit hole, this time into yet another seemingly simple problem: how do you turn a name into an address that you can connect() to without blocking your thread, in 2016. Lets survey state of the world:

getaddrinfo_a()

Look, its exactly what we need! Just joking. Its has the same resolution behavior as getaddrinfo, but:

  • Its a GNU extension. This may matter to you or not, but it does tie you to glibc.
  • It allocates memory and gives you no way to free it. Calling freeaddrinfo() doesnt do it. The manpage doesnt even seem to consider that one might want to free memory. Its possible that its a first-time-only allocation that hasnt made it into valgrinds default suppressions yet.
  • It uses sigevent to notify completion. This gives you a choice between a signal and a new thread. I thought we were doing this to avoid having to create a new thread for each resolution?

libadns

  • Its GPLed. Thats cool, but it does limit options.
  • Its a direct DNS library, not a getaddrinfo() implementation. That means you have to reproduce all the getaddrinfo() behavior, including the configuration file, if you want the behavior that users expect.
  • It will hand you file descriptors to block on, but you have to ask (using adns_beforeselect()). This is designed for poll(), but doesnt work well with epoll; it doesnt tell you when to add and remove fds, so you have to track them yourself (since you cant iterate an epoll set), diff them since the last result, and change the epoll set. Its a mess.

libasyncns

  • It uses getaddrinfo() underneath, so you get standard behavior. Woo!
  • It notifies you via a file descriptor. Unfortunately, its only a single file descriptor across all requests. It doesnt allow you to associate a pointer with a request, epoll-style, so you still have to track your own query list and higher-level data associations.
  • Its API isnt too crazy, but you wouldnt call it simple.

c-ares

I failed to find docs for this, but I found a gist with an example. Looks like the API is designed for use with select(), though theres a hook to get an fd when its created, so you might be able to associate it with a query, possibly unreliably. Again, youd have to recreate getaddrinfo() behavior yourself. Also, this gem is at the top of the header:

#elif defined(WIN32)
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
# include <winsock2.h>
# include <ws2tcpip.h>

So maybe not.

So now what?

Maybe we can build something. I really dont need to write another DNS library in my lifetime (the c-ares Other Libraries page links to my previous one, humorously). Lets see if we can scope some requirements:

  • Asynchronous resolution
  • Configurable parallelism
  • Per-request notification via file descriptor
  • Easy epoll compatibility, including associating pointers with requests
  • Full getaddrinfo() compatibility

And some nice-to-haves:

  • Tiny: doesnt need to be a separate library
  • Doesnt expose internal state beyond the file descriptor
  • Simple threading model thats hard to screw up

After looking at all these libraries, youd think this would be a massive job. In fact, its 127 lines of C, and thats with generous error checking and readability. Hopefully, I never have to solve this problem again.