How do I register/unregister a service dynamically using the AVAHI D-Bus API (and what’s the simplest example flow)?
Networking System Software

How do I register/unregister a service dynamically using the AVAHI D-Bus API (and what’s the simplest example flow)?

8 min read

Most applications that “speak Avahi” at runtime should be using the D-Bus API, not embedding a second mDNS stack. That’s exactly what the Avahi daemon is for: you register and withdraw services over D-Bus, and Avahi handles mDNS/DNS-SD publication, host name collisions, and daemon restarts for you.

Below is a practical, minimal flow for dynamically registering and unregistering a service with the Avahi D-Bus API, plus a concrete example in Python. I’ll walk through the mechanics first, then show the simplest end-to-end code.


What “dynamic registration over D-Bus” actually means

When you use the Avahi D-Bus API, you’re:

  • Talking to the avahi-daemon process (the mDNS/DNS-SD stack) via system D-Bus.
  • Creating an EntryGroup object that holds one or more DNS-SD records (your service).
  • Asking Avahi to commit that group so it’s published on the LAN.
  • Optionally resetting or destroying that group later to withdraw the service.

This is the D-Bus equivalent of the C flow documented for avahi_entry_group_new() / avahi_entry_group_add_service() / commit/reset.

High‑level runtime story:

  1. Connect to D-Bus.
  2. Get the Avahi Server object.
  3. Create an EntryGroup.
  4. Add your service (_http._tcp, _ssh._tcp, etc.) to that group.
  5. Commit the group (service becomes visible via mDNS/DNS-SD).
  6. When done, reset or free the group (service disappears).

Minimal D-Bus objects and interfaces involved

On the system bus, the Avahi daemon exposes:

  • Bus name: org.freedesktop.Avahi
  • Server object path: /
  • Server interface: org.freedesktop.Avahi.Server
  • EntryGroup interface: org.freedesktop.Avahi.EntryGroup

The sequence for dynamic registration is:

  1. Call Server.GetNetworkInterfaceIndexByName() (optional) or use -1 for “all interfaces”.
  2. Call Server.EntryGroupNew() to get an EntryGroup object path.
  3. On that EntryGroup, call AddService() / AddServiceTxt() or their variants.
  4. On that EntryGroup, call Commit().
  5. Later, call Reset() or Free() to remove the service.

On the wire, Avahi will then broadcast the appropriate mDNS records (SRV, TXT, A/AAAA) for your service.


Core parameters you need to decide

For AddService() / AddServiceTxt() you must pick:

  • Interface / protocol
    • -1 / -1 for “all interfaces / any protocol” is the usual choice.
  • Flags
    • 0 for “no special flags” is fine for most basic services.
  • Service name
    • Human-facing name, e.g. "My HTTP Service" or "Backup API on ${HOSTNAME}".
  • Service type
    • DNS-SD type, e.g. "_http._tcp", "_ssh._tcp", "_ipp._tcp", "_afpovertcp._tcp".
  • Domain
    • Usually empty string "" for default (local).
  • Host
    • Usually empty string "" to use the host’s default mDNS name.
  • Port
    • TCP or UDP port your service listens on (e.g. 8080).
  • TXT records
    • Key=value pairs describing capabilities, e.g. path=/, version=1. Can be empty.

Simplest example flow in plain steps

This is the bare-bones control flow for “register/unregister a service dynamically using the Avahi D-Bus API”:

  1. Connect to system D-Bus

    Use your language’s D-Bus bindings to connect to the system bus.

  2. Get the Avahi Server object

    • Bus name: org.freedesktop.Avahi
    • Object path: /
    • Interface: org.freedesktop.Avahi.Server
  3. Create an EntryGroup

    • Call EntryGroupNew() on the Server object.
    • This returns an object path, e.g. /Client1/EntryGroup1.
  4. Add your service

    On the EntryGroup object (interface org.freedesktop.Avahi.EntryGroup), call:

    • AddService(interface=-1, protocol=-1, flags=0, name="My HTTP Service", type="_http._tcp", domain="", host="", port=8080, txt=[])

    or AddServiceTxt() if you want to add TXT data in one call.

  5. Commit the EntryGroup

    Still on the EntryGroup object:

    • Call Commit().

    At this point, Avahi announces your service on the local network via mDNS/DNS-SD. Tools like avahi-browse -a or dns-sd / dns-sd.py should see it.

  6. Unregister the service

    When your application shuts down or wants to withdraw the service:

    • Call Reset() on the EntryGroup to remove all its records but keep the group object for future reuse, or
    • Call Free() (if exposed by your binding) to destroy the group.

    When the process exits or disconnects from D-Bus, Avahi will also clean up its registrations.


Concrete example: Python, system D-Bus, _http._tcp service

This example uses dbus-python, which is a thin binding around libdbus. It matches the internal C flow (EntryGroup → add service → commit → reset).

1. Basic registration script

#!/usr/bin/env python3
import dbus
import time
import signal
import sys

BUS_NAME = "org.freedesktop.Avahi"
SERVER_PATH = "/"
SERVER_IFACE = "org.freedesktop.Avahi.Server"
ENTRYGROUP_IFACE = "org.freedesktop.Avahi.EntryGroup"

