Issue #3 January 2005

Get on D-BUS

Introduction

For the longest time Linux applications were islands unto themselves, unable to effectively communicate and cooperate except through primitive means of pipes, sockets, shared memory, and file systems. The problem with these mechanisms were that they could be used differently by different applications. If a connection was made, it was often only between a couple of application as the work needed to get the applications talking was often not worth the trouble. It would be like a country constructing ports that only fit one type of ship. Anyone could send a ship over the ocean to this port, but only those ships that were build to fit would have any success of docking. What was needed was a standard for ships to dock and a security protocol to make sure they were allowed to dock. Many solutions such as Corba, DCOP, SOAP, XML-RPC, and XPCOM sprung up during the years to address parts of the problem. Most of these solutions were either too hard to use or confined to specific groups of users or problem domains. One solution stands out as way to unite Linux from the kernel up to the desktop. D-BUS has the momentum and backing of a diverse group of users and is designed from the ground up for the purpose of easy to use communication between applications and also the OS. If you haven't already, it is time to get on D-BUS!

If you haven't already, it is time to get on the D-BUS!

What is D-BUS?

D-BUS is an IPC mechanism for sending and receiving messages across a common communications channel. It was started in 2002 by Havoc Pennington and Alex Larsson of Red Hat, Inc. and Anders Carlsson formerly of CodeFactory AB as part of the freedesktop.org project to standardize around one messaging platform for the desktop. Today it is rapidly moving towards 1.0 status with a few major changes in the works.

At the protocol level D-BUS can be used as a peer-to-peer message transport for applications to communicate directly with each other. The real power of D-BUS comes from the bus daemons which act as routers for messages. There are two standard buses that a developer can rely on always being around. These are the system bus and the session bus.

The system bus is a global daemon that any application running in any context can use as a transport. It is a single point where applications can export services that anyone can use. Only one system bus daemon can be run at a time.

The session bus is a bus local to the current user's session. It is used for communication between applications running within the same X session. For every login to X, a session bus daemon is started.

Anatomy of a message

D-BUS specifies a common binary format in which to transport information. This is commonly referred to as a message. Because it is a binary protocol, D-BUS messages incur low overhead when marshaling and dmarshling data. Messages consist of a two sections, the header and the body. The header contains the meta data for the message. This can include routing information and (planned for the future 1.0 release) the type signature for the data. The body contains the data being sent.

Each piece of data has a type code associated with it and is packed into the body accordingly. Some common types include bytes, 32 and 64 bit integers, doubles, and strings. On top of that, complex types such as arrays, dictionaries, and in the future, named structs can be encoded into a message. This allows communication between applications written in a wide range of languages with the ability to retain a common set of data structures. Right now data types are encoded with the data inside the body of the message in a type code, data, type code, data format. Work is currently being done to separate the types from the data, creating a type signature field in the header and making the body a pure data stream.

In the D-BUS world, messages are sent to objects, not applications. Applications themselves are free to register as many objects as they wish. A D-BUS object can be thought of like any other object in most programming language with the exception that they are pointed to not by memory addresses but by object paths. Object paths take the form of a string which looks similar to Unix file system paths. For example D-BUS exports the /org/freedesktop/DBus object.

Interfaces are also supported by D-BUS. They simply allow the same method name to be used more than once with the interface specifying which of those methods is actually invoked. Interfaces group methods and signals together and can be thought of as the type for the object instance. Interfaces are specified by period delimited strings. D-BUS exports the org.freedesktop.DBus interface.

To route the message to the correct recipient, objects and interfaces are not enough. For this we have the concept of services. A service is a unique location on the bus owned by an application. When a D-BUS application starts it registers one or more services that it will then own until it releases them. If another process tries to own a service that is already registered, it will get back an error message stating that the service is not available. Because of this uniqueness, services can be used not just as a way to route messages but also as a way to track the life cycle of an application. D-BUS supports activation of services. If a service is not registered when a object is invoked on it and auto-activation has been enabled, D-BUS will look into its listing of activate services to find a match and start the application. Upon termination of the application the D-BUS daemon will de-register any services associated with that process. It will also send a signal to anyone who is watching that the service has been closed, thereby allowing the listening application to react appropriately. Services are specified by a period delimited string similar to interface names. The service exported by D-BUS is org.freedesktop.DBus.

D-BUS objects are invoked through both methods and signals. D-BUS methods are like any other method in an object-oriented language. You invoke a method on an object directly by sending a method message to an object's interface on a service. The message may contain a list of parameters you wish to send to the method. A method can reply back both synchronously, where your program waits for a reply, or asynchronously where your program will be notified when a reply has been received. Methods do not have to send a reply back. Signals are simple notifications that an event has occurred. A signal is broadcast over the bus and therefor does not require a service to send to. Anyone listening for a particular signal will be notified when it is emitted.

