linux_course_doc/modules/qualifying/learning_dbus.md

12 KiB

dbus

From our system administrator's point of view, dbus is a bit like systemd. Most of the time we're unaware of it's existence but it's an essential part of most modern Linux distributions. But what is it and what does it do?

The plain truth is that dbus is an inter process communication bus or IPC. Each program of server we launch is an independent process that is managed by our operating system. It will claim the necessary memory it needs to run, to store data and variables, but this chunk of memory is exclusive to the process. No other processes should be able to access this memory and data. But how can programs talk to each other if they need to? This is where dbus comes on the scene.

dbus

Dbus allows for programs to expose variables and method, functions the program can execute, to the other program connected to dbus. The program exposing has full control over what it will do when methods are called or variables accessed. Dbus is in charge of connecting and delivering what it's client demand of each other. While this sounds simple, it does this with grate care and precision. The dbus specification states that:

D-Bus is a system for low-overhead, easy to use interprocess communication (IPC). In more detail:

  • D-Bus is low-overhead because it uses a binary protocol, and does not have to convert to and from a text format such as XML. Because D-Bus is intended for potentially high-resolution same-machine IPC, not primarily for Internet IPC, this is an interesting optimization. D-Bus is also designed to avoid round trips and allow asynchronous operation, much like the X protocol.
  • D-Bus is easy to use because it works in terms of messages rather than byte streams, and automatically handles a lot of the hard IPC issues. Also, the D-Bus library is designed to be wrapped in a way that lets developers use their framework's existing object/type system, rather than learning a new one specifically for IPC.

Two different busses

On most machines you'll encounter two different, and independent, busses.

  • a system bus used by the system to communicate
  • a session bus (per logged in user) for programs run by the user

This separation is needed from both practical and security stand point. Practically speaking I want to control my VLC player and not some other one. From a security point of view unprivileged users should not be allowed to circumvent permissions by hopping on the system bus and triggering all sorts of things they are not allowed to!

Let's have a look at what's running on my home computer. We clearly see two independent busses running.

➜  ~ git:(master) ✗ ps aux | grep dbus         
message+     548  0.0  0.0   8864  5192 ?        Ss   Aug28   0:06 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
waldek      1780  0.0  0.0   8628  5136 ?        Ss   Aug28   0:01 /usr/bin/dbus-daemon --session --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
waldek    799748  0.0  0.0   6316   656 pts/2    S+   20:42   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox dbus
➜  ~ git:(master)

Now on a server. Where we only see one bus which is the system bus. This server is headless, so no graphical login is possible.

➜  ~ ps aux | grep dbus
message+   596  0.0  0.1   8956  3744 ?        Ss   Mar22  29:09 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
waldek    2982  0.0  0.0   6144   824 pts/0    S+   18:44   0:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox dbus
➜  ~ 

Both PID's are in the same ballpark number wise and on the lower end of the spectrum. This probably means the dbus system bus process is started at boot. Let's find out a bit more about it. As it's a program running at boot, it's probably started by systemd.

➜  ~ sudo systemctl list-dependencies dbus.service --reverse
dbus.service
● └─multi-user.target
●   └─graphical.target
➜  ~ 

Here we can see that dbus is started when the computer reaches the multi-user.target runlevel. If we would reboot in single user mode, or rescue.target, dbus would not be launched! What about the user bus? We don't see any specific service files for that one? Let's have a look at the systemctl --user output.

➜  ~ git:(master) ✗ systemctl --user --type=service | grep dbus
  dbus.service                        loaded active running D-Bus User Message Bus
➜  ~ git:(master)
➜  ~ git:(master) ✗ systemctl --user list-dependencies dbus.service 
dbus.service
● ├─app.slice
● ├─dbus.socket
● └─basic.target
●   ├─paths.target
●   ├─sockets.target
●   │ ├─dbus.socket
●   │ ├─dirmngr.socket
●   │ ├─gpg-agent-browser.socket
●   │ ├─gpg-agent-extra.socket
●   │ ├─gpg-agent-ssh.socket
●   │ ├─gpg-agent.socket
●   │ ├─pipewire.socket
●   │ ├─pk-debconf-helper.socket
●   │ └─pulseaudio.socket
●   └─timers.target
➜  ~ git:(master)
➜  ~ git:(master) ✗ systemctl --user list-dependencies dbus.service --reverse      
dbus.service
➜  ~ git:(master)

Inspecting the bus

Graphical

When you're in a graphical environment the easiest tool to understand what is exposed on the bus is d-feet. To see what is going on on the bus you're better off with bustle. Let's install both and open up d-feet first. We can do all of this in the command line as well, but first we'll have a look at the graphical applications. For an easy demonstration I would like you to install vlc as well.

➜  ~ git:(master) ✗ sudo apt install d-feet bustle vlc
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
bustle is already the newest version (0.8.0-1).
d-feet is already the newest version (0.3.15-3).
vlc is already the newest version (3.0.16-1).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
➜  ~ git:(master)

