diff --git a/man/systemd.socket.xml b/man/systemd.socket.xml index f977f11541..e4f1f544cb 100644 --- a/man/systemd.socket.xml +++ b/man/systemd.socket.xml @@ -392,36 +392,32 @@ Accept= - Takes a boolean argument. If yes, a service - instance is spawned for each incoming connection and only the - connection socket is passed to it. If no, all listening - sockets themselves are passed to the started service unit, and - only one service unit is spawned for all connections (also see - above). This value is ignored for datagram sockets and FIFOs - where a single service unit unconditionally handles all - incoming traffic. Defaults to . For - performance reasons, it is recommended to write new daemons - only in a way that is suitable for - . A daemon listening on an - AF_UNIX socket may, but does not need to, - call - close2 - on the received socket before exiting. However, it must not - unlink the socket from a file system. It should not invoke - shutdown2 - on sockets it got with Accept=no, but it - may do so for sockets it got with - Accept=yes set. Setting - Accept=yes is mostly useful to allow - daemons designed for usage with - inetd8 - to work unmodified with systemd socket - activation. + Takes a boolean argument. If yes, a service instance is spawned for each incoming + connection and only the connection socket is passed to it. If no, all listening sockets themselves + are passed to the started service unit, and only one service unit is spawned for all connections + (also see above). This value is ignored for datagram sockets and FIFOs where a single service unit + unconditionally handles all incoming traffic. Defaults to . For performance + reasons, it is recommended to write new daemons only in a way that is suitable for + . A daemon listening on an AF_UNIX socket may, but + does not need to, call + close2 on the + received socket before exiting. However, it must not unlink the socket from a file system. It should + not invoke + shutdown2 on + sockets it got with Accept=no, but it may do so for sockets it got with + Accept=yes set. Setting Accept=yes is mostly useful to allow + daemons designed for usage with inetd8 to work + unmodified with systemd socket activation. For IPv4 and IPv6 connections, the REMOTE_ADDR environment variable will contain the remote IP address, and REMOTE_PORT will contain the remote port. This is the same as the format used by CGI. For SOCK_RAW, the port is the IP - protocol. + protocol. + + It is recommended to set CollectMode=inactive-or-failed for service + instances activated via Accept=yes, to ensure that failed connection services are + cleaned up and released from memory, and do not accumulate. diff --git a/src/core/service.c b/src/core/service.c index 17c19a2c4a..49579f7998 100644 --- a/src/core/service.c +++ b/src/core/service.c @@ -190,6 +190,8 @@ void service_close_socket_fd(Service *s) { socket_connection_unref(SOCKET(UNIT_DEREF(s->accept_socket))); unit_ref_unset(&s->accept_socket); } + + s->socket_peer = socket_peer_unref(s->socket_peer); } static void service_stop_watchdog(Service *s) { @@ -388,7 +390,6 @@ static void service_done(Unit *u) { s->usb_function_strings = mfree(s->usb_function_strings); service_close_socket_fd(s); - s->peer = socket_peer_unref(s->peer); unit_ref_unset(&s->accept_socket); @@ -1237,8 +1238,8 @@ static int service_coldplug(Unit *u) { /* Make a best-effort attempt at bumping the connection count */ if (socket_acquire_peer(socket, s->socket_fd, &peer) > 0) { - socket_peer_unref(s->peer); - s->peer = peer; + socket_peer_unref(s->socket_peer); + s->socket_peer = peer; } } } @@ -4285,8 +4286,14 @@ static void service_bus_name_owner_change(Unit *u, const char *new_owner) { } } -int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context_net) { - _cleanup_free_ char *peer = NULL; +int service_set_socket_fd( + Service *s, + int fd, + Socket *sock, + SocketPeer *peer, + bool selinux_context_net) { + + _cleanup_free_ char *peer_text = NULL; int r; assert(s); @@ -4301,22 +4308,23 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context if (s->socket_fd >= 0) return -EBUSY; + assert(!s->socket_peer); + if (s->state != SERVICE_DEAD) return -EAGAIN; - if (getpeername_pretty(fd, true, &peer) >= 0) { + if (getpeername_pretty(fd, true, &peer_text) >= 0) { if (UNIT(s)->description) { _cleanup_free_ char *a = NULL; - a = strjoin(UNIT(s)->description, " (", peer, ")"); + a = strjoin(UNIT(s)->description, " (", peer_text, ")"); if (!a) return -ENOMEM; r = unit_set_description(UNIT(s), a); } else - r = unit_set_description(UNIT(s), peer); - + r = unit_set_description(UNIT(s), peer_text); if (r < 0) return r; } @@ -4326,6 +4334,7 @@ int service_set_socket_fd(Service *s, int fd, Socket *sock, bool selinux_context return r; s->socket_fd = fd; + s->socket_peer = socket_peer_ref(peer); s->socket_fd_selinux_context_net = selinux_context_net; unit_ref_set(&s->accept_socket, UNIT(s), UNIT(sock)); diff --git a/src/core/service.h b/src/core/service.h index 70ce70fba5..778551d844 100644 --- a/src/core/service.h +++ b/src/core/service.h @@ -157,8 +157,11 @@ struct Service { DynamicCreds dynamic_creds; pid_t main_pid, control_pid; + + /* if we are a socket activated service instance, store information of the connection/peer/socket */ int socket_fd; - SocketPeer *peer; + SocketPeer *socket_peer; + UnitRef accept_socket; bool socket_fd_selinux_context_net; bool permissions_start_only; @@ -186,8 +189,6 @@ struct Service { char *status_text; int status_errno; - UnitRef accept_socket; - sd_event_source *timer_event_source; PathSpec *pid_file_pathspec; @@ -226,7 +227,7 @@ static inline usec_t service_get_watchdog_usec(Service *s) { extern const UnitVTable service_vtable; -int service_set_socket_fd(Service *s, int fd, struct Socket *socket, bool selinux_context_net); +int service_set_socket_fd(Service *s, int fd, struct Socket *socket, struct SocketPeer *peer, bool selinux_context_net); void service_close_socket_fd(Service *s); const char* service_restart_to_string(ServiceRestart i) _const_; diff --git a/src/core/socket.c b/src/core/socket.c index e6d168188a..6b5ec9d987 100644 --- a/src/core/socket.c +++ b/src/core/socket.c @@ -463,10 +463,6 @@ static int socket_load(Unit *u) { assert(u); assert(u->load_state == UNIT_STUB); - r = set_ensure_allocated(&s->peers_by_address, &peer_address_hash_ops); - if (r < 0) - return r; - r = unit_load_fragment_and_dropin(u, true); if (r < 0) return r; @@ -485,12 +481,13 @@ static int socket_load(Unit *u) { static SocketPeer *socket_peer_new(void) { SocketPeer *p; - p = new0(SocketPeer, 1); + p = new(SocketPeer, 1); if (!p) return NULL; - p->n_ref = 1; - + *p = (SocketPeer) { + .n_ref = 1, + }; return p; } @@ -507,14 +504,15 @@ DEFINE_TRIVIAL_REF_UNREF_FUNC(SocketPeer, socket_peer, socket_peer_free); int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { _cleanup_(socket_peer_unrefp) SocketPeer *remote = NULL; - SocketPeer sa = {}, *i; - socklen_t salen = sizeof(sa.peer); + SocketPeer sa = { + .peer_salen = sizeof(union sockaddr_union), + }, *i; int r; assert(fd >= 0); assert(s); - if (getpeername(fd, &sa.peer.sa, &salen) < 0) + if (getpeername(fd, &sa.peer.sa, &sa.peer_salen) < 0) return log_unit_error_errno(UNIT(s), errno, "getpeername failed: %m"); if (!IN_SET(sa.peer.sa.sa_family, AF_INET, AF_INET6, AF_VSOCK)) { @@ -522,6 +520,10 @@ int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { return 0; } + r = set_ensure_allocated(&s->peers_by_address, &peer_address_hash_ops); + if (r < 0) + return r; + i = set_get(s->peers_by_address, &sa); if (i) { *p = socket_peer_ref(i); @@ -533,7 +535,7 @@ int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { return log_oom(); remote->peer = sa.peer; - remote->peer_salen = salen; + remote->peer_salen = sa.peer_salen; r = set_put(s->peers_by_address, remote); if (r < 0) @@ -542,7 +544,6 @@ int socket_acquire_peer(Socket *s, int fd, SocketPeer **p) { remote->socket = s; *p = TAKE_PTR(remote); - return 1; } @@ -2409,7 +2410,7 @@ static void socket_enter_running(Socket *s, int cfd_in) { s->n_accepted++; - r = service_set_socket_fd(SERVICE(service), cfd, s, s->selinux_context_from_net); + r = service_set_socket_fd(SERVICE(service), cfd, s, p, s->selinux_context_from_net); if (ERRNO_IS_DISCONNECT(r)) return; if (r < 0) @@ -2418,8 +2419,6 @@ static void socket_enter_running(Socket *s, int cfd_in) { TAKE_FD(cfd); /* We passed ownership of the fd to the service now. Forget it here. */ s->n_connections++; - SERVICE(service)->peer = TAKE_PTR(p); /* Pass ownership of the peer reference */ - r = manager_add_job(UNIT(s)->manager, JOB_START, service, JOB_REPLACE, NULL, &error, NULL); if (r < 0) { /* We failed to activate the new service, but it still exists. Let's make sure the