There are a couple of other message types which are sent on the bus. These are method return messages and error messages. Method return messages are similar to method messages. They, however, do not have a method to invoke, and instead of parameter data being sent, return data is sent in the body of the message. Error messages are a special case of a method return message which return the error type and error string of the exception raised by the method call.

Now that you have read about the basics, let us look at some practical code for talking to the org.freedesktop.DBus service. Example 1, “Low level C ListServices example”, Example 2, “glib ListServices example”, and Example 3, “python ListServices example” show how to invoke the ListServices method using the lowlevel C, glib and python bindings. They are all modified from the D-BUS tutorial if you wish to read more about the code.


int
main (int argc, char **argv)
{
  DBusConnection *connection;
  DBusError error;
  DBusMessage *message;
  DBusMessage *reply;
  int reply_timeout;
  char **service_list;
  int service_list_len;
  int i;
  
  dbus_error_init (&error);

  connection = dbus_bus_get (DBUS_BUS_SYSTEM,
                               &error);
  if (connection == NULL)
    {
      fprintf(stderr, "Failed to open connection to bus: %s\n",
                  error.message);
      dbus_error_free (error);
      exit (1);
    }

  /* Construct the message */
  message = dbus_message_new_method_call ("org.freedesktop.DBus",       /*service*/
                                                                            "/org/freedesktop/DBus",     /*path*/
                                                                            "org.freedesktop.DBus",      /*interface*/
                                                                            "ListServices"); 

  /* Call ListServices method */
  reply_timeout = -1;   /*don't timeout*/
  reply = dbus_connection_send_with_reply_and_block (connection,
                                                                                           message, reply_timeout, 
                                                                                           &error);

  if (dbus_error_is_set (&error))
    {
      fprintf (stderr, "Error: %s\n",
                   error.message);
       exit (1);
    }

/* Extract the data from the reply */
 if (!dbus_message_get_args (reply, &error, 
                               DBUS_TYPE_ARRAY, &service_list,
                               DBUS_TYPE_INT32, &service_list_len,
                               DBUS_TYPE_INVALID))
   { 
      fprintf (stderr, "Failed to complete ListServices call: %s\n",
                  error.message);
      exit (1);
    }
  dbus_message_unref (reply);
  dbus_message_unref (message);

  /* Print the results */
 
  printf ("Services on the message bus:\n");
  i = 0;
  while (i < service_list_len)
    {
      assert (service_list[i] != NULL);
      printf ("  %s\n", service_list[i]);
      ++i;
    }
  assert (service_list[i] == NULL);

  for (i = 0; i < service_list_len; i++)
    free (service_list[i]);
  free (service_list);

  return 0;
}

Example 1. Low level C ListServices example

int
main (int argc, char **argv)
{
  DBusGConnection *connection;
  GError *error;
  DBusGProxy *proxy;
  DBusGPendingCall *call;
  char **service_list;
  int service_list_len;
  int i;
  
  g_type_init ();

  error = NULL;
  connection = dbus_g_bus_get (DBUS_BUS_SYSTEM,
                               &error);
  if (connection == NULL)
    {
      g_printerr ("Failed to open connection to bus: %s\n",
                  error->message);
      g_error_free (error);
      exit (1);
    }

  /* Create a proxy object for the "bus driver" (service org.freedesktop.DBus) */
  
  proxy = dbus_g_proxy_new_for_service (connection,
                                        "org.freedesktop.DBus",
                                        "/org/freedesktop/DBus",
                                        "org.freedesktop.DBus");

  /* Call ListServices method */
  
  call = dbus_g_proxy_begin_call (proxy, "ListServices", DBUS_TYPE_INVALID);

  error = NULL;
  if (!dbus_g_proxy_end_call (proxy, call, &error,
                              DBUS_TYPE_ARRAY, &service_list, 
			      DBUS_TYPE_INT32, &service_list_len,
                              DBUS_TYPE_INVALID))
    {
      g_printerr ("Failed to complete ListServices call: %s\n",
                  error->message);
      g_error_free (error);
      exit (1);
    }

  /* Print the results */
 
  g_print ("Services on the message bus:\n");
  i = 0;
  while (i < service_list_len)
    {
      g_assert (service_list[i] != NULL);
      g_print ("  %s\n", service_list[i]);
      ++i;
    }
  g_assert (service_list[i] == NULL);

  g_strfreev (service_list);

  return 0;
}

Example 2. glib ListServices example

