_ _
_ __ ___ ___ | |_| |___
| '_ \/ __/ __|_____| __| / __|
| | | \__ \__ \_____| |_| \__ \
|_| |_|___/___/ \__|_|___/
Unlike most web browser traffic, which is encrypted thanks to HTTPS, the resolving of domain names to internet addresses still happens through DNS, an old, unencrypted protocol. This benefits analytics companies, advertisers, internet providers and attackers, but not the end-user, who seeks online privacy and security.
nss-tls is an alternative, encrypted name resolving library for Linux distributions with glibc, which uses DNS-over-HTTPS (DoH).
The glibc name resolver can be configured through nsswitch.conf(5) to use nss-tls instead of the DNS resolver, or fall back to DNS when nss-tls fails.
This way, all applications that use the standard resolver API (getaddrinfo(), gethostbyname(), etc'), are transparently migrated from DNS to encrypted means of name resolving, with zero application-side changes and minimal resource consumption footprint. However, nss-tls does not deal with applications that use their own, built-in DNS resolver.
nss-tls consists of three parts:
- nss-tlsd runs in the background, receives name resolving requests over a Unix socket and replies with resolved addresses.
- libnss_tls.so is a tiny client library, which delegates the resolving work to nss-tlsd through the Unix socket and passes the results back to the application, without dependencies other than libc. This way, applications that resolve through nss-tls are not affected by the complexity and resource consumption of runtime libraries (e.g. libstdc++) and dependency libraries used by nss-tlsd, or the constraints they impose on applications that load them (like signal or thread safety issues).
- tlslookup is equivalent to nslookup(1), but uses libnss_tls.so instead of DNS.
An unprivileged user can start a private, unprivileged instance of nss-tlsd and libnss-tls.so will automatically use that one, instead of the system-wide instance of nss-tlsd. Each user's nss-tlsd instance holds its own cache of lookup results, to speed up resolving. Because the cache is not shared with other users, it remains "hot" even if other users resolve many names.
Users who don't have such a private instance will continue to use the system-wide instance. which does not perform caching by default, to prevent a user from extracting the browsing history of another user, using timing-based methods. In addition, nss-tlsd drops its privileges to greatly reduce its attack surface.
Also, nss-tlsd is capable of using multiple DoH servers, with a deterministic algorithm that chooses which server to use to resolve a domain. This way, no DoH server can track the user's entire browsing history.
To avoid bloat, duplicate effort and potential remotely-exploitable vulnerabilities, nss-tlsd uses the libc API for building DNS queries and parsing responses, instead of implementing its own parser.
nss-tls depends on:
If systemd is present, the installation of nss-tls includes unit files for nss-tlsd, and nss-tlsd may co-exist with systemd-resolved.
However, nss-tls does not depend on systemd. When systemd is not present, other means of running a nss-tlsd instance for each user (e.g. xinitrc) and root (e.g. an init script) should be used.
nss-tls uses Meson as its build system.
On Debian and derivatives, these dependencies can be obtained using:
apt install libglib2.0-dev libsoup2.4-dev ninja-build python3-pip
pip3 install meson
Assuming your system runs systemd:
meson --prefix=/usr --buildtype=release -Dstrip=true build
ninja -C build install
systemctl daemon-reload
systemctl enable nss-tlsd
systemctl start nss-tlsd
systemctl --user --global enable nss-tlsd
systemctl --user start nss-tlsd
ldconfig
Then, add "tls" to the "hosts" entry in /etc/nsswitch.conf, before "resolve", "dns" or anything else that contains "dns".
This will enable a system nss-tlsd instance for all non-interactive processes (which runs as an unprivileged user) and a private instance of nss-tlsd for each user. Name resolving will happen through nss-tls and DNS will be attempted only if nss-tls fails.
By default, nss-tls uses the DNS servers specified in /etc/resolv.conf, assuming they support DoH.
To use a different DoH server, change the "resolvers" key of nss-tls.conf:
[global]
resolvers=https://9.9.9.9/dns-query
nss-tlsd looks for nss-tls.conf in user's home directory (only when running as an unprivileged user; usually under .config) and the system configuration file directory (usually /etc). If both files exist, nss-tlsd prefers the user's one.
nss-tlsd monitors the chosen configuration file and /etc/resolv.conf for changes and deletion, so changes are applied without having to restart nss-tlsd.
If /etc/resolv.conf is a symlink and specifies a stub DNS resolver, because of systemd-resolved, nss-tlsd tries to guess where the real resolv.conf is (usually /run/systemd/resolve/resolv.conf).
If the "resolvers" key is missing or empty, nss-tlsd falls back to using the DNS servers specified in /etc/resolv.conf.
To change the server selection in the default configuration file created at build time, use the "resolvers" build option:
meson configure -Dresolvers=cloudflare-dns.com/dns-query
It is also possible to use multiple DoH servers:
[global]
resolvers=https://dns9.quad9.net/dns-query,https://cloudflare-dns.com/dns-query
When nss-tls is configured like this, it pseudo-randomly chooses one of the servers, for each name lookup. The choice of the server is consistent: if the same domain is resolved twice (e.g. for its IPv4 and IPv6 addresses, respectively), nss-tlsd will use the same DoH server for both queries. If nss-tlsd is restarted, it will keep using the same DoH server to resolve that domain. This contributes to privacy, since every DoH server sees only a portion of the user's browsing history.
A standard DoH server should support both GET and POST requests. By default, nss-tlsd sends POST requests, beause they are faster to craft and tend to be smaller.
However, one might wish to use GET requests if this makes a specific DoH server respond faster (for example, if the server does not cache responses to POST requests). This can be done by adding "+get" after the server URL:
[global]
resolvers=https://dns.google/dns-query+get
If the DoH servers used by nss-tls are specified using their domain names, nss-tls needs a way to resolve the address of each DoH server and it cannot resolve it through itself.
To build nss-tls without dependency on other resolving methods (like DNS), specify the DoH servers using their addresses, e.g.:
[global]
resolvers=https://9.9.9.9/dns-query,https://1.1.1.1/dns-query
Alternatively, the DoH server addresses can be hardcoded using /etc/hosts, e.g:
echo "8.8.8.8 dns.google" >> /etc/hosts
Then, the DoH server can be specified by its domain name:
[global]
resolvers=https://dns.google/dns-query
To disable DNS and use nss-tls exclusively, remove all DNS resolvers from the "hosts" entry in /etc/nsswitch.conf (but keep "tls").
On paper, DNS over HTTPS is much slower than DNS, due to the overhead of TCP and TLS.
Therefore, each nss-tls instance keeps established HTTPS connections open and reuses them. Also, if running with the -c option, each user's nss-tls instance maintains an internal cache of lookup results. In this cache, IPv4 and IPv6 addresses are stored in separate hash tables, to make the cache faster to iterate over.
Therefore, in reality, DNS over HTTPS using nss-tls may be much faster than DNS.
One may wish to use a system-wide cache that also covers DNS, instead of the internal cache of nss-tls; nscd(8) can do that. To enable system-wide cache on Debian and derivatives:
apt install unscd
Then, set "enable-cache" for "hosts" to "yes" in /etc/nscd.conf. Then:
systemctl enable unscd
systemctl start unscd
nss-tls is free and unencumbered software released under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version license.
nss-tls is not affiliated with Quad9, Cloudflare or Google.
The ASCII art logo at the top was made using FIGlet.