1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 
3 #include "resolved-socket-graveyard.h"
4 
5 #define SOCKET_GRAVEYARD_USEC (5 * USEC_PER_SEC)
6 #define SOCKET_GRAVEYARD_MAX 100
7 
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.)
14  *
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. */
19 
socket_graveyard_free(SocketGraveyard * g)20 static SocketGraveyard* socket_graveyard_free(SocketGraveyard *g) {
21         if (!g)
22                 return NULL;
23 
24         if (g->manager) {
25                 assert(g->manager->n_socket_graveyard > 0);
26                 g->manager->n_socket_graveyard--;
27 
28                 if (g->manager->socket_graveyard_oldest == g)
29                         g->manager->socket_graveyard_oldest = g->graveyard_prev;
30 
31                 LIST_REMOVE(graveyard, g->manager->socket_graveyard, g);
32 
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);
35         }
36 
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);
40         }
41 
42         return mfree(g);
43 }
44 
45 DEFINE_TRIVIAL_CLEANUP_FUNC(SocketGraveyard*, socket_graveyard_free);
46 
manager_socket_graveyard_process(Manager * m)47 void manager_socket_graveyard_process(Manager *m) {
48         usec_t n = USEC_INFINITY;
49 
50         assert(m);
51 
52         while (m->socket_graveyard_oldest) {
53                 SocketGraveyard *g = m->socket_graveyard_oldest;
54 
55                 if (n == USEC_INFINITY)
56                         assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &n) >= 0);
57 
58                 if (g->deadline > n)
59                         break;
60 
61                 socket_graveyard_free(g);
62         }
63 }
64 
manager_socket_graveyard_clear(Manager * m)65 void manager_socket_graveyard_clear(Manager *m) {
66         assert(m);
67 
68         while (m->socket_graveyard)
69                 socket_graveyard_free(m->socket_graveyard);
70 }
71 
on_io_event(sd_event_source * s,int fd,uint32_t revents,void * userdata)72 static int on_io_event(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
73         SocketGraveyard *g = userdata;
74 
75         assert(g);
76 
77         /* An IO event happened on the graveyard fd. We don't actually care which event that is, and we don't
78          * read any incoming packet off the socket. We just close the fd, that's enough to not trigger the
79          * ICMP unreachable port event */
80 
81         socket_graveyard_free(g);
82         return 0;
83 }
84 
manager_socket_graveyard_make_room(Manager * m)85 static void manager_socket_graveyard_make_room(Manager *m) {
86         assert(m);
87 
88         while (m->n_socket_graveyard >= SOCKET_GRAVEYARD_MAX)
89                 socket_graveyard_free(m->socket_graveyard_oldest);
90 }
91 
manager_add_socket_to_graveyard(Manager * m,int fd)92 int manager_add_socket_to_graveyard(Manager *m, int fd) {
93         _cleanup_(socket_graveyard_freep) SocketGraveyard *g = NULL;
94         int r;
95 
96         assert(m);
97         assert(fd >= 0);
98 
99         manager_socket_graveyard_process(m);
100         manager_socket_graveyard_make_room(m);
101 
102         g = new(SocketGraveyard, 1);
103         if (!g)
104                 return log_oom();
105 
106         *g = (SocketGraveyard) {
107                 .manager = m,
108         };
109 
110         LIST_PREPEND(graveyard, m->socket_graveyard, g);
111         if (!m->socket_graveyard_oldest)
112                 m->socket_graveyard_oldest = g;
113 
114         m->n_socket_graveyard++;
115 
116         assert_se(sd_event_now(m->event, CLOCK_BOOTTIME, &g->deadline) >= 0);
117         g->deadline += SOCKET_GRAVEYARD_USEC;
118 
119         r = sd_event_add_io(m->event, &g->io_event_source, fd, EPOLLIN, on_io_event, g);
120         if (r < 0)
121                 return log_error_errno(r, "Failed to create graveyard IO source: %m");
122 
123         r = sd_event_source_set_io_fd_own(g->io_event_source, true);
124         if (r < 0)
125                 return log_error_errno(r, "Failed to enable graveyard IO source fd ownership: %m");
126 
127         (void) sd_event_source_set_description(g->io_event_source, "graveyard");
128 
129         log_debug("Added socket %i to graveyard", fd);
130 
131         TAKE_PTR(g);
132         return 0;
133 }
134