import dbus
                                                                                
bus = dbus.SystemBus()
remote_service = bus.get_service("org.freedesktop.DBus")
remote_object = remote_service.get_object("/org/freedesktop/DBus",
                                          "org.freedesktop.DBus")
                                                                                
service_list = remote_object.ListServices()

print ("Services on the message bus:\n")                                                                         
for service in service_list:
	print service

Example 3. python ListServices example

Built-in security

As was stated earlier, security is a requirement for any solution that wants to become the standard for inter-application communications. D-BUS was designed with this in mind and has security built-in. Security policy is specified in XML files commonly placed in the /etc/dbus-1/system.d/ directory and loaded when D-BUS starts. The following NetworkManager.conf file serves as a nice example of D-BUS policy:


<!DOCTYPE busconfig PUBLIC
 "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
 "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
        <policy user="root">
                <allow own="org.freedesktop.NetworkManager"/>

                <allow send_destination="org.freedesktop.NetworkManager"/>
                <allow send_interface="org.freedesktop.NetworkManager"/>
        </policy>
        <policy at_console="true">
                <allow send_destination="org.freedesktop.NetworkManager"/>
                <allow send_interface="org.freedesktop.NetworkManager"/>
        </policy>
        <policy context="default">
                <deny own="org.freedesktop.NetworkManager"/>
                <deny send_destination="org.freedesktop.NetworkManager"/>
                <deny send_interface="org.freedesktop.NetworkManager"/>
        </policy>
</busconfig>

As can be seen, if no rules match, by default we deny anyone from owning or sending to the NetworkManager service and interface. As one can also see, any process running as the root user can own the service and send messages to it through the NetworkManager interface.

What is most interesting in this security policy is the at_console rule. This feature is Red Hat specific though any distro that uses the pam_console module can take advantage of the at_console rules. It can be generally assumed that anyone marked as a console user is physically sitting in front of the computer. For a certain class of applications we may wish to allow the console user to control functionality that would normally be reserved for the root user. An example of this is changing network configuration. The at_console rule allows a console user to invoke methods on the NetworkManager service which is running as the root user. This in turn allows a user who is physically at the computer to change which wired or wireless network they are connected to without typing in a root password. This might sound insecure at first but the truth is anyone physically at the computer has the power to do much more harmful things such as simply unplugging from the network. Restricting what can be changed through security policy and methods exported by the NetworkManager daemon mitigates the risk. As with anything security related, using the at_console rule to allow access to root functionality should be evaluated carefully on a case by case basis.

Here is a complete listing of allow and deny rules:


send_interface="interface_name"
send_member="method_or_signal_name" 
send_error="error_name" 
send_destination="service_name" 
send_type="method_call" | "method_return" | "signal" | "error" 
send_path="/path/name"

receive_interface="interface_name"
receive_member="method_or_signal_name" 
receive_error="error_name" 
receive_sender="service_name" 
receive_type="method_call" | "method_return" | "signal" | "error"
receive_path="/path/name"

send_requested_reply="true" | "false"
receive_requested_reply="true" | "false"

eavesdrop="true" | "false"

own="servicename"
user="username"
group="groupname"
at_console="true"|"false"

Even more security with a little help from a friend

In some instances the built-in security policies are not enough. This is why SELinux security contexts have been integrated into D-BUS. What SELinux brings to the table is the ability to restrict access to services by specific applications. For instance, we could add an SELinux policy to say that only the NetworkManagerNotification process can send messages to the NetworkManager service. Suppose we wanted to implement this. We must first associate the the NetworkManager service to an SELinux context. This is done in the D-BUS policy file:


<selinux>
	<associate own="org.freedesktop.NetworkManager" context="system_u:object_r:dbus_networkmanager_service_t"/>
</selinux>

You would then add SELinux rules to match up NetworkManagerNotification's context to the context we just associated with the NetworkManager service. More information about SELinux and D-BUS can be found in the dbus-daemon-1 man page in the D-BUS CVS module.

A conduit for making things just work

While D-BUS itself is just a generic IPC system, what makes it exciting are the things it is being used for today. One of the most exciting areas being exploited with D-BUS is hardware discovery and management. D-BUS allows us to unify the world of hardware management tools by allowing them to talk with each other and get information from a single ease to use source. By unifying the hardware layer we are able to deliver a much better experience to the user. The goal is to make hardware "just work" instead of making the user work to use their hardware.

