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;
383 if (NULL != cache->timeout_task)
385 GNUNET_SCHEDULER_cancel (cache->timeout_task);
386 cache->timeout_task = NULL;
388 if (NULL != cache->resolve_handle)
390 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
391 cache->resolve_handle = NULL;
394 GNUNET_DNSPARSER_free_packet (parsed);
399 handle_resolve_timeout (void *cls)
401 struct ResolveCache *cache = cls;
403 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
405 if (NULL != cache->resolve_handle)
407 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
408 cache->resolve_handle = NULL;
410 GNUNET_CONTAINER_DLL_remove (cache_head,
413 free_cache_entry (cache);
418 resolve_and_cache (const char* hostname,
419 uint16_t record_type,
421 struct GNUNET_SERVICE_Client *client)
425 struct GNUNET_DNSPARSER_Query query;
426 struct GNUNET_DNSPARSER_Packet packet;
427 struct ResolveCache *cache;
428 struct GNUNET_TIME_Relative timeout =
429 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
431 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
432 "resolve_and_cache\n");
433 query.name = (char *)hostname;
434 query.type = record_type;
435 query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
439 packet.num_queries = 1;
440 packet.queries = &query;
441 packet.id = htons (request_id);
442 packet.flags.recursion_desired = 1;
444 GNUNET_DNSPARSER_pack (&packet,
449 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
450 "Failed to pack query for hostname `%s'\n",
452 return GNUNET_SYSERR;
455 cache = GNUNET_malloc (sizeof (struct ResolveCache));
456 cache->record_type = record_type;
457 cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id));
458 cache->client = client;
459 cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
460 &handle_resolve_timeout,
462 cache->resolve_handle =
463 GNUNET_DNSSTUB_resolve (dnsstub_ctx,
466 &handle_resolve_result,
468 GNUNET_CONTAINER_DLL_insert (cache_head,
471 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
472 "resolve %s, request_id = %u\n",
475 GNUNET_free (packet_buf);
481 get_hostname (struct ResolveCache *cache_entry)
483 if (NULL != cache_entry->records_head)
485 GNUNET_assert (NULL != cache_entry->records_head);
486 GNUNET_assert (NULL != cache_entry->records_head->record);
487 GNUNET_assert (NULL != cache_entry->records_head->record->name);
488 return cache_entry->records_head->record->name;
494 static const uint16_t *
495 get_record_type (struct ResolveCache *cache_entry)
497 if (NULL != cache_entry->records_head)
498 return &cache_entry->record_type;
503 static const struct GNUNET_TIME_Absolute *
504 get_expiration_time (struct ResolveCache *cache_entry)
506 if (NULL != cache_entry->records_head)
507 return &cache_entry->records_head->record->expiration_time;
513 remove_if_expired (struct ResolveCache *cache_entry)
515 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
517 if ( (NULL != cache_entry->records_head) &&
518 (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) )
520 GNUNET_CONTAINER_DLL_remove (cache_head,
523 free_cache_entry (cache_entry);
531 * Get an IP address as a string (works for both IPv4 and IPv6). Note
532 * that the resolution happens asynchronously and that the first call
533 * may not immediately result in the FQN (but instead in a
534 * human-readable IP address).
536 * @param client handle to the client making the request (for sending the reply)
537 * @param af AF_INET or AF_INET6
538 * @param ip `struct in_addr` or `struct in6_addr`
541 try_cache (const char *hostname,
542 uint16_t record_type,
544 struct GNUNET_SERVICE_Client *client)
546 struct ResolveCache *pos;
547 struct ResolveCache *next;
550 while ( (NULL != (pos = next)) &&
551 ( (NULL == pos->records_head) ||
552 (0 != strcmp (get_hostname (pos), hostname)) ||
553 (*get_record_type (pos) != record_type) ) )
556 remove_if_expired (pos);
560 if (GNUNET_NO == remove_if_expired (pos))
562 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
563 "found cache entry for '%s', record type '%u'\n",
566 struct Record *cache_pos = pos->records_head;
567 while (NULL != cache_pos)
569 send_reply (cache_pos->record,
572 cache_pos = cache_pos->next;
574 send_end_msg (request_id,
579 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
580 "no cache entry for '%s'\n",
587 * Verify well-formedness of GET-message.
589 * @param cls closure, unused
590 * @param get the actual message
591 * @return #GNUNET_OK if @a get is well-formed
594 check_get (void *cls,
595 const struct GNUNET_RESOLVER_GetMessage *get)
602 size = ntohs (get->header.size) - sizeof (*get);
603 direction = ntohl (get->direction);
604 if (GNUNET_NO == direction)
606 /* IP from hostname */
607 const char *hostname;
609 hostname = (const char *) &get[1];
610 if (hostname[size - 1] != '\0')
613 return GNUNET_SYSERR;
617 af = ntohl (get->af);
621 if (size != sizeof (struct in_addr))
624 return GNUNET_SYSERR;
628 if (size != sizeof (struct in6_addr))
631 return GNUNET_SYSERR;
636 return GNUNET_SYSERR;
643 process_get (const char *hostname,
644 uint16_t record_type,
646 struct GNUNET_SERVICE_Client *client)
648 if (GNUNET_NO == try_cache (hostname, record_type, request_id, client))
650 int result = resolve_and_cache (hostname,
654 GNUNET_assert (GNUNET_OK == result);
660 * Handle GET-message.
662 * @param cls identification of the client
663 * @param msg the actual message
666 handle_get (void *cls,
667 const struct GNUNET_RESOLVER_GetMessage *msg)
669 struct GNUNET_SERVICE_Client *client = cls;
673 const char *hostname;
675 direction = ntohl (msg->direction);
676 af = ntohl (msg->af);
677 request_id = ntohs (msg->id);
678 if (GNUNET_NO == direction)
680 /* IP from hostname */
681 hostname = GNUNET_strdup ((const char *) &msg[1]);
686 process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client);
691 process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client);
696 process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client);
701 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
702 "got invalid af: %d\n",
710 /* hostname from IP */
711 hostname = make_reverse_hostname (&msg[1], af);
712 process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client);
714 GNUNET_free_non_null ((char *)hostname);
715 GNUNET_SERVICE_client_continue (client);
720 shutdown_task (void *cls)
723 struct ResolveCache *pos;
725 while (NULL != (pos = cache_head))
727 GNUNET_CONTAINER_DLL_remove (cache_head,
730 free_cache_entry (pos);
732 GNUNET_DNSSTUB_stop (dnsstub_ctx);
738 const struct GNUNET_CONFIGURATION_Handle *cfg,
739 struct GNUNET_SERVICE_Handle *sh)
744 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
746 dnsstub_ctx = GNUNET_DNSSTUB_start (128);
748 ssize_t num_dns_servers = lookup_dns_servers (&dns_servers);
749 if (0 == num_dns_servers)
751 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
752 "no DNS server available. DNS resolution will not be possible.\n");
754 for (int i = 0; i != num_dns_servers; i++)
756 int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
757 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
758 "Adding DNS server '%s': %s\n",
760 GNUNET_OK == result ? "success" : "failure");
761 GNUNET_free (dns_servers[i]);
763 GNUNET_free_non_null (dns_servers);
768 * Callback called when a client connects to the service.
770 * @param cls closure for the service, unused
771 * @param c the new client that connected to the service
772 * @param mq the message queue used to send messages to the client
776 connect_cb (void *cls,
777 struct GNUNET_SERVICE_Client *c,
778 struct GNUNET_MQ_Handle *mq)
788 * Callback called when a client disconnected from the service
790 * @param cls closure for the service
791 * @param c the client that disconnected
792 * @param internal_cls should be equal to @a c
795 disconnect_cb (void *cls,
796 struct GNUNET_SERVICE_Client *c,
800 struct ResolveCache *pos = cache_head;
804 if (pos->client == c)
810 GNUNET_assert (c == internal_cls);
815 * Define "main" method using service macro.
819 GNUNET_SERVICE_OPTION_NONE,
824 GNUNET_MQ_hd_var_size (get,
825 GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
826 struct GNUNET_RESOLVER_GetMessage,
828 GNUNET_MQ_handler_end ());
831 #if defined(LINUX) && defined(__GLIBC__)
835 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
837 void __attribute__ ((constructor))
838 GNUNET_RESOLVER_memory_init ()
840 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
841 mallopt (M_TOP_PAD, 1 * 1024);
847 /* end of gnunet-service-resolver.c */