Branch data Line data Source code
1 : : /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 : :
3 : : /* GIO - GLib Input, Output and Streaming Library
4 : : *
5 : : * Copyright (C) 2008 Red Hat, Inc.
6 : : * Copyright (C) 2018 Igalia S.L.
7 : : *
8 : : * SPDX-License-Identifier: LGPL-2.1-or-later
9 : : *
10 : : * This library is free software; you can redistribute it and/or
11 : : * modify it under the terms of the GNU Lesser General Public
12 : : * License as published by the Free Software Foundation; either
13 : : * version 2.1 of the License, or (at your option) any later version.
14 : : *
15 : : * This library is distributed in the hope that it will be useful,
16 : : * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 : : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 : : * Lesser General Public License for more details.
19 : : *
20 : : * You should have received a copy of the GNU Lesser General
21 : : * Public License along with this library; if not, see <http://www.gnu.org/licenses/>.
22 : : */
23 : :
24 : : #include "config.h"
25 : : #include <glib.h>
26 : : #include "glibintl.h"
27 : :
28 : : #include <stdio.h>
29 : : #include <string.h>
30 : :
31 : : #include "glib/glib-private.h"
32 : : #include "gthreadedresolver.h"
33 : : #include "gthreadedresolver-private.h"
34 : : #include "gnetworkingprivate.h"
35 : :
36 : : #include "gcancellable.h"
37 : : #include "ginetaddress.h"
38 : : #include "ginetsocketaddress.h"
39 : : #include "gtask.h"
40 : : #include "gsocketaddress.h"
41 : : #include "gsrvtarget.h"
42 : :
43 : : /*
44 : : * GThreadedResolver is a threaded wrapper around the system libc’s
45 : : * `getaddrinfo()`.
46 : : *
47 : : * It has to be threaded, as `getaddrinfo()` is synchronous. libc does provide
48 : : * `getaddrinfo_a()` as an asynchronous version of `getaddrinfo()`, but it does
49 : : * not integrate with a poll loop. It requires use of sigevent to notify of
50 : : * completion of an asynchronous operation. That either emits a signal, or calls
51 : : * a callback function in a newly spawned thread.
52 : : *
53 : : * A signal (`SIGEV_SIGNAL`) can’t be used for completion as (aside from being
54 : : * another expensive round trip into the kernel) GLib cannot pick a `SIG*`
55 : : * number which is guaranteed to not be in use elsewhere in the process. Various
56 : : * other things could be interfering with signal dispositions, such as gdb or
57 : : * other libraries in the process. Using a `signalfd()`
58 : : * [cannot improve this situation](https://ldpreload.com/blog/signalfd-is-useless).
59 : : *
60 : : * A callback function in a newly spawned thread (`SIGEV_THREAD`) could be used,
61 : : * but that is very expensive. Internally, glibc currently also just implements
62 : : * `getaddrinfo_a()`
63 : : * [using its own thread pool](https://github.com/bminor/glibc/blob/master/resolv/gai_misc.c),
64 : : * and then
65 : : * [spawns an additional thread for each completion callback](https://github.com/bminor/glibc/blob/master/resolv/gai_notify.c).
66 : : * That is very expensive.
67 : : *
68 : : * No other appropriate sigevent callback types
69 : : * [currently exist](https://sourceware.org/bugzilla/show_bug.cgi?id=30287), and
70 : : * [others agree that sigevent is not great](http://davmac.org/davpage/linux/async-io.html#posixaio).
71 : : *
72 : : * Hence, #GThreadedResolver calls the normal synchronous `getaddrinfo()` in its
73 : : * own thread pool. Previously, #GThreadedResolver used the thread pool which is
74 : : * internal to #GTask by calling g_task_run_in_thread(). That lead to exhaustion
75 : : * of the #GTask thread pool in some situations, though, as DNS lookups are
76 : : * quite frequent leaf operations in some use cases. Now, #GThreadedResolver
77 : : * uses its own private thread pool.
78 : : *
79 : : * This is similar to what
80 : : * [libasyncns](http://git.0pointer.net/libasyncns.git/tree/libasyncns/asyncns.h)
81 : : * and other multi-threaded users of `getaddrinfo()` do.
82 : : */
83 : :
84 : : struct _GThreadedResolver
85 : : {
86 : : GResolver parent_instance;
87 : :
88 : : GThreadPool *thread_pool; /* (owned) */
89 : : };
90 : :
91 : 18 : G_DEFINE_TYPE (GThreadedResolver, g_threaded_resolver, G_TYPE_RESOLVER)
92 : :
93 : : static void run_task_in_thread_pool_async (GThreadedResolver *self,
94 : : GTask *task);
95 : : static void run_task_in_thread_pool_sync (GThreadedResolver *self,
96 : : GTask *task);
97 : : static void threaded_resolver_worker_cb (gpointer task_data,
98 : : gpointer user_data);
99 : :
100 : : static void
101 : 4 : g_threaded_resolver_init (GThreadedResolver *self)
102 : : {
103 : 4 : self->thread_pool = g_thread_pool_new_full (threaded_resolver_worker_cb,
104 : : self,
105 : : (GDestroyNotify) g_object_unref,
106 : : 20,
107 : : FALSE,
108 : : NULL);
109 : 4 : }
110 : :
111 : : static void
112 : 0 : g_threaded_resolver_finalize (GObject *object)
113 : : {
114 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (object);
115 : :
116 : 0 : g_thread_pool_free (self->thread_pool, TRUE, FALSE);
117 : 0 : self->thread_pool = NULL;
118 : :
119 : 0 : G_OBJECT_CLASS (g_threaded_resolver_parent_class)->finalize (object);
120 : 0 : }
121 : :
122 : : static GResolverError
123 : 3 : g_resolver_error_from_addrinfo_error (gint err)
124 : : {
125 : 3 : switch (err)
126 : : {
127 : 3 : case EAI_FAIL:
128 : : #if defined(EAI_NODATA) && (EAI_NODATA != EAI_NONAME)
129 : : case EAI_NODATA:
130 : : #endif
131 : : case EAI_NONAME:
132 : 3 : return G_RESOLVER_ERROR_NOT_FOUND;
133 : :
134 : 0 : case EAI_AGAIN:
135 : 0 : return G_RESOLVER_ERROR_TEMPORARY_FAILURE;
136 : :
137 : 0 : default:
138 : 0 : return G_RESOLVER_ERROR_INTERNAL;
139 : : }
140 : : }
141 : :
142 : : typedef struct {
143 : : enum {
144 : : LOOKUP_BY_NAME,
145 : : LOOKUP_BY_ADDRESS,
146 : : LOOKUP_RECORDS,
147 : : } lookup_type;
148 : :
149 : : union {
150 : : struct {
151 : : char *hostname;
152 : : int address_family;
153 : : } lookup_by_name;
154 : : struct {
155 : : GInetAddress *address; /* (owned) */
156 : : } lookup_by_address;
157 : : struct {
158 : : char *rrname;
159 : : GResolverRecordType record_type;
160 : : } lookup_records;
161 : : };
162 : :
163 : : GCond cond; /* used for signalling completion of the task when running it sync */
164 : : GMutex lock;
165 : :
166 : : GSource *timeout_source; /* (nullable) (owned) */
167 : : GSource *cancellable_source; /* (nullable) (owned) */
168 : :
169 : : /* This enum indicates that a particular code path has claimed the
170 : : * task and is shortly about to call g_task_return_*() on it.
171 : : * This must be accessed with GThreadedResolver.lock held. */
172 : : enum
173 : : {
174 : : NOT_YET,
175 : : COMPLETED, /* libc lookup call has completed successfully or errored */
176 : : TIMED_OUT,
177 : : CANCELLED,
178 : : } will_return;
179 : :
180 : : /* Whether the thread pool thread executing this lookup has finished executing
181 : : * it and g_task_return_*() has been called on it already.
182 : : * This must be accessed with GThreadedResolver.lock held. */
183 : : gboolean has_returned;
184 : : } LookupData;
185 : :
186 : : static LookupData *
187 : 3 : lookup_data_new_by_name (const char *hostname,
188 : : int address_family)
189 : : {
190 : 3 : LookupData *data = g_new0 (LookupData, 1);
191 : 3 : data->lookup_type = LOOKUP_BY_NAME;
192 : 3 : g_cond_init (&data->cond);
193 : 3 : g_mutex_init (&data->lock);
194 : 3 : data->lookup_by_name.hostname = g_strdup (hostname);
195 : 3 : data->lookup_by_name.address_family = address_family;
196 : 3 : return g_steal_pointer (&data);
197 : : }
198 : :
199 : : static LookupData *
200 : 0 : lookup_data_new_by_address (GInetAddress *address)
201 : : {
202 : 0 : LookupData *data = g_new0 (LookupData, 1);
203 : 0 : data->lookup_type = LOOKUP_BY_ADDRESS;
204 : 0 : g_cond_init (&data->cond);
205 : 0 : g_mutex_init (&data->lock);
206 : 0 : data->lookup_by_address.address = g_object_ref (address);
207 : 0 : return g_steal_pointer (&data);
208 : : }
209 : :
210 : : static LookupData *
211 : 0 : lookup_data_new_records (const gchar *rrname,
212 : : GResolverRecordType record_type)
213 : : {
214 : 0 : LookupData *data = g_new0 (LookupData, 1);
215 : 0 : data->lookup_type = LOOKUP_RECORDS;
216 : 0 : g_cond_init (&data->cond);
217 : 0 : g_mutex_init (&data->lock);
218 : 0 : data->lookup_records.rrname = g_strdup (rrname);
219 : 0 : data->lookup_records.record_type = record_type;
220 : 0 : return g_steal_pointer (&data);
221 : : }
222 : :
223 : : static void
224 : 0 : lookup_data_free (LookupData *data)
225 : : {
226 : 0 : switch (data->lookup_type) {
227 : 0 : case LOOKUP_BY_NAME:
228 : 0 : g_free (data->lookup_by_name.hostname);
229 : 0 : break;
230 : 0 : case LOOKUP_BY_ADDRESS:
231 : 0 : g_clear_object (&data->lookup_by_address.address);
232 : 0 : break;
233 : 0 : case LOOKUP_RECORDS:
234 : 0 : g_free (data->lookup_records.rrname);
235 : 0 : break;
236 : 0 : default:
237 : : g_assert_not_reached ();
238 : : }
239 : :
240 : 0 : if (data->timeout_source != NULL)
241 : : {
242 : 0 : g_source_destroy (data->timeout_source);
243 : 0 : g_clear_pointer (&data->timeout_source, g_source_unref);
244 : : }
245 : :
246 : 0 : if (data->cancellable_source != NULL)
247 : : {
248 : 0 : g_source_destroy (data->cancellable_source);
249 : 0 : g_clear_pointer (&data->cancellable_source, g_source_unref);
250 : : }
251 : :
252 : 0 : g_mutex_clear (&data->lock);
253 : 0 : g_cond_clear (&data->cond);
254 : :
255 : 0 : g_free (data);
256 : 0 : }
257 : :
258 : : static GList *
259 : 3 : do_lookup_by_name (const gchar *hostname,
260 : : int address_family,
261 : : GCancellable *cancellable,
262 : : GError **error)
263 : : {
264 : 3 : struct addrinfo *res = NULL;
265 : : GList *addresses;
266 : : gint retval;
267 : 3 : struct addrinfo addrinfo_hints = { 0 };
268 : :
269 : : #ifdef AI_ADDRCONFIG
270 : 3 : addrinfo_hints.ai_flags = AI_ADDRCONFIG;
271 : : #endif
272 : : /* socktype and protocol don't actually matter, they just get copied into the
273 : : * returned addrinfo structures (and then we ignore them). But if
274 : : * we leave them unset, we'll get back duplicate answers.
275 : : */
276 : 3 : addrinfo_hints.ai_socktype = SOCK_STREAM;
277 : 3 : addrinfo_hints.ai_protocol = IPPROTO_TCP;
278 : :
279 : 3 : addrinfo_hints.ai_family = address_family;
280 : 3 : retval = getaddrinfo (hostname, NULL, &addrinfo_hints, &res);
281 : :
282 : 3 : if (retval == 0)
283 : : {
284 : : struct addrinfo *ai;
285 : : GSocketAddress *sockaddr;
286 : : GInetAddress *addr;
287 : :
288 : 0 : addresses = NULL;
289 : 0 : for (ai = res; ai; ai = ai->ai_next)
290 : : {
291 : 0 : sockaddr = g_socket_address_new_from_native (ai->ai_addr, ai->ai_addrlen);
292 : 0 : if (!sockaddr)
293 : 0 : continue;
294 : 0 : if (!G_IS_INET_SOCKET_ADDRESS (sockaddr))
295 : : {
296 : 0 : g_clear_object (&sockaddr);
297 : 0 : continue;
298 : : }
299 : :
300 : 0 : addr = g_object_ref (g_inet_socket_address_get_address ((GInetSocketAddress *)sockaddr));
301 : 0 : addresses = g_list_prepend (addresses, addr);
302 : 0 : g_object_unref (sockaddr);
303 : : }
304 : :
305 : 0 : g_clear_pointer (&res, freeaddrinfo);
306 : :
307 : 0 : if (addresses != NULL)
308 : : {
309 : 0 : addresses = g_list_reverse (addresses);
310 : 0 : return g_steal_pointer (&addresses);
311 : : }
312 : : else
313 : : {
314 : : /* All addresses failed to be converted to GSocketAddresses. */
315 : 0 : g_set_error (error,
316 : : G_RESOLVER_ERROR,
317 : : G_RESOLVER_ERROR_NOT_FOUND,
318 : : _("Error resolving “%s”: %s"),
319 : : hostname,
320 : : _("No valid addresses were found"));
321 : 0 : return NULL;
322 : : }
323 : : }
324 : : else
325 : : {
326 : : #ifdef G_OS_WIN32
327 : : gchar *error_message = g_win32_error_message (WSAGetLastError ());
328 : : #else
329 : 3 : gchar *error_message = g_locale_to_utf8 (gai_strerror (retval), -1, NULL, NULL, NULL);
330 : 3 : if (error_message == NULL)
331 : 0 : error_message = g_strdup ("[Invalid UTF-8]");
332 : : #endif
333 : :
334 : 3 : g_clear_pointer (&res, freeaddrinfo);
335 : :
336 : 6 : g_set_error (error,
337 : : G_RESOLVER_ERROR,
338 : 3 : g_resolver_error_from_addrinfo_error (retval),
339 : : _("Error resolving “%s”: %s"),
340 : : hostname, error_message);
341 : 3 : g_free (error_message);
342 : :
343 : 3 : return NULL;
344 : : }
345 : : }
346 : :
347 : : static GList *
348 : 3 : lookup_by_name (GResolver *resolver,
349 : : const gchar *hostname,
350 : : GCancellable *cancellable,
351 : : GError **error)
352 : : {
353 : 3 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
354 : : GTask *task;
355 : : GList *addresses;
356 : : LookupData *data;
357 : :
358 : 3 : data = lookup_data_new_by_name (hostname, AF_UNSPEC);
359 : 3 : task = g_task_new (resolver, cancellable, NULL, NULL);
360 : 3 : g_task_set_source_tag (task, lookup_by_name);
361 : 3 : g_task_set_name (task, "[gio] resolver lookup");
362 : 3 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
363 : :
364 : 3 : run_task_in_thread_pool_sync (self, task);
365 : :
366 : 3 : addresses = g_task_propagate_pointer (task, error);
367 : 3 : g_object_unref (task);
368 : :
369 : 3 : return addresses;
370 : : }
371 : :
372 : : static int
373 : 0 : flags_to_family (GResolverNameLookupFlags flags)
374 : : {
375 : 0 : int address_family = AF_UNSPEC;
376 : :
377 : 0 : if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY)
378 : 0 : address_family = AF_INET;
379 : :
380 : 0 : if (flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV6_ONLY)
381 : : {
382 : 0 : address_family = AF_INET6;
383 : : /* You can only filter by one family at a time */
384 : 0 : g_return_val_if_fail (!(flags & G_RESOLVER_NAME_LOOKUP_FLAGS_IPV4_ONLY), address_family);
385 : : }
386 : :
387 : 0 : return address_family;
388 : : }
389 : :
390 : : static GList *
391 : 0 : lookup_by_name_with_flags (GResolver *resolver,
392 : : const gchar *hostname,
393 : : GResolverNameLookupFlags flags,
394 : : GCancellable *cancellable,
395 : : GError **error)
396 : : {
397 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
398 : : GTask *task;
399 : : GList *addresses;
400 : : LookupData *data;
401 : :
402 : 0 : data = lookup_data_new_by_name (hostname, flags_to_family (flags));
403 : 0 : task = g_task_new (resolver, cancellable, NULL, NULL);
404 : 0 : g_task_set_source_tag (task, lookup_by_name_with_flags);
405 : 0 : g_task_set_name (task, "[gio] resolver lookup");
406 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
407 : :
408 : 0 : run_task_in_thread_pool_sync (self, task);
409 : :
410 : 0 : addresses = g_task_propagate_pointer (task, error);
411 : 0 : g_object_unref (task);
412 : :
413 : 0 : return addresses;
414 : : }
415 : :
416 : : static void
417 : 0 : lookup_by_name_with_flags_async (GResolver *resolver,
418 : : const gchar *hostname,
419 : : GResolverNameLookupFlags flags,
420 : : GCancellable *cancellable,
421 : : GAsyncReadyCallback callback,
422 : : gpointer user_data)
423 : : {
424 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
425 : : GTask *task;
426 : : LookupData *data;
427 : :
428 : 0 : data = lookup_data_new_by_name (hostname, flags_to_family (flags));
429 : 0 : task = g_task_new (resolver, cancellable, callback, user_data);
430 : :
431 : 0 : g_debug ("%s: starting new lookup for %s with GTask %p, LookupData %p",
432 : : G_STRFUNC, hostname, task, data);
433 : :
434 : 0 : g_task_set_source_tag (task, lookup_by_name_with_flags_async);
435 : 0 : g_task_set_name (task, "[gio] resolver lookup");
436 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
437 : :
438 : 0 : run_task_in_thread_pool_async (self, task);
439 : :
440 : 0 : g_object_unref (task);
441 : 0 : }
442 : :
443 : : static void
444 : 0 : lookup_by_name_async (GResolver *resolver,
445 : : const gchar *hostname,
446 : : GCancellable *cancellable,
447 : : GAsyncReadyCallback callback,
448 : : gpointer user_data)
449 : : {
450 : 0 : lookup_by_name_with_flags_async (resolver,
451 : : hostname,
452 : : G_RESOLVER_NAME_LOOKUP_FLAGS_DEFAULT,
453 : : cancellable,
454 : : callback,
455 : : user_data);
456 : 0 : }
457 : :
458 : : static GList *
459 : 0 : lookup_by_name_finish (GResolver *resolver,
460 : : GAsyncResult *result,
461 : : GError **error)
462 : : {
463 : 0 : g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
464 : :
465 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
466 : : }
467 : :
468 : : static GList *
469 : 0 : lookup_by_name_with_flags_finish (GResolver *resolver,
470 : : GAsyncResult *result,
471 : : GError **error)
472 : : {
473 : 0 : g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
474 : :
475 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
476 : : }
477 : :
478 : : static gchar *
479 : 0 : do_lookup_by_address (GInetAddress *address,
480 : : GCancellable *cancellable,
481 : : GError **error)
482 : : {
483 : : struct sockaddr_storage sockaddr_address;
484 : : gsize sockaddr_address_size;
485 : : GSocketAddress *gsockaddr;
486 : : gchar name[NI_MAXHOST];
487 : : gint retval;
488 : :
489 : 0 : gsockaddr = g_inet_socket_address_new (address, 0);
490 : 0 : g_socket_address_to_native (gsockaddr, (struct sockaddr *)&sockaddr_address,
491 : : sizeof (sockaddr_address), NULL);
492 : 0 : sockaddr_address_size = g_socket_address_get_native_size (gsockaddr);
493 : 0 : g_object_unref (gsockaddr);
494 : :
495 : 0 : retval = getnameinfo ((struct sockaddr *) &sockaddr_address, sockaddr_address_size,
496 : : name, sizeof (name), NULL, 0, NI_NAMEREQD);
497 : 0 : if (retval == 0)
498 : 0 : return g_strdup (name);
499 : : else
500 : : {
501 : : gchar *phys;
502 : :
503 : : #ifdef G_OS_WIN32
504 : : gchar *error_message = g_win32_error_message (WSAGetLastError ());
505 : : #else
506 : 0 : gchar *error_message = g_locale_to_utf8 (gai_strerror (retval), -1, NULL, NULL, NULL);
507 : 0 : if (error_message == NULL)
508 : 0 : error_message = g_strdup ("[Invalid UTF-8]");
509 : : #endif
510 : :
511 : 0 : phys = g_inet_address_to_string (address);
512 : 0 : g_set_error (error,
513 : : G_RESOLVER_ERROR,
514 : 0 : g_resolver_error_from_addrinfo_error (retval),
515 : : _("Error reverse-resolving “%s”: %s"),
516 : : phys ? phys : "(unknown)",
517 : : error_message);
518 : 0 : g_free (phys);
519 : 0 : g_free (error_message);
520 : :
521 : 0 : return NULL;
522 : : }
523 : : }
524 : :
525 : : static gchar *
526 : 0 : lookup_by_address (GResolver *resolver,
527 : : GInetAddress *address,
528 : : GCancellable *cancellable,
529 : : GError **error)
530 : : {
531 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
532 : 0 : LookupData *data = NULL;
533 : : GTask *task;
534 : : gchar *name;
535 : :
536 : 0 : data = lookup_data_new_by_address (address);
537 : 0 : task = g_task_new (resolver, cancellable, NULL, NULL);
538 : 0 : g_task_set_source_tag (task, lookup_by_address);
539 : 0 : g_task_set_name (task, "[gio] resolver lookup");
540 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
541 : :
542 : 0 : run_task_in_thread_pool_sync (self, task);
543 : :
544 : 0 : name = g_task_propagate_pointer (task, error);
545 : 0 : g_object_unref (task);
546 : :
547 : 0 : return name;
548 : : }
549 : :
550 : : static void
551 : 0 : lookup_by_address_async (GResolver *resolver,
552 : : GInetAddress *address,
553 : : GCancellable *cancellable,
554 : : GAsyncReadyCallback callback,
555 : : gpointer user_data)
556 : : {
557 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
558 : 0 : LookupData *data = NULL;
559 : : GTask *task;
560 : :
561 : 0 : data = lookup_data_new_by_address (address);
562 : 0 : task = g_task_new (resolver, cancellable, callback, user_data);
563 : 0 : g_task_set_source_tag (task, lookup_by_address_async);
564 : 0 : g_task_set_name (task, "[gio] resolver lookup");
565 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
566 : :
567 : 0 : run_task_in_thread_pool_async (self, task);
568 : :
569 : 0 : g_object_unref (task);
570 : 0 : }
571 : :
572 : : static gchar *
573 : 0 : lookup_by_address_finish (GResolver *resolver,
574 : : GAsyncResult *result,
575 : : GError **error)
576 : : {
577 : 0 : g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
578 : :
579 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
580 : : }
581 : :
582 : :
583 : : #if defined(G_OS_UNIX)
584 : :
585 : : #if defined __BIONIC__ && !defined BIND_4_COMPAT
586 : : /* Copy from bionic/libc/private/arpa_nameser_compat.h
587 : : * and bionic/libc/private/arpa_nameser.h */
588 : : typedef struct {
589 : : unsigned id :16; /* query identification number */
590 : : #if BYTE_ORDER == BIG_ENDIAN
591 : : /* fields in third byte */
592 : : unsigned qr: 1; /* response flag */
593 : : unsigned opcode: 4; /* purpose of message */
594 : : unsigned aa: 1; /* authoritative answer */
595 : : unsigned tc: 1; /* truncated message */
596 : : unsigned rd: 1; /* recursion desired */
597 : : /* fields in fourth byte */
598 : : unsigned ra: 1; /* recursion available */
599 : : unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */
600 : : unsigned ad: 1; /* authentic data from named */
601 : : unsigned cd: 1; /* checking disabled by resolver */
602 : : unsigned rcode :4; /* response code */
603 : : #endif
604 : : #if BYTE_ORDER == LITTLE_ENDIAN || BYTE_ORDER == PDP_ENDIAN
605 : : /* fields in third byte */
606 : : unsigned rd :1; /* recursion desired */
607 : : unsigned tc :1; /* truncated message */
608 : : unsigned aa :1; /* authoritative answer */
609 : : unsigned opcode :4; /* purpose of message */
610 : : unsigned qr :1; /* response flag */
611 : : /* fields in fourth byte */
612 : : unsigned rcode :4; /* response code */
613 : : unsigned cd: 1; /* checking disabled by resolver */
614 : : unsigned ad: 1; /* authentic data from named */
615 : : unsigned unused :1; /* unused bits (MBZ as of 4.9.3a3) */
616 : : unsigned ra :1; /* recursion available */
617 : : #endif
618 : : /* remaining bytes */
619 : : unsigned qdcount :16; /* number of question entries */
620 : : unsigned ancount :16; /* number of answer entries */
621 : : unsigned nscount :16; /* number of authority entries */
622 : : unsigned arcount :16; /* number of resource entries */
623 : : } HEADER;
624 : :
625 : : #define NS_INT32SZ 4 /* #/bytes of data in a uint32_t */
626 : : #define NS_INT16SZ 2 /* #/bytes of data in a uint16_t */
627 : :
628 : : #define NS_GET16(s, cp) do { \
629 : : const u_char *t_cp = (const u_char *)(cp); \
630 : : (s) = ((uint16_t)t_cp[0] << 8) \
631 : : | ((uint16_t)t_cp[1]) \
632 : : ; \
633 : : (cp) += NS_INT16SZ; \
634 : : } while (/*CONSTCOND*/0)
635 : :
636 : : #define NS_GET32(l, cp) do { \
637 : : const u_char *t_cp = (const u_char *)(cp); \
638 : : (l) = ((uint32_t)t_cp[0] << 24) \
639 : : | ((uint32_t)t_cp[1] << 16) \
640 : : | ((uint32_t)t_cp[2] << 8) \
641 : : | ((uint32_t)t_cp[3]) \
642 : : ; \
643 : : (cp) += NS_INT32SZ; \
644 : : } while (/*CONSTCOND*/0)
645 : :
646 : : #define GETSHORT NS_GET16
647 : : #define GETLONG NS_GET32
648 : :
649 : : #define C_IN 1
650 : :
651 : : /* From bionic/libc/private/resolv_private.h */
652 : : int dn_expand(const u_char *, const u_char *, const u_char *, char *, int);
653 : : #define dn_skipname __dn_skipname
654 : : int dn_skipname(const u_char *, const u_char *);
655 : :
656 : : /* From bionic/libc/private/arpa_nameser_compat.h */
657 : : #define T_MX ns_t_mx
658 : : #define T_TXT ns_t_txt
659 : : #define T_SOA ns_t_soa
660 : : #define T_NS ns_t_ns
661 : :
662 : : /* From bionic/libc/private/arpa_nameser.h */
663 : : typedef enum __ns_type {
664 : : ns_t_invalid = 0, /* Cookie. */
665 : : ns_t_a = 1, /* Host address. */
666 : : ns_t_ns = 2, /* Authoritative server. */
667 : : ns_t_md = 3, /* Mail destination. */
668 : : ns_t_mf = 4, /* Mail forwarder. */
669 : : ns_t_cname = 5, /* Canonical name. */
670 : : ns_t_soa = 6, /* Start of authority zone. */
671 : : ns_t_mb = 7, /* Mailbox domain name. */
672 : : ns_t_mg = 8, /* Mail group member. */
673 : : ns_t_mr = 9, /* Mail rename name. */
674 : : ns_t_null = 10, /* Null resource record. */
675 : : ns_t_wks = 11, /* Well known service. */
676 : : ns_t_ptr = 12, /* Domain name pointer. */
677 : : ns_t_hinfo = 13, /* Host information. */
678 : : ns_t_minfo = 14, /* Mailbox information. */
679 : : ns_t_mx = 15, /* Mail routing information. */
680 : : ns_t_txt = 16, /* Text strings. */
681 : : ns_t_rp = 17, /* Responsible person. */
682 : : ns_t_afsdb = 18, /* AFS cell database. */
683 : : ns_t_x25 = 19, /* X_25 calling address. */
684 : : ns_t_isdn = 20, /* ISDN calling address. */
685 : : ns_t_rt = 21, /* Router. */
686 : : ns_t_nsap = 22, /* NSAP address. */
687 : : ns_t_nsap_ptr = 23, /* Reverse NSAP lookup (deprecated). */
688 : : ns_t_sig = 24, /* Security signature. */
689 : : ns_t_key = 25, /* Security key. */
690 : : ns_t_px = 26, /* X.400 mail mapping. */
691 : : ns_t_gpos = 27, /* Geographical position (withdrawn). */
692 : : ns_t_aaaa = 28, /* Ip6 Address. */
693 : : ns_t_loc = 29, /* Location Information. */
694 : : ns_t_nxt = 30, /* Next domain (security). */
695 : : ns_t_eid = 31, /* Endpoint identifier. */
696 : : ns_t_nimloc = 32, /* Nimrod Locator. */
697 : : ns_t_srv = 33, /* Server Selection. */
698 : : ns_t_atma = 34, /* ATM Address */
699 : : ns_t_naptr = 35, /* Naming Authority PoinTeR */
700 : : ns_t_kx = 36, /* Key Exchange */
701 : : ns_t_cert = 37, /* Certification record */
702 : : ns_t_a6 = 38, /* IPv6 address (deprecates AAAA) */
703 : : ns_t_dname = 39, /* Non-terminal DNAME (for IPv6) */
704 : : ns_t_sink = 40, /* Kitchen sink (experimental) */
705 : : ns_t_opt = 41, /* EDNS0 option (meta-RR) */
706 : : ns_t_apl = 42, /* Address prefix list (RFC 3123) */
707 : : ns_t_tkey = 249, /* Transaction key */
708 : : ns_t_tsig = 250, /* Transaction signature. */
709 : : ns_t_ixfr = 251, /* Incremental zone transfer. */
710 : : ns_t_axfr = 252, /* Transfer zone of authority. */
711 : : ns_t_mailb = 253, /* Transfer mailbox records. */
712 : : ns_t_maila = 254, /* Transfer mail agent records. */
713 : : ns_t_any = 255, /* Wildcard match. */
714 : : ns_t_zxfr = 256, /* BIND-specific, nonstandard. */
715 : : ns_t_max = 65536
716 : : } ns_type;
717 : :
718 : : #endif /* __BIONIC__ */
719 : :
720 : : /* Wrapper around dn_expand() which does associated length checks and returns
721 : : * errors as #GError. */
722 : : static gboolean
723 : 15 : expand_name (const gchar *rrname,
724 : : const guint8 *answer,
725 : : const guint8 *end,
726 : : const guint8 **p,
727 : : gchar *namebuf,
728 : : gsize namebuf_len,
729 : : GError **error)
730 : : {
731 : : int expand_result;
732 : :
733 : 15 : expand_result = dn_expand (answer, end, *p, namebuf, namebuf_len);
734 : 15 : if (expand_result < 0 || end - *p < expand_result)
735 : : {
736 : 7 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
737 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
738 : : _("Error parsing DNS %s record: malformed DNS packet"), rrname);
739 : 7 : return FALSE;
740 : : }
741 : :
742 : 8 : *p += expand_result;
743 : :
744 : 8 : return TRUE;
745 : : }
746 : :
747 : : static GVariant *
748 : 4 : parse_res_srv (const guint8 *answer,
749 : : const guint8 *end,
750 : : const guint8 **p,
751 : : GError **error)
752 : : {
753 : : gchar namebuf[1024];
754 : : guint16 priority, weight, port;
755 : :
756 : 4 : if (end - *p < 6)
757 : : {
758 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
759 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
760 : : _("Error parsing DNS %s record: malformed DNS packet"), "SRV");
761 : 1 : return NULL;
762 : : }
763 : :
764 : 3 : GETSHORT (priority, *p);
765 : 3 : GETSHORT (weight, *p);
766 : 3 : GETSHORT (port, *p);
767 : :
768 : : /* RFC 2782 says (on page 4) that “Unless and until permitted by future
769 : : * standards action, name compression is not to be used for this field.”, so
770 : : * technically we shouldn’t be expanding names here for SRV records.
771 : : *
772 : : * However, other DNS resolvers (such as systemd[1]) do, and it seems in
773 : : * keeping with the principle of being liberal in what you accept and strict
774 : : * in what you emit. It also seems harmless.
775 : : *
776 : : * An earlier version of the RFC, RFC 2052 (now obsolete) specified that name
777 : : * compression *was* to be used for SRV targets[2].
778 : : *
779 : : * See discussion on https://gitlab.gnome.org/GNOME/glib/-/issues/2622.
780 : : *
781 : : * [1]: https://github.com/yuwata/systemd/blob/2d23cc3c07c49722ce93170737b3efd2692a2d08/src/resolve/resolved-dns-packet.c#L1674
782 : : * [2]: https://datatracker.ietf.org/doc/html/rfc2052#page-3
783 : : */
784 : 3 : if (!expand_name ("SRV", answer, end, p, namebuf, sizeof (namebuf), error))
785 : 2 : return NULL;
786 : :
787 : 1 : return g_variant_new ("(qqqs)",
788 : : priority,
789 : : weight,
790 : : port,
791 : : namebuf);
792 : : }
793 : :
794 : : static GVariant *
795 : 4 : parse_res_soa (const guint8 *answer,
796 : : const guint8 *end,
797 : : const guint8 **p,
798 : : GError **error)
799 : : {
800 : : gchar mnamebuf[1024];
801 : : gchar rnamebuf[1024];
802 : : guint32 serial, refresh, retry, expire, ttl;
803 : :
804 : 4 : if (!expand_name ("SOA", answer, end, p, mnamebuf, sizeof (mnamebuf), error))
805 : 1 : return NULL;
806 : :
807 : 3 : if (!expand_name ("SOA", answer, end, p, rnamebuf, sizeof (rnamebuf), error))
808 : 1 : return NULL;
809 : :
810 : 2 : if (end - *p < 20)
811 : : {
812 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
813 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
814 : : _("Error parsing DNS %s record: malformed DNS packet"), "SOA");
815 : 1 : return NULL;
816 : : }
817 : :
818 : 1 : GETLONG (serial, *p);
819 : 1 : GETLONG (refresh, *p);
820 : 1 : GETLONG (retry, *p);
821 : 1 : GETLONG (expire, *p);
822 : 1 : GETLONG (ttl, *p);
823 : :
824 : 1 : return g_variant_new ("(ssuuuuu)",
825 : : mnamebuf,
826 : : rnamebuf,
827 : : serial,
828 : : refresh,
829 : : retry,
830 : : expire,
831 : : ttl);
832 : : }
833 : :
834 : : static GVariant *
835 : 2 : parse_res_ns (const guint8 *answer,
836 : : const guint8 *end,
837 : : const guint8 **p,
838 : : GError **error)
839 : : {
840 : : gchar namebuf[1024];
841 : :
842 : 2 : if (!expand_name ("NS", answer, end, p, namebuf, sizeof (namebuf), error))
843 : 1 : return NULL;
844 : :
845 : 1 : return g_variant_new ("(s)", namebuf);
846 : : }
847 : :
848 : : static GVariant *
849 : 4 : parse_res_mx (const guint8 *answer,
850 : : const guint8 *end,
851 : : const guint8 **p,
852 : : GError **error)
853 : : {
854 : : gchar namebuf[1024];
855 : : guint16 preference;
856 : :
857 : 4 : if (end - *p < 2)
858 : : {
859 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
860 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
861 : : _("Error parsing DNS %s record: malformed DNS packet"), "MX");
862 : 1 : return NULL;
863 : : }
864 : :
865 : 3 : GETSHORT (preference, *p);
866 : :
867 : 3 : if (!expand_name ("MX", answer, end, p, namebuf, sizeof (namebuf), error))
868 : 2 : return NULL;
869 : :
870 : 1 : return g_variant_new ("(qs)",
871 : : preference,
872 : : namebuf);
873 : : }
874 : :
875 : : static GVariant *
876 : 5 : parse_res_txt (const guint8 *answer,
877 : : const guint8 *end,
878 : : const guint8 **p,
879 : : GError **error)
880 : : {
881 : : GVariant *record;
882 : : GPtrArray *array;
883 : 5 : const guint8 *at = *p;
884 : : gsize len;
885 : :
886 : 5 : if (end - *p == 0)
887 : : {
888 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
889 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
890 : : _("Error parsing DNS %s record: malformed DNS packet"), "TXT");
891 : 1 : return NULL;
892 : : }
893 : :
894 : 4 : array = g_ptr_array_new_with_free_func (g_free);
895 : 8 : while (at < end)
896 : : {
897 : 5 : len = *(at++);
898 : 5 : if (len > (gsize) (end - at))
899 : : {
900 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
901 : : /* Translators: the placeholder is a DNS record type, such as ‘MX’ or ‘SRV’ */
902 : : _("Error parsing DNS %s record: malformed DNS packet"), "TXT");
903 : 1 : g_ptr_array_free (array, TRUE);
904 : 1 : return NULL;
905 : : }
906 : :
907 : 4 : g_ptr_array_add (array, g_strndup ((gchar *)at, len));
908 : 4 : at += len;
909 : : }
910 : :
911 : 3 : *p = at;
912 : 3 : record = g_variant_new ("(@as)",
913 : 3 : g_variant_new_strv ((const gchar **)array->pdata, array->len));
914 : 3 : g_ptr_array_free (array, TRUE);
915 : 3 : return record;
916 : : }
917 : :
918 : : gint
919 : 41 : g_resolver_record_type_to_rrtype (GResolverRecordType type)
920 : : {
921 : 41 : switch (type)
922 : : {
923 : 8 : case G_RESOLVER_RECORD_SRV:
924 : 8 : return T_SRV;
925 : 9 : case G_RESOLVER_RECORD_TXT:
926 : 9 : return T_TXT;
927 : 8 : case G_RESOLVER_RECORD_SOA:
928 : 8 : return T_SOA;
929 : 8 : case G_RESOLVER_RECORD_NS:
930 : 8 : return T_NS;
931 : 8 : case G_RESOLVER_RECORD_MX:
932 : 8 : return T_MX;
933 : : }
934 : : g_return_val_if_reached (-1);
935 : : }
936 : :
937 : : GList *
938 : 30 : g_resolver_records_from_res_query (const gchar *rrname,
939 : : gint rrtype,
940 : : const guint8 *answer,
941 : : gssize len,
942 : : gint herr,
943 : : GError **error)
944 : : {
945 : : uint16_t count;
946 : : gchar namebuf[1024];
947 : : const guint8 *end, *p;
948 : : guint16 type, qclass, rdlength;
949 : : const HEADER *header;
950 : : GList *records;
951 : : GVariant *record;
952 : : gsize len_unsigned;
953 : 30 : GError *parsing_error = NULL;
954 : :
955 : 30 : if (len <= 0)
956 : : {
957 : 1 : if (len == 0 || herr == HOST_NOT_FOUND || herr == NO_DATA)
958 : : {
959 : 1 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
960 : : _("No DNS record of the requested type for “%s”"), rrname);
961 : : }
962 : 0 : else if (herr == TRY_AGAIN)
963 : : {
964 : 0 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE,
965 : : _("Temporarily unable to resolve “%s”"), rrname);
966 : : }
967 : : else
968 : : {
969 : 0 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
970 : : _("Error resolving “%s”"), rrname);
971 : : }
972 : :
973 : 1 : return NULL;
974 : : }
975 : :
976 : : /* We know len ≥ 0 now. */
977 : 29 : len_unsigned = (gsize) len;
978 : :
979 : 29 : if (len_unsigned < sizeof (HEADER))
980 : : {
981 : 2 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
982 : : /* Translators: the first placeholder is a domain name, the
983 : : * second is an error message */
984 : : _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet"));
985 : 2 : return NULL;
986 : : }
987 : :
988 : 27 : records = NULL;
989 : :
990 : 27 : header = (HEADER *)answer;
991 : 27 : p = answer + sizeof (HEADER);
992 : 27 : end = answer + len_unsigned;
993 : :
994 : : /* Skip query */
995 : 27 : count = ntohs (header->qdcount);
996 : 27 : while (count-- && p < end)
997 : : {
998 : : int expand_result;
999 : :
1000 : 6 : expand_result = dn_expand (answer, end, p, namebuf, sizeof (namebuf));
1001 : 6 : if (expand_result < 0 || end - p < expand_result + 4)
1002 : : {
1003 : : /* Not possible to recover parsing as the length of the rest of the
1004 : : * record is unknown or is too short. */
1005 : 6 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
1006 : : /* Translators: the first placeholder is a domain name, the
1007 : : * second is an error message */
1008 : : _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet"));
1009 : 6 : return NULL;
1010 : : }
1011 : :
1012 : 0 : p += expand_result;
1013 : 0 : p += 4; /* skip TYPE and CLASS */
1014 : :
1015 : : /* To silence gcc warnings */
1016 : 0 : namebuf[0] = namebuf[1];
1017 : : }
1018 : :
1019 : : /* Read answers */
1020 : 21 : count = ntohs (header->ancount);
1021 : 29 : while (count-- && p < end)
1022 : : {
1023 : : int expand_result;
1024 : :
1025 : 20 : expand_result = dn_expand (answer, end, p, namebuf, sizeof (namebuf));
1026 : 20 : if (expand_result < 0 || end - p < expand_result + 10)
1027 : : {
1028 : : /* Not possible to recover parsing as the length of the rest of the
1029 : : * record is unknown or is too short. */
1030 : 0 : g_set_error (&parsing_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
1031 : : /* Translators: the first placeholder is a domain name, the
1032 : : * second is an error message */
1033 : : _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet"));
1034 : 0 : break;
1035 : : }
1036 : :
1037 : 20 : p += expand_result;
1038 : 20 : GETSHORT (type, p);
1039 : 20 : GETSHORT (qclass, p);
1040 : 20 : p += 4; /* ignore the ttl (type=long) value */
1041 : 20 : GETSHORT (rdlength, p);
1042 : :
1043 : 20 : if (end - p < rdlength)
1044 : : {
1045 : 0 : g_set_error (&parsing_error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
1046 : : /* Translators: the first placeholder is a domain name, the
1047 : : * second is an error message */
1048 : : _("Error resolving “%s”: %s"), rrname, _("Malformed DNS packet"));
1049 : 0 : break;
1050 : : }
1051 : :
1052 : 20 : if (type != rrtype || qclass != C_IN)
1053 : : {
1054 : 0 : p += rdlength;
1055 : 0 : continue;
1056 : : }
1057 : :
1058 : 20 : switch (rrtype)
1059 : : {
1060 : 4 : case T_SRV:
1061 : 4 : record = parse_res_srv (answer, p + rdlength, &p, &parsing_error);
1062 : 4 : break;
1063 : 4 : case T_MX:
1064 : 4 : record = parse_res_mx (answer, p + rdlength, &p, &parsing_error);
1065 : 4 : break;
1066 : 4 : case T_SOA:
1067 : 4 : record = parse_res_soa (answer, p + rdlength, &p, &parsing_error);
1068 : 4 : break;
1069 : 2 : case T_NS:
1070 : 2 : record = parse_res_ns (answer, p + rdlength, &p, &parsing_error);
1071 : 2 : break;
1072 : 5 : case T_TXT:
1073 : 5 : record = parse_res_txt (answer, p + rdlength, &p, &parsing_error);
1074 : 5 : break;
1075 : 1 : default:
1076 : 1 : g_debug ("Unrecognized DNS record type %u", rrtype);
1077 : 1 : record = NULL;
1078 : 1 : break;
1079 : : }
1080 : :
1081 : 20 : if (record != NULL)
1082 : 7 : records = g_list_prepend (records, g_variant_ref_sink (record));
1083 : :
1084 : 20 : if (parsing_error != NULL)
1085 : 12 : break;
1086 : : }
1087 : :
1088 : 21 : if (parsing_error != NULL)
1089 : : {
1090 : 12 : g_propagate_prefixed_error (error, parsing_error, _("Failed to parse DNS response for “%s”: "), rrname);
1091 : 12 : g_list_free_full (records, (GDestroyNotify)g_variant_unref);
1092 : 12 : return NULL;
1093 : : }
1094 : 9 : else if (records == NULL)
1095 : : {
1096 : 2 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
1097 : : _("No DNS record of the requested type for “%s”"), rrname);
1098 : :
1099 : 2 : return NULL;
1100 : : }
1101 : : else
1102 : 7 : return records;
1103 : : }
1104 : :
1105 : : #elif defined(G_OS_WIN32)
1106 : :
1107 : : static GVariant *
1108 : : parse_dns_srv (DNS_RECORDA *rec)
1109 : : {
1110 : : return g_variant_new ("(qqqs)",
1111 : : (guint16)rec->Data.SRV.wPriority,
1112 : : (guint16)rec->Data.SRV.wWeight,
1113 : : (guint16)rec->Data.SRV.wPort,
1114 : : rec->Data.SRV.pNameTarget);
1115 : : }
1116 : :
1117 : : static GVariant *
1118 : : parse_dns_soa (DNS_RECORDA *rec)
1119 : : {
1120 : : return g_variant_new ("(ssuuuuu)",
1121 : : rec->Data.SOA.pNamePrimaryServer,
1122 : : rec->Data.SOA.pNameAdministrator,
1123 : : (guint32)rec->Data.SOA.dwSerialNo,
1124 : : (guint32)rec->Data.SOA.dwRefresh,
1125 : : (guint32)rec->Data.SOA.dwRetry,
1126 : : (guint32)rec->Data.SOA.dwExpire,
1127 : : (guint32)rec->Data.SOA.dwDefaultTtl);
1128 : : }
1129 : :
1130 : : static GVariant *
1131 : : parse_dns_ns (DNS_RECORDA *rec)
1132 : : {
1133 : : return g_variant_new ("(s)", rec->Data.NS.pNameHost);
1134 : : }
1135 : :
1136 : : static GVariant *
1137 : : parse_dns_mx (DNS_RECORDA *rec)
1138 : : {
1139 : : return g_variant_new ("(qs)",
1140 : : (guint16)rec->Data.MX.wPreference,
1141 : : rec->Data.MX.pNameExchange);
1142 : : }
1143 : :
1144 : : static GVariant *
1145 : : parse_dns_txt (DNS_RECORDA *rec)
1146 : : {
1147 : : GVariant *record;
1148 : : GPtrArray *array;
1149 : : DWORD i;
1150 : :
1151 : : array = g_ptr_array_new ();
1152 : : for (i = 0; i < rec->Data.TXT.dwStringCount; i++)
1153 : : g_ptr_array_add (array, rec->Data.TXT.pStringArray[i]);
1154 : : record = g_variant_new ("(@as)",
1155 : : g_variant_new_strv ((const gchar **)array->pdata, array->len));
1156 : : g_ptr_array_free (array, TRUE);
1157 : : return record;
1158 : : }
1159 : :
1160 : : static WORD
1161 : : g_resolver_record_type_to_dnstype (GResolverRecordType type)
1162 : : {
1163 : : switch (type)
1164 : : {
1165 : : case G_RESOLVER_RECORD_SRV:
1166 : : return DNS_TYPE_SRV;
1167 : : case G_RESOLVER_RECORD_TXT:
1168 : : return DNS_TYPE_TEXT;
1169 : : case G_RESOLVER_RECORD_SOA:
1170 : : return DNS_TYPE_SOA;
1171 : : case G_RESOLVER_RECORD_NS:
1172 : : return DNS_TYPE_NS;
1173 : : case G_RESOLVER_RECORD_MX:
1174 : : return DNS_TYPE_MX;
1175 : : }
1176 : : g_return_val_if_reached (-1);
1177 : : }
1178 : :
1179 : : static GList *
1180 : : g_resolver_records_from_DnsQuery (const gchar *rrname,
1181 : : WORD dnstype,
1182 : : DNS_STATUS status,
1183 : : DNS_RECORDA *results,
1184 : : GError **error)
1185 : : {
1186 : : DNS_RECORDA *rec;
1187 : : gpointer record;
1188 : : GList *records;
1189 : :
1190 : : if (status != ERROR_SUCCESS)
1191 : : {
1192 : : if (status == DNS_ERROR_RCODE_NAME_ERROR)
1193 : : {
1194 : : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
1195 : : _("No DNS record of the requested type for “%s”"), rrname);
1196 : : }
1197 : : else if (status == DNS_ERROR_RCODE_SERVER_FAILURE)
1198 : : {
1199 : : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_TEMPORARY_FAILURE,
1200 : : _("Temporarily unable to resolve “%s”"), rrname);
1201 : : }
1202 : : else
1203 : : {
1204 : : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
1205 : : _("Error resolving “%s”"), rrname);
1206 : : }
1207 : :
1208 : : return NULL;
1209 : : }
1210 : :
1211 : : records = NULL;
1212 : : for (rec = results; rec; rec = rec->pNext)
1213 : : {
1214 : : if (rec->wType != dnstype)
1215 : : continue;
1216 : : switch (dnstype)
1217 : : {
1218 : : case DNS_TYPE_SRV:
1219 : : record = parse_dns_srv (rec);
1220 : : break;
1221 : : case DNS_TYPE_SOA:
1222 : : record = parse_dns_soa (rec);
1223 : : break;
1224 : : case DNS_TYPE_NS:
1225 : : record = parse_dns_ns (rec);
1226 : : break;
1227 : : case DNS_TYPE_MX:
1228 : : record = parse_dns_mx (rec);
1229 : : break;
1230 : : case DNS_TYPE_TEXT:
1231 : : record = parse_dns_txt (rec);
1232 : : break;
1233 : : default:
1234 : : g_warn_if_reached ();
1235 : : record = NULL;
1236 : : break;
1237 : : }
1238 : : if (record != NULL)
1239 : : records = g_list_prepend (records, g_variant_ref_sink (record));
1240 : : }
1241 : :
1242 : : if (records == NULL)
1243 : : {
1244 : : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_NOT_FOUND,
1245 : : _("No DNS record of the requested type for “%s”"), rrname);
1246 : :
1247 : : return NULL;
1248 : : }
1249 : : else
1250 : : return records;
1251 : : }
1252 : :
1253 : : #endif
1254 : :
1255 : : static void
1256 : 0 : free_records (GList *records)
1257 : : {
1258 : 0 : g_list_free_full (records, (GDestroyNotify) g_variant_unref);
1259 : 0 : }
1260 : :
1261 : : #if defined(G_OS_UNIX)
1262 : : #ifdef __BIONIC__
1263 : : #ifndef C_IN
1264 : : #define C_IN 1
1265 : : #endif
1266 : : int res_query(const char *, int, int, u_char *, int);
1267 : : #endif
1268 : : #endif
1269 : :
1270 : : static GList *
1271 : 0 : do_lookup_records (const gchar *rrname,
1272 : : GResolverRecordType record_type,
1273 : : GCancellable *cancellable,
1274 : : GError **error)
1275 : : {
1276 : : GList *records;
1277 : :
1278 : : #if defined(G_OS_UNIX)
1279 : 0 : gint len = 512;
1280 : : gint herr;
1281 : : GByteArray *answer;
1282 : : gint rrtype;
1283 : :
1284 : : #ifdef HAVE_RES_NQUERY
1285 : : /* Load the resolver state. This is done once per worker thread, and the
1286 : : * #GResolver::reload signal is ignored (since we always reload). This could
1287 : : * be improved by having an explicit worker thread pool, with each thread
1288 : : * containing some state which is initialised at thread creation time and
1289 : : * updated in response to #GResolver::reload.
1290 : : *
1291 : : * What we have currently is not particularly worse than using res_query() in
1292 : : * worker threads, since it would transparently call res_init() for each new
1293 : : * worker thread. (Although the workers would get reused by the
1294 : : * #GThreadPool.)
1295 : : *
1296 : : * FreeBSD requires the state to be zero-filled before calling res_ninit(). */
1297 : 0 : struct __res_state res = { 0, };
1298 : 0 : if (res_ninit (&res) != 0)
1299 : : {
1300 : 0 : g_set_error (error, G_RESOLVER_ERROR, G_RESOLVER_ERROR_INTERNAL,
1301 : : _("Error resolving “%s”"), rrname);
1302 : 0 : return NULL;
1303 : : }
1304 : : #endif
1305 : :
1306 : 0 : rrtype = g_resolver_record_type_to_rrtype (record_type);
1307 : 0 : answer = g_byte_array_new ();
1308 : : for (;;)
1309 : : {
1310 : 0 : g_byte_array_set_size (answer, len * 2);
1311 : : #if defined(HAVE_RES_NQUERY)
1312 : 0 : len = res_nquery (&res, rrname, C_IN, rrtype, answer->data, answer->len);
1313 : : #else
1314 : : len = res_query (rrname, C_IN, rrtype, answer->data, answer->len);
1315 : : #endif
1316 : :
1317 : : /* If answer fit in the buffer then we're done */
1318 : 0 : if (len < 0 || len < (gint)answer->len)
1319 : : break;
1320 : :
1321 : : /*
1322 : : * On overflow some res_query's return the length needed, others
1323 : : * return the full length entered. This code works in either case.
1324 : : */
1325 : : }
1326 : :
1327 : 0 : herr = h_errno;
1328 : 0 : records = g_resolver_records_from_res_query (rrname, rrtype, answer->data, len, herr, error);
1329 : 0 : g_byte_array_free (answer, TRUE);
1330 : :
1331 : : #ifdef HAVE_RES_NQUERY
1332 : :
1333 : : #if defined(HAVE_RES_NDESTROY)
1334 : : res_ndestroy (&res);
1335 : : #elif defined(HAVE_RES_NCLOSE)
1336 : 0 : res_nclose (&res);
1337 : : #elif defined(HAVE_RES_NINIT)
1338 : : #error "Your platform has res_ninit() but not res_nclose() or res_ndestroy(). Please file a bug at https://gitlab.gnome.org/GNOME/glib/issues/new"
1339 : : #endif
1340 : :
1341 : : #endif /* HAVE_RES_NQUERY */
1342 : :
1343 : : #else
1344 : :
1345 : : DNS_STATUS status;
1346 : : DNS_RECORDA *results = NULL;
1347 : : WORD dnstype;
1348 : :
1349 : : /* Work around differences in Windows SDK and mingw-w64 headers */
1350 : : #ifdef _MSC_VER
1351 : : typedef DNS_RECORDW * PDNS_RECORD_UTF8_;
1352 : : #else
1353 : : typedef DNS_RECORDA * PDNS_RECORD_UTF8_;
1354 : : #endif
1355 : :
1356 : : dnstype = g_resolver_record_type_to_dnstype (record_type);
1357 : : status = DnsQuery_UTF8 (rrname, dnstype, DNS_QUERY_STANDARD, NULL, (PDNS_RECORD_UTF8_*)&results, NULL);
1358 : : records = g_resolver_records_from_DnsQuery (rrname, dnstype, status, results, error);
1359 : : if (results != NULL)
1360 : : DnsRecordListFree (results, DnsFreeRecordList);
1361 : :
1362 : : #endif
1363 : :
1364 : 0 : return g_steal_pointer (&records);
1365 : : }
1366 : :
1367 : : static GList *
1368 : 0 : lookup_records (GResolver *resolver,
1369 : : const gchar *rrname,
1370 : : GResolverRecordType record_type,
1371 : : GCancellable *cancellable,
1372 : : GError **error)
1373 : : {
1374 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
1375 : : GTask *task;
1376 : : GList *records;
1377 : 0 : LookupData *data = NULL;
1378 : :
1379 : 0 : task = g_task_new (resolver, cancellable, NULL, NULL);
1380 : 0 : g_task_set_source_tag (task, lookup_records);
1381 : 0 : g_task_set_name (task, "[gio] resolver lookup records");
1382 : :
1383 : 0 : data = lookup_data_new_records (rrname, record_type);
1384 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
1385 : :
1386 : 0 : run_task_in_thread_pool_sync (self, task);
1387 : :
1388 : 0 : records = g_task_propagate_pointer (task, error);
1389 : 0 : g_object_unref (task);
1390 : :
1391 : 0 : return records;
1392 : : }
1393 : :
1394 : : static void
1395 : 0 : lookup_records_async (GResolver *resolver,
1396 : : const char *rrname,
1397 : : GResolverRecordType record_type,
1398 : : GCancellable *cancellable,
1399 : : GAsyncReadyCallback callback,
1400 : : gpointer user_data)
1401 : : {
1402 : 0 : GThreadedResolver *self = G_THREADED_RESOLVER (resolver);
1403 : : GTask *task;
1404 : 0 : LookupData *data = NULL;
1405 : :
1406 : 0 : task = g_task_new (resolver, cancellable, callback, user_data);
1407 : 0 : g_task_set_source_tag (task, lookup_records_async);
1408 : 0 : g_task_set_name (task, "[gio] resolver lookup records");
1409 : :
1410 : 0 : data = lookup_data_new_records (rrname, record_type);
1411 : 0 : g_task_set_task_data (task, g_steal_pointer (&data), (GDestroyNotify) lookup_data_free);
1412 : :
1413 : 0 : run_task_in_thread_pool_async (self, task);
1414 : :
1415 : 0 : g_object_unref (task);
1416 : 0 : }
1417 : :
1418 : : static GList *
1419 : 0 : lookup_records_finish (GResolver *resolver,
1420 : : GAsyncResult *result,
1421 : : GError **error)
1422 : : {
1423 : 0 : g_return_val_if_fail (g_task_is_valid (result, resolver), NULL);
1424 : :
1425 : 0 : return g_task_propagate_pointer (G_TASK (result), error);
1426 : : }
1427 : :
1428 : : /* Will be called in the GLib worker thread, so must lock all accesses to shared
1429 : : * data. */
1430 : : static gboolean
1431 : 0 : timeout_cb (gpointer user_data)
1432 : : {
1433 : 0 : GWeakRef *weak_task = user_data;
1434 : 0 : GTask *task = NULL; /* (owned) */
1435 : : LookupData *data;
1436 : : gboolean should_return;
1437 : :
1438 : 0 : task = g_weak_ref_get (weak_task);
1439 : 0 : if (task == NULL)
1440 : 0 : return G_SOURCE_REMOVE;
1441 : :
1442 : 0 : data = g_task_get_task_data (task);
1443 : :
1444 : 0 : g_mutex_lock (&data->lock);
1445 : :
1446 : 0 : should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, TIMED_OUT);
1447 : 0 : g_clear_pointer (&data->timeout_source, g_source_unref);
1448 : :
1449 : 0 : g_mutex_unlock (&data->lock);
1450 : :
1451 : 0 : if (should_return)
1452 : : {
1453 : 0 : g_task_return_new_error_literal (task, G_IO_ERROR, G_IO_ERROR_TIMED_OUT,
1454 : 0 : _("Socket I/O timed out"));
1455 : : }
1456 : :
1457 : : /* Signal completion of the task. */
1458 : 0 : g_mutex_lock (&data->lock);
1459 : 0 : data->has_returned = TRUE;
1460 : 0 : g_cond_broadcast (&data->cond);
1461 : 0 : g_mutex_unlock (&data->lock);
1462 : :
1463 : 0 : g_object_unref (task);
1464 : :
1465 : 0 : return G_SOURCE_REMOVE;
1466 : : }
1467 : :
1468 : : /* Will be called in the GLib worker thread, so must lock all accesses to shared
1469 : : * data. */
1470 : : static gboolean
1471 : 0 : cancelled_cb (GCancellable *cancellable,
1472 : : gpointer user_data)
1473 : : {
1474 : 0 : GWeakRef *weak_task = user_data;
1475 : 0 : GTask *task = NULL; /* (owned) */
1476 : : LookupData *data;
1477 : : gboolean should_return;
1478 : :
1479 : 0 : task = g_weak_ref_get (weak_task);
1480 : 0 : if (task == NULL)
1481 : 0 : return G_SOURCE_REMOVE;
1482 : :
1483 : 0 : data = g_task_get_task_data (task);
1484 : :
1485 : 0 : g_mutex_lock (&data->lock);
1486 : :
1487 : 0 : g_assert (g_cancellable_is_cancelled (cancellable));
1488 : 0 : should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, CANCELLED);
1489 : 0 : g_clear_pointer (&data->cancellable_source, g_source_unref);
1490 : :
1491 : 0 : g_mutex_unlock (&data->lock);
1492 : :
1493 : 0 : if (should_return)
1494 : 0 : g_task_return_error_if_cancelled (task);
1495 : :
1496 : : /* Signal completion of the task. */
1497 : 0 : g_mutex_lock (&data->lock);
1498 : 0 : data->has_returned = TRUE;
1499 : 0 : g_cond_broadcast (&data->cond);
1500 : 0 : g_mutex_unlock (&data->lock);
1501 : :
1502 : 0 : g_object_unref (task);
1503 : :
1504 : 0 : return G_SOURCE_REMOVE;
1505 : : }
1506 : :
1507 : : static void
1508 : 0 : weak_ref_clear_and_free (GWeakRef *weak_ref)
1509 : : {
1510 : 0 : g_weak_ref_clear (weak_ref);
1511 : 0 : g_free (weak_ref);
1512 : 0 : }
1513 : :
1514 : : static void
1515 : 3 : run_task_in_thread_pool_async (GThreadedResolver *self,
1516 : : GTask *task)
1517 : : {
1518 : 3 : LookupData *data = g_task_get_task_data (task);
1519 : 3 : guint timeout_ms = g_resolver_get_timeout (G_RESOLVER (self));
1520 : 3 : GCancellable *cancellable = g_task_get_cancellable (task);
1521 : :
1522 : 3 : g_mutex_lock (&data->lock);
1523 : :
1524 : 3 : g_thread_pool_push (self->thread_pool, g_object_ref (task), NULL);
1525 : :
1526 : 3 : if (timeout_ms != 0)
1527 : : {
1528 : 3 : GWeakRef *weak_task = g_new0 (GWeakRef, 1);
1529 : 3 : g_weak_ref_set (weak_task, task);
1530 : :
1531 : 3 : data->timeout_source = g_timeout_source_new (timeout_ms);
1532 : 3 : g_source_set_static_name (data->timeout_source, "[gio] threaded resolver timeout");
1533 : 3 : g_source_set_callback (data->timeout_source, G_SOURCE_FUNC (timeout_cb), g_steal_pointer (&weak_task), (GDestroyNotify) weak_ref_clear_and_free);
1534 : 3 : g_source_attach (data->timeout_source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
1535 : : }
1536 : :
1537 : 3 : if (cancellable != NULL)
1538 : : {
1539 : 0 : GWeakRef *weak_task = g_new0 (GWeakRef, 1);
1540 : 0 : g_weak_ref_set (weak_task, task);
1541 : :
1542 : 0 : data->cancellable_source = g_cancellable_source_new (cancellable);
1543 : 0 : g_source_set_static_name (data->cancellable_source, "[gio] threaded resolver cancellable");
1544 : 0 : g_source_set_callback (data->cancellable_source, G_SOURCE_FUNC (cancelled_cb), g_steal_pointer (&weak_task), (GDestroyNotify) weak_ref_clear_and_free);
1545 : 0 : g_source_attach (data->cancellable_source, GLIB_PRIVATE_CALL (g_get_worker_context) ());
1546 : : }
1547 : :
1548 : 3 : g_mutex_unlock (&data->lock);
1549 : 3 : }
1550 : :
1551 : : static void
1552 : 3 : run_task_in_thread_pool_sync (GThreadedResolver *self,
1553 : : GTask *task)
1554 : : {
1555 : 3 : LookupData *data = g_task_get_task_data (task);
1556 : :
1557 : 3 : run_task_in_thread_pool_async (self, task);
1558 : :
1559 : 3 : g_mutex_lock (&data->lock);
1560 : 6 : while (!data->has_returned)
1561 : 3 : g_cond_wait (&data->cond, &data->lock);
1562 : 3 : g_mutex_unlock (&data->lock);
1563 : 3 : }
1564 : :
1565 : : static void
1566 : 3 : threaded_resolver_worker_cb (gpointer task_data,
1567 : : gpointer user_data)
1568 : : {
1569 : 3 : GTask *task = G_TASK (g_steal_pointer (&task_data));
1570 : 3 : LookupData *data = g_task_get_task_data (task);
1571 : 3 : GCancellable *cancellable = g_task_get_cancellable (task);
1572 : 3 : GError *local_error = NULL;
1573 : : gboolean should_return;
1574 : :
1575 : 3 : switch (data->lookup_type) {
1576 : 3 : case LOOKUP_BY_NAME:
1577 : : {
1578 : 3 : GList *addresses = do_lookup_by_name (data->lookup_by_name.hostname,
1579 : : data->lookup_by_name.address_family,
1580 : : cancellable,
1581 : : &local_error);
1582 : :
1583 : 3 : g_mutex_lock (&data->lock);
1584 : 3 : should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED);
1585 : 3 : g_mutex_unlock (&data->lock);
1586 : :
1587 : 3 : if (should_return)
1588 : : {
1589 : 3 : if (addresses != NULL)
1590 : 0 : g_task_return_pointer (task, g_steal_pointer (&addresses), (GDestroyNotify) g_resolver_free_addresses);
1591 : : else
1592 : 3 : g_task_return_error (task, g_steal_pointer (&local_error));
1593 : : }
1594 : :
1595 : 3 : g_clear_pointer (&addresses, g_resolver_free_addresses);
1596 : 3 : g_clear_error (&local_error);
1597 : : }
1598 : 3 : break;
1599 : 0 : case LOOKUP_BY_ADDRESS:
1600 : : {
1601 : 0 : gchar *name = do_lookup_by_address (data->lookup_by_address.address,
1602 : : cancellable,
1603 : : &local_error);
1604 : :
1605 : 0 : g_mutex_lock (&data->lock);
1606 : 0 : should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED);
1607 : 0 : g_mutex_unlock (&data->lock);
1608 : :
1609 : 0 : if (should_return)
1610 : : {
1611 : 0 : if (name != NULL)
1612 : 0 : g_task_return_pointer (task, g_steal_pointer (&name), g_free);
1613 : : else
1614 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
1615 : : }
1616 : :
1617 : 0 : g_clear_pointer (&name, g_free);
1618 : 0 : g_clear_error (&local_error);
1619 : : }
1620 : 0 : break;
1621 : 0 : case LOOKUP_RECORDS:
1622 : : {
1623 : 0 : GList *records = do_lookup_records (data->lookup_records.rrname,
1624 : : data->lookup_records.record_type,
1625 : : cancellable,
1626 : : &local_error);
1627 : :
1628 : 0 : g_mutex_lock (&data->lock);
1629 : 0 : should_return = g_atomic_int_compare_and_exchange (&data->will_return, NOT_YET, COMPLETED);
1630 : 0 : g_mutex_unlock (&data->lock);
1631 : :
1632 : 0 : if (should_return)
1633 : : {
1634 : 0 : if (records != NULL)
1635 : 0 : g_task_return_pointer (task, g_steal_pointer (&records), (GDestroyNotify) free_records);
1636 : : else
1637 : 0 : g_task_return_error (task, g_steal_pointer (&local_error));
1638 : : }
1639 : :
1640 : 0 : g_clear_pointer (&records, free_records);
1641 : 0 : g_clear_error (&local_error);
1642 : : }
1643 : 0 : break;
1644 : 0 : default:
1645 : : g_assert_not_reached ();
1646 : : }
1647 : :
1648 : : /* Signal completion of a task. */
1649 : 3 : g_mutex_lock (&data->lock);
1650 : 3 : data->has_returned = TRUE;
1651 : 3 : g_cond_broadcast (&data->cond);
1652 : 3 : g_mutex_unlock (&data->lock);
1653 : :
1654 : 3 : g_object_unref (task);
1655 : 3 : }
1656 : :
1657 : : static void
1658 : 5 : g_threaded_resolver_class_init (GThreadedResolverClass *threaded_class)
1659 : : {
1660 : 5 : GObjectClass *object_class = G_OBJECT_CLASS (threaded_class);
1661 : 5 : GResolverClass *resolver_class = G_RESOLVER_CLASS (threaded_class);
1662 : :
1663 : 5 : object_class->finalize = g_threaded_resolver_finalize;
1664 : :
1665 : 5 : resolver_class->lookup_by_name = lookup_by_name;
1666 : 5 : resolver_class->lookup_by_name_async = lookup_by_name_async;
1667 : 5 : resolver_class->lookup_by_name_finish = lookup_by_name_finish;
1668 : 5 : resolver_class->lookup_by_name_with_flags = lookup_by_name_with_flags;
1669 : 5 : resolver_class->lookup_by_name_with_flags_async = lookup_by_name_with_flags_async;
1670 : 5 : resolver_class->lookup_by_name_with_flags_finish = lookup_by_name_with_flags_finish;
1671 : 5 : resolver_class->lookup_by_address = lookup_by_address;
1672 : 5 : resolver_class->lookup_by_address_async = lookup_by_address_async;
1673 : 5 : resolver_class->lookup_by_address_finish = lookup_by_address_finish;
1674 : 5 : resolver_class->lookup_records = lookup_records;
1675 : 5 : resolver_class->lookup_records_async = lookup_records_async;
1676 : 5 : resolver_class->lookup_records_finish = lookup_records_finish;
1677 : 5 : }
|