2 This file is part of GNUnet.
3 Copyright (C) 2007-2016 GNUnet e.V.
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * @file util/gnunet-service-resolver.c
21 * @brief code to do DNS resolution
22 * @author Christian Grothoff
25 #include "gnunet_util_lib.h"
26 #include "gnunet_protocols.h"
27 #include "gnunet_statistics_service.h"
37 struct GNUNET_DNSPARSER_Record *record;
41 * A cached DNS lookup result.
46 * This is a doubly linked list.
48 struct ResolveCache *next;
51 * This is a doubly linked list.
53 struct ResolveCache *prev;
56 * type of queried DNS record
61 * a pointer to the request_id if a query for this hostname/record_type
62 * is currently pending, NULL otherwise.
67 * The client that queried the records contained in this cache entry.
69 struct GNUNET_SERVICE_Client *client;
72 * head of a double linked list containing the lookup results
74 struct Record *records_head;
77 * tail of a double linked list containing the lookup results
79 struct Record *records_tail;
82 * handle for cancelling a request
84 struct GNUNET_DNSSTUB_RequestSocket *resolve_handle;
87 * handle for the resolution timeout task
89 struct GNUNET_SCHEDULER_Task *timeout_task;
95 * Start of the linked list of cached DNS lookup results.
97 static struct ResolveCache *cache_head;
100 * Tail of the linked list of cached DNS lookup results.
102 static struct ResolveCache *cache_tail;
105 * context of dnsstub library
107 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
110 void free_cache_entry (struct ResolveCache *entry)
115 next = entry->records_head;
116 while (NULL != (pos = next))
119 GNUNET_CONTAINER_DLL_remove (entry->records_head,
122 if (NULL != pos->record)
124 GNUNET_DNSPARSER_free_record (pos->record);
125 GNUNET_free (pos->record);
129 if (NULL != entry->resolve_handle)
131 GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle);
132 entry->resolve_handle = NULL;
134 if (NULL != entry->timeout_task)
136 GNUNET_SCHEDULER_cancel (entry->timeout_task);
137 entry->timeout_task = NULL;
139 GNUNET_free_non_null (entry->request_id);
145 extract_dns_server (const char* line, size_t line_len)
147 if (0 == strncmp (line, "nameserver ", 11))
148 return GNUNET_strndup (line + 11, line_len - 11);
154 * reads the list of nameservers from /etc/resolve.conf
156 * @param server_addrs[out] a list of null-terminated server address strings
157 * @return the number of server addresses in @server_addrs, -1 on error
160 lookup_dns_servers (char ***server_addrs)
162 struct GNUNET_DISK_FileHandle *fh;
165 size_t read_offset = 0;
166 unsigned int num_dns_servers = 0;
168 fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
169 GNUNET_DISK_OPEN_READ,
170 GNUNET_DISK_PERM_NONE);
173 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
174 "Could not open /etc/resolv.conf. "
175 "DNS resolution will not be possible.\n");
178 bytes_read = GNUNET_DISK_file_read (fh,
181 *server_addrs = NULL;
182 while (read_offset < bytes_read)
188 newline = strchr (buf + read_offset, '\n');
193 line_len = newline - buf - read_offset;
194 dns_server = extract_dns_server (buf + read_offset, line_len);
195 if (NULL != dns_server)
197 GNUNET_array_append (*server_addrs,
201 read_offset += line_len + 1;
203 GNUNET_DISK_file_close (fh);
204 return num_dns_servers;
209 make_reverse_hostname (const void *ip, int af)
211 char *buf = GNUNET_new_array (80, char);
215 struct in_addr *addr = (struct in_addr *)ip;
216 uint32_t ip_int = addr->s_addr;
217 for (int i = 3; i >= 0; i--)
219 int n = GNUNET_snprintf (buf + pos,
222 ((uint8_t *)&ip_int)[i]);
230 pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa");
232 else if (AF_INET6 == af)
234 struct in6_addr *addr = (struct in6_addr *)ip;
235 for (int i = 15; i >= 0; i--)
237 int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf);
244 n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4);
252 pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa");
260 send_reply (struct GNUNET_DNSPARSER_Record *record,
262 struct GNUNET_SERVICE_Client *client)
264 struct GNUNET_RESOLVER_ResponseMessage *msg;
265 struct GNUNET_MQ_Envelope *env;
269 switch (record->type)
271 case GNUNET_DNSPARSER_TYPE_PTR:
273 char *hostname = record->data.hostname;
275 payload_len = strlen (hostname) + 1;
278 case GNUNET_DNSPARSER_TYPE_A:
279 case GNUNET_DNSPARSER_TYPE_AAAA:
281 payload = record->data.raw.data;
282 payload_len = record->data.raw.data_len;
287 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
288 "Cannot handle DNS response type: unimplemented\n");
292 env = GNUNET_MQ_msg_extra (msg,
294 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
295 msg->id = request_id;
296 GNUNET_memcpy (&msg[1],
299 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
305 send_end_msg (uint16_t request_id,
306 struct GNUNET_SERVICE_Client *client)
308 struct GNUNET_RESOLVER_ResponseMessage *msg;
309 struct GNUNET_MQ_Envelope *env;
311 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
312 "Sending end message\n");
313 env = GNUNET_MQ_msg (msg,
314 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
315 msg->id = request_id;
316 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
322 handle_resolve_result (void *cls,
323 const struct GNUNET_TUN_DnsHeader *dns,
326 struct ResolveCache *cache = cls;
327 struct GNUNET_DNSPARSER_Packet *parsed;
328 uint16_t request_id = *cache->request_id;
329 struct GNUNET_SERVICE_Client *client = cache->client;
331 parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
335 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
336 "Failed to parse DNS reply (request ID %u\n",
340 if (request_id != ntohs (parsed->id))
342 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
343 "Request ID in DNS reply does not match\n");
346 else if (0 == parsed->num_answers)
348 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
349 "DNS reply (request ID %u) contains no answers\n",
351 GNUNET_CONTAINER_DLL_remove (cache_head,
354 free_cache_entry (cache);
359 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
360 "Got reply for request ID %u\n",
362 for (unsigned int i = 0; i != parsed->num_answers; i++)
364 struct Record *cache_entry = GNUNET_new (struct Record);
365 struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
366 cache_entry->record = GNUNET_DNSPARSER_duplicate_record (record);
367 GNUNET_CONTAINER_DLL_insert (cache->records_head,
370 send_reply (cache_entry->record,
374 GNUNET_free_non_null (cache->request_id);
375 cache->request_id = NULL;
377 send_end_msg (request_id,
380 cache->client = NULL;
381 GNUNET_SCHEDULER_cancel (cache->timeout_task);
382 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
383 cache->timeout_task = NULL;
384 cache->resolve_handle = NULL;
385 GNUNET_DNSPARSER_free_packet (parsed);
390 handle_resolve_timeout (void *cls)
392 struct ResolveCache *cache = cls;
394 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
396 if (NULL != cache->resolve_handle)
398 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
399 cache->resolve_handle = NULL;
401 GNUNET_CONTAINER_DLL_remove (cache_head,
404 free_cache_entry (cache);
409 resolve_and_cache (const char* hostname,
410 uint16_t record_type,
412 struct GNUNET_SERVICE_Client *client)
416 struct GNUNET_DNSPARSER_Query query;
417 struct GNUNET_DNSPARSER_Packet packet;
418 struct ResolveCache *cache;
419 struct GNUNET_TIME_Relative timeout =
420 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
423 "resolve_and_cache\n");
424 query.name = (char *)hostname;
425 query.type = record_type;
426 query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
430 packet.num_queries = 1;
431 packet.queries = &query;
432 packet.id = htons (request_id);
433 packet.flags.recursion_desired = 1;
435 GNUNET_DNSPARSER_pack (&packet,
440 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
441 "Failed to pack query for hostname `%s'\n",
443 return GNUNET_SYSERR;
446 cache = GNUNET_malloc (sizeof (struct ResolveCache));
447 cache->record_type = record_type;
448 cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id));
449 cache->client = client;
450 cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
451 &handle_resolve_timeout,
453 cache->resolve_handle =
454 GNUNET_DNSSTUB_resolve (dnsstub_ctx,
457 &handle_resolve_result,
459 GNUNET_CONTAINER_DLL_insert (cache_head,
462 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
463 "resolve %s, request_id = %u\n",
466 GNUNET_free (packet_buf);
472 get_hostname (struct ResolveCache *cache_entry)
474 if (NULL != cache_entry->records_head)
476 GNUNET_assert (NULL != cache_entry->records_head);
477 GNUNET_assert (NULL != cache_entry->records_head->record);
478 GNUNET_assert (NULL != cache_entry->records_head->record->name);
479 return cache_entry->records_head->record->name;
485 static const uint16_t *
486 get_record_type (struct ResolveCache *cache_entry)
488 if (NULL != cache_entry->records_head)
489 return &cache_entry->record_type;
494 static const struct GNUNET_TIME_Absolute *
495 get_expiration_time (struct ResolveCache *cache_entry)
497 if (NULL != cache_entry->records_head)
498 return &cache_entry->records_head->record->expiration_time;
504 remove_if_expired (struct ResolveCache *cache_entry)
506 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
508 if ( (NULL != cache_entry->records_head) &&
509 (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) )
511 GNUNET_CONTAINER_DLL_remove (cache_head,
514 free_cache_entry (cache_entry);
522 * Get an IP address as a string (works for both IPv4 and IPv6). Note
523 * that the resolution happens asynchronously and that the first call
524 * may not immediately result in the FQN (but instead in a
525 * human-readable IP address).
527 * @param client handle to the client making the request (for sending the reply)
528 * @param af AF_INET or AF_INET6
529 * @param ip `struct in_addr` or `struct in6_addr`
532 try_cache (const char *hostname,
533 uint16_t record_type,
535 struct GNUNET_SERVICE_Client *client)
537 struct ResolveCache *pos;
538 struct ResolveCache *next;
541 while ( (NULL != (pos = next)) &&
542 ( (NULL == pos->records_head) ||
543 (0 != strcmp (get_hostname (pos), hostname)) ||
544 (*get_record_type (pos) != record_type) ) )
547 remove_if_expired (pos);
551 if (GNUNET_NO == remove_if_expired (pos))
553 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
554 "found cache entry for '%s', record type '%u'\n",
557 struct Record *cache_pos = pos->records_head;
558 while (NULL != cache_pos)
560 send_reply (cache_pos->record,
563 cache_pos = cache_pos->next;
565 send_end_msg (request_id,
570 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
571 "no cache entry for '%s'\n",
578 * Verify well-formedness of GET-message.
580 * @param cls closure, unused
581 * @param get the actual message
582 * @return #GNUNET_OK if @a get is well-formed
585 check_get (void *cls,
586 const struct GNUNET_RESOLVER_GetMessage *get)
593 size = ntohs (get->header.size) - sizeof (*get);
594 direction = ntohl (get->direction);
595 if (GNUNET_NO == direction)
597 /* IP from hostname */
598 const char *hostname;
600 hostname = (const char *) &get[1];
601 if (hostname[size - 1] != '\0')
604 return GNUNET_SYSERR;
608 af = ntohl (get->af);
612 if (size != sizeof (struct in_addr))
615 return GNUNET_SYSERR;
619 if (size != sizeof (struct in6_addr))
622 return GNUNET_SYSERR;
627 return GNUNET_SYSERR;
634 process_get (const char *hostname,
635 uint16_t record_type,
637 struct GNUNET_SERVICE_Client *client)
639 if (GNUNET_NO == try_cache (hostname, record_type, request_id, client))
641 int result = resolve_and_cache (hostname,
645 GNUNET_assert (GNUNET_OK == result);
651 * Handle GET-message.
653 * @param cls identification of the client
654 * @param msg the actual message
657 handle_get (void *cls,
658 const struct GNUNET_RESOLVER_GetMessage *msg)
660 struct GNUNET_SERVICE_Client *client = cls;
664 const char *hostname;
666 direction = ntohl (msg->direction);
667 af = ntohl (msg->af);
668 request_id = ntohs (msg->id);
669 if (GNUNET_NO == direction)
671 /* IP from hostname */
672 hostname = GNUNET_strdup ((const char *) &msg[1]);
677 process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client);
682 process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client);
687 process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client);
692 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
693 "got invalid af: %d\n",
701 /* hostname from IP */
702 hostname = make_reverse_hostname (&msg[1], af);
703 process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client);
705 GNUNET_free_non_null ((char *)hostname);
706 GNUNET_SERVICE_client_continue (client);
711 shutdown_task (void *cls)
714 struct ResolveCache *pos;
716 while (NULL != (pos = cache_head))
718 GNUNET_CONTAINER_DLL_remove (cache_head,
721 free_cache_entry (pos);
723 GNUNET_DNSSTUB_stop (dnsstub_ctx);
729 const struct GNUNET_CONFIGURATION_Handle *cfg,
730 struct GNUNET_SERVICE_Handle *sh)
735 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
737 dnsstub_ctx = GNUNET_DNSSTUB_start (128);
739 ssize_t num_dns_servers = lookup_dns_servers (&dns_servers);
740 if (0 == num_dns_servers)
742 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
743 "no DNS server available. DNS resolution will not be possible.\n");
745 for (int i = 0; i != num_dns_servers; i++)
747 int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
748 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
749 "Adding DNS server '%s': %s\n",
751 GNUNET_OK == result ? "success" : "failure");
752 GNUNET_free (dns_servers[i]);
754 GNUNET_free_non_null (dns_servers);
759 * Callback called when a client connects to the service.
761 * @param cls closure for the service, unused
762 * @param c the new client that connected to the service
763 * @param mq the message queue used to send messages to the client
767 connect_cb (void *cls,
768 struct GNUNET_SERVICE_Client *c,
769 struct GNUNET_MQ_Handle *mq)
779 * Callback called when a client disconnected from the service
781 * @param cls closure for the service
782 * @param c the client that disconnected
783 * @param internal_cls should be equal to @a c
786 disconnect_cb (void *cls,
787 struct GNUNET_SERVICE_Client *c,
791 struct ResolveCache *pos = cache_head;
795 if (pos->client == c)
801 GNUNET_assert (c == internal_cls);
806 * Define "main" method using service macro.
810 GNUNET_SERVICE_OPTION_NONE,
815 GNUNET_MQ_hd_var_size (get,
816 GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
817 struct GNUNET_RESOLVER_GetMessage,
819 GNUNET_MQ_handler_end ());
822 #if defined(LINUX) && defined(__GLIBC__)
826 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
828 void __attribute__ ((constructor))
829 GNUNET_RESOLVER_memory_init ()
831 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
832 mallopt (M_TOP_PAD, 1 * 1024);
838 /* end of gnunet-service-resolver.c */