• Pl chevron_right

      Christian Hergert: ((lib)Re)bonjour

      news.movim.eu / PlanetGnome • 2 days ago • 4 minutes

    I made another weird side project while unemployed. In fact I’ve wanted it for a while but once I learned that “Rebonjour” is the word for “hello again” I just had to finish the library.

    librebonjour is an asynchronous DNS-SD and mDNS client library for GLib applications. Or, more practically, it is a small GObject API over the two local service-discovery providers you are likely to find on a Linux system: Avahi and systemd-resolved.

    It does not link against either of them. It only talks to them over D-Bus.

    The reason for that is mostly boring, which is usually where the useful things are. Applications should not need to care if a machine has Avahi running, or if it is using systemd-resolved for mDNS. They should be able to discover a service, resolve it, maybe advertise something, and get on with whatever they were actually trying to do.

    So RebonjourClient selects a backend internally. If org.freedesktop.Avahi is available on the system bus, it uses Avahi. If not, it falls back to systemd-resolved’s org.freedesktop.resolve1 API. If neither is around, availability checks fail like you would expect.

    The public API stays the same either way.

    What It Does

    There are three common things I wanted to make pleasant.

    First, one-shot discovery. Ask for the service types in local , ask for instances of something like _ipp._tcp , then resolve one of those instances into addresses and TXT metadata.

    Second, browser-style discovery. A RebonjourBrowser owns a stable GListModel of RebonjourService objects. That fits nicely into GTK code because the model object can stay the same while the contents change underneath it.

    Third, registration. You can describe a local service with RebonjourServiceDescription , register it, and keep the returned RebonjourRegistration alive for as long as the service should be advertised.

    Resolving a service gives you a RebonjourResolvedService . That contains the SRV result, TXT data, priority, weight, and a model of RebonjourEndpoint objects. The endpoints hold the GSocketAddress you would actually use to connect.

    Why Two Backends

    Avahi is the nicer backend for browsing. Its D-Bus API gives you long-lived browser objects and emits signals when services appear and disappear. That maps very naturally to GListModel changes.

    systemd-resolved is different. It has useful DNS-SD and mDNS operations over D-Bus, but the browsing side is lookup-based. That means you can ask what is there, but you do not get the same live add/remove signal stream that Avahi provides.

    I did not want applications to have to care about that distinction unless they really want to. So the browser has auto-refresh and refresh-interval properties. With Avahi, auto-refresh is effectively harmless because the model is already live. With systemd-resolved, it starts an internal refresh loop and updates the model for you.

    It is not magic. It is just putting the backend-shaped unpleasantness in one place so application code can stay boring.

    Asynchronous with libdex

    The whole thing is built on libdex . Anything that might touch D-Bus or the network returns a DexFuture .

    That means construction, availability checks, service-type lookup, instance lookup, resolving, registration, browser refresh, and unregistering are all future-based. If you are already writing fiber-style code with libdex, the API fits into that directly:

    g_autoptr(RebonjourClient) client = NULL;
    g_autoptr(GListModel) services = NULL;
    g_autoptr(GError) error = NULL;
    
    if (!(client = dex_await_object (rebonjour_client_new (), &error)))
      g_error ("%s", error->message);
    
    services = dex_await_object (rebonjour_client_lookup_instances (client,
                                                                    0,
                                                                    "_ipp._tcp",
                                                                    NULL,
                                                                    REBONJOUR_LOOKUP_FLAGS_NONE),
                                 &error);
    

    The 0 there means any interface. Passing NULL for the domain uses local . The common case should not require looking up interface indexes which I’m pretty sure most people reading this have never even done before.

    Advertising

    Advertising is where things get more system-policy-oriented.

    With Avahi, registration goes through Avahi’s D-Bus API. With systemd-resolved, registration uses RegisterService and UnregisterService , which are polkit-protected. Also, resolved needs full mDNS enabled with MulticastDNS=yes ; MulticastDNS=resolve is enough to browse and resolve, but not enough to respond as a service.

    So librebonjour can expose one API for registration, but it cannot make host policy disappear. Applications still need to handle authorization failure, missing mDNS responder support, sandbox boundaries, or whatever policy the system administrator has decided is appropriate.

    That seems like the right way to demarcate things. The library should hide the provider mechanics, not the permissions of the platform.

    Why

    Mostly because I wanted this to exist.

    DNS-SD is handy. Local-network service discovery is still useful. But using it from a GLib application means either caring too much about the provider or writing just enough glue that every application gets to have its own slightly different version of the same code.

    And even worse is having to bundle things to build projects like Avahi for Flatpak when you only use the library which calls into D-Bus anyway.

    This is not a grand platform initiative. It is not something I am employed to maintain. So you know, use wisely.