1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
3 #include "resolved-socket-graveyard.h"
5 #define SOCKET_GRAVEYARD_USEC (5 * USEC_PER_SEC)
6 #define SOCKET_GRAVEYARD_MAX 100
8 /* This implements a socket "graveyard" for UDP sockets. If a socket fd is added to the graveyard it is kept
9 * open for a couple of more seconds, expecting one reply. Once the reply is received the fd is closed
10 * immediately, or if none is received it is closed after the timeout. Why all this? So that if we contact a
11 * DNS server, and it doesn't reply instantly, and we lose interest in the response and thus close the fd, we
12 * don't end up sending back an ICMP error once the server responds but we aren't listening anymore. (See
13 * https://github.com/systemd/systemd/issues/17421 for further information.)
15 * Note that we don't allocate any timer event source to clear up the graveyard once the socket's timeout is
16 * reached. Instead we operate lazily: we close old entries when adding a new fd to the graveyard, or
17 * whenever any code runs manager_socket_graveyard_process() — which the DNS transaction code does right
18 * before allocating a new UDP socket. */
20 static SocketGraveyard
* socket_graveyard_free(SocketGraveyard
*g
) {
25 assert(g
->manager
->n_socket_graveyard
> 0);
26 g
->manager
->n_socket_graveyard
--;
28 if (g
->manager
->socket_graveyard_oldest
== g
)
29 g
->manager
->socket_graveyard_oldest
= g
->graveyard_prev
;
31 LIST_REMOVE(graveyard
, g
->manager
->socket_graveyard
, g
);
33 assert((g
->manager
->n_socket_graveyard
> 0) == !!g
->manager
->socket_graveyard
);
34 assert((g
->manager
->n_socket_graveyard
> 0) == !!g
->manager
->socket_graveyard_oldest
);
37 if (g
->io_event_source
) {
38 log_debug("Closing graveyard socket fd %i", sd_event_source_get_io_fd(g
->io_event_source
));
39 sd_event_source_disable_unref(g
->io_event_source
);
45 DEFINE_TRIVIAL_CLEANUP_FUNC(SocketGraveyard
*, socket_graveyard_free
);
47 void manager_socket_graveyard_process(Manager
*m
) {
48 usec_t n
= USEC_INFINITY
;
52 while (m
->socket_graveyard_oldest
) {
53 SocketGraveyard
*g
= m
->socket_graveyard_oldest
;
55 if (n
== USEC_INFINITY
)
56 assert_se(sd_event_now(m
->event
, CLOCK_BOOTTIME
, &n
) >= 0);
61 socket_graveyard_free(g
);
65 void manager_socket_graveyard_clear(Manager
*m
) {
68 while (m
->socket_graveyard
)
69 socket_graveyard_free(m
->socket_graveyard
);
72 static int on_io_event(sd_event_source
*s
, int fd
, uint32_t revents
, void *userdata
) {
73 SocketGraveyard
*g
= ASSERT_PTR(userdata
);
75 /* An IO event happened on the graveyard fd. We don't actually care which event that is, and we don't
76 * read any incoming packet off the socket. We just close the fd, that's enough to not trigger the
77 * ICMP unreachable port event */
79 socket_graveyard_free(g
);
83 static void manager_socket_graveyard_make_room(Manager
*m
) {
86 while (m
->n_socket_graveyard
>= SOCKET_GRAVEYARD_MAX
)
87 socket_graveyard_free(m
->socket_graveyard_oldest
);
90 int manager_add_socket_to_graveyard(Manager
*m
, int fd
) {
91 _cleanup_(socket_graveyard_freep
) SocketGraveyard
*g
= NULL
;
97 manager_socket_graveyard_process(m
);
98 manager_socket_graveyard_make_room(m
);
100 g
= new(SocketGraveyard
, 1);
104 *g
= (SocketGraveyard
) {
108 LIST_PREPEND(graveyard
, m
->socket_graveyard
, g
);
109 if (!m
->socket_graveyard_oldest
)
110 m
->socket_graveyard_oldest
= g
;
112 m
->n_socket_graveyard
++;
114 assert_se(sd_event_now(m
->event
, CLOCK_BOOTTIME
, &g
->deadline
) >= 0);
115 g
->deadline
+= SOCKET_GRAVEYARD_USEC
;
117 r
= sd_event_add_io(m
->event
, &g
->io_event_source
, fd
, EPOLLIN
, on_io_event
, g
);
119 return log_error_errno(r
, "Failed to create graveyard IO source: %m");
121 r
= sd_event_source_set_io_fd_own(g
->io_event_source
, true);
123 return log_error_errno(r
, "Failed to enable graveyard IO source fd ownership: %m");
125 (void) sd_event_source_set_description(g
->io_event_source
, "graveyard");
127 log_debug("Added socket %i to graveyard", fd
);