permalink: "/{{ year }}/{{ month }}/{{ day }}/doh-everything" title: "DOH everything" published_date: "2018-02-09 14:52:00 +0100" layout: post.liquid data: route: blog --- Two days ago [I wrote about a new small utility crate](/2018/02/07/d-oh-dns-over-https-in-rust/) for doing DNS over HTTPS. When I started with the code on Monday I had an actual use case in mind. Due to some combination of [SmartOS](https://www.joyent.com/smartos), Ubuntu 17.04 and glibc my server currently has problems resolving DNS. It turns out a server without a working DNS resolver is kind of a pain, adding hostnames and their IPs to `/etc/hosts` gets tiring pretty fast. Luckily, glibc's [`gethostbyname`](http://man7.org/linux/man-pages/man3/gethostbyname.3.html) makes us of external plugins to do the actual resolving. That's how `/etc/hosts` and an external resolver work in the first place. The [documentation on the exact plugin mechanism](http://www.gnu.org/software/libc/manual/html_node/NSS-Modules-Interface.html#NSS-Modules-Interface) is quite sparse, but luckily *systemd* comes with its own resolve plugin: [nss-resolve.c](https://github.com/systemd/systemd/blob/master/src/nss-resolve/nss-resolve.c). All it takes to build a resolver plugin is to implement two functions[^1]: ``` enum nss_status _nss_$plugin_gethostbyname4_r(...) enum nss_status _nss_$plugin_gethostbyname3_r(...) ``` Both take a bunch of arguments, but it boils down to this: 1. Get the hostname (and optionally the address family to look for) 2. Resolve this hostname to its IP addresses[^2] 3. Copy the data into the provided buffer 4. Let the result point somewhere into this buffer 5. Clear the error values and return a success value If at any point an error is encountered, several error values are set and the function returns a failure. If all goes right, the requesting application will receive a data structure to read the different IP addresses from, which it can then use to establish a connection. Equipped with [dnsoverhttps](https://crates.io/crates/dnsoverhttps) the resolving part is suprisingly easy. First turn the passed name into a string, then call into the library and collect the results. ```rust unsafe { let slice = CStr::from_ptr(orig_name); let name = slice.to_string_lossy(); let addrs = match dnsoverhttps::resolve_host(&name) { Ok(a) => a, Err(_) => { *errnop = EINVAL; *h_errnop = NO_RECOVERY; return NSS_STATUS_UNAVAIL; } }; // ... } ``` Filling the appropiate data structures and memory buffer is a bit more complex and involves some pointer trickery. Instead of trying to find the right (unsafe) approach in Rust to do it correctly I opted to copy the `systemd`-code for now. In the Rust part we can forward the received arguments and add an array of results[^3]: ```rust let addrs : Vec<_> = addrs.into_iter().map(ip_addr_to_tuple).collect(); write_addresses4(orig_name, pat, buffer, buflen, errnop, h_errnop, ttlp, addrs.as_ptr(), addrs.len()) ``` This calls into a function [written in C](https://github.com/badboy/libnss_dnsoverhttps/blob/a3f25c92881ec1d1100ea4e7f7d399d1c51b80b6/src/write_addr.c#L13-L19) and [compiled just before the Rust code is compiled](https://github.com/badboy/libnss_dnsoverhttps/blob/a3f25c92881ec1d1100ea4e7f7d399d1c51b80b6/build.rs). It's also statically linked into the resulting shared object, ready to be deployed. In order to use it, the library has to be copied into a directory where the system can find it: ``` cp target/release/libnss_dnsoverhttps.so /usr/lib/libnss_dnsoverhttps.so.2 ``` And then it can finally be used by adding it to `/etc/nsswitch.conf` like this: ``` hosts: dnsoverhttps files mymachines dns myhostname ``` `curl` and other processes calling `gethostbyname` will from now on get addresses resolved by this small module.
--- [^1]: And forward `_nss_$plugin_gethostbyname2_r` and `_nss_$plugin_gethostbyname_r` to `_nss_$plugin_gethostbyname3_r`. [^2]: Remember that you can't use `gethostbyname` here, because you _are_ `gethostbyname`. [^3]: In between turning our vector of addresses into a simpler structure.