1
Fork 0

new post: DOH everything

This commit is contained in:
Jan-Erik Rediger 2018-02-09 14:52:51 +01:00
parent a005485d33
commit 1fa590da70

View file

@ -0,0 +1,95 @@
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.
<br>
---
[^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.