A trip into dbus-send

DBus is the interprocess communication mechanism used by the plumbing layer of Linux to allow the various components to use each others services without each of them needing to implement custom code for every other component. Even for a sysadmin its a fairly esoteric subject but it does help explain how another bit of linux works. However I’ve always found exploring it confusing and I tend to forget the hard won lessons soon afterwards. This tim eI decided to post my notes to the internet in the hope that next time I feel the need to explore the world of dbus I have a place to start.

Firstly there are two buses, a session bus that is individual to the users session and used heavily by gnome and other user software, and the system bus used by the plumbing layer.

Each bus has a number of services registered on it that can be listed with the dbus-send command by querying the DBus service itself.

dbus-send --system --print-reply --dest=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames

which produces an array of the services:††

   array [
      string "org.freedesktop.DBus"
    ...
      string "com.redhat.NewPrinterNotification"
      string "com.redhat.PrinterDriversInstaller"
      string "com.redhat.ifcfgrh1"
      string "fi.w1.wpa_supplicant1"
      string "org.fedoraproject.FirewallD1"
      string "org.freedesktop.Accounts"
      string "org.freedesktop.Avahi"
      string "org.freedesktop.ColorManager"
      string "org.freedesktop.ModemManager1"
      string "org.freedesktop.NetworkManager"
      string "org.freedesktop.PolicyKit1"
      string "org.freedesktop.RealtimeKit1"
      string "org.freedesktop.resolve1"
      string "org.freedesktop.systemd1"
      string "org.gnome.DisplayManager"
   ]

Each service implements at least one object, which has a number of interfaces, which are groups of methods and properties. Lots of different objects can implement the same interface, and so implement the same set of methods and properties this makes it easy to treat them all in a similar way. Two interfaces pretty much everything implements are the org.freedesktop.DBus.Introspectable interface which has a single method “Introspect” that returns a list of all the interfaces, methods, and properties the object implements. The other common interface is org.freedesktop.DBus.Properties which provides common methods to get and set all the properties of the object.

A dbus send command to invoke one of these methods looks like

dbus-send --system --print-reply --dest=[service] [objectname] [interface].[method] [parameters]

so a call to the org.freedesktop.systemd1 service listed in the output above needs the name of an object, but all services implement an object named similar to their service name except with / replacing dots giving /org/freedesktop/systemd1 and as discussed above all objects implement the org.freedesktop.DBus.Introspectable interface with its Introspect method, so we can call:

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1   org.freedesktop.DBus.Introspectable.Introspect

Which lists all the methods and properties available for systemd in an xml format, e.g.:

<interface name="org.freedesktop.systemd1.Manager">
  <method name="ClearJobs">
  </method>
  <method name="ResetFailed">
  </method>
  <method name="ListUnits">
   <arg type="a(ssssssouso)" direction="out"/>
  </method>
  <method name="ListUnitsFiltered">
   <arg type="as" direction="in"/>
   <arg type="a(ssssssouso)" direction="out"/>
  </method>
        ...
</interface>

If you’ve followed along so far it should then come as no surprise that you can run the ListUnits method and get a list of all the units managed by systemd:

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1   org.freedesktop.systemd1.Manager.ListUnits 
...
      struct {            
         string "ypbind.service"                                                                                       
         string "ypbind.service"                                                                                       
         string "not-found"                                                                                            
         string "inactive"
         string "dead"                                  
         string ""                                                                                                     
         object path "/org/freedesktop/systemd1/unit/ypbind_2eservice"                                                 
         uint32 0         
         string ""                                                                                                     
         object path "/"                                                                                               
      }         

(the 2e in the path is the ascii code for a period. the question of why I have ypbind installed on my laptop is for another day, but at least its not running)

This tells us that ypbind has its own object on the bus called /org/freedesktop/systemd1/unit/ypbind_2eservice, and indeed

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/ypbind_2eservice  org.freedesktop.DBus.Introspectable.Introspect

Lists its methods and properties because it too implements the org.freedesktop.DBus.Introspectable interface. The –dest option is still set to org.freedesktop.systemd1 because ybbind_2eservice is a child object of systemd1. But why was this not listed when we introspected the org.freedsktop.DBus object?

Objects within a service form a hierarchy, and introspecting any object will only list the direct child objects that belong to it. org.freedesktop.DBus has no child objects of its own and the ListNames method we used initially lists the top level services register on the bus, not all the objects they provide. So how then do we seek out these child objects?

