Using D-Bus in golang
D-Bus
D-Bus is a message bus system, a simple way for applications to talk to one another. In addition to interprocess communication, D-Bus helps coordinate process lifecycle; it makes it simple and reliable to code a "single instance" application or daemon, and to launch applications and daemons on demand when their services are needed.
In computing, D-Bus or DBus (for “Desktop Bus”), a software bus, is an inter-process communication (IPC) and remote procedure call (RPC) mechanism that allows communication between multiple computer programs (that is, processes) concurrently running on the same machine. [Wikipedia]
D-Bus allows different processes to communicate indirectly through a known interface. As a short analogy would be that D-Bus is to unix sockets what HTTP/REST is to TCP.
D-Bus is a service daemon that runs in the background, we use it to interact with applications and their functionalities. The bus daemon sends/receives messages to and from applications.
There are two types of daemons:
SystemBus: one per system, for system services
SessionBus: one per user, for user services
bus: is where you look for IPC services.
service: is a program that offers an IPC API on a bus. Services are identified by a reverse domain name, e.g.
org.freedesktop.login1
exposessystemd-logind
's APIs.client: Program that makes uise of some IPC API on a bus- talking to or monitoring a service. Doesn't (need to) provide any services of their own.
peer: Generalization used to refer to either a service or a client.
object path: Identifier for an object on a given service. Comparable to a
C
pointer in that you used to reference an object. Is a memory address meaningful outside the service./org/freedesktop/login1
is the object path of the "manager" object of theorg.freedesktop.login1
service.An object identified by object path has one or more interfaces. An interface is a collection of members; signals, methods, and properties. Interface names are in reverse domain notation as well, like service names. Some interfaces are standardized:
org.freedesktop.DBus.Peer
org.freedesktop.DBus.Properties
org.freedesktop.DBus.Instrospectable
method: Function that can be invoked through the API. A method call a single client issues a request on a signle service, the service sends back a response to the client.
systemd-logind
exposes anActivateSession
method on theorg.freedesktop.login1.Manager
interface available on the/org/freedesktop/login1
object of theorg.freedesktop.login1
service.signal: Member type of D-Bus object sytem used for asynchronous notification of peers or broadcasting over the bus.
systemd-logind
broadcasts aSessionNew
signal from its manager object each time a user logs in, and aSessionRemoved
every time a user logs out.property: Similar to OOP concept of properties. A variable exposed by the object that can be read or altered by clients.
sytemd-logind
exposes theDocked
property of signatureb
.signature: Set of parameters a function, signal, or property takes or returns. Series of characters that each encode one parameter by its type. e.g.
s
for string,u
for 32bit int,as
array of strings,a(sb)
array of structures consiting of one string and one bolean each.
D-Bus services work well with systemd services.
D-Bus offers to messaging patterns:
- Methods
- Signals
Method calls go to a single destination and get a reply- either method return or error. Signals are sent to anyone that is interested. They have no response.
Four message types:
- methods
- signals
- returns
- errors
They can carry different data types:
- Numbers
- UTF-8 strings
- Arrays
- Dictionaries
- File descriptors
For a full list of data types see the D-Bus specification.
The bus is the hub that brokers messages to consumers. Programs using D-Bus send and receive messages through the bus. You can also send messages to the bus itself, to tell it who you are or to subscribe to a signal.
If you send a message to a bus name which isn't claimed, the bus may be able to start a program to handle it. This is called D-Bus activation, and it's governed by simple files saying which program to start for which name.
System services like wpa_supplicant describe an API available over D-Bus.
Cheat sheet
To list all peers connected to the bus use busctl
. Peer names like :1.2
are known as unique names, they are much like an internal IP address. Names like org.freedesktop.login1
are called well-known names, similar to DNS host names.
$ busctl
NAME PID PROCESS USER CONNECTION UNIT SESSION DESCRIPTION
:1.0 1 systemd root :1.0 init.scope - -
:1.1 320 avahi-daemon avahi :1.1 avahi-daemon.service - -
:1.16 31577 polkitd root :1.16 polkit.service - -
:1.196 30270 busctl pi :1.196 session-c73.scope c73 -
:1.2 326 systemd-logind root :1.2 systemd-logind.service - -
:1.24 32348 bluetoothd root :1.24 bluetooth.service - -
fi.epitest.hostap.WPASupplicant - - - (activatable) - -
fi.w1.wpa_supplicant1 - - - (activatable) - -
org.blueman.Mechanism - - - (activatable) - -
org.bluez 32348 bluetoothd root :1.24 bluetooth.service - -
org.freedesktop.Avahi 320 avahi-daemon avahi :1.1 avahi-daemon.service - -
org.freedesktop.DBus 321 dbus-daemon messagebus org.freedesktop.DBus dbus.service - -
org.freedesktop.PolicyKit1 31577 polkitd root :1.16 polkit.service - -
org.freedesktop.hello - - - (activatable) - -
org.freedesktop.hostname1 - - - (activatable) - -
org.freedesktop.locale1 - - - (activatable) - -
org.freedesktop.login1 326 systemd-logind root :1.2 systemd-logind.service - -
org.freedesktop.network1 - - - (activatable) - -
org.freedesktop.resolve1 - - - (activatable) - -
org.freedesktop.systemd1 1 systemd root :1.0 init.scope - -
org.freedesktop.timedate1 - - - (activatable) - -
To list objets exposed by the org.freedesktop.login1
service:
$ busctl tree org.freedesktop.login1
└─/org
└─/org/freedesktop
└─/org/freedesktop/login1
├─/org/freedesktop/login1/seat
│ └─/org/freedesktop/login1/seat/seat0
├─/org/freedesktop/login1/session
│ ├─/org/freedesktop/login1/session/c1
│ ├─/org/freedesktop/login1/session/c73
│ └─/org/freedesktop/login1/session/self
└─/org/freedesktop/login1/user
├─/org/freedesktop/login1/user/_1000
└─/org/freedesktop/login1/user/self
To instrospect an object and show the interfaces, methods, signals and properties exposed:
$ busctl introspect org.freedesktop.login1 /org/freedesktop/login1/session/c73
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
org.freedesktop.DBus.Introspectable interface - - -
.Introspect method - s -
org.freedesktop.DBus.Peer interface - - -
.GetMachineId method - s -
.Ping method - - -
org.freedesktop.DBus.Properties interface - - -
.Get method ss v -
.GetAll method s a{sv} -
.Set method ssv - -
.PropertiesChanged signal sa{sv}as - -
org.freedesktop.login1.Session interface - - -
.Activate method - - -
.Kill method si - -
.Lock method - - -
.PauseDeviceComplete method uu - -
.ReleaseControl method - - -
.ReleaseDevice method uu - -
.SetIdleHint method b - -
.SetLockedHint method b - -
.TakeControl method b - -
.TakeDevice method uu hb -
.Terminate method - - -
.Unlock method - - -
.Active property b true emits-change
.Audit property u 0 const
.Class property s "user" const
.Desktop property s "" const
.Display property s "" const
.Id property s "c73" const
.IdleHint property b false emits-change
.IdleSinceHint property t 0 emits-change
.IdleSinceHintMonotonic property t 0 emits-change
.Leader property u 30007 const
.LockedHint property b false emits-change
.Name property s "pi" const
.Remote property b true const
.RemoteHost property s "192.168.10.104" const
.RemoteUser property s "" const
.Scope property s "session-c73.scope" const
.Seat property (so) "" "/" const
.Service property s "sshd" const
.State property s "active" -
.TTY property s "" const
.Timestamp property t 1558206890617603 const
.TimestampMonotonic property t 13873648709262 const
.Type property s "tty" const
.User property (uo) 1000 "/org/freedesktop/login1/user/_1…0" const
.VTNr property u 0 const
.Lock signal - - -
.PauseDevice signal uus - -
.ResumeDevice signal uuh - -
.Unlock signal - - -
To call a method:
$ busctl call org.freedesktop.login1 /org/freedesktop/login1/session/c73 org.freedesktop.DBus.Peer GetMachineId
137f05d485c24a764b39a6cf5ce0634b
You can monitor services, in this case systemd-logind
. This will fire events when you ssh
in a new terminal:
$ busctl monitor org.freedesktop.login1
Monitoring bus message stream.
‣ Type=signal Endian=l Flags=1 Version=1 Priority=0 Cookie=2
Sender=org.freedesktop.DBus Destination=:1.275 Path=/org/freedesktop/DBus Interface=org.freedesktop.DBus Member=NameAcquired
MESSAGE "s" {
STRING ":1.275";
};
‣ Type=signal Endian=l Flags=1 Version=1 Priority=0 Cookie=4
Sender=org.freedesktop.DBus Destination=:1.275 Path=/org/freedesktop/DBus Interface=org.freedesktop.DBus Member=NameLost
MESSAGE "s" {
STRING ":1.275";
};
‣ Type=method_call Endian=l Flags=0 Version=1 Priority=0 Cookie=2
Sender=:1.276 Destination=org.freedesktop.login1 Path=/org/freedesktop/login1 Interface=org.freedesktop.login1.Manager Member=CreateSession
UniqueName=:1.276
MESSAGE "uusssssussbssa(sv)" {
UINT32 1000;
UINT32 31029;
STRING "sshd";
STRING "tty";
STRING "user";
STRING "";
STRING "";
UINT32 0;
STRING "";
STRING "";
BOOLEAN true;
STRING "";
STRING "192.168.10.104";
ARRAY "(sv)" {
};
};
‣ Type=method_call Endian=l Flags=0 Version=1 Priority=0 Cookie=1218
Sender=:1.2 Destination=org.freedesktop.DBus Path=/org/freedesktop/DBus Interface=org.freedesktop.DBus Member=GetConnectionUnixUser
UniqueName=:1.2
MESSAGE "s" {
STRING ":1.276";
};
‣ Type=method_return Endian=l Flags=1 Version=1 Priority=0 Cookie=299 ReplyCookie=1218
Sender=org.freedesktop.DBus Destination=:1.2
MESSAGE "u" {
UINT32 0;
};
‣ Type=method_call Endian=l Flags=0 Version=1 Priority=0 Cookie=1219
Sender=:1.2 Destination=org.freedesktop.systemd1 Path=/org/freedesktop/systemd1 Interface=org.freedesktop.systemd1.Manager Member=StartTransientUnit
UniqueName=:1.2
MESSAGE "ssa(sv)a(sa(sv))" {
STRING "session-c74.scope";
STRING "fail";
ARRAY "(sv)" {
STRUCT "sv" {
STRING "Slice";
VARIANT "s" {
STRING "user-1000.slice";
};
};
STRUCT "sv" {
STRING "Description";
VARIANT "s" {
STRING "Session c74 of user pi";
};
};
STRUCT "sv" {
STRING "After";
VARIANT "as" {
ARRAY "s" {
STRING "systemd-logind.service";
};
};
};
STRUCT "sv" {
STRING "After";
VARIANT "as" {
ARRAY "s" {
STRING "systemd-user-sessions.service";
};
};
};
STRUCT "sv" {
STRING "SendSIGHUP";
VARIANT "b" {
BOOLEAN true;
};
};
STRUCT "sv" {
STRING "PIDs";
VARIANT "au" {
ARRAY "u" {
UINT32 31029;
};
};
};
STRUCT "sv" {
STRING "TasksMax";
VARIANT "t" {
UINT64 18446744073709551615;
};
};
};
ARRAY "(sa(sv))" {
};
};
‣ Type=method_return Endian=l Flags=1 Version=1 Priority=0 Cookie=30469 ReplyCookie=1219
Sender=:1.0 Destination=:1.2
UniqueName=:1.0
MESSAGE "o" {
OBJECT_PATH "/org/freedesktop/systemd1/job/33886";
};
‣ Type=signal Endian=l Flags=1 Version=1 Priority=0 Cookie=1220
Sender=:1.2 Path=/org/freedesktop/login1 Interface=org.freedesktop.login1.Manager Member=SessionNew
UniqueName=:1.2
MESSAGE "so" {
STRING "c74";
OBJECT_PATH "/org/freedesktop/login1/session/c74";
};
‣ Type=signal Endian=l Flags=1 Version=1 Priority=0 Cookie=1221
Sender=:1.2 Path=/org/freedesktop/login1/user/_1000 Interface=org.freedesktop.DBus.Properties Member=PropertiesChanged
UniqueName=:1.2
MESSAGE "sa{sv}as" {
STRING "org.freedesktop.login1.User";
ARRAY "{sv}" {
DICT_ENTRY "sv" {
STRING "Display";
VARIANT "(so)" {
STRUCT "so" {
STRING "c1";
OBJECT_PATH "/org/freedesktop/login1/session/c1";
};
};
};
};
ARRAY "s" {
};
};
‣ Type=method_return Endian=l Flags=1 Version=1 Priority=0 Cookie=1222 ReplyCookie=2
Sender=:1.2 Destination=:1.276
UniqueName=:1.2
MESSAGE "soshusub" {
STRING "c74";
OBJECT_PATH "/org/freedesktop/login1/session/c74";
STRING "/run/user/1000";
UNIX_FD 4;
UINT32 1000;
STRING "";
UINT32 0;
BOOLEAN false;
};
List Registered Services:
# List system services
dbus-send --system --dest=org.freedesktop.DBus --type=method_call --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep -v '":'
# List session services
dbus-send --session --dest=org.freedesktop.DBus --type=method_call --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep -v '":'
Registry Locations:
/usr/share/dbus-1/services/*.service
/usr/share/dbus-1/system-services/*.service
/usr/share/dbus-1/interfaces/*.xml
Call RPC:
dbus-send --session --dest=<class> <namespace> <method> [<parameters>]
Example:
dbus-send --session --dest=org.gnome.feed.Reader \
/org/gnome/feed/Reader \
org.gnome.feed.Reader.Subscribe \
string:http://osnews.com/files/recent.rdf
Go d-bus
The tcd project exposes an object both over gRPC and D-Bus.
Library interfacing with Ubuntu upstart to manage jobs.
The ble-adapter-go project uses godbus to interact with BLE.
Examples
client.go:
package main
import (
"github.com/guelfey/go.dbus"
)
func main() {
conn, err := dbus.SessionBus()
if err != nil {
panic(err)
}
// func (conn *Conn) Object(dest string, path ObjectPath) *Object
obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
// Interface from the specification:
// UINT32 org.freedesktop.Notifications.Notify (STRING app_name, UINT32 replaces_id, STRING app_icon, STRING summary, STRING body, ARRAY actions, DICT hints, INT32 expire_timeout);
// func (o *Object) Call(method string, flags Flags, args ...interface{}) *Call
call := obj.Call("org.freedesktop.Notifications.Notify", 0, "c¼h", uint32(0), "", "Hallo Chaostreff!", "Ich begrüße euch herzlich zu meiner c¼h!", []string{}, map[string]dbus.Variant{}, int32(1000))
if call.Err != nil {
panic(call.Err)
}
}
server.go:
package main
import (
"fmt"
"github.com/guelfey/go.dbus"
)
type Server struct {
id uint32
}
func (s Server) Notify(appName string, replacesId uint32, appIcon string, summary string, body string, actions []string, hints map[string]dbus.Variant, expireTimeout int32) (ret uint32, err *dbus.Error) {
fmt.Printf("Got Notification from %s:\n", appName)
fmt.Printf("==== %s ====\n", summary)
fmt.Println(body)
fmt.Printf("==== END %s ====\n", summary)
s.id++
return s.id, nil
}
func main() {
conn, err := dbus.SessionBus()
if err != nil {
panic(err)
}
reply, err := conn.RequestName("org.freedesktop.Notifications", dbus.NameFlagDoNotQueue)
if err != nil {
panic(err)
}
if reply != dbus.RequestNameReplyPrimaryOwner {
panic("Name already taken")
}
s := Server{ id: 0 }
conn.Export(s, "/org/freedesktop/Notifications", "org.freedesktop.Notifications")
select {}
}
Registering a service with Avahi using D-Bus:
package main
import (
"github.com/guelfey/go.dbus"
"log"
"bufio"
"os"
)
func main() {
var dconn *dbus.Conn
var obj *dbus.Object
var path dbus.ObjectPath
var err error
dconn, err = dbus.SystemBus()
if err != nil {
log.Fatal("Fatal error ", err.Error())
}
obj = dconn.Object("org.freedesktop.Avahi", "/")
obj.Call("org.freedesktop.Avahi.Server.EntryGroupNew", 0).Store(&path)
obj = dconn.Object("org.freedesktop.Avahi", path)
var AAY [][]byte
for _, s := range []string{"email=lemenkov@gmail.com", "jid=lemenkov@gmail.com", "status=avail"} {
AAY = append(AAY, []byte(s))
}
// http://www.dns-sd.org/ServiceTypes.html
obj.Call("org.freedesktop.Avahi.EntryGroup.AddService", 0,
int32(-1), // avahi.IF_UNSPEC
int32(-1), // avahi.PROTO_UNSPEC
uint32(0), // flags
"epmd@Hostname",// sname
"_epmd._tcp", // stype
"local", // sdomain
"work.local", // shost
uint16(4369), // port
AAY) // text record
obj.Call("org.freedesktop.Avahi.EntryGroup.Commit", 0)
// Wait for getch and exit
bufio.NewReader(os.Stdin).ReadString('\n')
}
Python D-Bus
Examples
C D-Bus
C
has three established D-Bus libraries:
libdbus
: shipped in the reference implementation of D-Bus, OOM.GDBus
: part of GLib, the low-level tool library of GNOME- sd-bus: Support for kdbus, better performance, shipped with
systemd
.
Examples
Using sd-bus
:
#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
int main(int argc, char *argv[]) {
sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus_message *m = NULL;
sd_bus *bus = NULL;
const char *path;
int r;
/* Connect to the system bus */
r = sd_bus_open_system(&bus);
if (r < 0) {
fprintf(stderr, "Failed to connect to system bus: %s\n", strerror(-r));
goto finish;
}
/* Issue the method call and store the respons message in m */
r = sd_bus_call_method(bus,
"org.freedesktop.systemd1", /* service to contact */
"/org/freedesktop/systemd1", /* object path */
"org.freedesktop.systemd1.Manager", /* interface name */
"StartUnit", /* method name */
&error, /* object to return error in */
&m, /* return message on success */
"ss", /* input signature */
"cups.service", /* first argument */
"replace"); /* second argument */
if (r < 0) {
fprintf(stderr, "Failed to issue method call: %s\n", error.message);
goto finish;
}
/* Parse the response message */
r = sd_bus_message_read(m, "o", &path);
if (r < 0) {
fprintf(stderr, "Failed to parse response message: %s\n", strerror(-r));
goto finish;
}
printf("Queued service job as %s.\n", path);
finish:
sd_bus_error_free(&error);
sd_bus_message_unref(m);
sd_bus_unref(bus);
return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
}
See Invoking a Method, from C, with sd-bus.
GDBus is a newer implementation.
Using GDBus:
/**
* Define enumerations for the different signals that we can generate
* (so that we can refer to them within the signals-array [below]
* using symbolic names). These are not the same as the signal name
* strings.
*
* NOTE: E_SIGNAL_COUNT is NOT a signal enum. We use it as a
* convenient constant giving the number of signals defined so
* far. It needs to be listed last.
*/
typedef enum {
E_SIGNAL_CHANGED_VALUE1,
E_SIGNAL_CHANGED_VALUE2,
E_SIGNAL_OUTOFRANGE_VALUE1,
E_SIGNAL_OUTOFRANGE_VALUE2,
E_SIGNAL_COUNT
} ValueSignalNumber;
/*... Listing cut for brevity ...*/
typedef struct {
/* The parent class state. */
GObjectClass parent;
/* The minimum number under which values will cause signals to be
emitted. */
gint thresholdMin;
/* The maximum number over which values will cause signals to be
emitted. */
gint thresholdMax;
/* Signals created for this class. */
guint signals[E_SIGNAL_COUNT];
} ValueObjectClass;
/*... Listing cut for brevity ...*/
/**
* Per class initializer
*
* Sets up the thresholds (-100 .. 100), creates the signals that we
* can emit from any object of this class and finally registers the
* type into the GLib/D-Bus wrapper so that it may add its own magic.
*/
static void value_object_class_init(ValueObjectClass* klass) {
/* Since all signals have the same prototype (each will get one
string as a parameter), we create them in a loop below. The only
difference between them is the index into the klass->signals
array, and the signal name.
Since the index goes from 0 to E_SIGNAL_COUNT-1, we just specify
the signal names into an array and iterate over it.
Note that the order here must correspond to the order of the
enumerations before. */
const gchar* signalNames[E_SIGNAL_COUNT] = {
SIGNAL_CHANGED_VALUE1,
SIGNAL_CHANGED_VALUE2,
SIGNAL_OUTOFRANGE_VALUE1,
SIGNAL_OUTOFRANGE_VALUE2 };
/* Loop variable */
int i;
dbg("Called");
g_assert(klass != NULL);
/* Setup sane minimums and maximums for the thresholds. There is no
way to change these afterwards (currently), so you can consider
them as constants. */
klass->thresholdMin = -100;
klass->thresholdMax = 100;
dbg("Creating signals");
/* Create the signals in one loop, since they all are similar
(except for the names). */
for (i = 0; i < E_SIGNAL_COUNT; i++) {
guint signalId;
/* Most of the time you will encounter the following code without
comments. This is why all the parameters are documented
directly below. */
signalId =
g_signal_new(signalNames[i], /* str name of the signal */
/* GType to which signal is bound to */
G_OBJECT_CLASS_TYPE(klass),
/* Combination of GSignalFlags which tell the
signal dispatch machinery how and when to
dispatch this signal. The most common is the
G_SIGNAL_RUN_LAST specification. */
G_SIGNAL_RUN_LAST,
/* Offset into the class structure for the type
function pointer. Since we're implementing a
simple class/type, we'll leave this at zero. */
0,
/* GSignalAccumulator to use. We don't need one. */
NULL,
/* User-data to pass to the accumulator. */
NULL,
/* Function to use to marshal the signal data into
the parameters of the signal call. Luckily for
us, GLib (GCClosure) already defines just the
function that we want for a signal handler that
we don't expect any return values (void) and
one that will accept one string as parameter
(besides the instance pointer and pointer to
user-data).
If no such function would exist, you would need
to create a new one (by using glib-genmarshal
tool). */
g_cclosure_marshal_VOID__STRING,
/* Return GType of the return value. The handler
does not return anything, so we use G_TYPE_NONE
to mark that. */
G_TYPE_NONE,
/* Number of parameter GTypes to follow. */
1,
/* GType(s) of the parameters. We only have one. */
G_TYPE_STRING);
/* Store the signal Id into the class state, so that we can use
it later. */
klass->signals[i] = signalId;
/* Proceed with the next signal creation. */
}
/* All signals created. */
dbg("Binding to GLib/D-Bus");
/*... Listing cut for brevity ...*/
}
Node D-Bus
D-Bus over Network
This tutorial explains how to use gabriel
to connect over the network using SSH
. Might be outdated.
There's a C
library, dbus-daemon-proxy, that can be used to proxy two machines over the network.
- D-Bus remote connection
- D-Bus Authentication And Authorization
- Connecting to D-Bus over TCP
- Using D-Bus for multiple machines over the internet