> Second, D-Bus was replaced as the IPC protocol. Instead, a native fully asynchronous protocol that was inspired by Wayland — without the XML serialization part — was implemented over Unix-domain sockets. Taymans wanted a protocol that is simple and hard-realtime safe.
I'm surprised to read this; I was under the impression that D-Bus was the de jure path forward for interprocess communication like this. That's not to say I'm disappointed - the simpler, Unix-y style of domain sockets sounds much more in the style of what I hope for in a Linux service. I've written a little bit of D-Bus code and it always felt very ceremonial as opposed to "send bytes to this path".
Are there any discussions somewhere about this s/D-Bus/domain socket/ trend, which the article implies is a broader movement given Wayland's similar decision as well?
D-Bus isn't suitable for realtime, to get that to work would require additional changes within the D-Bus daemon to add realtime scheduling, and even with all that, it would still introduce latency because it requires an extra context switch from client -> dbus-daemon -> pipewire. Maybe they could have re-used the D-Bus wire format? That's the only bit that might have been suitable.
To this day I still don't understand why messages are routed through dbus-daemon instead of just using FD-passing to establish the p2p connection directly. I remember we were using D-Bus on WebOS @ Palm & a coworker rewrote the DBus internals (keeping the same API) to do just that & the performance win was significant (at least 10 years ago).
Among other things, pushing everything through the message bus allows for global message ordering, and security policies down to the individual message. Rewriting the internals would work in an embedded situation like that where every application is linking against the same version of libdbus, but that is not really the case on a desktop system, where there are multiple different D-Bus protocol implementations.
If applications have hard performance requirements, most D-Bus implementations do have support for sending peer-to-peer messages, but applications have to set up and manage the socket themselves.
I didn't mention those because in theory a lot of that could be done by the library, or done by the daemon before passing off the fd for a peer-to-peer connection. (If a connection dies, the library would transparently handle that by sending a request back to the daemon for another connection, etc) But of course another thing that having a message bus allows you to do is reduce the amount of fds that a client has to poll on to just one for the bus socket.
That sounds great! D-bus always seemed to me like a byzantine overcomplicated design, and i was pleasantly surprised when i saw Wayland protocol with its simple design.
Well, D-Bus was originally designed to solve the problem of... a message bus. So you can pass messages down the bus and multiple consumers can see it, you can call "into" other bus services as a kind of RPC, etc. Even today, there's no real alternative, natively-built solution to the message bus problem for Linux. There have been various proposals to solve this directly in Linux (e.g. multicast AF_UNIX, bus1, k-dbus) but they've all hit various snags or been rejected by upstream. It's something Linux has always really lacked as an IPC primitive. The biggest is multicast; as far as I know there's just no good way to write a message once and have it appear atomically for N listeners, without D-Bus...
Now, the more general model of moving towards "domain sockets" and doing things like giving handles to file descriptors by transporting them over sockets, etc can all be traced back to the ideas of "capability-oriented security". The idea behind capability oriented security is very simple: if you want to perform an operation on some object, you need a handle to that object. Easy!
For example, consider rmdir(2). It just takes a filepath. This isn't capability-secure, because it requires ambient authority: you simply refer to a thing by name and the kernel figures out if you have access, based on the filesystem permissions of the object. But this can lead to all kinds of huge ramifications; filesystem race conditions, for instance, almost always come down to exploiting ambient authority.
In contrast, in a capability oriented design, rmdir would take a file descriptor that pointed to a directory. And you can only produce or create this file descriptor either A) from a more general, permissive file descriptor or B) on behalf of someone else (e.g. a privileged program passes a file descriptor it created to you over a socket.... sound familiar, all of a sudden?) And this file descriptor is permanent, immutable, and cannot be turned into "another" descriptor of any kind that is more permissive. A file descriptor can only become "more restrictive" and never "more permissive" — a property called "capability monotonicity." You can extend this idea basically as much as you want. Capabilities (glorified file descriptors) can be extremely granular.
As an example, you might obtain a capability to your homedir (let's say every process, on startup, has such a capability.) Then you could turn that into a capability for access to `$HOME/tmp`. And from that, you could turn it into a read-only capability. And from that, you could turn it into a read-only capability for exactly one file. Now, you can hand that capability to, say, gzip as its input file. Gzip can now never read from any other file on the whole system, no matter if it was exploited or ran malicious code.
For the record, this kind of model is what Google Chrome used from the beginning. As an example, rendering processes in Chrome, the process that determines how to render a "thing" on the screen, don't actually talk to OpenGL contexts or your GPU at all; they actually write command buffers over sockets to a separate process that manages the context. Rendering logic is a browser is extremely security sensitive since it is based exactly on potentially untrusted input. (This might have changed over time, but I believe it was true at one point.)
There's one problem with capability oriented design: once you learn about it, everything else is obviously, painfully broken and inadequate. Because then you start realizing things like "Oh, my password manager could actually rm -rf my entire homedir or read my ssh key, and it shouldn't be able to do that, honestly" or "Why the hell can an exploit for zlib result in my whole system being compromised" and it's because our entire permission model for modern Unix is built on a 1970s model that had vastly different assumptions about how programs are composed to create a usable computing system.
In any case, Linux is moving more and more towards adopting a capability-based models for userspace. Such a design is absolutely necessary for a future where sandboxing is a key feature (Flatpak, AppImage, etc.) I think the kernel actually has enough features now to where you could reasonably write a userspace library, similar to libcapsicum for FreeBSD, which would allow you to program with this model quite easily.
Capabilities are an intensely underused design pattern.
> Well, D-Bus was originally designed to solve the problem of... a message bus.
I guess no one has ever told me what the "message bus problem" actually is. I get sending messages -- a very useful way of structuring computation, but why do I want a message _bus_?
I get wanting service discovery, but don't see why that means a bus.
I get wanting RPC, but don't see why that means a bus.
Heck, I don't even know what "have it appear atomically for N listeners" means if you have less than N CPUs or the listeners are scheduled separately. Or why that's a good thing. Did you just mean globally consistent ordering of all messages?
> The biggest is multicast; as far as I know there's just no good way to write a message once and have it appear atomically for N listeners, without D-Bus...
I once wrote a proof of concept that uses the file system to do this. Basically, writers write their message as a file to a directory that readers watch via inotify. When done in a RAM based file system like tmpfs, you need not even touch the disk. There are security and permission snags that I hadn't thought of and it may be difficult if not totally infeasible to work in production, but yeah... the file system is pretty much the traditional one-to-many communication channel.
If you use shared memory, then you can use interprocess futexes for signaling, no need for inotify. That's pretty much how posix mq_open is implemented.
I don't know of any discussions on it, but I like it. Client-server architectures seem like a Good Thing, and I'm growing to like the idea of a small handful of core "system busses" that can interoperate with each other.
The problem with these busses is that each hand rolls its own security primitives and policies.
This sort of thing is better handled by the kernel, with filesystem device file permissions. As a bonus, you save context switching into the bus userspace process on the fast path. So, “the unix way” is simpler, faster and more secure.
File permissions are completely insufficient to achieve the kind of design that PipeWire is aiming for. None of the problems outlined in the article regarding PulseAudio (e.g. the ability of applications to interfere or snoop on each other, or requesting code being loaded into a shared address space that have unlimited access) can be easily handled with file permissions at all. The model is simply not expressive enough; no amount of hand-wringing about context switching will change that. This is one of the first things addressed in the article and it's very simple to see how file permissions aren't good enough to solve it.
That won't work here, the design of pipewire is to allow for things like a confirmation dialog appearing when an application tries to use the microphone or webcam, the application can then get temporary access to the device. That is a security policy that isn't really easy to do with filesystem device file permissions.
I'm surprised to read this; I was under the impression that D-Bus was the de jure path forward for interprocess communication like this. That's not to say I'm disappointed - the simpler, Unix-y style of domain sockets sounds much more in the style of what I hope for in a Linux service. I've written a little bit of D-Bus code and it always felt very ceremonial as opposed to "send bytes to this path".
Are there any discussions somewhere about this s/D-Bus/domain socket/ trend, which the article implies is a broader movement given Wayland's similar decision as well?