When I use containers, I don’t want to fumble around with IPs. Especially not when they change each time a container is created from scratch. Of course Docker has its way to address this problem but since I use systemd-nspawn most of the time I wanted to figure out how to do that on my own.

Name resolution on linux

On a linux system there are multiple ways how a program can resolve a name to an address. The most common way is to ask the glibc system library via a call of getaddrinfo() for example. Another one would be to ask another program via D-Bus interface.
But that library can do way more than just do a usual DNS request. It is backed by a system called Name Service Switch (NSS) which performs lookups for many function calls.
NSS has a lot of modular libraries which can be enabled and ordered in /etc/nswitch.conf. For example libnss-mdns would allow doing DNS lookups via a multicastDNS resolver like avahi, libnss-resolve allows the usage of systemd-resolved and finally libnss-mymachines can lookup local container names.
So with that we could easily reach our containers by name. But we also want the container to be able to lookup its host.

While researching I stumbled upon a technology called Link-Local Multicast Name Resolution (LLMNR). It allows the Multicast Name Resolution bound to a link and is therefore ideal for this usecase. Its available in systemd-resolved by default and ca be enabled via

#/etc/systemd/resolved.conf
[Resolve]
LLMNR=yes

If you cannot or want not use systemd-resolved have a look at llmnrd.

Configuration

Setting up the host

On the host system we want to directly lookup local containers. Therefore we install the libnss-mymachines packages and enabled it in the configuration:

#/etc/nsswitch.conf
hosts: files mymachines dns

It does not matter what is in your /etc/resolv.conf because NSS will first try to lookup a name in files like /etc/hosts then asks the mymachines library.
Of course you can reorder the libraries but be aware that each library delays all DNS request which it cannot resolve.

Setting up the container

Inside the container we want to configure LLMNR instead but it’s the same principle: First install the libnss-resolve package and then activate it:

#/etc/nsswitch.conf
hosts: files resolve dns 

Ensure that systemd-resolved is configured correctly and is up and runnning:

#/etc/systemd/resolved.conf
[Resolve]
LLMNR=yes
Cache=no
MulticastDNS=no
sudo systemctl enabe --now systemd-resolved

You have to enable LLMNR on the host and inside the container so get them both speak to each other. I also disabled multicast DNS and caching inside the container so that they cannot interfere with LLMNR which now discovers automatically any configured addresses of your container, both IPv4 and IPv6 including link-local addressing:

root@host ~# machinectl
MACHINE           CLASS     SERVICE        OS     VERSION ADDRESSES       
test-container    container systemd-nspawn ubuntu 18.04   192.168.240.200…
1 machines listed.

root@host ~# ping -c 1 test-container
PING test-container(test-container (fe80::2473:aeff:fe41:3bcb%ve-test-container)) 56 data bytes
64 bytes from test-container (fe80::2473:aeff:fe41:3bcb%ve-test-container): icmp_seq=1 ttl=64 time=0.054 ms
--- test-container ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.054/0.054/0.054/0.000 ms

root@host ~# ping -4 -c 1 test-container
PING test-container (169.254.179.218) 56(84) bytes of data.
64 bytes from test-container (169.254.179.218): icmp_seq=1 ttl=64 time=0.085 ms
--- test-container ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.085/0.085/0.085/0.000 ms

Even if the LLMNR configuration is broken and not working, the host can still resolve the containers names via mymachines lib:

root@host ~# systemctl stop systemd-resolved.service 
root@host ~# ping -c 1 test-container
PING test-container(fe80::2473:aeff:fe41:3bcb%ve-test-container) 56 data bytes
64 bytes from fe80::2473:aeff:fe41:3bcb%ve-test-container: icmp_seq=1 ttl=64 time=0.058 ms
--- test-container ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.058/0.058/0.058/0.000 ms

Alternative setup

Instead of using libnss-mymachines and libnss-resolve you can also set up systemd-resolved to use LLMNR and the point to it in you /etc/resolv.conf:

#/etc/resovl.conf
nameserver 127.0.0.53

The systemd-resolved is listening on that address by default and can resolve your requests for you via different protocols including LLMNR if its enabled in the config:

root@host ~# systemctl start systemd-resolved.service
root@host ~# echo "nameserver 127.0.0.53" > /etc/resolv.conf
root@host ~# ping -c 1 test-container
PING test-container (169.254.179.218) 56(84) bytes of data.
64 bytes from test-container (169.254.179.218): icmp_seq=1 ttl=64 time=0.068 ms
--- test-container ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.068/0.068/0.068/0.000 ms