One barrier to this that currently exists is the system/user barrier. This barrier exists for security reasons and was conceived at a time when most people were using shared systems and direct access to the system could be disastrous for the other users. In today's world, more and more people own their own computers. One of the ways to solve this problem was to prompt the user for the root password whenever a program needed access to the system. This proves to be tedious for the user and potentially a security risk as is suffers from being vulnerable to spoofing attacks or someone looking over a user's shoulder. With the new approach, D-BUS is used to expose carefully selected system functionality to the user. The built-in security of D-BUS takes over for the traditional Unix permissions. Unlike with password prompts, if a vulnerability is exploited with the D-BUS service only a subset of the system is exposed, which can be further limited by careful use of SELinux policies. With the password prompt, once an attacker is able to obtain root they have a run of the system. The less times a user has to enter the root password, the better. Another benefit of the user not having to use password dialogs is that to the user the system seems to just work where previously it needed user input.

Today there are a handful of applications in Fedora which utilize D-BUS to realize the Just Works design philosophy. As areas of need are identified more applications will be written or modified to utilize D-BUS and help make Linux easier to use.

HAL is the glue of the hardware layer. It is the single point where D-BUS aware applications can get information about hardware that exists on the computer. HAL knows when hardware has been added or removed and can gather information about the device from the kernel and other sources. It can also fire off scripts and send D-BUS signals whenever a change happens to the hardware of the system. (For more information about HAL, read the Desktop and hardware configuration article in this issue of Red Hat Magazine.)

For configuring printers in Fedora this is cups-config-daemon. It works in conjunction with the printer dialog in eggcups. When HAL detects a printer has been added, it calls a script to try and automatically configure the printer. If the printer can not be detected the GetUserDriver method is invoked on the /com/redhat/PrintDriverSelection object running as a service in eggcups. The eggcups code then looks to see if the user had previously configured the printer driver. If it has, cups-config-daemon is notified via a D-BUS call and the printer is configured. If not, a dialog is shown to the user so that they can select a printer that most closely matches the make and model of their printer. Once the user has selected a printer, the users choice is stored in gconf and cups-config-daemon is notified. The reason for storing printer driver configuration in the user session as opposed to the system is to accommodate systems with read only roots such as live CDs or the Stateless Linux project. Ideally the printer will always be auto-configured and the user will never have to see the driver dialog.

The gnome-volume-manager daemon is a policy daemon that runs in the session and whose sole purpose in life is to detect when media is added that the user can use. Such media include USB thumb drives, cameras, flash cards, blank CD-Rs, and audio CDs. Based on user configured policy, certain actions are taken depending on the type of media. For mass storage devices such as thumb drives, the media is mounted. Blank CD-Rs pop up the Nautilus CD burner, cameras invoke a photo viewing application, and audio CDs start up the CD player. It does this by watching for device added and device removed D-BUS signals from HAL and invoking policy code if a device which it cares about changes state.

NetworkManager is able to keep track of all network connections and automatically change networks based on link status. For instance, if HAL detects that the link to the wired network has gone down it will send a D-BUS signal that the property has changed on the device. NetworkManager will see this and ask HAL through D-BUS if there are any other devices that have a link. If so it will switch the network connection to use that device. Other applications can even use NetworkManager's D-BUS API to get information on devices and link status or even control those devices with ease. For more information about NetworkManager, read the NetworkManager article in this month's issue.

There are many other application that need to be written to get to the dream of having hardware just working. These are first generation applications that will only get better and serve as a template as we move into other areas in need of the same treatment. What is nice is we have proven it can be done, and with D-BUS the user experience of Linux can be improved in leaps and bounds.

A fresh new view on how applications are designed

D-BUS has given us a fresh new view on how applications can be designed. No longer are we constrained by islands of functionality. Common information can be placed in a D-BUS service that acts as a single definitive source, as we have seen with HAL, instead of application trying and mostly failing to gather that information on their own. We have also seen processes reach across the system/session barrier to make configuring hardware a breeze. We may even see applications in the future that are designed not as a single monolithic process but as a series of smaller D-BUS services that work together to form a larger whole.

Another area that is being researched as a use for D-BUS is security through process segregation. Headed up by Colin Walters, the imsep project aims to kill a whole class of security exploits, and they are using D-BUS to accomplish it. Image loaders are a complex mess of code. More than once a security alert has gone out over flaws in the way code such as that found in Mozilla and gtk+ loads images. In the case of Mozilla, images are loaded off the Internet, causing concerns for remote exploits that are able to take over a victim's computer simply by downloading an image off the Internet. While this attack vector is pretty hard to accomplish, imsep hopes to mitigating the risk even lower by locating the image loaders in a separate caged process that can decode images and send them raw to the applications over D-BUS. By separating out high risk code into their own processes, these bits of code can be further locked down using SELinux policy and other security measures such as running as an unprivileged user. Even if the code could be exploited, there isn't much it could do because it would be locked away from the rest of the application and the system. Though this code is not ready for mainstream use yet, it shows the potential for more secure designs using D-BUS. You can read more about imsep on the project's home page.

