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.
66 struct GNUNET_SERVICE_Client *client;
69 * head of a double linked list containing the lookup results
71 struct Record *records_head;
74 * tail of a double linked list containing the lookup results
76 struct Record *records_tail;
79 * handle for cancelling a request
81 struct GNUNET_DNSSTUB_RequestSocket *resolve_handle;
84 * handle for the resolution timeout task
86 struct GNUNET_SCHEDULER_Task *timeout_task;
92 * Start of the linked list of cached DNS lookup results.
94 static struct ResolveCache *cache_head;
97 * Tail of the linked list of cached DNS lookup results.
99 static struct ResolveCache *cache_tail;
102 * context of dnsstub library
104 static struct GNUNET_DNSSTUB_Context *dnsstub_ctx;
107 void free_cache_entry (struct ResolveCache *entry)
112 next = entry->records_head;
113 while (NULL != (pos = next))
116 GNUNET_CONTAINER_DLL_remove (entry->records_head,
119 if (NULL != pos->record)
121 GNUNET_DNSPARSER_free_record (pos->record);
122 GNUNET_free (pos->record);
126 if (NULL != entry->resolve_handle)
128 GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle);
129 entry->resolve_handle = NULL;
131 if (NULL != entry->timeout_task)
133 GNUNET_SCHEDULER_cancel (entry->timeout_task);
134 entry->timeout_task = NULL;
136 GNUNET_free_non_null (entry->request_id);
142 extract_dns_server (const char* line, size_t line_len)
144 if (0 == strncmp (line, "nameserver ", 11))
145 return GNUNET_strndup (line + 11, line_len - 11);
151 * reads the list of nameservers from /etc/resolve.conf
153 * @param server_addrs[out] a list of null-terminated server address strings
154 * @return the number of server addresses in @server_addrs, -1 on error
157 lookup_dns_servers (char ***server_addrs)
159 struct GNUNET_DISK_FileHandle *fh;
162 size_t read_offset = 0;
163 unsigned int num_dns_servers = 0;
165 fh = GNUNET_DISK_file_open ("/etc/resolv.conf",
166 GNUNET_DISK_OPEN_READ,
167 GNUNET_DISK_PERM_NONE);
170 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
171 "Could not open /etc/resolv.conf. "
172 "DNS resolution will not be possible.\n");
175 bytes_read = GNUNET_DISK_file_read (fh,
178 *server_addrs = NULL;
179 while (read_offset < bytes_read)
185 newline = strchr (buf + read_offset, '\n');
190 line_len = newline - buf - read_offset;
191 dns_server = extract_dns_server (buf + read_offset, line_len);
192 if (NULL != dns_server)
194 GNUNET_array_append (*server_addrs,
198 read_offset += line_len + 1;
200 GNUNET_DISK_file_close (fh);
201 return num_dns_servers;
206 make_reverse_hostname (const void *ip, int af)
208 char *buf = GNUNET_new_array (80, char);
212 struct in_addr *addr = (struct in_addr *)ip;
213 uint32_t ip_int = addr->s_addr;
214 for (int i = 3; i >= 0; i--)
216 int n = GNUNET_snprintf (buf + pos,
219 ((uint8_t *)&ip_int)[i]);
227 pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa");
229 else if (AF_INET6 == af)
231 struct in6_addr *addr = (struct in6_addr *)ip;
232 for (int i = 15; i >= 0; i--)
234 int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf);
241 n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4);
249 pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa");
257 send_reply (struct GNUNET_DNSPARSER_Record *record,
259 struct GNUNET_SERVICE_Client *client)
261 struct GNUNET_RESOLVER_ResponseMessage *msg;
262 struct GNUNET_MQ_Envelope *env;
266 switch (record->type)
268 case GNUNET_DNSPARSER_TYPE_PTR:
270 char *hostname = record->data.hostname;
272 payload_len = strlen (hostname) + 1;
275 case GNUNET_DNSPARSER_TYPE_A:
276 case GNUNET_DNSPARSER_TYPE_AAAA:
278 payload = record->data.raw.data;
279 payload_len = record->data.raw.data_len;
284 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
285 "Cannot handle DNS response type: unimplemented\n");
289 env = GNUNET_MQ_msg_extra (msg,
291 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
292 msg->id = request_id;
293 GNUNET_memcpy (&msg[1],
296 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
302 send_end_msg (uint16_t request_id,
303 struct GNUNET_SERVICE_Client *client)
305 struct GNUNET_RESOLVER_ResponseMessage *msg;
306 struct GNUNET_MQ_Envelope *env;
308 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
309 "Sending end message\n");
310 env = GNUNET_MQ_msg (msg,
311 GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE);
312 msg->id = request_id;
313 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client),
319 handle_resolve_result (void *cls,
320 const struct GNUNET_TUN_DnsHeader *dns,
323 struct ResolveCache *cache = cls;
324 struct GNUNET_DNSPARSER_Packet *parsed;
325 uint16_t request_id = *cache->request_id;
326 struct GNUNET_SERVICE_Client *client = cache->client;
328 parsed = GNUNET_DNSPARSER_parse ((const char *)dns,
332 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
333 "Failed to parse DNS reply (request ID %u\n",
337 if (request_id != ntohs (parsed->id))
339 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
340 "Request ID in DNS reply does not match\n");
343 else if (0 == parsed->num_answers)
345 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
346 "DNS reply (request ID %u) contains no answers\n",
348 GNUNET_CONTAINER_DLL_remove (cache_head,
351 free_cache_entry (cache);
356 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
357 "Got reply for request ID %u\n",
359 for (unsigned int i = 0; i != parsed->num_answers; i++)
361 struct Record *cache_entry = GNUNET_new (struct Record);
362 struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i];
363 cache_entry->record = GNUNET_DNSPARSER_duplicate_record (record);
364 GNUNET_CONTAINER_DLL_insert (cache->records_head,
367 send_reply (cache_entry->record,
371 GNUNET_free_non_null (cache->request_id);
372 cache->request_id = NULL;
374 send_end_msg (request_id,
377 cache->client = NULL;
378 GNUNET_SCHEDULER_cancel (cache->timeout_task);
379 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
380 cache->timeout_task = NULL;
381 cache->resolve_handle = NULL;
382 GNUNET_DNSPARSER_free_packet (parsed);
387 handle_resolve_timeout (void *cls)
389 struct ResolveCache *cache = cls;
391 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
393 if (NULL != cache->resolve_handle)
395 GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle);
396 cache->resolve_handle = NULL;
398 GNUNET_CONTAINER_DLL_remove (cache_head,
401 free_cache_entry (cache);
406 resolve_and_cache (const char* hostname,
407 uint16_t record_type,
409 struct GNUNET_SERVICE_Client *client)
413 struct GNUNET_DNSPARSER_Query query;
414 struct GNUNET_DNSPARSER_Packet packet;
415 struct ResolveCache *cache;
416 struct GNUNET_TIME_Relative timeout =
417 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5);
419 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
420 "resolve_and_cache\n");
421 query.name = (char *)hostname;
422 query.type = record_type;
423 query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET;
427 packet.num_queries = 1;
428 packet.queries = &query;
429 packet.id = htons (request_id);
430 packet.flags.recursion_desired = 1;
432 GNUNET_DNSPARSER_pack (&packet,
437 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
438 "Failed to pack query for hostname `%s'\n",
440 return GNUNET_SYSERR;
443 cache = GNUNET_malloc (sizeof (struct ResolveCache));
444 cache->record_type = record_type;
445 cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id));
446 cache->client = client;
447 cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout,
448 &handle_resolve_timeout,
450 cache->resolve_handle =
451 GNUNET_DNSSTUB_resolve (dnsstub_ctx,
454 &handle_resolve_result,
456 GNUNET_CONTAINER_DLL_insert (cache_head,
459 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
460 "resolve %s, request_id = %u\n",
463 GNUNET_free (packet_buf);
469 get_hostname (struct ResolveCache *cache_entry)
471 if (NULL != cache_entry->records_head)
473 GNUNET_assert (NULL != cache_entry->records_head);
474 GNUNET_assert (NULL != cache_entry->records_head->record);
475 GNUNET_assert (NULL != cache_entry->records_head->record->name);
476 return cache_entry->records_head->record->name;
482 static const uint16_t *
483 get_record_type (struct ResolveCache *cache_entry)
485 if (NULL != cache_entry->records_head)
486 return &cache_entry->record_type;
491 static const struct GNUNET_TIME_Absolute *
492 get_expiration_time (struct ResolveCache *cache_entry)
494 if (NULL != cache_entry->records_head)
495 return &cache_entry->records_head->record->expiration_time;
501 remove_if_expired (struct ResolveCache *cache_entry)
503 struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get ();
505 if ( (NULL != cache_entry->records_head) &&
506 (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) )
508 GNUNET_CONTAINER_DLL_remove (cache_head,
511 free_cache_entry (cache_entry);
519 * Get an IP address as a string (works for both IPv4 and IPv6). Note
520 * that the resolution happens asynchronously and that the first call
521 * may not immediately result in the FQN (but instead in a
522 * human-readable IP address).
524 * @param client handle to the client making the request (for sending the reply)
525 * @param af AF_INET or AF_INET6
526 * @param ip `struct in_addr` or `struct in6_addr`
529 try_cache (const char *hostname,
530 uint16_t record_type,
532 struct GNUNET_SERVICE_Client *client)
534 struct ResolveCache *pos;
535 struct ResolveCache *next;
538 while ( (NULL != (pos = next)) &&
539 ( (NULL == pos->records_head) ||
540 (0 != strcmp (get_hostname (pos), hostname)) ||
541 (*get_record_type (pos) != record_type) ) )
544 remove_if_expired (pos);
548 if (GNUNET_NO == remove_if_expired (pos))
550 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
551 "found cache entry for '%s', record type '%u'\n",
554 struct Record *cache_pos = pos->records_head;
555 while (NULL != cache_pos)
557 send_reply (cache_pos->record,
560 cache_pos = cache_pos->next;
562 send_end_msg (request_id,
567 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
568 "no cache entry for '%s'\n",
575 * Verify well-formedness of GET-message.
577 * @param cls closure, unused
578 * @param get the actual message
579 * @return #GNUNET_OK if @a get is well-formed
582 check_get (void *cls,
583 const struct GNUNET_RESOLVER_GetMessage *get)
590 size = ntohs (get->header.size) - sizeof (*get);
591 direction = ntohl (get->direction);
592 if (GNUNET_NO == direction)
594 /* IP from hostname */
595 const char *hostname;
597 hostname = (const char *) &get[1];
598 if (hostname[size - 1] != '\0')
601 return GNUNET_SYSERR;
605 af = ntohl (get->af);
609 if (size != sizeof (struct in_addr))
612 return GNUNET_SYSERR;
616 if (size != sizeof (struct in6_addr))
619 return GNUNET_SYSERR;
624 return GNUNET_SYSERR;
631 process_get (const char *hostname,
632 uint16_t record_type,
634 struct GNUNET_SERVICE_Client *client)
636 if (GNUNET_NO == try_cache (hostname, record_type, request_id, client))
638 int result = resolve_and_cache (hostname,
642 GNUNET_assert (GNUNET_OK == result);
648 * Handle GET-message.
650 * @param cls identification of the client
651 * @param msg the actual message
654 handle_get (void *cls,
655 const struct GNUNET_RESOLVER_GetMessage *msg)
657 struct GNUNET_SERVICE_Client *client = cls;
661 const char *hostname;
663 direction = ntohl (msg->direction);
664 af = ntohl (msg->af);
665 request_id = ntohs (msg->id);
666 if (GNUNET_NO == direction)
668 /* IP from hostname */
669 hostname = GNUNET_strdup ((const char *) &msg[1]);
674 process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client);
679 process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client);
684 process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client);
689 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
690 "got invalid af: %d\n",
698 /* hostname from IP */
699 hostname = make_reverse_hostname (&msg[1], af);
700 process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client);
702 GNUNET_free_non_null ((char *)hostname);
703 GNUNET_SERVICE_client_continue (client);
708 shutdown_task (void *cls)
711 struct ResolveCache *pos;
713 while (NULL != (pos = cache_head))
715 GNUNET_CONTAINER_DLL_remove (cache_head,
718 free_cache_entry (pos);
720 GNUNET_DNSSTUB_stop (dnsstub_ctx);
726 const struct GNUNET_CONFIGURATION_Handle *cfg,
727 struct GNUNET_SERVICE_Handle *sh)
732 GNUNET_SCHEDULER_add_shutdown (&shutdown_task,
734 dnsstub_ctx = GNUNET_DNSSTUB_start (128);
736 ssize_t num_dns_servers = lookup_dns_servers (&dns_servers);
737 if (0 == num_dns_servers)
739 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
740 "no DNS server available. DNS resolution will not be possible.\n");
742 for (int i = 0; i != num_dns_servers; i++)
744 int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]);
745 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
746 "Adding DNS server '%s': %s\n",
748 GNUNET_OK == result ? "success" : "failure");
749 GNUNET_free (dns_servers[i]);
751 GNUNET_free_non_null (dns_servers);
756 * Callback called when a client connects to the service.
758 * @param cls closure for the service, unused
759 * @param c the new client that connected to the service
760 * @param mq the message queue used to send messages to the client
764 connect_cb (void *cls,
765 struct GNUNET_SERVICE_Client *c,
766 struct GNUNET_MQ_Handle *mq)
776 * Callback called when a client disconnected from the service
778 * @param cls closure for the service
779 * @param c the client that disconnected
780 * @param internal_cls should be equal to @a c
783 disconnect_cb (void *cls,
784 struct GNUNET_SERVICE_Client *c,
788 struct ResolveCache *pos = cache_head;
792 if (pos->client == c)
798 GNUNET_assert (c == internal_cls);
803 * Define "main" method using service macro.
807 GNUNET_SERVICE_OPTION_NONE,
812 GNUNET_MQ_hd_var_size (get,
813 GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST,
814 struct GNUNET_RESOLVER_GetMessage,
816 GNUNET_MQ_handler_end ());
819 #if defined(LINUX) && defined(__GLIBC__)
823 * MINIMIZE heap size (way below 128k) since this process doesn't need much.
825 void __attribute__ ((constructor))
826 GNUNET_RESOLVER_memory_init ()
828 mallopt (M_TRIM_THRESHOLD, 4 * 1024);
829 mallopt (M_TOP_PAD, 1 * 1024);
835 /* end of gnunet-service-resolver.c */