+/* SPDX-License-Identifier: LGPL-2.1+ */
/***
This file is part of systemd.
#include <linux/sctp.h>
#include "alloc-util.h"
+#include "bpf-firewall.h"
#include "bus-error.h"
#include "bus-util.h"
#include "copy.h"
#include "exit-status.h"
#include "fd-util.h"
#include "format-util.h"
+#include "fs-util.h"
+#include "in-addr-util.h"
#include "io-util.h"
#include "label.h"
#include "log.h"
#include "unit-name.h"
#include "unit.h"
#include "user-util.h"
-#include "in-addr-util.h"
struct SocketPeer {
unsigned n_ref;
unit_ref_set(&s->service, u);
- return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false);
+ return unit_add_two_dependencies(UNIT(s), UNIT_BEFORE, UNIT_TRIGGERS, u, false, UNIT_DEPENDENCY_IMPLICIT);
}
static bool have_non_accept_socket(Socket *s) {
return false;
}
-static int socket_add_mount_links(Socket *s) {
+static int socket_add_mount_dependencies(Socket *s) {
SocketPort *p;
int r;
if (!path)
continue;
- r = unit_require_mounts_for(UNIT(s), path);
+ r = unit_require_mounts_for(UNIT(s), path, UNIT_DEPENDENCY_FILE);
if (r < 0)
return r;
}
return 0;
}
-static int socket_add_device_link(Socket *s) {
+static int socket_add_device_dependencies(Socket *s) {
char *t;
assert(s);
return 0;
t = strjoina("/sys/subsystem/net/devices/", s->bind_to_device);
- return unit_add_node_link(UNIT(s), t, false, UNIT_BINDS_TO);
+ return unit_add_node_dependency(UNIT(s), t, false, UNIT_BINDS_TO, UNIT_DEPENDENCY_FILE);
}
static int socket_add_default_dependencies(Socket *s) {
if (!UNIT(s)->default_dependencies)
return 0;
- r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true);
+ r = unit_add_dependency_by_name(UNIT(s), UNIT_BEFORE, SPECIAL_SOCKETS_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
if (r < 0)
return r;
if (MANAGER_IS_SYSTEM(UNIT(s)->manager)) {
- r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true);
+ r = unit_add_two_dependencies_by_name(UNIT(s), UNIT_AFTER, UNIT_REQUIRES, SPECIAL_SYSINIT_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
if (r < 0)
return r;
}
- return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true);
+ return unit_add_two_dependencies_by_name(UNIT(s), UNIT_BEFORE, UNIT_CONFLICTS, SPECIAL_SHUTDOWN_TARGET, NULL, true, UNIT_DEPENDENCY_DEFAULT);
}
_pure_ static bool socket_has_exec(Socket *s) {
unit_ref_set(&s->service, x);
}
- r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true);
+ r = unit_add_two_dependencies(u, UNIT_BEFORE, UNIT_TRIGGERS, UNIT_DEREF(s->service), true, UNIT_DEPENDENCY_IMPLICIT);
if (r < 0)
return r;
}
- r = socket_add_mount_links(s);
+ r = socket_add_mount_dependencies(s);
if (r < 0)
return r;
- r = socket_add_device_link(s);
+ r = socket_add_device_dependencies(s);
if (r < 0)
return r;
r = unit_add_exec_dependencies(u, &s->exec_context);
if (r < 0)
return r;
-
- r = unit_set_default_slice(u);
- if (r < 0)
- return r;
}
+ r = unit_set_default_slice(u);
+ if (r < 0)
+ return r;
+
r = socket_add_default_dependencies(s);
if (r < 0)
return r;
exec_command_dump_list(s->exec_command[c], f, prefix2);
}
+
+ cgroup_context_dump(&s->cgroup_context, f, prefix);
}
static int instance_from_socket(int fd, unsigned nr, char **instance) {
assert(path);
- mkdir_parents_label(path, directory_mode);
+ (void) mkdir_parents_label(path, directory_mode);
r = mac_selinux_create_file_prepare(path, S_IFIFO);
if (r < 0)
static int socket_symlink(Socket *s) {
const char *p;
char **i;
+ int r;
assert(s);
if (!p)
return 0;
- STRV_FOREACH(i, s->symlinks)
- symlink_label(p, *i);
+ STRV_FOREACH(i, s->symlinks) {
+ (void) mkdir_parents_label(*i, s->directory_mode);
+
+ r = symlink_idempotent(p, *i);
+
+ if (r == -EEXIST && s->remove_on_stop) {
+ /* If there's already something where we want to create the symlink, and the destructive
+ * RemoveOnStop= mode is set, then we might as well try to remove what already exists and try
+ * again. */
+
+ if (unlink(*i) >= 0)
+ r = symlink_idempotent(p, *i);
+ }
+
+ if (r < 0)
+ log_unit_warning_errno(UNIT(s), r, "Failed to create symlink %s → %s, ignoring: %m", p, *i);
+ }
return 0;
}
goto no_label;
r = mac_selinux_get_create_label_from_exe(c->path, ret);
- if (r == -EPERM || r == -EOPNOTSUPP)
+ if (IN_SET(r, -EPERM, -EOPNOTSUPP))
goto no_label;
}
return 0;
}
+static int socket_address_listen_do(
+ Socket *s,
+ const SocketAddress *address,
+ const char *label) {
+
+ assert(s);
+ assert(address);
+
+ return socket_address_listen(
+ address,
+ SOCK_CLOEXEC|SOCK_NONBLOCK,
+ s->backlog,
+ s->bind_ipv6_only,
+ s->bind_to_device,
+ s->reuse_port,
+ s->free_bind,
+ s->transparent,
+ s->directory_mode,
+ s->socket_mode,
+ label);
+}
+
+static int socket_address_listen_in_cgroup(
+ Socket *s,
+ const SocketAddress *address,
+ const char *label) {
+
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ int fd, r;
+ pid_t pid;
+
+ assert(s);
+ assert(address);
+
+ /* This is a wrapper around socket_address_listen(), that forks off a helper process inside the socket's cgroup
+ * in which the socket is actually created. This way we ensure the socket is actually properly attached to the
+ * unit's cgroup for the purpose of BPF filtering and such. */
+
+ if (!IN_SET(address->sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ goto shortcut; /* BPF filtering only applies to IPv4 + IPv6, shortcut things for other protocols */
+
+ r = bpf_firewall_supported();
+ if (r < 0)
+ return r;
+ if (r == 0) /* If BPF firewalling isn't supported anyway — there's no point in this forking complexity */
+ goto shortcut;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
+ return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m");
+
+ r = unit_fork_helper_process(UNIT(s), &pid);
+ if (r < 0)
+ return log_unit_error_errno(UNIT(s), r, "Failed to fork off listener stub process: %m");
+ if (r == 0) {
+ /* Child */
+
+ pair[0] = safe_close(pair[0]);
+
+ fd = socket_address_listen_do(s, address, label);
+ if (fd < 0) {
+ log_unit_error_errno(UNIT(s), fd, "Failed to create listening socket: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = send_one_fd(pair[1], fd, 0);
+ if (r < 0) {
+ log_unit_error_errno(UNIT(s), r, "Failed to send listening socket to parent: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+ fd = receive_one_fd(pair[0], 0);
+
+ /* We synchronously wait for the helper, as it shouldn't be slow */
+ r = wait_for_terminate_and_warn("listen-cgroup-helper", pid, false);
+ if (r < 0) {
+ safe_close(fd);
+ return r;
+ }
+
+ if (fd < 0)
+ return log_unit_error_errno(UNIT(s), fd, "Failed to receive listening socket: %m");
+
+ return fd;
+
+shortcut:
+ fd = socket_address_listen_do(s, address, label);
+ if (fd < 0)
+ return log_error_errno(fd, "Failed to create listening socket: %m");
+
+ return fd;
+}
+
static int socket_open_fds(Socket *s) {
_cleanup_(mac_selinux_freep) char *label = NULL;
bool know_label = false;
break;
}
- r = socket_address_listen(
- &p->address,
- SOCK_CLOEXEC|SOCK_NONBLOCK,
- s->backlog,
- s->bind_ipv6_only,
- s->bind_to_device,
- s->reuse_port,
- s->free_bind,
- s->transparent,
- s->directory_mode,
- s->socket_mode,
- label);
+ r = socket_address_listen_in_cgroup(s, &p->address, label);
if (r < 0)
goto rollback;
assert(_pid);
(void) unit_realize_cgroup(UNIT(s));
- if (s->reset_cpu_usage) {
- (void) unit_reset_cpu_usage(UNIT(s));
- s->reset_cpu_usage = false;
+ if (s->reset_accounting) {
+ (void) unit_reset_cpu_accounting(UNIT(s));
+ (void) unit_reset_ip_accounting(UNIT(s));
+ s->reset_accounting = false;
}
+ unit_export_state_files(UNIT(s));
+
r = unit_setup_exec_runtime(UNIT(s));
if (r < 0)
return r;
/* We have to resolve the user names out-of-process, hence
* let's fork here. It's messy, but well, what can we do? */
- pid = fork();
- if (pid < 0)
- return -errno;
-
- if (pid == 0) {
- SocketPort *p;
+ r = unit_fork_helper_process(UNIT(s), &pid);
+ if (r < 0)
+ return r;
+ if (r == 0) {
uid_t uid = UID_INVALID;
gid_t gid = GID_INVALID;
- int ret;
+ SocketPort *p;
- (void) default_signals(SIGNALS_CRASH_HANDLER, SIGNALS_IGNORE, -1);
- (void) ignore_signals(SIGPIPE, -1);
- log_forget_fds();
+ /* Child */
if (!isempty(s->user)) {
const char *user = s->user;
r = get_user_creds(&user, &uid, &gid, NULL, NULL);
if (r < 0) {
- ret = EXIT_USER;
- goto fail_child;
+ log_unit_error_errno(UNIT(s), r, "Failed to resolve user %s: %m", user);
+ _exit(EXIT_USER);
}
}
r = get_group_creds(&group, &gid);
if (r < 0) {
- ret = EXIT_GROUP;
- goto fail_child;
+ log_unit_error_errno(UNIT(s), r, "Failed to resolve group %s: %m", group);
+ _exit(EXIT_GROUP);
}
}
continue;
if (chown(path, uid, gid) < 0) {
- r = -errno;
- ret = EXIT_CHOWN;
- goto fail_child;
+ log_unit_error_errno(UNIT(s), errno, "Failed to chown(): %m");
+ _exit(EXIT_CHOWN);
}
}
- _exit(0);
-
- fail_child:
- log_open();
- log_error_errno(r, "Failed to chown socket at step %s: %m", exit_status_to_string(ret, EXIT_STATUS_SYSTEMD));
-
- _exit(ret);
+ _exit(EXIT_SUCCESS);
}
r = unit_watch_pid(UNIT(s), pid);
if (s->result == SOCKET_SUCCESS)
s->result = f;
+ if (s->result != SOCKET_SUCCESS)
+ log_unit_warning(UNIT(s), "Failed with result '%s'.", socket_result_to_string(s->result));
+
socket_set_state(s, s->result != SOCKET_SUCCESS ? SOCKET_FAILED : SOCKET_DEAD);
exec_runtime_destroy(s->exec_runtime);
r = unit_kill_context(
UNIT(s),
&s->kill_context,
- (state != SOCKET_STOP_PRE_SIGTERM && state != SOCKET_FINAL_SIGTERM) ?
+ !IN_SET(state, SOCKET_STOP_PRE_SIGTERM, SOCKET_FINAL_SIGTERM) ?
KILL_KILL : KILL_TERMINATE,
-1,
s->control_pid,
fail:
log_unit_warning_errno(UNIT(s), r, "Failed to kill processes: %m");
- if (state == SOCKET_STOP_PRE_SIGTERM || state == SOCKET_STOP_PRE_SIGKILL)
+ if (IN_SET(state, SOCKET_STOP_PRE_SIGTERM, SOCKET_STOP_PRE_SIGKILL))
socket_enter_stop_post(s, SOCKET_FAILURE_RESOURCES);
else
socket_enter_dead(s, SOCKET_FAILURE_RESOURCES);
}
if (cfd < 0) {
- Iterator i;
- Unit *other;
bool pending = false;
+ Unit *other;
+ Iterator i;
+ void *v;
/* If there's already a start pending don't bother to
* do anything */
- SET_FOREACH(other, UNIT(s)->dependencies[UNIT_TRIGGERS], i)
+ HASHMAP_FOREACH_KEY(v, other, UNIT(s)->dependencies[UNIT_TRIGGERS], i)
if (unit_active_or_pending(other)) {
pending = true;
break;
/* If the service is already active we cannot start the
* socket */
- if (service->state != SERVICE_DEAD &&
- service->state != SERVICE_FAILED &&
- service->state != SERVICE_AUTO_RESTART) {
+ if (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED, SERVICE_AUTO_RESTART)) {
log_unit_error(u, "Socket service %s already active, refusing.", UNIT(service)->id);
return -EBUSY;
}
}
- assert(s->state == SOCKET_DEAD || s->state == SOCKET_FAILED);
+ assert(IN_SET(s->state, SOCKET_DEAD, SOCKET_FAILED));
r = unit_start_limit_test(u);
if (r < 0) {
return r;
s->result = SOCKET_SUCCESS;
- s->reset_cpu_usage = true;
+ s->reset_accounting = true;
socket_enter_start_pre(s);
return 1;
return -EAGAIN;
}
- assert(s->state == SOCKET_LISTENING || s->state == SOCKET_RUNNING);
+ assert(IN_SET(s->state, SOCKET_LISTENING, SOCKET_RUNNING));
socket_enter_stop_pre(s, SOCKET_SUCCESS);
return 1;
return s->n_connections > 0;
}
+static int socket_accept_do(Socket *s, int fd) {
+ int cfd;
+
+ assert(s);
+ assert(fd >= 0);
+
+ for (;;) {
+ cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK);
+ if (cfd < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ break;
+ }
+
+ return cfd;
+}
+
+static int socket_accept_in_cgroup(Socket *s, SocketPort *p, int fd) {
+ _cleanup_close_pair_ int pair[2] = { -1, -1 };
+ int cfd, r;
+ pid_t pid;
+
+ assert(s);
+ assert(p);
+ assert(fd >= 0);
+
+ /* Similar to socket_address_listen_in_cgroup(), but for accept() rathern than socket(): make sure that any
+ * connection socket is also properly associated with the cgroup. */
+
+ if (!IN_SET(p->address.sockaddr.sa.sa_family, AF_INET, AF_INET6))
+ goto shortcut;
+
+ r = bpf_firewall_supported();
+ if (r < 0)
+ return r;
+ if (r == 0)
+ goto shortcut;
+
+ if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, pair) < 0)
+ return log_unit_error_errno(UNIT(s), errno, "Failed to create communication channel: %m");
+
+ r = unit_fork_helper_process(UNIT(s), &pid);
+ if (r < 0)
+ return log_unit_error_errno(UNIT(s), r, "Failed to fork off accept stub process: %m");
+ if (r == 0) {
+ /* Child */
+
+ pair[0] = safe_close(pair[0]);
+
+ cfd = socket_accept_do(s, fd);
+ if (cfd < 0) {
+ log_unit_error_errno(UNIT(s), cfd, "Failed to accept connection socket: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ r = send_one_fd(pair[1], cfd, 0);
+ if (r < 0) {
+ log_unit_error_errno(UNIT(s), r, "Failed to send connection socket to parent: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ pair[1] = safe_close(pair[1]);
+ cfd = receive_one_fd(pair[0], 0);
+
+ /* We synchronously wait for the helper, as it shouldn't be slow */
+ r = wait_for_terminate_and_warn("accept-cgroup-helper", pid, false);
+ if (r < 0) {
+ safe_close(cfd);
+ return r;
+ }
+
+ if (cfd < 0)
+ return log_unit_error_errno(UNIT(s), cfd, "Failed to receive connection socket: %m");
+
+ return cfd;
+
+shortcut:
+ cfd = socket_accept_do(s, fd);
+ if (cfd < 0)
+ return log_unit_error_errno(UNIT(s), cfd, "Failed to accept connection socket: %m");
+
+ return cfd;
+}
+
static int socket_dispatch_io(sd_event_source *source, int fd, uint32_t revents, void *userdata) {
SocketPort *p = userdata;
int cfd = -1;
p->type == SOCKET_SOCKET &&
socket_address_can_accept(&p->address)) {
- for (;;) {
-
- cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK);
- if (cfd < 0) {
-
- if (errno == EINTR)
- continue;
-
- log_unit_error_errno(UNIT(p->socket), errno, "Failed to accept socket: %m");
- goto fail;
- }
-
- break;
- }
+ cfd = socket_accept_in_cgroup(p->socket, p, fd);
+ if (cfd < 0)
+ goto fail;
socket_apply_socket_options(p->socket, cfd);
}