Integration is the name of the game

D-BUS integrates the system from the kernel up to the desktop. For a server system, segregation of the different levels made sense. It was the easy thing to do and server roles are pretty static and well defined for the most part. Desktops are different. Because the users of desktop systems are so diverse the system needs to be dynamic to serve the changing needs of its users. One minute a piece of hardware may exist, and the next the user might decide to pull it out.

While it was not feasible to embed D-BUS directly into the kernel, integrating D-BUS and the kernel has become a top priority for some. For this Robert Love came up with the kobject patch which is a simple event layer in the kernel that broadcasts over a netlink socket. Kay Sievers has a number of projects in relation to this including the kernel event system to D-BUS bridge.

By integrating the different layers, we will be able to have the whole system act as one unit and adapt to the relatively chaotic environment that desktop users tend to create on their computers. In theory all applications on the system should utilize D-BUS in some way. I can see D-BUS being used to track the running state of all the servers on a computer. If one goes down a service can automatically restart it or notify the user through the use of a notification service such as the one being developed by Mike Hern and Christian Hammond. These things can not happen unless applications are integrated with D-BUS. Integration is one of the things that will really set Linux desktops apart from other offering. Because of the nature of open source, the ability for all applications to be integrated through D-BUS in some significant way should be fairly easy, making the desktop and Linux in general much more useful. Applications no longer have to be an island unto themselves.

Because of the nature of open source, the ability for all applications to be integrated through D-BUS in some significant way should be fairly easy, making the desktop and Linux in general much more useful.

Problems we face

There are a number of problem we face when embracing D-BUS. There are problems with integration in that it is a huge undertaking and involves many different groups thinking differently about how they design their applications. Political problems have already arisen where some projects are taking a wait and see approach before embrace the use of D-BUS. Some resistance comes from those who think that D-BUS leads us away from how Unix has worked in the past. For D-BUS to become successful we need a critical mass of projects using it. Once that happens more and more projects will start to find it useful and integrate it.

Another huge problem with any IPC mechanism is keeping APIs standard. D-BUS is useless if projects publish non-compatible APIs every time they do a release since the sole purpose of D-BUS is to enable communication between different applications.

All of these are important problems but they are by no means unsurmountable. Integration has already started and many groups are looking at how they can use D-BUS in their project. D-BUS was actually created to bridge one of the biggest political hurdles that effect the Linux desktop today. D-BUS was designed with both the GNOME and KDE communities in mind, and both groups seem to be putting down their differences and embracing D-BUS as evidenced by bindings for both GNOME's glib base library and KDE's Qt base library. Because of this and the fact that many distributions are embracing D-BUS, critical mass can't be that far behind. And, as far as the API is concerned, there may be problems but it shouldn't be any worse than the problems faced by traditional shared libraries.

The future of D-BUS

1.0 is coming soon. When that happens D-BUS's own APIs will solidify. While not much of the API is expected to change there will be some changes needed to reflect the change in how we handle types and type signatures. It is now left up to application designers to figure out what the future of D-BUS has in store. D-BUS should become just another boring layer underneath the rest of the system. The real interesting parts should be the layers above it. That is where the innovation is going to happen. D-BUS will just be one of the transports to get us there.

Conclusion

You the reader should be using D-BUS in your applications today. Or, if you are not a programmer but know one, you should send them this article to read. D-BUS is an enabling technology. By itself it is useless, but integrated into applications the possibilities are endless. D-BUS helps break down the barriers and do things that were previously impossible or hard to do in Linux and it helps us do those things securely. The resources are there and the benefits are obvious. It is time to get on D-BUS.

Further reading

  • D-BUS project page — The project page for all things D-BUS. You will find the specification, API docs and a tutorial here.
  • Connect desktop apps using D-BUS — An article by Ross Burton for the IBM developerWorks website. A nice primer on how to use D-BUS.
  • SELinux project page — The project page for SE-Linux setup by the National Security Agency. SE-Linux is a core component of D-BUS's security architecture.

About the author

John Palmieri, a.k.a J5, is a member of the Core Desktop team at Red Hat. He is fed up with the little annoyances people must go through to make their computers useful and has declared that computers should Just Work. To accomplish this he gladly bites the bullet and gets his hands dirty in the lower layers of the Desktop so that others may enjoy a much improved computing experience. In his spare time, he enjoys skiboarding, rock climbing, aggressive inline skating and song writing on his beloved Martin acoustic.