Open up d-feet and vlc and search for vlc on the session bus. You should see something very similar to the screenshot below. Open a video or audio file in vlc and try to pause it from d-feet. Have a look at some properties and some methods. What would the signals be?

d-feet

Now keep both vlc and d-feet open and we'll add bustle to the mix. Bustle can record events on either the system or your session bus. As we're using vlc for the demonstration go ahead and record the session bus. Try to pause and play your video and try to spit the flow of operations.

bustle

From the command line

Dbus comes with it's own command line tools to interact with programs exposed on the bus. Let's have a look at them. First up is dbus-monitor and let's see it's arguments.

➜  ~ git:(master) ✗ dbus-monitor --help  
Usage: dbus-monitor [--system | --session | --address ADDRESS] [--monitor | --profile | --pcap | --binary ] [watch expressions]
➜  ~ git:(master)

The most general way to invoke it is with either --system or --session. We've already looked at the session bus so let's now look at the system bus. Let's one op one terminal, run sudo dbus-monitor --system and keep it running. In a second terminal stop one of your running services, such as ssh. You'll see a bunch of messages appearing in the monitor terminal. Start the service back up. Do you understand what is happening here? Try and visualize both terminals at the same time as in the screenshot below. Try some tabcomplete and see what is happening.

tab complete

Sending messages

We can send things over dbus ourselves with the dbus-send program but first we'll use a special program that's very handy to send messages to the notification program of our desktop environment. It's called notify-send and you can install it by installing the libnotify-bin package ins Debian. Once it's installed try it out as follows.

➜  ~ git:(master) ✗ notify-send "hello"
➜  ~ git:(master) ✗ 

It immediately return but a notification should have popped up on your screen. You can use this from within bash scripts to signal the user about updates or errors. But let's dig a little deeper to see what's happening on the inside. For this we need to inspect the session bus and post a message again. Look for your message and observe the method that was called. It should look similar to the one below.

method call time=1630874537.889652 sender=:1.544 -> destination=:1.52 serial=7 path=/org/freedesktop/Notifications; interface=org.freedesktop.Notifications; member=Notify
   string "notify-send"
   uint32 0
   string "dialog-information"
   string "Hello world!"
   string "I am a notification..."
   array [
   ]
   array [
      dict entry(
         string "urgency"
         variant             byte 1
      )
   ]
   int32 -1

With this information we can construct a raw dbus-send command ourselves (but we'll run into a problem!). Let's try and map the usage to what we observed above. Luckily dbus-send does autocomplete! But we can take d-feet on the side to better understand the structure.

➜  ~ git:(master) ✗ dbus-send --help
Usage: dbus-send [--help] [--system | --session | --bus=ADDRESS | --peer=ADDRESS] [--dest=NAME] [--type=TYPE] [--print-reply[=literal]] [--reply-timeout=MSEC] <destination object path> <message name> [contents ...]
➜  ~ git:(master) ✗ dbus-send --session --print-reply --dest=org.freedesktop.Notifications /org/freedesktop/Notifications org.freedesktop.Notifications.Notify

We need to send a message to:

  • --session bus
  • we want to see the reply, or error, if any --print-reply
  • our destination is --dest the notifications
  • at this destination we only have one object /org/freedesktop/Notifications
  • on this object we can call the org.freedesktop.Notifications.Notify method

But we run in to an error! (that's why we want the --print-reply argument)

Error org.freedesktop.DBus.Error.InvalidArgs: Type of message, “()”, does not match expected type(susssasa{sv}i)

The Notify method needs arguments and quite a lot of them! In d-feet or our dbus-monitor output above we can visualize what these arguments are. The type in the error message looks abstract but when we put it side by side with our dbus-monitor output it starts to make sense.

string "notify-send"
uint32 0
string "dialog-information"
string "Hello world!"
string "I am a notification..."
array [
]
array [
   dict entry(
      string "urgency"
      variant             byte 1
   )
]
int32 -1

Dbus can send only specific types over the wire and they are described in the specification online. We could try to get get to work on the command line but sadly it won't! dbus-send and not send the variant type in an array so we're out of luck (I might be wrong here!). An alternative program that can complete this challenge is gdbus. Try to get that one to display a notification!

Let's try to interact with vlc via dbus-send instead. Below are some examples that you'll have to tweak to suit your needs.

dbus-send --session --print-reply=literal --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Pause 
dbus-send --session --print-reply=literal --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Play
dbus-send --session --print-reply=literal --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.SetPosition objpath:/org/videolan/vlc/playlist/3 int64:100000000
dbus-send --session --print-reply=literal --dest=org.mpris.MediaPlayer2.vlc /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.OpenUri string:"file:///home/waldek/Coralis_MASTER+VO-V5.mp4"