mirror of
https://github.com/troglobit/finit.git
synced 2026-10-27 12:20:54 +00:00
libink: a message bus is not a peer
libink was written against the only bus it had, its own, where the peer on the other end is the client. A broker is not: it routes for senders it names itself, expects a DESTINATION on anything addressed through it, and answers on its own schedule rather than next. Runlevels go on the wire as S and N rather than the digits Finit keeps internally, since that is what a caller outside Finit means by one. The library stays a convenience library, linked into finit and initctl and installed nowhere: the ABI promise waits until libink is its own project. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
1 parent
6310d9e760
commit
b55dada80b
16 files changed
+340
-250
No files matched your search
@@ -3,6 +3,5 @@
|
||||
.dirstamp
|
||||
*.lo
|
||||
libink.*
|
||||
!libink.pc.in
|
||||
Makefile
|
||||
Makefile.in
|
||||
+7
-11
@@ -1,5 +1,10 @@
|
||||
# libink — brokerless D-Bus library (server + client), born inside Finit
|
||||
lib_LTLIBRARIES = libink.la
|
||||
#
|
||||
# Internal convenience library: linked statically into finit and
|
||||
# initctl, nothing installed. Public install (and the ABI promise
|
||||
# that comes with it) is deferred until libink is extracted into its
|
||||
# own project.
|
||||
noinst_LTLIBRARIES = libink.la
|
||||
libink_la_SOURCES = server.c auth.c connection.c \
|
||||
proto.c proto.h \
|
||||
marshal.c marshal.h \
|
||||
@@ -7,16 +12,7 @@ libink_la_SOURCES = server.c auth.c connection.c \
|
||||
match.c \
|
||||
path.c \
|
||||
client.c io.c \
|
||||
internal.h
|
||||
link.h path.h internal.h
|
||||
|
||||
libink_la_LDFLAGS = -version-info 0:0:0
|
||||
libink_la_CPPFLAGS = -D_GNU_SOURCE -D_DEFAULT_SOURCE -D_BSD_SOURCE
|
||||
libink_la_CFLAGS = -W -Wall -Wextra -Wno-unused-parameter -std=gnu99
|
||||
|
||||
# pkg-config support
|
||||
pkgconfigdir = $(libdir)/pkgconfig
|
||||
pkgconfig_DATA = libink.pc
|
||||
|
||||
# Public headers install to $(includedir)/ink/
|
||||
inkdir = $(includedir)/ink
|
||||
ink_HEADERS = link.h path.h
|
||||
@@ -0,0 +1,64 @@
|
||||
libink — brokerless D-Bus for Finit
|
||||
===================================
|
||||
|
||||
libink is a small C library implementing the [D-Bus wire protocol][spec],
|
||||
both the server and the client side, without a broker and without any
|
||||
dependency on `libdbus`, `sd-bus`, or GIO. It was born inside Finit to
|
||||
let PID 1 be a bus of its own: clients connect straight to the listening
|
||||
socket, authenticate with the standard SASL EXTERNAL handshake, and get
|
||||
kernel-authenticated credentials via `SO_PEERCRED`.
|
||||
|
||||
For what the bus exposes and how to talk to it, see the User Guide,
|
||||
[D-Bus Integration](../doc/dbus.md). This file covers the library
|
||||
itself.
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
libink is an internal implementation detail of Finit: built as a libtool
|
||||
convenience library, linked statically into `finit` and `initctl`,
|
||||
nothing installed. There is deliberately no ABI promise yet — that
|
||||
comes if/when libink is extracted into a project of its own. External
|
||||
D-Bus clients need none of this; the wire protocol is the compatibility
|
||||
surface, any standard D-Bus library works.
|
||||
|
||||
Layout
|
||||
------
|
||||
|
||||
| File | Contents |
|
||||
|-----------------|-------------------------------------------------------|
|
||||
| `server.c` | Listening socket, accept, peer credential capture |
|
||||
| `auth.c` | SASL EXTERNAL handshake, uid verification |
|
||||
| `connection.c` | Per-peer state machine, message framing |
|
||||
| `proto.c` | Wire header parse/build |
|
||||
| `marshal.c` | Body (de)marshalling: basic types, arrays, variants |
|
||||
| `dispatch.c` | Object tree, vtable registration, method dispatch |
|
||||
| `builtin.c` | `org.freedesktop.DBus.*` stock interfaces |
|
||||
| `match.c` | AddMatch/RemoveMatch rule parsing and signal filter |
|
||||
| `path.c` | systemd-style `_HH` object path encoding |
|
||||
| `client.c` | Outgoing connections, method calls, reply/signal wait |
|
||||
| `io.c` | Shared EINTR-resilient read/write loops |
|
||||
|
||||
Public API symbols carry the `link_*` prefix (`link.h`), internal ones
|
||||
`__*` (`internal.h`). Method handlers are registered as vtables of
|
||||
`link_method_t`/`link_property_t`; the framework emits variant
|
||||
signatures from the property table so the declared type is the single
|
||||
source of truth.
|
||||
|
||||
The boundary to Finit is deliberate: nothing under `libink/` includes a
|
||||
Finit header. All glue lives in `src/dbus.c` — object registration,
|
||||
signal emission from the service/condition/runlevel hook points, and
|
||||
the uev event loop bridge. `initctl` uses the client half of the same
|
||||
library, so one wire-format implementation serves both ends. If libink
|
||||
is ever spun out, that file is the cut line.
|
||||
|
||||
Testing
|
||||
-------
|
||||
|
||||
The `test/dbus-*.sh` suite exercises the library end to end against a
|
||||
live Finit in a namespace, driven by `test/src/dbus-auth-client.c`.
|
||||
Wire-format conformance against third-party tools (`dbus-send`,
|
||||
`dbus-monitor`) and fuzzing of the parsers are tracked as pre-merge
|
||||
work — this is PID 1's attack surface.
|
||||
|
||||
[spec]: https://dbus.freedesktop.org/doc/dbus-specification.html
|
||||
+44
-1
@@ -24,6 +24,7 @@
|
||||
struct link_client {
|
||||
int fd;
|
||||
uint32_t next_serial;
|
||||
const char *destination; /* not owned; NULL when brokerless */
|
||||
link_reply_t reply; /* most recent reply view (points into rxbuf) */
|
||||
/* Distinct from "reply.type == 0": LINK_MSG_INVALID is 0, which
|
||||
* is a wire-valid (if malformed) type, so we need an out-of-band
|
||||
@@ -87,6 +88,12 @@ link_client_t *link_client_open(const char *path)
|
||||
return link_client_open_timeout(path, 0);
|
||||
}
|
||||
|
||||
void link_client_set_destination(link_client_t *c, const char *destination)
|
||||
{
|
||||
if (c)
|
||||
c->destination = destination;
|
||||
}
|
||||
|
||||
void link_client_close(link_client_t *c)
|
||||
{
|
||||
if (!c)
|
||||
@@ -187,6 +194,41 @@ static void clear_reply(link_client_t *c)
|
||||
c->have_reply = 0;
|
||||
}
|
||||
|
||||
/* A broker interleaves traffic of its own with our replies: claiming a
|
||||
* name makes it emit NameAcquired, and it arrives before the reply to
|
||||
* the call that caused it. Read past anything that is not the reply
|
||||
* we are waiting for. On a brokerless link nothing is interleaved and
|
||||
* the first message read is always the one we want.
|
||||
*
|
||||
* Bounded so a chatty or hostile broker cannot stall PID 1 here; each
|
||||
* read is bounded in turn by SO_RCVTIMEO when the caller asked for a
|
||||
* timeout at open. */
|
||||
#define LINK_CALL_MAX_SKIP 16
|
||||
|
||||
static int read_reply(link_client_t *c, uint32_t serial)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LINK_CALL_MAX_SKIP; i++) {
|
||||
struct link_msg msg;
|
||||
|
||||
if (read_one(c, &msg) < 0)
|
||||
return -1;
|
||||
|
||||
/* Not a reply at all, or a reply to something else. */
|
||||
if (msg.type != LINK_MSG_METHOD_RETURN && msg.type != LINK_MSG_ERROR)
|
||||
continue;
|
||||
if (msg.reply_serial != serial)
|
||||
continue;
|
||||
|
||||
publish_reply(c, &msg);
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno = EPROTO;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Wait up to timeout_ms (-1 = forever) for one full inbound frame
|
||||
* and publish it. Returns 0 on success, 1 on timeout, -1 on error. */
|
||||
static int read_and_publish(link_client_t *c, int timeout_ms)
|
||||
@@ -235,6 +277,7 @@ int link_client_call(link_client_t *c,
|
||||
serial = c->next_serial++;
|
||||
hlen = __msg_build_method_call(hdr, sizeof(hdr), serial,
|
||||
obj_path, interface, member,
|
||||
c->destination,
|
||||
signature, (uint32_t)body_len);
|
||||
if (hlen < 0)
|
||||
return LINK_CALL_FAIL;
|
||||
@@ -244,7 +287,7 @@ int link_client_call(link_client_t *c,
|
||||
if (body_len > 0 && send_all(c->fd, body, body_len) < 0)
|
||||
return LINK_CALL_FAIL;
|
||||
|
||||
if (read_and_publish(c, -1) != 0)
|
||||
if (read_reply(c, serial) < 0)
|
||||
return LINK_CALL_FAIL;
|
||||
|
||||
if (c->reply.type == LINK_MSG_METHOD_RETURN)
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
prefix=@prefix@
|
||||
exec_prefix=@exec_prefix@
|
||||
libdir=@libdir@
|
||||
includedir=@includedir@
|
||||
|
||||
Name: libink
|
||||
Description: Brokerless D-Bus server library, born inside Finit
|
||||
Version: @PACKAGE_VERSION@
|
||||
Libs: -L${libdir} -link
|
||||
Cflags: -I${includedir}
|
||||
@@ -226,6 +226,13 @@ link_client_t *link_client_open_timeout(const char *path, int timeout_ms);
|
||||
|
||||
void link_client_close(link_client_t *c);
|
||||
|
||||
/* Address subsequent calls on `c` to a well-known name. Needed when
|
||||
* a broker routes the message, e.g. "org.freedesktop.DBus" to reach
|
||||
* the bus driver itself; a brokerless link has a single peer and
|
||||
* needs no destination, which is the default. `destination` is not
|
||||
* copied, so it must outlive the client. */
|
||||
void link_client_set_destination(link_client_t *c, const char *destination);
|
||||
|
||||
/* Detach the authenticated socket from the client and return the raw
|
||||
* fd; subsequent link_client_close on `c` is invalid because the
|
||||
* structure has already been freed. Used by callers (e.g. system-bus
|
||||
|
||||
@@ -354,6 +354,7 @@ ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *member,
|
||||
const char *destination,
|
||||
const char *signature,
|
||||
uint32_t body_len)
|
||||
{
|
||||
@@ -369,6 +370,9 @@ ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
||||
return -1;
|
||||
if (put_field_string(buf, cap, &off, LINK_HDR_MEMBER, 's', member) < 0)
|
||||
return -1;
|
||||
if (destination &&
|
||||
put_field_string(buf, cap, &off, LINK_HDR_DESTINATION, 's', destination) < 0)
|
||||
return -1;
|
||||
if (signature && *signature &&
|
||||
put_field_string(buf, cap, &off, LINK_HDR_SIGNATURE, 'g', signature) < 0)
|
||||
return -1;
|
||||
|
||||
+3
-1
@@ -89,12 +89,14 @@ ssize_t __msg_build_signal(uint8_t *buf, size_t cap,
|
||||
const char *member,
|
||||
const char *signature, uint32_t body_len);
|
||||
|
||||
/* Build a method-call header (client side). */
|
||||
/* Build a method-call header (client side). `destination` is NULL
|
||||
* when no broker routes the message. */
|
||||
ssize_t __msg_build_method_call(uint8_t *buf, size_t cap,
|
||||
uint32_t serial,
|
||||
const char *path,
|
||||
const char *interface,
|
||||
const char *member,
|
||||
const char *destination,
|
||||
const char *signature,
|
||||
uint32_t body_len);
|
||||
|
||||
|
||||
Reference in new issue
Block a user