When you introspect an object it not only lists the methods and properties of the object, but it also lists the names of the child objects or nodes that sit directly below it. We can see these by introspecting again:

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1   org.freedesktop.DBus.Introspectable.Introspect  | grep node
<node>
 <node name="unit"/>
 <node name="job"/>
</node>

We can then introspect these in turn

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit   org.freedesktop.DBus.Introspectable.Introspect  | grep node
...
 <node name="rpcbind_2eservice"/>                                                                                                                                                                                                             
 <node name="iscsi_2eservice"/>                                                                                                                                                                                                               
 <node name="rpc_2dstatd_2eservice"/>        
 <node name="ypbind_2eservice"/>

and as these are children of /org/freedesktop/systemd1/unit that gives us the /org/freedesktop/systemd1/unit/ypbind_2eservice object we saw above.

While listing objects is fun we really want to be able to send then messages over the bus, or in other words call their methods.

Pretty much every object on the bus implements the org.freedesktop.DBus.Properties.Get interface that provides methods to set and get parameters. In the introspection output above there were methods described like:

 <interface name="org.freedesktop.DBus.Properties">                                                                    
  <method name="Get">                                                                                                  
   <arg name="interface" direction="in" type="s"/>                                                                     
   <arg name="property" direction="in" type="s"/>                                                                      
   <arg name="value" direction="out" type="v"/>                                                                        
  </method>                                                                                                            
  <method name="GetAll">                                                                                               
   <arg name="interface" direction="in" type="s"/>                                                                     
   <arg name="properties" direction="out" type="a{sv}"/>                                                               
  </method>                                                                                                            
  <method name="Set">                                                                                                  
   <arg name="interface" direction="in" type="s"/>                                                                     
   <arg name="property" direction="in" type="s"/>                                                                      
   <arg name="value" direction="in" type="v"/>                                                                         
  </method> 
...
 <interface name="org.freedesktop.systemd1.Service">
  <property name="MainPID" type="u" access="read">
  </property>

Arguments with direction “in” obviously need to be passed in and those with direction “out” are the return values. Each parameter needs to have its data type appended to its value, and they are passed on the dbus-send command line in the order specified. So to get the value of the MainPID property we need to pass the interface and the property name, both strings (s):

# dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/docker_2dcontainerd_2eservice org.freedesktop.DBus.Properties.Get string:org.freedesktop.systemd1.Service string:MainPID

method return time=1561025611.053851 sender=:1.5 -> destination=:1.28678 serial=39522 reply_serial=2
   variant       uint32 22193

and we get back a variant (type=v), in this case the pid of the docker containerd process

# ps -ef | grep 22193

root     22193     1  0 10:02 ?        00:00:00 /usr/libexec/docker/docker-containerd-current --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m

Other methods let us start and stop the unit

  <method name="Start">
   <arg type="s" direction="in"/>
   <arg type="o" direction="out"/>
  </method>
  <method name="Restart">                                                                                              
   <arg type="s" direction="in"/>                                                                                      
   <arg type="o" direction="out"/>                                                                                     
  </method>

These parameters have no names given but a bit of trial and reading of error messages suggests that in “in” parameter is job-mode, described in the systemctl man page as controling “how to deal with already queued jobs”, with “replace” as a valid option. So we can restart the service simply by:

dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/docker_2dcontainerd_2eservice  org.freedesktop.systemd1.Unit.Restart string:replace

And we can now inspect the PID of the process again:

# dbus-send --system --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/docker_2dcontainerd_2eservice org.freedesktop.DBus.Properties.Get string:org.freedesktop.systemd1.Service string:MainPID

method return time=1561069031.851603 sender=:1.5 -> destination=:1.34460 serial=47532 reply_serial=2
   variant       uint32 31169

The process number is now reporting as 31169, and when we double check:

# ps -ef | grep 31169
root     31169     1  0 23:14 ?        00:00:00 /usr/libexec/docker/docker-containerd-current --listen unix:///run/containerd.sock --shim /usr/libexec/docker/docker-containerd-shim-current --start-timeout 2m

And we can see the pid of the docker service has changed after it was restarted.

And there we have it, how to explore and manipulate the system via dbus.

One final bonus, systemd can also be used to control the power state of the machine, is this the most verbose reboot command possible?

dbus-send --system --type=method_call --print-reply --dest=org.freedesktop.systemd1 /org/freedesktop/systemd1/unit/reboot_2etarget org.freedesktop.systemd1.Unit.Start string:replace-irreversibly

Leave a comment