-
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.