dbus2 / busd Goto Github PK
View Code? Open in Web Editor NEWA D-Bus bus (broker) implementation based on zbus
License: Other
A D-Bus bus (broker) implementation based on zbus
License: Other
This is a clone of an issue filed by @poettering on dbus-broker (bus1/dbus-broker#259) 3 years ago. Copy&pasting verbatim:
I'd like to add support for a ".busname" concept in systemd, where people can declare a dbus service name to acquire as unit file, and it would list the full service name plus the matching policy. PID 1 would then parse that, and pass this to dbus-broker via some driver API, and get an fd back that can be used via socket activation.
The would fix a bunch of issues for us:
I'd suggest to drop the SASL and feature negotiation phase for sockets of this kind, i.e. sockets come readily set up for the main protocol of D-Bus. feature negotiation would have to take place in the unit files too, I guess.
Idea: dbus-broker's driver service would gain:
RegisterActivationService("sauaubas") → "has"
input:
s → name to acquire
au → list of uids that shall get access to this service
au → list of gids that shall get access to this service
b → boolean that decides if anyone else gets access
as → feature flags requested ("unix-fd", …)
output:
h → file handle of connection
as → agreed on feature flags ("unix-fd", …), subset of same array of input
@poettering Can you please confirm this will be useful still, with a 👍 reaction?
Following up on this long thread: https://fosstodon.org/@[email protected]/111002189225139704
The original proposal is here: https://bugs.freedesktop.org/show_bug.cgi?id=100344
And there's an ongoing discussion on the dbus issue tracker: https://gitlab.freedesktop.org/dbus/dbus/-/issues/171
This is required to eventually fix this issue: flatpak/xdg-desktop-portal#737
Some dbus applications (e.g.: xdg-desktop-portal) receive requests from other peers and need to know whether that peers is in a sandbox. In case that they are in a sandbox, they need to know a bit more information about it (specifically: sandbox_engine
, app_id
and instance_id
).
The following method would be used to pass a new fd to the broker. The broker would listen to new connections on this socket, and all client connections made through it would be tagged as belonging to the specified sandbox.
o.fd.DBus.Containers.AddContainerServer(
s: container_type, # e.g. "org.flatpak"
s: app_identifier, # e.g. "org.gnome.Recipes"
a{sv}: metadata, # defined by Flatpak in this case
h: socket_to_accept, # see link
h: ready_notification, # see link
h: close_notification, # see link
a(usos): rules, # see link, #Access control
) -> s: container_id
Some notes:
metadata
should not be included here; any metadata that the sandbox engine needs it should store on its own memory.instance_id
field here (and below). This would also align with the Wayland protocol which has the exact same purpose.rules
is probably out of scope; see my notes on ACL below.Aside from the above, other clients can use the following method to check whether they're receiving a message from a sandboxed peer:
o.fd.DBus.GetConnectionCredentials(s: bus_name) -> a{sv}
{
... all the current fields ...,
"ContainerID": <"47">,
"ContainerType": <"org.flatpak">,
"ContainerApp": <"org.gnome.Recipes">,
}
The original link issue (from 2017) also proposes including ACL in this scope. I'm not sure if this is still desirable, since this can be handled out-of-process in an xdg-dbus-proxy instance.
I found the performance is much worse than bus1 "dbus-broker" service on my OpenBMC platform "armv7-unknown-linux-gnueabihf", a 2-core SoC, the binary is built simply from below command:
~/workspace/zbus_broker/busd$ git log -1
commit ecfb268c3bb594f2a080df34d7214fd260f585fe (HEAD -> main, origin/main, origin/HEAD)
~/workspace/zbus_broker/busd$ cross build --release --target armv7-unknown-linux-gnueabihf
I modified the systemd/src/libsystemd/sd-bus/test-bus-benchmark.c test case, for it to run only 15000 round of IPCs.
For bus1 dbus-broker, I can get about 1050 round of IPC per second (but it will reach a system wide total bandwidth at 2100 round of IPC per second, if I run two test in parallel), but now I can get only 400 round of IPC per second on same platform (HW and SW):
root@xxxxxx:/tmp# time ./test-bus-benchmark
Total round: 15000
Copying/memfd are equally fast at 32768 bytes
real 0m38.844s
user 0m8.254s
sys 0m9.658s
Also, run two test in parallel, the performance also dropped like it's running on single thread(supposedly they shouldn't, as zbus support full parallelism, and I have a 2core SoC, and I can see the busd process is running in two threads from perf):
root@xxxx:/tmp# for i in {0..1}; do time /tmp/test-bus-benchmark & done
[2] 10105
[3] 10106
root@xxxx:/tmp# Total round: 15000
Copying/memfd are equally fast at 32768 bytes
real 1m15.207s
user 0m10.025s
sys 0m13.641s
Total round: 15000
Copying/memfd are equally fast at 32768 bytes
real 1m15.705s
user 0m10.562s
sys 0m13.344s
Please below source code for the benchmark test case.
/***
This file is part of systemd.
Copyright 2013 Lennart Poettering
systemd is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <assert.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/wait.h>
#include <sys/resource.h>
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdbool.h>
#include <sys/wait.h>
#include <unistd.h>
#include "sd-bus.h"
#include "alloc-util.h"
#include "bus-internal.h"
#include "constants.h"
#include "fd-util.h"
#include "missing_resource.h"
#include "string-util.h"
#include "time-util.h"
#define MAX_SIZE (16*1024)
static usec_t arg_loop_usec = 1000 * USEC_PER_MSEC;
typedef enum Type {
TYPE_LEGACY,
TYPE_DIRECT,
} Type;
static void server(sd_bus *b, size_t *result) {
int r;
for (;;) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
r = sd_bus_process(b, &m);
assert_se(r >= 0);
if (r == 0)
assert_se(sd_bus_wait(b, USEC_INFINITY) >= 0);
if (!m)
continue;
if (sd_bus_message_is_method_call(m, "benchmark.server", "Ping"))
assert_se(sd_bus_reply_method_return(m, NULL) >= 0);
else if (sd_bus_message_is_method_call(m, "benchmark.server", "Work")) {
const void *p;
size_t sz;
/* Make sure the mmap is mapped */
assert_se(sd_bus_message_read_array(m, 'y', &p, &sz) > 0);
r = sd_bus_reply_method_return(m, NULL);
assert_se(r >= 0);
} else if (sd_bus_message_is_method_call(m, "benchmark.server", "Exit")) {
uint64_t res;
assert_se(sd_bus_message_read(m, "t", &res) > 0);
*result = res;
return;
} else if (!sd_bus_message_is_signal(m, NULL, NULL))
assert_not_reached();
}
}
static int quit_callback(sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
bool *quit = userdata;
*quit = true;
return 1;
}
static void transaction(sd_bus *b, size_t sz, const char *server_name) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL, *reply = NULL;
uint8_t *p;
assert_se(sd_bus_message_new_method_call(b, &m, server_name, "/", "benchmark.server", "Work") >= 0);
assert_se(sd_bus_message_append_array_space(m, 'y', sz, (void**) &p) >= 0);
memset(p, 0x80, sz);
bool quit = false;
// assert_se(sd_bus_call(b, m, 0, NULL, &reply) >= 0);
assert_se(sd_bus_call_async(b, NULL, m, quit_callback, &quit, 2000 * USEC_PER_MSEC) >= 0);
while (!quit) {
int r = sd_bus_process(b, NULL);
assert_se(r >= 0);
if (r == 0)
assert_se(sd_bus_wait(b, 100) >= 0);
}
}
static void client_bisect(const char *address, const char *server_name) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL;
size_t lsize, rsize, csize;
sd_bus *b;
int r;
r = sd_bus_new(&b);
assert_se(r >= 0);
r = sd_bus_set_address(b, address);
assert_se(r >= 0);
r = sd_bus_start(b);
assert_se(r >= 0);
r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL);
assert_se(r >= 0);
lsize = 1;
rsize = MAX_SIZE;
b->use_memfd = 0;
unsigned total_round = 0;
for (csize = 1; csize <= MAX_SIZE; csize *= 2) {
usec_t t;
unsigned n_memfd;
for (n_memfd = 0; n_memfd < 1000; n_memfd++) {
transaction(b, csize, server_name);
}
total_round += n_memfd;
}
printf("Total round: %u\n", total_round);
b->use_memfd = 1;
assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0);
assert_se(sd_bus_message_append(x, "t", csize) >= 0);
assert_se(sd_bus_send(b, x, NULL) >= 0);
sd_bus_unref(b);
}
static void client_chart(Type type, const char *address, const char *server_name, int fd) {
_cleanup_(sd_bus_message_unrefp) sd_bus_message *x = NULL;
size_t csize = 16;
sd_bus *b;
int r;
r = sd_bus_new(&b);
assert_se(r >= 0);
if (type == TYPE_DIRECT) {
r = sd_bus_set_fd(b, fd, fd);
assert_se(r >= 0);
} else {
r = sd_bus_set_address(b, address);
assert_se(r >= 0);
r = sd_bus_set_bus_client(b, true);
assert_se(r >= 0);
}
r = sd_bus_start(b);
assert_se(r >= 0);
r = sd_bus_call_method(b, server_name, "/", "benchmark.server", "Ping", NULL, NULL, NULL);
assert_se(r >= 0);
b->use_memfd = 0;
unsigned total_round = 0;
for (csize = 1; csize <= MAX_SIZE; csize *= 2) {
usec_t t;
unsigned n_memfd;
for (n_memfd = 0; n_memfd < 1000; n_memfd++) {
transaction(b, csize, server_name);
}
total_round += n_memfd;
}
printf("Total round: %u\n", total_round);
b->use_memfd = 1;
assert_se(sd_bus_message_new_method_call(b, &x, server_name, "/", "benchmark.server", "Exit") >= 0);
assert_se(sd_bus_message_append(x, "t", csize) >= 0);
assert_se(sd_bus_send(b, x, NULL) >= 0);
sd_bus_unref(b);
}
int main(int argc, char *argv[]) {
enum {
MODE_BISECT,
MODE_CHART,
} mode = MODE_BISECT;
Type type = TYPE_LEGACY;
int i, pair[2] = { -1, -1 };
_cleanup_free_ char *name = NULL, *bus_name = NULL, *address = NULL, *server_name = NULL;
_cleanup_close_ int bus_ref = -1;
const char *unique;
cpu_set_t cpuset;
size_t result;
sd_bus *b;
pid_t pid;
int r;
for (i = 1; i < argc; i++) {
if (streq(argv[i], "chart")) {
mode = MODE_CHART;
continue;
} else if (streq(argv[i], "legacy")) {
type = TYPE_LEGACY;
continue;
} else if (streq(argv[i], "direct")) {
type = TYPE_DIRECT;
continue;
}
assert_se(parse_sec(argv[i], &arg_loop_usec) >= 0);
}
assert_se(arg_loop_usec > 0);
if (type == TYPE_LEGACY) {
const char *e;
e = secure_getenv("DBUS_SESSION_BUS_ADDRESS");
assert_se(e);
address = strdup(e);
assert_se(address);
}
r = sd_bus_new(&b);
assert_se(r >= 0);
if (type == TYPE_DIRECT) {
assert_se(socketpair(AF_UNIX, SOCK_STREAM, 0, pair) >= 0);
r = sd_bus_set_fd(b, pair[0], pair[0]);
assert_se(r >= 0);
r = sd_bus_set_server(b, true, SD_ID128_NULL);
assert_se(r >= 0);
} else {
r = sd_bus_set_address(b, address);
assert_se(r >= 0);
r = sd_bus_set_bus_client(b, true);
assert_se(r >= 0);
}
r = sd_bus_start(b);
assert_se(r >= 0);
if (type != TYPE_DIRECT) {
r = sd_bus_get_unique_name(b, &unique);
assert_se(r >= 0);
server_name = strdup(unique);
assert_se(server_name);
}
sync();
setpriority(PRIO_PROCESS, 0, -19);
pid = fork();
assert_se(pid >= 0);
if (pid == 0) {
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
safe_close(bus_ref);
sd_bus_unref(b);
switch (mode) {
case MODE_BISECT:
client_bisect(address, server_name);
break;
case MODE_CHART:
client_chart(type, address, server_name, pair[1]);
break;
}
_exit(0);
}
CPU_ZERO(&cpuset);
CPU_SET(1, &cpuset);
pthread_setaffinity_np(pthread_self(), sizeof(cpu_set_t), &cpuset);
server(b, &result);
if (mode == MODE_BISECT)
printf("Copying/memfd are equally fast at %zu bytes\n", result);
assert_se(waitpid(pid, NULL, 0) == pid);
safe_close(pair[1]);
sd_bus_unref(b);
return 0;
}
The Container1 interface proposed in #34 makes it possible to create new sockets which uniquely identify a container app instance. The only difference to the main socket is that connections from it are authenticated with a (sandbox engine, app id, app instance) triple which can be queried by services.
This is enough to get rid of a bunch of code in xdg-desktop-portal which tries to collect this authentication manually for flatpak and snap.
There are two other problems that can be solved by extending this Container1 interface:
Flatpak applications currently rely on xdg-dbus-proxy to restrict D-Bus access in addition to the usual D-Bus access control. By default almost no access is allowed and rules are added to broaden the access. Details are documented here https://github.com/flatpak/xdg-dbus-proxy/blob/8d87ac1664b42d1e73f370e4dd84e9c13d5e6108/flatpak-proxy.c#L49. This static access policy could be moved to the D-Bus broker itself to get rid of this proxy.
With the static access policy in place client to client communication becomes hard. Any app cannot by default talk to any other app because of the static access policy. If access to a bus name is specified in the flatpak manifest of an app, flatpak will create a rule to allow the access. Applications like firefox have addons which might want to talk to a particular bus name. It's impossible to enumerate all the bus names in the firefox flatpak manifest.
We need a better way to let flatpak apps talk to other apps.
Just giving access to all bus names owned by a flatpak app isn't a good idea: the flatpak app might not be aware that it's a flatpak app and that potentially malicious flatpak apps might talk to it. For example, if a password manager exposes a service to read the passwords and we expose this bus name to all flatpak applications, we've given all applications all the passwords.
We can only expose bus names when the application understands that potentially malicious actors are accessing it. For example, if the password manager exposes a service to all flatpak apps, it should first ask the user to authorize access by the applications which wants to use the service.
We need a mechanism for applications to opt-into exposing their bus name to all other, potentially malicious, applications.
One solution would be an interface for an app to own a bus name where a flag can be provided so that potentially malicious actors can connect to it. The static access policy would be extended by a flag which would allow connections on this socket to talk with bus names for which the previously mentioned flag was provided.
Hello, I've found ur project yesterday and I am wondering if it's possible to replace dbus-daemon completely with this thing? how fast is it than dbus-broker? is there support of systemd integration?
busd will have to implement a policy. However, I don't think we need to go as far as existing implementations. Most of the policies are not super useful. More specifically, we'll want service-level policy but not method-level. Admins can specify which names can be owned by which users and who can make calls to them. In the D-Bus configuration XML language, this would mean enable configurations like:
<allow send_destination="org.gnome.DisplayManager"/>
and not supporting send_interface
or send_member
:
<allow send_destination="org.gnome.DisplayManager"/>
send_interface="org.gnome.DisplayManager.Manager"/>
send_member="GetRemoteHostname"/>
We should not error out on encountering unsupported configuration nodes though, but rather just warn about them.
Prerequisites: #78.
busd already works on Windows so it would be great for clients to be able to easily connect to it, w/o having to re-export the DBUS_SESSION_BUS_ADDRESS
env.
Readiness notification allows a daemon to notify the service manager when it has finalised its start-up sequence. This is very important so the service manager can handle dependencies properly.
In particular, when a service depends on dbus, it needs to be started after the broker has started listening on the configured address, and not merely after the dbus broker process has started.
s6 has a very well though out (yet also extremely simple) readiness notificationmechanism: the service manager hands over a file descriptor to a service, and the service is expected write \n
to indicate that it is up and listening.
If the file descriptor is closed without anything being written to it, it is assumed that the service filed to start properly.
systemd uses a slightly different approach, and passes a socket where it expects the service to write READY=1\n
when it is ready to accept connections (it also reuses this channel for other, unrelated communication).
The swaylock
developers have figured out an approach that works well with both s6 and system: swaywm/swaylock#281. In essence, the service takes a file descriptor (parametrised via --ready-fd N
), writes READY=1\n
into it, and then closes it. This fulfils the expectation for both systemd and s6. (in the case of swaylock
, it is common to want to suspend the system after it has been locked, so this is also used for ordering dependencies).
To summarise the idea: busd
could be started with something like busd --ready-fd 3
and it would write READY=1\n
to file descriptor 3 when it is ready to receive incoming connections, indicting to the service manager that dependant services can now be started.
To be a drop-in replacement for the existing bus implementations, we need to be able to parse D-Bus XML configuration. Moreover, we should provide default files with easy method to install them as well (#77 should help with that).
@poettering needs this in systemd.
There is an error with this repository's Renovate configuration that needs to be fixed. As a precaution, Renovate will stop PRs until it is resolved.
Location: .github/renovate.json
Error type: The renovate configuration file contains some invalid settings
Message: packageRules[3]: Each packageRule must contain at least one match* or exclude* selector. Rule: {"rebaseWhen":"conflicted"}
This is what will likely be expected of a modern system service, especially from package managers and/or folks who are used to building existing bus implementations themselves for any reason.
This will complicate the build itself. We'll need to figure out if we want to go the usual hacks to call Cargo from meson or use the latest cargo support in meson.
OTOH it will make it very easy to (un)install the binary and auxiliary data files.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.