/src/bind9/lib/isc/netmgr/udp.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright (C) Internet Systems Consortium, Inc. ("ISC") |
3 | | * |
4 | | * SPDX-License-Identifier: MPL-2.0 |
5 | | * |
6 | | * This Source Code Form is subject to the terms of the Mozilla Public |
7 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
8 | | * file, you can obtain one at https://mozilla.org/MPL/2.0/. |
9 | | * |
10 | | * See the COPYRIGHT file distributed with this work for additional |
11 | | * information regarding copyright ownership. |
12 | | */ |
13 | | |
14 | | #include <unistd.h> |
15 | | |
16 | | #include <isc/async.h> |
17 | | #include <isc/atomic.h> |
18 | | #include <isc/barrier.h> |
19 | | #include <isc/buffer.h> |
20 | | #include <isc/errno.h> |
21 | | #include <isc/magic.h> |
22 | | #include <isc/mem.h> |
23 | | #include <isc/netmgr.h> |
24 | | #include <isc/random.h> |
25 | | #include <isc/refcount.h> |
26 | | #include <isc/region.h> |
27 | | #include <isc/result.h> |
28 | | #include <isc/sockaddr.h> |
29 | | #include <isc/stdtime.h> |
30 | | #include <isc/thread.h> |
31 | | #include <isc/util.h> |
32 | | #include <isc/uv.h> |
33 | | |
34 | | #include "../loop_p.h" |
35 | | #include "netmgr-int.h" |
36 | | |
37 | | #ifdef HAVE_NET_ROUTE_H |
38 | | #include <net/route.h> |
39 | | #if defined(RTM_VERSION) && defined(RTM_NEWADDR) && defined(RTM_DELADDR) |
40 | | #define USE_ROUTE_SOCKET 1 |
41 | | #define ROUTE_SOCKET_PF PF_ROUTE |
42 | | #define ROUTE_SOCKET_PROTOCOL 0 |
43 | | #define MSGHDR rt_msghdr |
44 | | #define MSGTYPE rtm_type |
45 | | #endif /* if defined(RTM_VERSION) && defined(RTM_NEWADDR) && \ |
46 | | * defined(RTM_DELADDR) */ |
47 | | #endif /* ifdef HAVE_NET_ROUTE_H */ |
48 | | |
49 | | #if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) |
50 | | #include <linux/netlink.h> |
51 | | #include <linux/rtnetlink.h> |
52 | | #if defined(RTM_NEWADDR) && defined(RTM_DELADDR) |
53 | | #define USE_ROUTE_SOCKET 1 |
54 | | #define USE_NETLINK 1 |
55 | | #define ROUTE_SOCKET_PF PF_NETLINK |
56 | | #define ROUTE_SOCKET_PROTOCOL NETLINK_ROUTE |
57 | | #define MSGHDR nlmsghdr |
58 | | #define MSGTYPE nlmsg_type |
59 | | #endif /* if defined(RTM_NEWADDR) && defined(RTM_DELADDR) */ |
60 | | #endif /* if defined(HAVE_LINUX_NETLINK_H) && defined(HAVE_LINUX_RTNETLINK_H) \ |
61 | | */ |
62 | | |
63 | | static void |
64 | | udp_send_cb(uv_udp_send_t *req, int status); |
65 | | |
66 | | static void |
67 | | udp_close_cb(uv_handle_t *handle); |
68 | | |
69 | | static uv_os_sock_t |
70 | 0 | isc__nm_udp_lb_socket(sa_family_t sa_family) { |
71 | 0 | isc_result_t result; |
72 | 0 | uv_os_sock_t sock = -1; |
73 | |
|
74 | 0 | result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &sock); |
75 | 0 | RUNTIME_CHECK(result == ISC_R_SUCCESS); |
76 | |
|
77 | 0 | (void)isc__nm_socket_disable_pmtud(sock, sa_family); |
78 | 0 | (void)isc__nm_socket_v6only(sock, sa_family); |
79 | |
|
80 | 0 | result = isc__nm_socket_reuse(sock, 1); |
81 | 0 | RUNTIME_CHECK(result == ISC_R_SUCCESS); |
82 | |
|
83 | 0 | if (isc__netmgr->load_balance_sockets) { |
84 | 0 | result = isc__nm_socket_reuse_lb(sock); |
85 | 0 | RUNTIME_CHECK(result == ISC_R_SUCCESS); |
86 | 0 | } |
87 | |
|
88 | 0 | return sock; |
89 | 0 | } |
90 | | |
91 | | /* |
92 | | * Asynchronous 'udplisten' call handler: start listening on a UDP socket. |
93 | | */ |
94 | | static void |
95 | 0 | start_udp_child_job(void *arg) { |
96 | 0 | isc_nmsocket_t *sock = arg; |
97 | |
|
98 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
99 | 0 | REQUIRE(VALID_NMSOCK(sock->parent)); |
100 | 0 | REQUIRE(sock->type == isc_nm_udpsocket); |
101 | 0 | REQUIRE(sock->tid == isc_tid()); |
102 | |
|
103 | 0 | int r, uv_bind_flags = 0; |
104 | 0 | int uv_init_flags = 0; |
105 | 0 | sa_family_t sa_family = sock->iface.type.sa.sa_family; |
106 | 0 | isc_result_t result = ISC_R_UNSET; |
107 | 0 | isc_loop_t *loop = sock->worker->loop; |
108 | |
|
109 | 0 | (void)isc__nm_socket_min_mtu(sock->fd, sa_family); |
110 | |
|
111 | 0 | #if HAVE_DECL_UV_UDP_RECVMMSG |
112 | 0 | uv_init_flags |= UV_UDP_RECVMMSG; |
113 | 0 | #endif |
114 | 0 | r = uv_udp_init_ex(&loop->loop, &sock->uv_handle.udp, uv_init_flags); |
115 | 0 | UV_RUNTIME_CHECK(uv_udp_init_ex, r); |
116 | 0 | uv_handle_set_data(&sock->uv_handle.handle, sock); |
117 | | /* This keeps the socket alive after everything else is gone */ |
118 | 0 | isc__nmsocket_attach(sock, &(isc_nmsocket_t *){ NULL }); |
119 | |
|
120 | 0 | r = uv_timer_init(&loop->loop, &sock->read_timer); |
121 | 0 | UV_RUNTIME_CHECK(uv_timer_init, r); |
122 | 0 | uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); |
123 | |
|
124 | 0 | r = uv_udp_open(&sock->uv_handle.udp, sock->fd); |
125 | 0 | if (r < 0) { |
126 | 0 | isc__nm_closesocket(sock->fd); |
127 | 0 | isc__nm_incstats(sock, STATID_OPENFAIL); |
128 | 0 | goto done; |
129 | 0 | } |
130 | 0 | isc__nm_incstats(sock, STATID_OPEN); |
131 | |
|
132 | 0 | if (sa_family == AF_INET6) { |
133 | 0 | uv_bind_flags |= UV_UDP_IPV6ONLY; |
134 | 0 | } |
135 | |
|
136 | 0 | if (isc__netmgr->load_balance_sockets) { |
137 | 0 | r = isc__nm_udp_freebind(&sock->uv_handle.udp, |
138 | 0 | &sock->parent->iface.type.sa, |
139 | 0 | uv_bind_flags); |
140 | 0 | if (r < 0) { |
141 | 0 | isc__nm_incstats(sock, STATID_BINDFAIL); |
142 | 0 | goto done; |
143 | 0 | } |
144 | 0 | } else if (sock->tid == 0) { |
145 | | /* This thread is first, bind the socket */ |
146 | 0 | r = isc__nm_udp_freebind(&sock->uv_handle.udp, |
147 | 0 | &sock->parent->iface.type.sa, |
148 | 0 | uv_bind_flags); |
149 | 0 | if (r < 0) { |
150 | 0 | isc__nm_incstats(sock, STATID_BINDFAIL); |
151 | 0 | goto done; |
152 | 0 | } |
153 | 0 | sock->parent->uv_handle.udp.flags = sock->uv_handle.udp.flags; |
154 | 0 | } else { |
155 | | /* The socket is already bound, just copy the flags */ |
156 | 0 | sock->uv_handle.udp.flags = sock->parent->uv_handle.udp.flags; |
157 | 0 | } |
158 | | |
159 | 0 | isc__nm_set_network_buffers(&sock->uv_handle.handle); |
160 | |
|
161 | 0 | r = uv_udp_recv_start(&sock->uv_handle.udp, isc__nm_alloc_cb, |
162 | 0 | isc__nm_udp_read_cb); |
163 | 0 | if (r != 0) { |
164 | 0 | isc__nm_incstats(sock, STATID_BINDFAIL); |
165 | 0 | goto done; |
166 | 0 | } |
167 | | |
168 | 0 | done: |
169 | 0 | result = isc_uverr2result(r); |
170 | |
|
171 | 0 | sock->result = result; |
172 | |
|
173 | 0 | REQUIRE(!loop->paused); |
174 | |
|
175 | 0 | if (sock->tid != 0) { |
176 | 0 | isc_barrier_wait(&sock->parent->listen_barrier); |
177 | 0 | } |
178 | 0 | } |
179 | | |
180 | | static void |
181 | | start_udp_child(isc_sockaddr_t *iface, isc_nmsocket_t *sock, uv_os_sock_t fd, |
182 | 0 | isc_tid_t tid) { |
183 | 0 | isc__networker_t *worker = isc__networker_get(tid); |
184 | 0 | isc_nmsocket_t *csock = &sock->children[tid]; |
185 | |
|
186 | 0 | isc__nmsocket_init(csock, worker, isc_nm_udpsocket, iface, sock); |
187 | 0 | csock->recv_cb = sock->recv_cb; |
188 | 0 | csock->recv_cbarg = sock->recv_cbarg; |
189 | 0 | csock->inactive_handles_max = ISC_NM_NMHANDLES_MAX; |
190 | |
|
191 | 0 | if (isc__netmgr->load_balance_sockets) { |
192 | 0 | csock->fd = isc__nm_udp_lb_socket(iface->type.sa.sa_family); |
193 | 0 | } else { |
194 | 0 | csock->fd = dup(fd); |
195 | 0 | } |
196 | 0 | INSIST(csock->fd >= 0); |
197 | |
|
198 | 0 | if (tid == 0) { |
199 | 0 | start_udp_child_job(csock); |
200 | 0 | } else { |
201 | 0 | isc_async_run(worker->loop, start_udp_child_job, csock); |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | isc_result_t |
206 | | isc_nm_listenudp(uint32_t workers, isc_sockaddr_t *iface, isc_nm_recv_cb_t cb, |
207 | 0 | void *cbarg, isc_nmsocket_t **sockp) { |
208 | 0 | isc_result_t result = ISC_R_UNSET; |
209 | 0 | isc_nmsocket_t *sock = NULL; |
210 | 0 | uv_os_sock_t fd = -1; |
211 | 0 | isc__networker_t *worker = isc__networker_get(0); |
212 | |
|
213 | 0 | REQUIRE(isc_tid() == 0); |
214 | |
|
215 | 0 | if (isc__nm_closing(worker)) { |
216 | 0 | return ISC_R_SHUTTINGDOWN; |
217 | 0 | } |
218 | | |
219 | 0 | sock = isc_mempool_get(worker->nmsocket_pool); |
220 | 0 | isc__nmsocket_init(sock, worker, isc_nm_udplistener, iface, NULL); |
221 | |
|
222 | 0 | if (workers == ISC_NM_LISTEN_ALL) { |
223 | 0 | sock->nchildren = (uint32_t)isc__netmgr->nloops; |
224 | 0 | } else { |
225 | 0 | sock->nchildren = workers; |
226 | 0 | } |
227 | 0 | REQUIRE(sock->nchildren <= isc__netmgr->nloops); |
228 | |
|
229 | 0 | sock->children = isc_mem_cget(worker->mctx, sock->nchildren, |
230 | 0 | sizeof(sock->children[0])); |
231 | |
|
232 | 0 | isc__nmsocket_barrier_init(sock); |
233 | |
|
234 | 0 | sock->recv_cb = cb; |
235 | 0 | sock->recv_cbarg = cbarg; |
236 | |
|
237 | 0 | if (!isc__netmgr->load_balance_sockets) { |
238 | 0 | fd = isc__nm_udp_lb_socket(iface->type.sa.sa_family); |
239 | 0 | } |
240 | |
|
241 | 0 | start_udp_child(iface, sock, fd, 0); |
242 | 0 | result = sock->children[0].result; |
243 | 0 | INSIST(result != ISC_R_UNSET); |
244 | |
|
245 | 0 | for (size_t i = 1; i < sock->nchildren; i++) { |
246 | 0 | start_udp_child(iface, sock, fd, i); |
247 | 0 | } |
248 | |
|
249 | 0 | isc_barrier_wait(&sock->listen_barrier); |
250 | |
|
251 | 0 | if (!isc__netmgr->load_balance_sockets) { |
252 | 0 | isc__nm_closesocket(fd); |
253 | 0 | } |
254 | | |
255 | | /* |
256 | | * If any of the child sockets have failed then isc_nm_listenudp |
257 | | * fails. |
258 | | */ |
259 | 0 | for (size_t i = 1; i < sock->nchildren; i++) { |
260 | 0 | if (result == ISC_R_SUCCESS && |
261 | 0 | sock->children[i].result != ISC_R_SUCCESS) |
262 | 0 | { |
263 | 0 | result = sock->children[i].result; |
264 | 0 | } |
265 | 0 | } |
266 | |
|
267 | 0 | if (result != ISC_R_SUCCESS) { |
268 | 0 | sock->active = false; |
269 | 0 | isc__nm_udp_stoplistening(sock); |
270 | 0 | isc_nmsocket_close(&sock); |
271 | |
|
272 | 0 | return result; |
273 | 0 | } |
274 | | |
275 | 0 | sock->active = true; |
276 | |
|
277 | 0 | *sockp = sock; |
278 | 0 | return ISC_R_SUCCESS; |
279 | 0 | } |
280 | | |
281 | | #ifdef USE_ROUTE_SOCKET |
282 | | static isc_result_t |
283 | 0 | route_socket(uv_os_sock_t *fdp) { |
284 | 0 | uv_os_sock_t fd = -1; |
285 | 0 | #ifdef USE_NETLINK |
286 | 0 | struct sockaddr_nl sa; |
287 | 0 | int r; |
288 | 0 | #endif |
289 | |
|
290 | 0 | RETERR(isc__nm_socket(ROUTE_SOCKET_PF, SOCK_RAW, ROUTE_SOCKET_PROTOCOL, |
291 | 0 | &fd)); |
292 | |
|
293 | 0 | #ifdef USE_NETLINK |
294 | 0 | sa.nl_family = PF_NETLINK; |
295 | 0 | sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; |
296 | 0 | r = bind(fd, (struct sockaddr *)&sa, sizeof(sa)); |
297 | 0 | if (r < 0) { |
298 | 0 | isc__nm_closesocket(fd); |
299 | 0 | return isc_errno_toresult(r); |
300 | 0 | } |
301 | 0 | #endif |
302 | | |
303 | 0 | *fdp = fd; |
304 | 0 | return ISC_R_SUCCESS; |
305 | 0 | } |
306 | | |
307 | | static isc_result_t |
308 | 0 | route_connect_direct(isc_nmsocket_t *sock) { |
309 | 0 | isc__networker_t *worker = NULL; |
310 | 0 | int r; |
311 | |
|
312 | 0 | REQUIRE(sock->tid == isc_tid()); |
313 | |
|
314 | 0 | worker = sock->worker; |
315 | |
|
316 | 0 | sock->connecting = true; |
317 | |
|
318 | 0 | r = uv_udp_init(&worker->loop->loop, &sock->uv_handle.udp); |
319 | 0 | UV_RUNTIME_CHECK(uv_udp_init, r); |
320 | 0 | uv_handle_set_data(&sock->uv_handle.handle, sock); |
321 | |
|
322 | 0 | r = uv_timer_init(&worker->loop->loop, &sock->read_timer); |
323 | 0 | UV_RUNTIME_CHECK(uv_timer_init, r); |
324 | 0 | uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); |
325 | |
|
326 | 0 | if (isc__nm_closing(worker)) { |
327 | 0 | return ISC_R_SHUTTINGDOWN; |
328 | 0 | } |
329 | | |
330 | 0 | r = uv_udp_open(&sock->uv_handle.udp, sock->fd); |
331 | 0 | if (r != 0) { |
332 | 0 | return isc_uverr2result(r); |
333 | 0 | } |
334 | | |
335 | 0 | isc__nm_set_network_buffers(&sock->uv_handle.handle); |
336 | |
|
337 | 0 | sock->connecting = false; |
338 | 0 | sock->connected = true; |
339 | |
|
340 | 0 | return ISC_R_SUCCESS; |
341 | 0 | } |
342 | | |
343 | | #endif /* USE_ROUTE_SOCKET */ |
344 | | |
345 | | isc_result_t |
346 | 0 | isc_nm_routeconnect(isc_nm_cb_t cb, void *cbarg) { |
347 | 0 | #ifdef USE_ROUTE_SOCKET |
348 | 0 | isc_result_t result = ISC_R_SUCCESS; |
349 | 0 | isc_nmsocket_t *sock = NULL; |
350 | 0 | isc__nm_uvreq_t *req = NULL; |
351 | 0 | isc__networker_t *worker = isc__networker_current(); |
352 | 0 | uv_os_sock_t fd = -1; |
353 | |
|
354 | 0 | REQUIRE(isc_tid() == 0); |
355 | |
|
356 | 0 | if (isc__nm_closing(worker)) { |
357 | 0 | return ISC_R_SHUTTINGDOWN; |
358 | 0 | } |
359 | | |
360 | 0 | RETERR(route_socket(&fd)); |
361 | |
|
362 | 0 | sock = isc_mempool_get(worker->nmsocket_pool); |
363 | 0 | isc__nmsocket_init(sock, worker, isc_nm_udpsocket, NULL, NULL); |
364 | |
|
365 | 0 | sock->connect_cb = cb; |
366 | 0 | sock->connect_cbarg = cbarg; |
367 | 0 | sock->client = true; |
368 | 0 | sock->route_sock = true; |
369 | 0 | sock->fd = fd; |
370 | |
|
371 | 0 | req = isc__nm_uvreq_get(sock); |
372 | 0 | req->cb.connect = cb; |
373 | 0 | req->cbarg = cbarg; |
374 | 0 | req->handle = isc__nmhandle_get(sock, NULL, NULL); |
375 | |
|
376 | 0 | sock->active = true; |
377 | |
|
378 | 0 | result = route_connect_direct(sock); |
379 | 0 | if (result != ISC_R_SUCCESS) { |
380 | 0 | sock->active = false; |
381 | 0 | isc__nm_udp_close(sock); |
382 | 0 | } |
383 | |
|
384 | 0 | isc__nm_connectcb(sock, req, result, true); |
385 | |
|
386 | 0 | isc__nmsocket_detach(&sock); |
387 | |
|
388 | 0 | return ISC_R_SUCCESS; |
389 | | #else /* USE_ROUTE_SOCKET */ |
390 | | UNUSED(mgr); |
391 | | UNUSED(cb); |
392 | | UNUSED(cbarg); |
393 | | UNUSED(extrahandlesize); |
394 | | return ISC_R_NOTIMPLEMENTED; |
395 | | #endif /* USE_ROUTE_SOCKET */ |
396 | 0 | } |
397 | | |
398 | | /* |
399 | | * Asynchronous 'udpstop' call handler: stop listening on a UDP socket. |
400 | | */ |
401 | | static void |
402 | 0 | stop_udp_child_job(void *arg) { |
403 | 0 | isc_nmsocket_t *sock = arg; |
404 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
405 | 0 | REQUIRE(sock->tid == isc_tid()); |
406 | 0 | REQUIRE(sock->parent != NULL); |
407 | |
|
408 | 0 | sock->active = false; |
409 | |
|
410 | 0 | isc__nm_udp_close(sock); |
411 | |
|
412 | 0 | REQUIRE(!sock->worker->loop->paused); |
413 | 0 | isc_barrier_wait(&sock->parent->stop_barrier); |
414 | 0 | } |
415 | | |
416 | | static void |
417 | 0 | stop_udp_child(isc_nmsocket_t *sock) { |
418 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
419 | |
|
420 | 0 | if (sock->tid == 0) { |
421 | 0 | stop_udp_child_job(sock); |
422 | 0 | } else { |
423 | 0 | isc_async_run(sock->worker->loop, stop_udp_child_job, sock); |
424 | 0 | } |
425 | 0 | } |
426 | | |
427 | | void |
428 | 0 | isc__nm_udp_stoplistening(isc_nmsocket_t *sock) { |
429 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
430 | 0 | REQUIRE(sock->type == isc_nm_udplistener); |
431 | 0 | REQUIRE(sock->tid == isc_tid()); |
432 | 0 | REQUIRE(sock->tid == 0); |
433 | 0 | REQUIRE(!sock->closing); |
434 | |
|
435 | 0 | sock->closing = true; |
436 | | |
437 | | /* Mark the parent socket inactive */ |
438 | 0 | sock->active = false; |
439 | | |
440 | | /* Stop all the other threads' children */ |
441 | 0 | for (size_t i = 1; i < sock->nchildren; i++) { |
442 | 0 | stop_udp_child(&sock->children[i]); |
443 | 0 | } |
444 | | |
445 | | /* Stop the child for the main thread */ |
446 | 0 | stop_udp_child(&sock->children[0]); |
447 | | |
448 | | /* Stop the parent */ |
449 | 0 | sock->closed = true; |
450 | 0 | isc__nmsocket_prep_destroy(sock); |
451 | 0 | } |
452 | | |
453 | | /* |
454 | | * udp_recv_cb handles incoming UDP packet from uv. The buffer here is |
455 | | * reused for a series of packets, so we need to allocate a new one. |
456 | | * This new one can be reused to send the response then. |
457 | | */ |
458 | | void |
459 | | isc__nm_udp_read_cb(uv_udp_t *handle, ssize_t nrecv, const uv_buf_t *buf, |
460 | 0 | const struct sockaddr *addr, unsigned int flags) { |
461 | 0 | isc_nmsocket_t *sock = uv_handle_get_data((uv_handle_t *)handle); |
462 | 0 | isc__nm_uvreq_t *req = NULL; |
463 | 0 | uint32_t maxudp; |
464 | 0 | isc_result_t result; |
465 | 0 | isc_sockaddr_t sockaddr, *sa = NULL; |
466 | |
|
467 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
468 | 0 | REQUIRE(sock->tid == isc_tid()); |
469 | | |
470 | | /* |
471 | | * When using recvmmsg(2), if no errors occur, there will be a final |
472 | | * callback with nrecv set to 0, addr set to NULL and the buffer |
473 | | * pointing at the initially allocated data with the UV_UDP_MMSG_CHUNK |
474 | | * flag cleared and the UV_UDP_MMSG_FREE flag set. |
475 | | */ |
476 | 0 | #if HAVE_DECL_UV_UDP_MMSG_FREE |
477 | 0 | if ((flags & UV_UDP_MMSG_FREE) == UV_UDP_MMSG_FREE) { |
478 | 0 | INSIST(nrecv == 0); |
479 | 0 | INSIST(addr == NULL); |
480 | 0 | goto free; |
481 | 0 | } |
482 | | #else |
483 | | UNUSED(flags); |
484 | | #endif |
485 | | /* |
486 | | * Possible reasons to return now without processing: |
487 | | * |
488 | | * - If we're simulating a firewall blocking UDP packets |
489 | | * bigger than 'maxudp' bytes for testing purposes. |
490 | | */ |
491 | 0 | maxudp = atomic_load_relaxed(&isc__netmgr->maxudp); |
492 | 0 | if (maxudp != 0 && (uint32_t)nrecv > maxudp) { |
493 | | /* |
494 | | * We need to keep the read_cb intact in case, so the |
495 | | * readtimeout_cb can trigger and not crash because of |
496 | | * missing read_req. |
497 | | */ |
498 | 0 | goto free; |
499 | 0 | } |
500 | | |
501 | | /* |
502 | | * - If there was a networking error. |
503 | | */ |
504 | 0 | if (nrecv < 0) { |
505 | 0 | isc__nm_failed_read_cb(sock, isc_uverr2result(nrecv), false); |
506 | 0 | goto free; |
507 | 0 | } |
508 | | |
509 | | /* |
510 | | * - If the network manager is shutting down |
511 | | */ |
512 | 0 | if (isc__nm_closing(sock->worker)) { |
513 | 0 | isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); |
514 | 0 | goto free; |
515 | 0 | } |
516 | | |
517 | | /* |
518 | | * - If the socket is no longer active. |
519 | | */ |
520 | 0 | if (!isc__nmsocket_active(sock)) { |
521 | 0 | isc__nm_failed_read_cb(sock, ISC_R_CANCELED, false); |
522 | 0 | goto free; |
523 | 0 | } |
524 | | |
525 | | /* |
526 | | * End of the current (iteration) datagram stream, just free the buffer. |
527 | | * The callback with nrecv == 0 and addr == NULL is called for both |
528 | | * normal UDP sockets and recvmmsg sockets at the end of every event |
529 | | * loop iteration. |
530 | | */ |
531 | 0 | if (nrecv == 0 && addr == NULL) { |
532 | 0 | INSIST(flags == 0); |
533 | 0 | goto free; |
534 | 0 | } |
535 | | |
536 | | /* |
537 | | * We could receive an empty datagram in which case: |
538 | | * nrecv == 0 and addr != NULL |
539 | | */ |
540 | 0 | INSIST(addr != NULL); |
541 | |
|
542 | 0 | if (!sock->route_sock) { |
543 | 0 | result = isc_sockaddr_fromsockaddr(&sockaddr, addr); |
544 | 0 | RUNTIME_CHECK(result == ISC_R_SUCCESS); |
545 | 0 | sa = &sockaddr; |
546 | 0 | } |
547 | |
|
548 | 0 | req = isc__nm_get_read_req(sock, sa); |
549 | | |
550 | | /* |
551 | | * The callback will be called synchronously, because result is |
552 | | * ISC_R_SUCCESS, so we are ok of passing the buf directly. |
553 | | */ |
554 | 0 | req->uvbuf.base = buf->base; |
555 | 0 | req->uvbuf.len = nrecv; |
556 | |
|
557 | 0 | sock->reading = false; |
558 | | |
559 | | /* |
560 | | * The client isc_nm_read() expects just a single message, so we need to |
561 | | * stop reading now. The reading could be restarted in the read |
562 | | * callback with another isc_nm_read() call. |
563 | | */ |
564 | 0 | if (sock->client) { |
565 | 0 | isc__nmsocket_timer_stop(sock); |
566 | 0 | isc__nm_stop_reading(sock); |
567 | 0 | isc__nmsocket_clearcb(sock); |
568 | 0 | } |
569 | |
|
570 | 0 | REQUIRE(!sock->processing); |
571 | 0 | sock->processing = true; |
572 | 0 | isc__nm_readcb(sock, req, ISC_R_SUCCESS, false); |
573 | 0 | sock->processing = false; |
574 | |
|
575 | 0 | free: |
576 | 0 | #if HAVE_DECL_UV_UDP_MMSG_CHUNK |
577 | | /* |
578 | | * When using recvmmsg(2), chunks will have the UV_UDP_MMSG_CHUNK flag |
579 | | * set, those must not be freed. |
580 | | */ |
581 | 0 | if ((flags & UV_UDP_MMSG_CHUNK) == UV_UDP_MMSG_CHUNK) { |
582 | 0 | return; |
583 | 0 | } |
584 | 0 | #endif |
585 | | |
586 | | /* |
587 | | * When using recvmmsg(2), if a UDP socket error occurs, nrecv will be < |
588 | | * 0. In either scenario, the callee can now safely free the provided |
589 | | * buffer. |
590 | | */ |
591 | 0 | if (nrecv < 0) { |
592 | | /* |
593 | | * The buffer may be a null buffer on error. |
594 | | */ |
595 | 0 | if (buf->base == NULL && buf->len == 0) { |
596 | 0 | return; |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | 0 | isc__nm_free_uvbuf(sock, buf); |
601 | 0 | } |
602 | | |
603 | | static void |
604 | 0 | udp_send_cb(uv_udp_send_t *req, int status) { |
605 | 0 | isc_result_t result = ISC_R_SUCCESS; |
606 | 0 | isc__nm_uvreq_t *uvreq = uv_handle_get_data((uv_handle_t *)req); |
607 | 0 | isc_nmsocket_t *sock = NULL; |
608 | |
|
609 | 0 | REQUIRE(VALID_UVREQ(uvreq)); |
610 | 0 | REQUIRE(VALID_NMHANDLE(uvreq->handle)); |
611 | |
|
612 | 0 | sock = uvreq->sock; |
613 | |
|
614 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
615 | 0 | REQUIRE(sock->tid == isc_tid()); |
616 | |
|
617 | 0 | if (status < 0) { |
618 | 0 | isc__nm_incstats(sock, STATID_SENDFAIL); |
619 | 0 | isc__nm_failed_send_cb(sock, uvreq, isc_uverr2result(status), |
620 | 0 | false); |
621 | 0 | return; |
622 | 0 | } |
623 | | |
624 | 0 | isc__nm_sendcb(sock, uvreq, result, false); |
625 | 0 | } |
626 | | |
627 | | static _Atomic(isc_stdtime_t) last_udpsends_log = 0; |
628 | | |
629 | | static bool |
630 | 0 | can_log_udp_sends(void) { |
631 | 0 | isc_stdtime_t now = isc_stdtime_now(); |
632 | 0 | isc_stdtime_t last = atomic_exchange_relaxed(&last_udpsends_log, now); |
633 | 0 | if (now != last) { |
634 | 0 | return true; |
635 | 0 | } |
636 | | |
637 | 0 | return false; |
638 | 0 | } |
639 | | |
640 | | /* |
641 | | * Send the data in 'region' to a peer via a UDP socket. We try to find |
642 | | * a proper sibling/child socket so that we won't have to jump to |
643 | | * another thread. |
644 | | */ |
645 | | void |
646 | | isc__nm_udp_send(isc_nmhandle_t *handle, const isc_region_t *region, |
647 | 0 | isc_nm_cb_t cb, void *cbarg) { |
648 | 0 | isc_nmsocket_t *sock = handle->sock; |
649 | 0 | const isc_sockaddr_t *peer = &handle->peer; |
650 | 0 | const struct sockaddr *sa = NULL; |
651 | 0 | isc__nm_uvreq_t *uvreq = NULL; |
652 | 0 | isc__networker_t *worker = NULL; |
653 | 0 | uint32_t maxudp; |
654 | 0 | int r; |
655 | 0 | isc_result_t result; |
656 | |
|
657 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
658 | 0 | REQUIRE(sock->type == isc_nm_udpsocket); |
659 | 0 | REQUIRE(sock->tid == isc_tid()); |
660 | |
|
661 | 0 | worker = sock->worker; |
662 | 0 | maxudp = atomic_load(&isc__netmgr->maxudp); |
663 | 0 | sa = sock->connected ? NULL : &peer->type.sa; |
664 | | |
665 | | /* |
666 | | * We're simulating a firewall blocking UDP packets bigger than |
667 | | * 'maxudp' bytes, for testing purposes. |
668 | | * |
669 | | * The client would ordinarily have unreferenced the handle |
670 | | * in the callback, but that won't happen in this case, so |
671 | | * we need to do so here. |
672 | | */ |
673 | 0 | if (maxudp != 0 && region->length > maxudp) { |
674 | 0 | isc_nmhandle_detach(&handle); |
675 | 0 | return; |
676 | 0 | } |
677 | | |
678 | 0 | uvreq = isc__nm_uvreq_get(sock); |
679 | 0 | uvreq->uvbuf.base = (char *)region->base; |
680 | 0 | uvreq->uvbuf.len = region->length; |
681 | |
|
682 | 0 | isc_nmhandle_attach(handle, &uvreq->handle); |
683 | |
|
684 | 0 | uvreq->cb.send = cb; |
685 | 0 | uvreq->cbarg = cbarg; |
686 | |
|
687 | 0 | if (isc__nm_closing(worker)) { |
688 | 0 | result = ISC_R_SHUTTINGDOWN; |
689 | 0 | goto fail; |
690 | 0 | } |
691 | | |
692 | 0 | if (isc__nmsocket_closing(sock)) { |
693 | 0 | result = ISC_R_CANCELED; |
694 | 0 | goto fail; |
695 | 0 | } |
696 | | |
697 | 0 | if (uv_udp_get_send_queue_size(&sock->uv_handle.udp) > |
698 | 0 | ISC_NETMGR_UDP_SENDBUF_SIZE) |
699 | 0 | { |
700 | | /* |
701 | | * The kernel UDP send queue is full, try sending the UDP |
702 | | * response synchronously instead of just failing. |
703 | | */ |
704 | 0 | r = uv_udp_try_send(&sock->uv_handle.udp, &uvreq->uvbuf, 1, sa); |
705 | 0 | if (r < 0) { |
706 | 0 | if (can_log_udp_sends()) { |
707 | 0 | isc__netmgr_log( |
708 | 0 | ISC_LOG_ERROR, |
709 | 0 | "Sending UDP messages failed: %s", |
710 | 0 | isc_result_totext(isc_uverr2result(r))); |
711 | 0 | } |
712 | |
|
713 | 0 | isc__nm_incstats(sock, STATID_SENDFAIL); |
714 | 0 | result = isc_uverr2result(r); |
715 | 0 | goto fail; |
716 | 0 | } |
717 | | |
718 | 0 | RUNTIME_CHECK(r == (int)region->length); |
719 | 0 | isc__nm_sendcb(sock, uvreq, ISC_R_SUCCESS, true); |
720 | |
|
721 | 0 | } else { |
722 | | /* Send the message asynchronously */ |
723 | 0 | r = uv_udp_send(&uvreq->uv_req.udp_send, &sock->uv_handle.udp, |
724 | 0 | &uvreq->uvbuf, 1, sa, udp_send_cb); |
725 | 0 | if (r < 0) { |
726 | 0 | isc__nm_incstats(sock, STATID_SENDFAIL); |
727 | 0 | result = isc_uverr2result(r); |
728 | 0 | goto fail; |
729 | 0 | } |
730 | 0 | } |
731 | 0 | return; |
732 | 0 | fail: |
733 | 0 | isc__nm_failed_send_cb(sock, uvreq, result, true); |
734 | 0 | } |
735 | | |
736 | | static isc_result_t |
737 | 0 | udp_connect_direct(isc_nmsocket_t *sock, isc__nm_uvreq_t *req) { |
738 | 0 | int uv_bind_flags = 0; |
739 | 0 | int r; |
740 | 0 | isc__networker_t *worker = sock->worker; |
741 | 0 | isc_result_t result; |
742 | |
|
743 | 0 | r = uv_udp_init(&worker->loop->loop, &sock->uv_handle.udp); |
744 | 0 | UV_RUNTIME_CHECK(uv_udp_init, r); |
745 | 0 | uv_handle_set_data(&sock->uv_handle.handle, sock); |
746 | |
|
747 | 0 | r = uv_timer_init(&worker->loop->loop, &sock->read_timer); |
748 | 0 | UV_RUNTIME_CHECK(uv_timer_init, r); |
749 | 0 | uv_handle_set_data((uv_handle_t *)&sock->read_timer, sock); |
750 | |
|
751 | 0 | r = uv_udp_open(&sock->uv_handle.udp, sock->fd); |
752 | 0 | if (r != 0) { |
753 | 0 | isc__nm_incstats(sock, STATID_OPENFAIL); |
754 | 0 | return isc_uverr2result(r); |
755 | 0 | } |
756 | 0 | isc__nm_incstats(sock, STATID_OPEN); |
757 | | |
758 | | /* |
759 | | * uv_udp_open() enables REUSE_ADDR, we need to disable it again. |
760 | | */ |
761 | 0 | result = isc__nm_socket_reuse(sock->fd, 0); |
762 | 0 | RUNTIME_CHECK(result == ISC_R_SUCCESS); |
763 | |
|
764 | 0 | if (sock->iface.type.sa.sa_family == AF_INET6) { |
765 | 0 | uv_bind_flags |= UV_UDP_IPV6ONLY; |
766 | 0 | } |
767 | |
|
768 | 0 | #if HAVE_DECL_UV_UDP_LINUX_RECVERR |
769 | 0 | uv_bind_flags |= UV_UDP_LINUX_RECVERR; |
770 | 0 | #endif |
771 | |
|
772 | 0 | r = uv_udp_bind(&sock->uv_handle.udp, &sock->iface.type.sa, |
773 | 0 | uv_bind_flags); |
774 | 0 | if (r != 0) { |
775 | 0 | isc__nm_incstats(sock, STATID_BINDFAIL); |
776 | 0 | return isc_uverr2result(r); |
777 | 0 | } |
778 | | |
779 | 0 | isc__nm_set_network_buffers(&sock->uv_handle.handle); |
780 | | |
781 | | /* |
782 | | * On FreeBSD the UDP connect() call sometimes results in a |
783 | | * spurious transient EADDRINUSE. Try a few more times before |
784 | | * giving up. |
785 | | */ |
786 | 0 | do { |
787 | 0 | r = uv_udp_connect(&sock->uv_handle.udp, &req->peer.type.sa); |
788 | 0 | } while (r == UV_EADDRINUSE && --req->connect_tries > 0); |
789 | 0 | if (r != 0) { |
790 | 0 | isc__nm_incstats(sock, STATID_CONNECTFAIL); |
791 | 0 | return isc_uverr2result(r); |
792 | 0 | } |
793 | 0 | isc__nm_incstats(sock, STATID_CONNECT); |
794 | |
|
795 | 0 | return ISC_R_SUCCESS; |
796 | 0 | } |
797 | | |
798 | | void |
799 | | isc_nm_udpconnect(isc_sockaddr_t *local, isc_sockaddr_t *peer, isc_nm_cb_t cb, |
800 | 0 | void *cbarg, unsigned int timeout) { |
801 | 0 | isc_result_t result = ISC_R_SUCCESS; |
802 | 0 | isc_nmsocket_t *sock = NULL; |
803 | 0 | isc__nm_uvreq_t *req = NULL; |
804 | 0 | sa_family_t sa_family; |
805 | 0 | isc__networker_t *worker = isc__networker_current(); |
806 | 0 | uv_os_sock_t fd = -1; |
807 | |
|
808 | 0 | REQUIRE(local != NULL); |
809 | 0 | REQUIRE(peer != NULL); |
810 | |
|
811 | 0 | if (isc__nm_closing(worker)) { |
812 | 0 | cb(NULL, ISC_R_SHUTTINGDOWN, cbarg); |
813 | 0 | return; |
814 | 0 | } |
815 | | |
816 | 0 | sa_family = peer->type.sa.sa_family; |
817 | |
|
818 | 0 | result = isc__nm_socket(sa_family, SOCK_DGRAM, 0, &fd); |
819 | 0 | if (result != ISC_R_SUCCESS) { |
820 | 0 | cb(NULL, result, cbarg); |
821 | 0 | return; |
822 | 0 | } |
823 | | |
824 | | /* Initialize the new socket */ |
825 | 0 | sock = isc_mempool_get(worker->nmsocket_pool); |
826 | 0 | isc__nmsocket_init(sock, worker, isc_nm_udpsocket, local, NULL); |
827 | |
|
828 | 0 | sock->connect_cb = cb; |
829 | 0 | sock->connect_cbarg = cbarg; |
830 | 0 | sock->read_timeout = timeout; |
831 | 0 | sock->peer = *peer; |
832 | 0 | sock->client = true; |
833 | |
|
834 | 0 | sock->fd = fd; |
835 | |
|
836 | 0 | (void)isc__nm_socket_disable_pmtud(sock->fd, sa_family); |
837 | |
|
838 | 0 | (void)isc__nm_socket_min_mtu(sock->fd, sa_family); |
839 | | |
840 | | /* Initialize the request */ |
841 | 0 | req = isc__nm_uvreq_get(sock); |
842 | 0 | req->cb.connect = cb; |
843 | 0 | req->cbarg = cbarg; |
844 | 0 | req->peer = *peer; |
845 | 0 | req->local = *local; |
846 | 0 | req->handle = isc__nmhandle_get(sock, &req->peer, &sock->iface); |
847 | |
|
848 | 0 | sock->active = true; |
849 | 0 | sock->connecting = true; |
850 | |
|
851 | 0 | result = udp_connect_direct(sock, req); |
852 | 0 | if (result != ISC_R_SUCCESS) { |
853 | 0 | sock->active = false; |
854 | 0 | isc__nm_failed_connect_cb(sock, req, result, true); |
855 | 0 | isc__nmsocket_detach(&sock); |
856 | 0 | return; |
857 | 0 | } |
858 | | |
859 | 0 | sock->connecting = false; |
860 | 0 | sock->connected = true; |
861 | |
|
862 | 0 | isc__nm_connectcb(sock, req, ISC_R_SUCCESS, true); |
863 | 0 | isc__nmsocket_detach(&sock); |
864 | 0 | } |
865 | | |
866 | | void |
867 | | isc__nm_udp_failed_read_cb(isc_nmsocket_t *sock, isc_result_t result, |
868 | 0 | bool async) { |
869 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
870 | 0 | REQUIRE(result != ISC_R_SUCCESS); |
871 | 0 | REQUIRE(sock->tid == isc_tid()); |
872 | | |
873 | | /* |
874 | | * For UDP server socket, we don't have child socket via |
875 | | * "accept", so we: |
876 | | * - we continue to read |
877 | | * - we don't clear the callbacks |
878 | | * - we don't destroy it (only stoplistening could do that) |
879 | | */ |
880 | |
|
881 | 0 | if (sock->client) { |
882 | 0 | isc__nmsocket_timer_stop(sock); |
883 | 0 | isc__nm_stop_reading(sock); |
884 | 0 | } |
885 | | |
886 | | /* Nobody expects the callback if isc_nm_read() wasn't called */ |
887 | 0 | if (sock->reading) { |
888 | 0 | sock->reading = false; |
889 | |
|
890 | 0 | if (sock->recv_cb != NULL) { |
891 | 0 | isc__nm_uvreq_t *req = isc__nm_get_read_req(sock, NULL); |
892 | 0 | isc__nm_readcb(sock, req, result, async); |
893 | 0 | } |
894 | 0 | } |
895 | |
|
896 | 0 | if (sock->client) { |
897 | 0 | isc__nmsocket_clearcb(sock); |
898 | 0 | isc__nmsocket_prep_destroy(sock); |
899 | 0 | return; |
900 | 0 | } |
901 | 0 | } |
902 | | |
903 | | void |
904 | 0 | isc__nm_udp_read(isc_nmhandle_t *handle, isc_nm_recv_cb_t cb, void *cbarg) { |
905 | 0 | isc_nmsocket_t *sock = NULL; |
906 | 0 | isc_result_t result; |
907 | |
|
908 | 0 | REQUIRE(VALID_NMHANDLE(handle)); |
909 | |
|
910 | 0 | sock = handle->sock; |
911 | |
|
912 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
913 | 0 | REQUIRE(sock->type == isc_nm_udpsocket); |
914 | 0 | REQUIRE(sock->statichandle == handle); |
915 | 0 | REQUIRE(sock->tid == isc_tid()); |
916 | | |
917 | | /* |
918 | | * We need to initialize the callback before checking for shutdown |
919 | | * conditions, so the callback is always called even on error condition. |
920 | | */ |
921 | 0 | sock->recv_cb = cb; |
922 | 0 | sock->recv_cbarg = cbarg; |
923 | 0 | sock->reading = true; |
924 | |
|
925 | 0 | if (isc__nm_closing(sock->worker)) { |
926 | 0 | result = ISC_R_SHUTTINGDOWN; |
927 | 0 | goto fail; |
928 | 0 | } |
929 | | |
930 | 0 | if (isc__nmsocket_closing(sock)) { |
931 | 0 | result = ISC_R_CANCELED; |
932 | 0 | goto fail; |
933 | 0 | } |
934 | | |
935 | 0 | result = isc__nm_start_reading(sock); |
936 | 0 | if (result != ISC_R_SUCCESS) { |
937 | 0 | goto fail; |
938 | 0 | } |
939 | | |
940 | 0 | isc__nmsocket_timer_restart(sock); |
941 | 0 | return; |
942 | | |
943 | 0 | fail: |
944 | 0 | sock->reading = true; /* required by the next call */ |
945 | 0 | isc__nm_failed_read_cb(sock, result, true); |
946 | 0 | } |
947 | | |
948 | | static void |
949 | 0 | udp_close_cb(uv_handle_t *handle) { |
950 | 0 | isc_nmsocket_t *sock = uv_handle_get_data(handle); |
951 | 0 | uv_handle_set_data(handle, NULL); |
952 | |
|
953 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
954 | 0 | REQUIRE(sock->tid == isc_tid()); |
955 | 0 | REQUIRE(sock->closing); |
956 | 0 | REQUIRE(!sock->closed); |
957 | |
|
958 | 0 | sock->closed = true; |
959 | |
|
960 | 0 | isc__nm_incstats(sock, STATID_CLOSE); |
961 | |
|
962 | 0 | if (sock->parent != NULL) { |
963 | | /* listening socket (listen) */ |
964 | 0 | isc__nmsocket_detach(&sock); |
965 | 0 | } else { |
966 | | /* client and server sockets */ |
967 | 0 | sock->connected = false; |
968 | 0 | isc__nmsocket_prep_destroy(sock); |
969 | 0 | } |
970 | 0 | } |
971 | | |
972 | | void |
973 | 0 | isc__nm_udp_close(isc_nmsocket_t *sock) { |
974 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
975 | 0 | REQUIRE(sock->type == isc_nm_udpsocket); |
976 | 0 | REQUIRE(sock->tid == isc_tid()); |
977 | 0 | REQUIRE(!sock->closing); |
978 | |
|
979 | 0 | sock->closing = true; |
980 | |
|
981 | 0 | isc__nmsocket_clearcb(sock); |
982 | 0 | isc__nmsocket_timer_stop(sock); |
983 | 0 | isc__nm_stop_reading(sock); |
984 | | |
985 | | /* |
986 | | * The order of the close operation is important here, the uv_close() |
987 | | * gets scheduled in the reverse order, so we need to close the timer |
988 | | * last, so its gone by the time we destroy the socket |
989 | | */ |
990 | | |
991 | | /* 2. close the listening socket */ |
992 | 0 | isc__nmsocket_clearcb(sock); |
993 | 0 | isc__nm_stop_reading(sock); |
994 | 0 | uv_close(&sock->uv_handle.handle, udp_close_cb); |
995 | | |
996 | | /* 1. close the read timer */ |
997 | 0 | isc__nmsocket_timer_stop(sock); |
998 | 0 | uv_close((uv_handle_t *)&sock->read_timer, NULL); |
999 | 0 | } |
1000 | | |
1001 | | void |
1002 | 0 | isc__nm_udp_shutdown(isc_nmsocket_t *sock) { |
1003 | 0 | REQUIRE(VALID_NMSOCK(sock)); |
1004 | 0 | REQUIRE(sock->tid == isc_tid()); |
1005 | 0 | REQUIRE(sock->type == isc_nm_udpsocket); |
1006 | | |
1007 | | /* |
1008 | | * If the socket is active, mark it inactive and |
1009 | | * continue. If it isn't active, stop now. |
1010 | | */ |
1011 | 0 | if (!sock->active) { |
1012 | 0 | return; |
1013 | 0 | } |
1014 | 0 | sock->active = false; |
1015 | | |
1016 | | /* uv_udp_connect is synchronous, we can't be in connected state */ |
1017 | 0 | REQUIRE(!sock->connecting); |
1018 | | |
1019 | | /* |
1020 | | * When the client detaches the last handle, the |
1021 | | * sock->statichandle would be NULL, in that case, nobody is |
1022 | | * interested in the callback. |
1023 | | */ |
1024 | 0 | if (sock->statichandle != NULL) { |
1025 | 0 | isc__nm_failed_read_cb(sock, ISC_R_SHUTTINGDOWN, false); |
1026 | 0 | return; |
1027 | 0 | } |
1028 | | |
1029 | | /* Destroy the non-listening socket */ |
1030 | 0 | if (sock->parent == NULL) { |
1031 | 0 | isc__nmsocket_prep_destroy(sock); |
1032 | 0 | return; |
1033 | 0 | } |
1034 | | |
1035 | | /* Destroy the listening socket if on the same loop */ |
1036 | 0 | if (sock->tid == sock->parent->tid) { |
1037 | 0 | isc__nmsocket_prep_destroy(sock->parent); |
1038 | 0 | } |
1039 | 0 | } |