mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
132 lines
4.7 KiB
C
132 lines
4.7 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include "resolved-socket-graveyard.h"
|
|
|
|
#define SOCKET_GRAVEYARD_USEC (5 * USEC_PER_SEC)
|
|
#define SOCKET_GRAVEYARD_MAX 100
|
|
|
|
/* This implements a socket "graveyard" for UDP sockets. If a socket fd is added to the graveyard it is kept
|
|
* open for a couple of more seconds, expecting one reply. Once the reply is received the fd is closed
|
|
* immediately, or if none is received it is closed after the timeout. Why all this? So that if we contact a
|
|
* DNS server, and it doesn't reply instantly, and we lose interest in the response and thus close the fd, we
|
|
* don't end up sending back an ICMP error once the server responds but we aren't listening anymore. (See
|
|
* https://github.com/systemd/systemd/issues/17421 for further information.)
|
|
*
|
|
* Note that we don't allocate any timer event source to clear up the graveyard once the socket's timeout is
|
|
* reached. Instead we operate lazily: we close old entries when adding a new fd to the graveyard, or
|
|
* whenever any code runs manager_socket_graveyard_process() — which the DNS transaction code does right
|
|
* before allocating a new UDP socket. */
|
|
|
|
static SocketGraveyard* socket_graveyard_free(SocketGraveyard *g) {
|
|
if (!g)
|
|
return NULL;
|
|
|
|
if (g->manager) {
|
|
assert(g->manager->n_socket_graveyard > 0);
|
|
g->manager->n_socket_graveyard--;
|
|
|
|
if (g->manager->socket_graveyard_oldest == g)
|
|
g->manager->socket_graveyard_oldest = g->graveyard_prev;
|
|
|
|
LIST_REMOVE(graveyard, g->manager->socket_graveyard, g);
|
|
|
|
assert((g->manager->n_socket_graveyard > 0) == !!g->manager->socket_graveyard);
|
|
assert((g->manager->n_socket_graveyard > 0) == !!g->manager->socket_graveyard_oldest);
|
|
}
|
|
|
|
if (g->io_event_source) {
|
|
log_debug("Closing graveyard socket fd %i", sd_event_source_get_io_fd(g->io_event_source));
|
|
sd_event_source_disable_unref(g->io_event_source);
|
|
}
|
|
|
|
return mfree(g);
|
|
}
|
|
|
|
DEFINE_TRIVIAL_CLEANUP_FUNC(SocketGraveyard*, socket_graveyard_free);
|
|
|
|
void manager_socket_graveyard_process(Manager *m) {
|
|
usec_t n = USEC_INFINITY;
|
|
|
|
assert(m);
|
|
|
|
while (m->socket_graveyard_oldest) {
|
|
SocketGraveyard *g = m->socket_graveyard_oldest;
|
|
|
|
if (n == USEC_INFINITY)
|
|
assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &n) >= 0);
|
|
|
|
if (g->deadline > n)
|
|
break;
|
|
|
|
socket_graveyard_free(g);
|
|
}
|
|
}
|
|
|
|
void manager_socket_graveyard_clear(Manager *m) {
|
|
assert(m);
|
|
|
|
while (m->socket_graveyard)
|
|
socket_graveyard_free(m->socket_graveyard);
|
|
}
|
|
|
|
static int on_io_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
|
|
SocketGraveyard *g = ASSERT_PTR(userdata);
|
|
|
|
/* An IO event happened on the graveyard fd. We don't actually care which event that is, and we don't
|
|
* read any incoming packet off the socket. We just close the fd, that's enough to not trigger the
|
|
* ICMP unreachable port event */
|
|
|
|
socket_graveyard_free(g);
|
|
return 0;
|
|
}
|
|
|
|
static void manager_socket_graveyard_make_room(Manager *m) {
|
|
assert(m);
|
|
|
|
while (m->n_socket_graveyard >= SOCKET_GRAVEYARD_MAX)
|
|
socket_graveyard_free(m->socket_graveyard_oldest);
|
|
}
|
|
|
|
int manager_add_socket_to_graveyard(Manager *m, int fd) {
|
|
_cleanup_(socket_graveyard_freep) SocketGraveyard *g = NULL;
|
|
int r;
|
|
|
|
assert(m);
|
|
assert(fd >= 0);
|
|
|
|
manager_socket_graveyard_process(m);
|
|
manager_socket_graveyard_make_room(m);
|
|
|
|
g = new(SocketGraveyard, 1);
|
|
if (!g)
|
|
return log_oom();
|
|
|
|
*g = (SocketGraveyard) {
|
|
.manager = m,
|
|
};
|
|
|
|
LIST_PREPEND(graveyard, m->socket_graveyard, g);
|
|
if (!m->socket_graveyard_oldest)
|
|
m->socket_graveyard_oldest = g;
|
|
|
|
m->n_socket_graveyard++;
|
|
|
|
assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &g->deadline) >= 0);
|
|
g->deadline += SOCKET_GRAVEYARD_USEC;
|
|
|
|
r = sd_event_add_io(m->event, &g->io_event_source, fd, EPOLLIN, on_io_event, g);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to create graveyard IO source: %m");
|
|
|
|
r = sd_event_source_set_io_fd_own(g->io_event_source, true);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to enable graveyard IO source fd ownership: %m");
|
|
|
|
(void) sd_event_source_set_description(g->io_event_source, "graveyard");
|
|
|
|
log_debug("Added socket %i to graveyard", fd);
|
|
|
|
TAKE_PTR(g);
|
|
return 0;
|
|
}
|