Skip to content

mDNS Reflector Service

Date: 2024/06/20

Context

Brewblox uses multicast DNS (mDNS) to automatically discover Spark controllers connected to the local network. mDNS support on the host side is most commonly provided by Avahi. As the Spark service is connected to a Docker bridge network, we must enable Avahi reflection to forward mDNS packets from the physical network interface to the Docker bridge network interface. For more details on this, see the decision doc on Avahi reflection.

As noted in the previous decision, we'd rather avoid editing host configuration where possible. With the introduction of brewblox.yml, we've gained more fine-grained control over system configuration, and can afford to look at alternatives.

Reflector service

https://github.com/vfreex/mdns-reflector is a dedicated mDNS reflector that comes with Docker support. It's small (5.6MB), and supports all our target CPU architectures. It does have three constraints: there must be a separate service for each reflected network interface, reflected network interface names must be provided in configuration, and the application will exit with an error if the physical network interface is not connected.

The first constraint is not a problem. We can use Jinja templates to generate a separate service for each network interface during brewblox-ctl config apply.

The second constraint has two separate implications. We must know the name(s) of the physical network interfaces on the host, and we must have a known and deterministic name for the Docker network bridge.

If we assume the host to be linux-based, we can find all our network interfaces in /sys/class/net/. This does include all virtual network interfaces generated by Docker. Virtual network interfaces don't have a device symlink, so we can fetch all physical network interfaces with the following Python code:

python
from glob import glob

physical_interfaces = [v.split('/')[4] for v in glob('/sys/class/net/*/device')]

For second constraint, Docker Compose allows us to set network names.

yaml
networks:
  default:
    driver_opts:
      com.docker.network.bridge.name: br-${COMPOSE_PROJECT_NAME}

Now our bridge network has a deterministic name (typically br-brewblox).

For the third constraint, we don't want to exclude (temporarily) disconnected network interfaces in configuration. We can, however, limit the impact of services immediately stopping. Instead of the customary restart: unless-stopped, we can limit restart attempts using restart: on-failure:3. Reflector services for disconnected interfaces will immediately restart three times, and then stay quiet.

With all this, a typical reflector service for the network interface wlan0 would be:

yaml
  reflector:
    image: yuxzhu/mdns-reflector:latest
    restart: on-failure:3
    network_mode: host
    command: mdns-reflector -fn4 wlan0 br-${COMPOSE_PROJECT_NAME}

Configuration

The reflector service is a replacement for enabling reflection in Avahi. For existing systems, this means we need to edit Avahi configuration again to disable reflection. During a fresh installation, we still need to check Avahi configuration, but don't need to make any changes if it's still using default values.

In brewblox.yml, we have two new settings, and a changed default value.

  • reflector.enabled is a new setting (default true)
  • reflector.interfaces is a new setting (default to autodetected interfaces)
  • avahi.reflection is now false by default