Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

D-Bus — The Linux Desktop IPC Bus

D-Bus is the inter-process communication mechanism of choice for the modern Linux desktop and for most system services that need structured, discoverable RPC. It is a bus (a many-to-many multiplexer) rather than a point-to-point pipe: a single bus daemon routes messages between many peers, and any peer can broadcast signals to all subscribers.

This page covers the on-the-wire protocol, the daemon, service activation, and how systemd’s sd-bus library implements the bus from inside PID 1. It complements systemd Internals which discusses how systemd uses D-Bus.

1. Two buses: system and session

There are two long-running D-Bus instances on a typical Linux box:

BusAddressScopeAuth
System busunix:path=/run/dbus/system_bus_socketWhole machine, persistentuid 0 + policy file in /usr/share/dbus-1/system.d/*.conf and /etc/dbus-1/system.d/*.conf
Session busunix:path=/run/user/$UID/bus (or autolaunched)One user login sessionEXTERNAL auth: same uid via SO_PEERCRED

The system bus hosts privileged services like org.freedesktop.systemd1, org.freedesktop.NetworkManager, org.freedesktop.login1, org.freedesktop.hostname1. The session bus hosts desktop services like org.freedesktop.Notifications, org.freedesktop.PowerManagement, org.freedesktop.ScreenSaver, and the per-app buses of browsers, IDEs, and IM clients.

A third bus type — the systemd private bus — is exposed by systemd itself at /run/systemd/private and speaks the same wire protocol but is only reachable by uid 0. It is what systemctl uses by default to avoid round-tripping through dbus-daemon.

2. The dbus-daemon

dbus-daemon is the userspace router. It accepts connections (typically on an AF_UNIX socket), authenticates each, and for every message it receives it consults the match rules other clients have installed to determine which clients should receive a copy. The daemon does not serialise to disk; it is purely in-memory, with unicast and broadcast semantics.

A bus address is a string like:

unix:path=/run/dbus/system_bus_socket
unix:abstract=/tmp/dbus-XXXXXX,guid=…     # session bus on older systems
unix:runtime=yes                          # use $XDG_RUNTIME_DIR/bus

The bus daemon is normally started by dbus.service (or dbus.socket for socket activation) on the system side, and by dbus-daemon --session invoked from dbus-launch or systemd --user on the session side.

Each connection is identified by a unique name like :1.42 (assigned sequentially by the daemon). Clients may then request a well-known name like org.freedesktop.NetworkManager via RequestName; the daemon resolves the unique-name-to-well-known-name mapping, applies name ownership policy, and dispatches.

3. The wire protocol

D-Bus messages are binary. Each message has a fixed-size header followed by a variable-length body.

+----------+----------------+------------------+
|  Header  |  Header padding|      Body        |
+----------+----------------+------------------+
      |              |
      v              v
  12 bytes mandatory + 4 bytes optional  aligned to 8 bytes

The header layout (/usr/include/dbus-1.0/dbus/dbus-protocol.h):

byte 0   : 'l' (little-endian) or 'B' (big-endian) — message byte order
byte 1   : message type (1=method_call, 2=method_return, 3=error, 4=signal)
byte 2   : flags (NO_REPLY_EXPECTED, NO_AUTO_START, ...)
byte 3   : protocol version (currently 1)
bytes 4-7: body length (uint32 LE)
bytes 8-11: serial number (uint32 LE) — monotonically increasing per connection
bytes 12+: header fields array

The header fields array carries a list of (field code, signature, value) tuples. Standard field codes (from dbus-protocol.h):

PATH         = 1   object path, e.g. /org/freedesktop/DBus
INTERFACE    = 2   e.g. org.freedesktop.DBus.Properties
MEMBER       = 3   method or signal name, e.g. GetAll
ERROR_NAME   = 4   on error replies, e.g. org.freedesktop.DBus.Error.ServiceUnknown
REPLY_SERIAL = 5   serial of the call this is replying to
DESTINATION  = 6   well-known name of the recipient
SENDER       = 7   unique name of the sender (filled in by daemon)
SIGNATURE    = 8   signature of the body
UNIX_FDS     = 9   number of file descriptors passed (out-of-band via SCM_RIGHTS)

The body contains arguments whose types are described by the SIGNATURE field. Type codes include y (byte), b (bool), n (int16), i (int32), u (uint32), x (int64), t (uint64), d (double), s (UTF-8 string), o (object path string), g (signature string), a (array), (...) (struct), v (variant), and a{...} (dict entry).

Example: the signature sa{sv} says “a string followed by an array of dict entries whose key is a string and value is a variant”. This is the canonical “properties bag” shape used by the Properties interface and many methods.

4. The org.freedesktop.DBus interface

Every connection automatically owns the org.freedesktop.DBus interface on the object path /org/freedesktop/DBus. It exposes the bus-management API: name registration, list querying, activation, and the message bus match rules.

$ dbus-send --print-reply --system \
    --dest=org.freedesktop.DBus \
    /org/freedesktop/DBus \
    org.freedesktop.DBus.ListNames
method return time=1717823456.987654 sender=org.freedesktop.DBus -> dest=:1.42 serial=3 reply_serial=2
   array string "org.freedesktop.DBus"
   ...

Notable methods:

MethodPurpose
HelloReturns the unique name assigned to this connection (:1.42); called once at connect time
RequestName(name, flags)Ask the bus to assign name to this connection
ReleaseName(name)Release a previously-requested name
ListNamesList all unique and well-known names on the bus
GetNameOwner(name)Get the unique name owning name
NameOwnerChanged(name, old, new)Signal emitted when a name is acquired/released
AddMatch(rule)Subscribe to signals matching rule
RemoveMatch(rule)Remove a match rule
GetConnectionCredentials(name)Returns uid, pid, SELinux context of name
GetIdReturns the bus’s globally-unique UUID
UpdateActivationEnvironmentUpdate environment for newly-activated services

The full interface is documented in the D-Bus specification, standard interfaces.

5. Method calls vs. signals

A D-Bus message is one of four types: method call, method return, error, or signal.

A method call is unicast to the unique name (or well-known name) of the recipient:

Client A                          Bus daemon                   Service B
   |  method_call                     |                            |
   |  serial=42                       |                            |
   |  dest=org.freedesktop.NetworkManager                         |
   |  path=/org/freedesktop/NetworkManager                         |
   |  iface=org.freedesktop.NetworkManager                         |
   |  member=GetDevices                                             |
   |  signature=                                                    |
   |  body=()                                                       |
   | ---------------------------->  | ---------------------------->|
   |                                |                             |
   |                                |  method_return              |
   |                                |  reply_serial=42            |
   |                                |  signature=ao               |
   |                                |  body=([/org/freedesktop/…]) |
   |                                | <----------------------------|
   | <---------------------------- | <---------------------------- |

A signal is broadcast by the daemon to any client whose match rule matches. The sender does not know (or care) who is listening:

Service B                         Bus daemon                   Many clients
   |  signal                                                          |
   |  path=/org/freedesktop/NetworkManager/Devices/0                  |
   |  iface=org.freedesktop.NetworkManager                            |
   |  member=PropertiesChanged                                        |
   |  signature=a{sv}as                                               |
   |  body=({"ActiveConnection": "/…"}, [])                          |
   | ----------------------------> | ------------------------------>| (match rule 1)
   |                                | ------------------------------>| (match rule 2)
   |                                | ------------------------------>| (match rule 3)

Match rules look like:

type='signal',
interface='org.freedesktop.NetworkManager',
member='PropertiesChanged',
path='/org/freedesktop/NetworkManager/Devices/0',
arg0='ActiveConnection'

The daemon stores each match rule as a string-tuple key in a hash; for every incoming signal it iterates all match rules and forwards to the matching connections. This O(peers × rules) dispatch is the main cost of the bus daemon and the reason very-high-rate signal paths bypass the bus entirely (e.g. video frames in Wayland).

6. Introspection

Every object on the bus with the path / and below can be asked “what do you do?” via the org.freedesktop.DBus.Introspectable.Introspect method, which returns an XML description of the object’s interfaces, methods, signals, and properties:

$ dbus-send --system --print-reply --dest=org.freedesktop.login1 \
    /org/freedesktop/login1 org.freedesktop.DBus.Introspectable.Introspect
method return time=1717823456.123456 sender=:1.5 -> dest=:1.42 serial=42 reply_serial=2
   string "<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
  <interface name="org.freedesktop.login1.Manager">
    <method name="ListSessions">
      <arg name="sessions" type="a(sssussso)" direction="out"/>
    </method>
    <method name="PowerOff">
      <arg name="interactive" type="b" direction="in"/>
    </method>
    ...
    <signal name="SeatNew"/>
    <property name="NCurrentSessions" type="u" access="read"/>
  </interface>
</node>"

The XML is human-readable and tooling-friendly. gdbus introspect, busctl introspect, d-feet, and qdbus all use it to render tree views of running services.

7. Service activation

D-Bus services can be autostarted. When a method call’s destination is a well-known name that is currently unowned, the daemon checks /usr/share/dbus-1/system-services/<name>.service (system bus) or ~/.local/share/dbus-1/services/*.service (session bus):

# /usr/share/dbus-1/system-services/org.freedesktop.networkManager.service
[D-BUS Service]
Name=org.freedesktop.NetworkManager
Exec=/usr/sbin/NetworkManager --no-daemon
User=root
SystemdService=NetworkManager.service

The daemon then forks/execs Exec, waits for the new process to call RequestName on the bus, and only then forwards the queued method call. The SystemdService= line tells systemd-aware daemons to start the corresponding systemd unit instead — this is systemd activation, used to avoid double-forking the service.

If the NO_AUTO_START flag is not set on the method call, this is transparent to the caller: a method call to an inactive service looks exactly like one to a long-running service, modulo a one-shot startup latency.

8. dbus-send and friends

dbus-send is the lowest-common-denominator CLI for D-Bus. It can send method calls and read replies, but cannot subscribe to signals. Examples:

# Suspend the system
dbus-send --system --print-reply --dest=org.freedesktop.login1 \
    /org/freedesktop/login1 org.freedesktop.login1.Manager.Suspend \
    boolean:true

# Get a property
dbus-send --system --print-reply --dest=org.freedesktop.systemd1 \
    /org/freedesktop/systemd1 org.freedesktop.DBus.Properties.Get \
    string:org.freedesktop.systemd1.Manager string:Version

# Start a unit
dbus-send --system --print-reply --dest=org.freedesktop.systemd1 \
    /org/freedesktop/systemd1 \
    org.freedesktop.systemd1.Manager.StartUnit \
    string:nginx.service string:replace

Better alternatives:

  • busctl — systemd’s all-singing D-Bus inspector: busctl list, busctl tree, busctl introspect, busctl monitor, busctl call, busctl emit.
  • gdbus — GLib’s CLI: gdbus call, gdbus introspect, gdbus monitor.
  • gdbus-codegen — generates C bindings from introspection XML.

9. sd-bus

sd-bus is systemd’s own D-Bus client library, written from scratch in C, with no external dependencies. It lives in src/libsystemd/sd-bus/ in the systemd tree and is exported as libsystemd.so. Compared to the traditional libdbus, it is significantly faster (zero-copy message construction, varlink-style builder macros), it integrates with sd-event for I/O, and it is what PID 1 itself uses to talk to other services.

A typical sd-bus method call looks like:

#include <stdio.h>
#include <systemd/sd-bus.h>

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_error err = SD_BUS_ERROR_NULL;
    sd_bus_message *m = NULL;
    const char *v;

    sd_bus_open_system(&bus);   /* connect to the system bus */

    sd_bus_call_method(bus,
        "org.freedesktop.systemd1",          /* destination */
        "/org/freedesktop/systemd1",         /* path */
        "org.freedesktop.DBus.Properties",   /* interface */
        "Get",                               /* member */
        &err, &m,                            /* error, return message */
        "ss",                                /* input signature */
        "org.freedesktop.systemd1.Manager",  /* arg0 */
        "Version");                          /* arg1 */

    sd_bus_message_read(m, "v", "s", &v);
    printf("systemd version: %s\n", v);

    sd_bus_message_unref(m);
    sd_bus_error_free(&err);
    sd_bus_unref(bus);
    return 0;
}

