makes the functionality implemented by sd-varlink available from the command line.</para>
</refsect1>
+ <refsect1>
+ <title>Recognizing Varlink Socket Inodes</title>
+
+ <para>On kernels that support extended attributes on socket inodes (available since Linux 7.0),
+ sd-varlink automatically tags the <constant>AF_UNIX</constant> socket inodes it creates and manages with
+ the <varname>user.varlink</varname> extended attribute. The attribute records the role the socket plays
+ in the Varlink communication, allowing other processes to discover and classify Varlink sockets, for
+ example via <command>varlinkctl list-sockets</command> (see
+ <citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>).</para>
+
+ <para>Four distinct roles are defined:</para>
+
+ <table>
+ <title><varname>user.varlink</varname> extended attribute roles</title>
+
+ <tgroup cols='2'>
+ <colspec colname='value' />
+ <colspec colname='meaning' />
+ <thead>
+ <row>
+ <entry>Attribute value</entry>
+ <entry>Meaning</entry>
+ </row>
+ </thead>
+ <tbody>
+ <row>
+ <entry><literal>client</literal></entry>
+ <entry>Set on the connecting end of a connection, i.e. a socket created via <function>socket()</function> + <function>connect()</function>. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
+ </row>
+ <row>
+ <entry><literal>server</literal></entry>
+ <entry>Set on the serving end of a connection, i.e. a socket obtained via <function>accept()</function> on a listening socket. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
+ </row>
+ <row>
+ <entry><literal>listen</literal></entry>
+ <entry>Set on a listening socket, i.e. a socket created via <function>socket()</function> + <function>listen()</function>. Only for inodes on the anonymous socket file system (<literal>sockfs</literal>).</entry>
+ </row>
+ <row>
+ <entry><literal>entrypoint</literal></entry>
+ <entry>Set on the entrypoint socket inode, i.e. the socket node bound into the regular file system that clients connect to. Unlike the other three, this attribute lives on a real file-system inode (not on <literal>sockfs</literal>) and is hence visible to any process that can resolve the socket path.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>Note that for socket activated Varlink services it is recommended to set these extended attributes
+ via <varname>XAttrEntryPoint=</varname>, <varname>XAttrListen=</varname> and
+ <varname>XAttrAccept=</varname> settings on the socket units, see
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details. Specifically, for socket units for which <varname>Accept=no</varname> is set:</para>
+
+ <programlisting>…
+[Socket]
+SocketMode=0644
+XAttrEntryPoint=user.varlink=entrypoint
+XAttrListen=user.varlink=listen
+…</programlisting>
+
+ <para>For socket units which have <varname>Accept=yes</varname>:</para>
+
+ <programlisting>…
+[Socket]
+Accept=yes
+SocketMode=0644
+XAttrEntryPoint=user.varlink=entrypoint
+XAttrListen=user.varlink=listen
+XAttrAccept=user.varlink=server
+…</programlisting>
+
+ <para>Note that for Varlink sockets that shall not be world-accessible it is recommended to unset the
+ relevant "w" bits in the socket access mode, but to keep the relevant "r" bits set. As the access check
+ done by <function>connect()</function> looks for the "w" bits, this does not make the service
+ unnecessarily accessible, however it has the benefit that the extended attributes that identify the
+ socket as Varlink-related may still be read by other users (as that's restricted by the "r" bit). Or in
+ other words, it's recommended to set <literal>SocketMode=0644</literal> for services that shall not be
+ accessible to arbitrary users, rather than <literal>SocketMode=0600</literal>.</para>
+ </refsect1>
+
<xi:include href="libsystemd-pkgconfig.xml" />
<refsect1>
<member><citerefentry><refentrytitle>sd-event</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd-json</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>varlinkctl</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
+ <member><citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry></member>
<member><citerefentry><refentrytitle>sd-bus</refentrytitle><manvolnum>3</manvolnum></citerefentry></member>
<member><citerefentry project='die-net'><refentrytitle>pkg-config</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
</simplelist></para>
#include "varlink-io.systemd.h"
#include "varlink-org.varlink.service.h"
#include "varlink-util.h"
+#include "xattr-util.h"
#define VARLINK_DEFAULT_CONNECTIONS_MAX 4096U
#define VARLINK_DEFAULT_CONNECTIONS_PER_UID_MAX 128U
return 0;
}
+static int mark_varlink_socket(int fd, const char *path, const char *role) {
+ int r;
+
+ assert(wildcard_fd_is_valid(fd));
+ assert(role);
+
+ /* We define four roles:
+ *
+ * 1. "client" → this socket was created via socket()+connect() [sockfs]
+ * 2. "server" → this socket was created via accept() [sockfs]
+ * 3. "listen" → this socket was created via socket()+listen() [sockfs]
+ * 4. "entrypoint" → this is the entrypoint socket inode [not sockfs]
+ */
+
+ r = socket_xattr_supported(); /* Let's check for the feature directly, to make use of the cache */
+ if (r < 0)
+ return r;
+ if (r == 0)
+ return -EOPNOTSUPP;
+
+ r = xsetxattr(fd, path, AT_EMPTY_PATH, "user.varlink", role);
+ if (r < 0)
+ return log_debug_errno(r, "Failed to set 'user.varlink' to '%s': %m", role);
+
+ return 0;
+}
+
_public_ int sd_varlink_connect_address(sd_varlink **ret, const char *address) {
_cleanup_(sd_varlink_unrefp) sd_varlink *v = NULL;
int r;
if (r < 0)
return r;
+ (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client");
+
varlink_set_state(v, VARLINK_IDLE_CLIENT);
*ret = TAKE_PTR(v);
return 0;
}
-_public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, char **_argv) {
+static int varlink_socketpair(int *ret_client_fd, int *ret_server_fd) {
+ assert(ret_client_fd);
+ assert(ret_server_fd);
+
_cleanup_close_pair_ int pair[2] = EBADF_PAIR;
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
+ return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+
+ (void) mark_varlink_socket(pair[0], /* path= */ NULL, "client");
+ (void) mark_varlink_socket(pair[1], /* path= */ NULL, "server");
+
+ *ret_client_fd = TAKE_FD(pair[0]);
+ *ret_server_fd = TAKE_FD(pair[1]);
+ return 0;
+}
+
+_public_ int sd_varlink_connect_exec(sd_varlink **ret, const char *_command, char **_argv) {
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
_cleanup_free_ char *command = NULL;
_cleanup_strv_free_ char **argv = NULL;
log_debug("Forking off Varlink child process '%s'.", command);
- if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
- return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+ _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF;
+ r = varlink_socketpair(&client_fd, &server_fd);
+ if (r < 0)
+ return r;
- r = fd_nonblock(pair[1], false);
+ r = fd_nonblock(server_fd, false);
if (r < 0)
return log_debug_errno(r, "Failed to disable O_NONBLOCK for varlink socket: %m");
r = pidref_safe_fork_full(
"(sd-vlexec)",
/* stdio_fds= */ NULL,
- /* except_fds= */ (int[]) { pair[1] },
+ /* except_fds= */ (int[]) { server_fd },
/* n_except_fds= */ 1,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_PACK_FDS|FORK_CLOEXEC_OFF|FORK_REOPEN_LOG|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE,
&pidref);
_exit(EXIT_FAILURE);
}
- pair[1] = safe_close(pair[1]);
+ server_fd = safe_close(server_fd);
sd_varlink *v;
r = varlink_new(&v);
if (r < 0)
return log_debug_errno(r, "Failed to create varlink object: %m");
- int conn_fd = TAKE_FD(pair[0]);
+ int conn_fd = TAKE_FD(client_fd);
r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd);
if (r < 0)
return r;
}
static int varlink_connect_ssh_unix(sd_varlink **ret, const char *where) {
- _cleanup_close_pair_ int pair[2] = EBADF_PAIR;
_cleanup_(pidref_done_sigkill_wait) PidRef pidref = PIDREF_NULL;
int r;
log_debug("Forking off SSH child process '%s -W %s %s'.", ssh, p, h);
- if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK, 0, pair) < 0)
- return log_debug_errno(errno, "Failed to allocate AF_UNIX socket pair: %m");
+ _cleanup_close_ int client_fd = -EBADF, server_fd = -EBADF;
+ r = varlink_socketpair(&client_fd, &server_fd);
+ if (r < 0)
+ return r;
r = pidref_safe_fork_full(
"(sd-vlssh)",
- /* stdio_fds= */ (int[]) { pair[1], pair[1], STDERR_FILENO },
+ /* stdio_fds= */ (int[]) { server_fd, server_fd, STDERR_FILENO },
/* except_fds= */ NULL,
/* n_except_fds= */ 0,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG_SIGTERM|FORK_REOPEN_LOG|FORK_RLIMIT_NOFILE_SAFE|FORK_REARRANGE_STDIO,
_exit(EXIT_FAILURE);
}
- pair[1] = safe_close(pair[1]);
+ server_fd = safe_close(server_fd);
sd_varlink *v;
r = varlink_new(&v);
if (r < 0)
return log_debug_errno(r, "Failed to create varlink object: %m");
- int conn_fd = TAKE_FD(pair[0]);
+ int conn_fd = TAKE_FD(client_fd);
r = json_stream_attach_fds(&v->stream, conn_fd, conn_fd);
if (r < 0)
return r;
if (r < 0)
return r;
+ (void) mark_varlink_socket(v->stream.input_fd, /* path= */ NULL, "client");
+
if (override_ucred)
json_stream_set_peer_ucred(&v->stream, override_ucred);
}
}
+ (void) mark_varlink_socket(input_fd, /* path= */ NULL, "server");
+
if (ret)
*ret = v;
if (r < 0)
return r;
+ (void) mark_varlink_socket(ss->fd, /* path= */ NULL, "listen");
+
LIST_PREPEND(sockets, s->sockets, TAKE_PTR(ss));
return 0;
}
fd = fd_move_above_stdio(fd);
+ (void) mark_varlink_socket(fd, /* path= */ NULL, "listen");
+
/* See the comment in sd_varlink_server_listen_fd() */
if (FLAGS_SET(s->flags, SD_VARLINK_SERVER_FD_PASSING_INPUT_STRICT))
(void) setsockopt_int(fd, SOL_SOCKET, SO_PASSRIGHTS, FLAGS_SET(s->flags, SD_VARLINK_SERVER_ALLOW_FD_PASSING_INPUT));
if (r < 0)
return r;
+ if (path_is_absolute(address))
+ (void) mark_varlink_socket(AT_FDCWD, address, "entrypoint");
+
if (listen(fd, SOMAXCONN_DELUXE) < 0)
return -errno;