def main():
    # Connect to the *system* bus, where avahi-daemon lives
    bus = dbus.SystemBus()

    # Get the server proxy
    server = dbus.Interface(
        bus.get_object(BUS_NAME, SERVER_PATH),
        SERVER_IFACE
    )

    # Create a new EntryGroup
    entry_group_path = server.EntryGroupNew()
    entry_group = dbus.Interface(
        bus.get_object(BUS_NAME, entry_group_path),
        ENTRYGROUP_IFACE
    )

    # Interface/protocol: -1 means "use all interfaces / all protocols"
    interface = dbus.Int32(-1)
    protocol = dbus.Int32(-1)
    flags = dbus.UInt32(0)

    service_name = "My HTTP Service (Avahi D-Bus)"
    service_type = "_http._tcp"
    domain = ""
    host = ""
    port = dbus.UInt16(8080)

    # Example TXT record: version=1, path=/
    txt = dbus.Array(
        [
            dbus.ByteArray(b"version=1"),
            dbus.ByteArray(b"path=/"),
        ],
        signature='ay'
    )

    # Add the service
    entry_group.AddServiceTxt(
        interface,
        protocol,
        flags,
        service_name,
        service_type,
        domain,
        host,
        port,
        txt
    )

    # Commit the group so the service becomes visible
    entry_group.Commit()
    print(f"Service registered: {service_name} on port {port}")

    # Keep the process alive so the service stays published
    def shutdown(signum, frame):
        print("Unregistering service and exiting...")
        try:
            # Reset removes all records from this group
            entry_group.Reset()
        except dbus.DBusException as e:
            print(f"Error during Reset(): {e}")
        sys.exit(0)

    signal.signal(signal.SIGINT, shutdown)
    signal.signal(signal.SIGTERM, shutdown)

    # Boring sleep loop; in a real app you would integrate with your main loop
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        shutdown(None, None)

if __name__ == "__main__":
    main()

2. How to see that it works

On another terminal on the same LAN (or another host on the same multicast segment):

# Browse for http services
avahi-browse -rt _http._tcp

You should see a service with the name My HTTP Service (Avahi D-Bus) and port 8080. When you Ctrl+C the Python service, the script calls entry_group.Reset(), and the service disappears from avahi-browse output.


Handling daemon restarts and collisions (the “real” flow)

The example above keeps things intentionally minimal. In a real deployment you should handle:

1. avahi-daemon restarts

If the daemon is restarted underneath you, its D-Bus objects vanish and are recreated. The C docs call this out explicitly under “How to Write a Client That Can Deal with Daemon Restarts.”

Over D-Bus, the pattern is:

  • Listen for NameOwnerChanged on org.freedesktop.DBus for org.freedesktop.Avahi.
  • When Avahi reappears, re‑create your Server proxy, allocate a new EntryGroup, re‑add your services, and Commit() again.

Most higher-level bindings provide some way to attach to D-Bus signals; the logic is the same as the C guidance: treat a daemon restart as a cue to rebuild your entries.

2. Name collisions

When the host’s .local name or your service name collides, Avahi may:

  • Change the host name (e.g. hostname.localhostname-2.local), or
  • Auto-rename your service (e.g. append (2)), or
  • Enter AVAHI_SERVER_COLLISION / AVAHI_SERVER_REGISTERING states and then settle on a new, non-conflicting name.

The C docs emphasize:

move your services when the server enters AVAHI_SERVER_COLLISION or AVAHI_SERVER_REGISTERING state. Your services may not be reachable anymore since the local host name is no longer established or is currently in the process of being established.

On D-Bus, watch StateChanged (or equivalent) signals on the Server object and respond by:

  • Temporarily resetting or suspending your entry groups when the server is in collision/transition.
  • Re‑committing them once the server reports AVAHI_SERVER_RUNNING.

Not all bindings expose the constants directly; you may have to map the integer state values yourself using the Avahi headers/docs.


When to prefer D-Bus over the C core API

Avahi’s own docs draw the line this way:

  • C “core API” (libavahi-core): For embedded appliances or special cases where you need to embed the mDNS/DNS-SD stack directly. The docs explicitly say they discourage running multiple mDNS stacks on one host.
  • D-Bus API: “We recommend using this API for software written in any language other than C (e.g. Python).” It talks to avahi-daemon and avoids multiple stacks, and it’s the right choice for most desktop/server apps.

If your code isn’t a specialized appliance, stick with the D-Bus API. The dynamic registration/unregistration flow you’ve seen here is the intended pattern.


Putting it together as a repeatable recipe

To summarize the simplest dynamic service lifecycle over the Avahi D-Bus API:

  1. Connect to system D-Bus and get org.freedesktop.Avahi server at /.
  2. Create an EntryGroup via Server.EntryGroupNew().
  3. Add your service using EntryGroup.AddService() or AddServiceTxt() with:
    • interface=-1, protocol=-1, flags=0
    • Service name, type (e.g. _http._tcp), domain="", host=""
    • port and optional TXT key/value pairs.
  4. Commit the EntryGroup with EntryGroup.Commit().
  5. Keep your process (or main loop) alive; the service is now discoverable via mDNS/DNS-SD.
  6. Unregister when you’re done:
    • Call EntryGroup.Reset() (to remove all records), or simply exit and let Avahi clean up on D-Bus disconnect.
  7. For robust behavior:
    • Watch for daemon restarts and rebuild your entry groups.
    • Watch server state changes and adapt around collisions.

From there, you can scale from the simple _http._tcp example to whatever service types and TXT payloads your stack needs, while still using the same D-Bus registration/unregistration flow.

Next Step

Get Started