To build:

gcc example.c -o example $(pkg-config --cflags --libs libsystemd)

sd-bus also exposes a high-level vtable API for writing service objects declaratively:

static int method_hello(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
    return sd_bus_reply_method_return(m, "s", "hello from the bus");
}

static const sd_bus_vtable test_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("Hello", "", "s", method_hello, 0),
    SD_BUS_SIGNAL("Pong", "s", 0),
    SD_BUS_VTABLE_END
};

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_slot *slot = NULL;
    sd_bus_open_user(&bus);
    sd_bus_add_object_vtable(bus, &slot, "/org/example/Test",
                             "org.example.Test", test_vtable, NULL);
    sd_bus_request_name(bus, "org.example.Test", 0);
    for (;;) sd_bus_process(bus, NULL);     /* dispatch */
}

For the full reference, see the sd-bus documentation and Lennart Poettering’s Writing a D-Bus service in C with sd-bus blog post.

10. D-Bus vs other IPC

D-BusUNIX socketgRPCAvahi/mDNS
TopologyBus (N-to-N)Point-to-pointPoint-to-pointDiscovery only
Formatbinary, hand-codedbytes (you choose)Protobuf over HTTP/2text
DiscoveryNames on busnonenone (no service registry)yes
Activationyes (D-Bus activation)nonono
Latency~10–100 µs typical~10 µs~ms (HTTP overhead)seconds
Throughputlow (MB/s)high (GB/s)highn/a
Cross-hostno (by default)noyesyes
Use caseDesktop IPC, system controlad-hoc servicesmicroservicesmDNS only

For hot paths (e.g. Wayland), D-Bus is bypassed in favour of custom UNIX socket protocols because the daemon in the loop adds latency and a single point of failure. For control plane (“tell the system to do X”), D-Bus’s combination of introspection, activation, and bus semantics makes it the right tool.

References

  • D-Bus specification, https://dbus.freedesktop.org/doc/dbus-specification.html
  • D-Bus tutorial, https://dbus.freedesktop.org/doc/dbus-tutorial.html
  • man dbus-send(1), https://manpages.debian.org/dbus-send.1
  • man dbus-daemon(1), https://manpages.debian.org/dbus-daemon.1
  • sd-bus documentation, https://www.freedesktop.org/software/systemd/man/sd-bus.html
  • Lennart Poettering, “The New sd-bus API”, https://0pointer.de/blog/projects/the-new-systemd-bus-api.html
  • LWN: “A look at D-Bus” — Jake Edge, https://lwn.net/Articles/328516/
  • LWN: “D-Bus 1.0 released” — Jonathan Corbet, https://lwn.net/Articles/211540/
  • busctl(1) man page, https://www.freedesktop.org/software/systemd/man/busctl.html
  • gdbus(1) man page, https://manpages.debian.org/gdbus.1