/* This file is part of GNUnet. Copyright (C) 2007-2016 GNUnet e.V. GNUnet is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. GNUnet is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ /** * @file util/gnunet-service-resolver.c * @brief code to do DNS resolution * @author Christian Grothoff */ #include "platform.h" #include "gnunet_util_lib.h" #include "gnunet_protocols.h" #include "gnunet_statistics_service.h" #include "resolver.h" struct Record { struct Record *next; struct Record *prev; struct GNUNET_DNSPARSER_Record *record; }; /** * A cached DNS lookup result. */ struct ResolveCache { /** * This is a doubly linked list. */ struct ResolveCache *next; /** * This is a doubly linked list. */ struct ResolveCache *prev; /** * type of queried DNS record */ uint16_t record_type; /** * a pointer to the request_id if a query for this hostname/record_type * is currently pending, NULL otherwise. */ int16_t *request_id; /** * The client that queried the records contained in this cache entry. */ struct GNUNET_SERVICE_Client *client; /** * head of a double linked list containing the lookup results */ struct Record *records_head; /** * tail of a double linked list containing the lookup results */ struct Record *records_tail; /** * handle for cancelling a request */ struct GNUNET_DNSSTUB_RequestSocket *resolve_handle; /** * handle for the resolution timeout task */ struct GNUNET_SCHEDULER_Task *timeout_task; }; /** * Start of the linked list of cached DNS lookup results. */ static struct ResolveCache *cache_head; /** * Tail of the linked list of cached DNS lookup results. */ static struct ResolveCache *cache_tail; /** * context of dnsstub library */ static struct GNUNET_DNSSTUB_Context *dnsstub_ctx; void free_cache_entry (struct ResolveCache *entry) { struct Record *pos; struct Record *next; next = entry->records_head; while (NULL != (pos = next)) { next = pos->next; GNUNET_CONTAINER_DLL_remove (entry->records_head, entry->records_tail, pos); if (NULL != pos->record) { GNUNET_DNSPARSER_free_record (pos->record); GNUNET_free (pos->record); } GNUNET_free (pos); } if (NULL != entry->resolve_handle) { GNUNET_DNSSTUB_resolve_cancel (entry->resolve_handle); entry->resolve_handle = NULL; } if (NULL != entry->timeout_task) { GNUNET_SCHEDULER_cancel (entry->timeout_task); entry->timeout_task = NULL; } GNUNET_free_non_null (entry->request_id); GNUNET_free (entry); } static char* extract_dns_server (const char* line, size_t line_len) { if (0 == strncmp (line, "nameserver ", 11)) return GNUNET_strndup (line + 11, line_len - 11); return NULL; } /** * reads the list of nameservers from /etc/resolve.conf * * @param server_addrs[out] a list of null-terminated server address strings * @return the number of server addresses in @server_addrs, -1 on error */ static ssize_t lookup_dns_servers (char ***server_addrs) { struct GNUNET_DISK_FileHandle *fh; char buf[2048]; ssize_t bytes_read; size_t read_offset = 0; unsigned int num_dns_servers = 0; fh = GNUNET_DISK_file_open ("/etc/resolv.conf", GNUNET_DISK_OPEN_READ, GNUNET_DISK_PERM_NONE); if (NULL == fh) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Could not open /etc/resolv.conf. " "DNS resolution will not be possible.\n"); return -1; } bytes_read = GNUNET_DISK_file_read (fh, buf, sizeof (buf)); *server_addrs = NULL; while (read_offset < bytes_read) { char *newline; size_t line_len; char *dns_server; newline = strchr (buf + read_offset, '\n'); if (NULL == newline) { break; } line_len = newline - buf - read_offset; dns_server = extract_dns_server (buf + read_offset, line_len); if (NULL != dns_server) { GNUNET_array_append (*server_addrs, num_dns_servers, dns_server); } read_offset += line_len + 1; } GNUNET_DISK_file_close (fh); return num_dns_servers; } static char * make_reverse_hostname (const void *ip, int af) { char *buf = GNUNET_new_array (80, char); int pos = 0; if (AF_INET == af) { struct in_addr *addr = (struct in_addr *)ip; uint32_t ip_int = addr->s_addr; for (int i = 3; i >= 0; i--) { int n = GNUNET_snprintf (buf + pos, 80 - pos, "%u.", ((uint8_t *)&ip_int)[i]); if (n < 0) { GNUNET_free (buf); return NULL; } pos += n; } pos += GNUNET_snprintf (buf + pos, 80 - pos, "in-addr.arpa"); } else if (AF_INET6 == af) { struct in6_addr *addr = (struct in6_addr *)ip; for (int i = 15; i >= 0; i--) { int n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] & 0xf); if (n < 0) { GNUNET_free (buf); return NULL; } pos += n; n = GNUNET_snprintf (buf + pos, 80 - pos, "%x.", addr->s6_addr[i] >> 4); if (n < 0) { GNUNET_free (buf); return NULL; } pos += n; } pos += GNUNET_snprintf (buf + pos, 80 - pos, "ip6.arpa"); } buf[pos] = '\0'; return buf; } static void send_reply (struct GNUNET_DNSPARSER_Record *record, uint16_t request_id, struct GNUNET_SERVICE_Client *client) { struct GNUNET_RESOLVER_ResponseMessage *msg; struct GNUNET_MQ_Envelope *env; void *payload; size_t payload_len; switch (record->type) { case GNUNET_DNSPARSER_TYPE_PTR: { char *hostname = record->data.hostname; payload = hostname; payload_len = strlen (hostname) + 1; break; } case GNUNET_DNSPARSER_TYPE_A: case GNUNET_DNSPARSER_TYPE_AAAA: { payload = record->data.raw.data; payload_len = record->data.raw.data_len; break; } default: { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Cannot handle DNS response type: unimplemented\n"); return; } } env = GNUNET_MQ_msg_extra (msg, payload_len, GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE); msg->id = request_id; GNUNET_memcpy (&msg[1], payload, payload_len); GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); } static void send_end_msg (uint16_t request_id, struct GNUNET_SERVICE_Client *client) { struct GNUNET_RESOLVER_ResponseMessage *msg; struct GNUNET_MQ_Envelope *env; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Sending end message\n"); env = GNUNET_MQ_msg (msg, GNUNET_MESSAGE_TYPE_RESOLVER_RESPONSE); msg->id = request_id; GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); } static void handle_resolve_result (void *cls, const struct GNUNET_TUN_DnsHeader *dns, size_t dns_len) { struct ResolveCache *cache = cls; struct GNUNET_DNSPARSER_Packet *parsed; uint16_t request_id = *cache->request_id; struct GNUNET_SERVICE_Client *client = cache->client; parsed = GNUNET_DNSPARSER_parse ((const char *)dns, dns_len); if (NULL == parsed) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to parse DNS reply (request ID %u\n", request_id); return; } if (request_id != ntohs (parsed->id)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Request ID in DNS reply does not match\n"); return; } else if (0 == parsed->num_answers) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "DNS reply (request ID %u) contains no answers\n", request_id); GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, cache); free_cache_entry (cache); cache = NULL; } else { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got reply for request ID %u\n", request_id); for (unsigned int i = 0; i != parsed->num_answers; i++) { struct Record *cache_entry = GNUNET_new (struct Record); struct GNUNET_DNSPARSER_Record *record = &parsed->answers[i]; cache_entry->record = GNUNET_DNSPARSER_duplicate_record (record); GNUNET_CONTAINER_DLL_insert (cache->records_head, cache->records_tail, cache_entry); send_reply (cache_entry->record, request_id, cache->client); } GNUNET_free_non_null (cache->request_id); cache->request_id = NULL; } send_end_msg (request_id, client); if (NULL != cache) cache->client = NULL; if (NULL != cache) { if (NULL != cache->timeout_task) { GNUNET_SCHEDULER_cancel (cache->timeout_task); cache->timeout_task = NULL; } if (NULL != cache->resolve_handle) { GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle); cache->resolve_handle = NULL; } } GNUNET_DNSPARSER_free_packet (parsed); } static void handle_resolve_timeout (void *cls) { struct ResolveCache *cache = cls; GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "timeout!\n"); if (NULL != cache->resolve_handle) { GNUNET_DNSSTUB_resolve_cancel (cache->resolve_handle); cache->resolve_handle = NULL; } GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, cache); free_cache_entry (cache); } static int resolve_and_cache (const char* hostname, uint16_t record_type, uint16_t request_id, struct GNUNET_SERVICE_Client *client) { char *packet_buf; size_t packet_size; struct GNUNET_DNSPARSER_Query query; struct GNUNET_DNSPARSER_Packet packet; struct ResolveCache *cache; struct GNUNET_TIME_Relative timeout = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "resolve_and_cache\n"); query.name = (char *)hostname; query.type = record_type; query.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; memset (&packet, 0, sizeof (packet)); packet.num_queries = 1; packet.queries = &query; packet.id = htons (request_id); packet.flags.recursion_desired = 1; if (GNUNET_OK != GNUNET_DNSPARSER_pack (&packet, UINT16_MAX, &packet_buf, &packet_size)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to pack query for hostname `%s'\n", hostname); return GNUNET_SYSERR; } cache = GNUNET_malloc (sizeof (struct ResolveCache)); cache->record_type = record_type; cache->request_id = GNUNET_memdup (&request_id, sizeof (request_id)); cache->client = client; cache->timeout_task = GNUNET_SCHEDULER_add_delayed (timeout, &handle_resolve_timeout, cache); cache->resolve_handle = GNUNET_DNSSTUB_resolve (dnsstub_ctx, packet_buf, packet_size, &handle_resolve_result, cache); GNUNET_CONTAINER_DLL_insert (cache_head, cache_tail, cache); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "resolve %s, request_id = %u\n", hostname, request_id); GNUNET_free (packet_buf); return GNUNET_OK; } static const char * get_hostname (struct ResolveCache *cache_entry) { if (NULL != cache_entry->records_head) { GNUNET_assert (NULL != cache_entry->records_head); GNUNET_assert (NULL != cache_entry->records_head->record); GNUNET_assert (NULL != cache_entry->records_head->record->name); return cache_entry->records_head->record->name; } return NULL; } static const uint16_t * get_record_type (struct ResolveCache *cache_entry) { if (NULL != cache_entry->records_head) return &cache_entry->record_type; return NULL; } static const struct GNUNET_TIME_Absolute * get_expiration_time (struct ResolveCache *cache_entry) { if (NULL != cache_entry->records_head) return &cache_entry->records_head->record->expiration_time; return NULL; } static int remove_if_expired (struct ResolveCache *cache_entry) { struct GNUNET_TIME_Absolute now = GNUNET_TIME_absolute_get (); if ( (NULL != cache_entry->records_head) && (now.abs_value_us > get_expiration_time (cache_entry)->abs_value_us) ) { GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, cache_entry); free_cache_entry (cache_entry); return GNUNET_YES; } return GNUNET_NO; } /** * Get an IP address as a string (works for both IPv4 and IPv6). Note * that the resolution happens asynchronously and that the first call * may not immediately result in the FQN (but instead in a * human-readable IP address). * * @param client handle to the client making the request (for sending the reply) * @param af AF_INET or AF_INET6 * @param ip `struct in_addr` or `struct in6_addr` */ static int try_cache (const char *hostname, uint16_t record_type, uint16_t request_id, struct GNUNET_SERVICE_Client *client) { struct ResolveCache *pos; struct ResolveCache *next; next = cache_head; while ( (NULL != (pos = next)) && ( (NULL == pos->records_head) || (0 != strcmp (get_hostname (pos), hostname)) || (*get_record_type (pos) != record_type) ) ) { next = pos->next; remove_if_expired (pos); } if (NULL != pos) { if (GNUNET_NO == remove_if_expired (pos)) { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "found cache entry for '%s', record type '%u'\n", hostname, record_type); struct Record *cache_pos = pos->records_head; while (NULL != cache_pos) { send_reply (cache_pos->record, request_id, client); cache_pos = cache_pos->next; } send_end_msg (request_id, client); return GNUNET_YES; } } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "no cache entry for '%s'\n", hostname); return GNUNET_NO; } /** * Verify well-formedness of GET-message. * * @param cls closure, unused * @param get the actual message * @return #GNUNET_OK if @a get is well-formed */ static int check_get (void *cls, const struct GNUNET_RESOLVER_GetMessage *get) { uint16_t size; int direction; int af; (void) cls; size = ntohs (get->header.size) - sizeof (*get); direction = ntohl (get->direction); if (GNUNET_NO == direction) { /* IP from hostname */ const char *hostname; hostname = (const char *) &get[1]; if (hostname[size - 1] != '\0') { GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } af = ntohl (get->af); switch (af) { case AF_INET: if (size != sizeof (struct in_addr)) { GNUNET_break (0); return GNUNET_SYSERR; } break; case AF_INET6: if (size != sizeof (struct in6_addr)) { GNUNET_break (0); return GNUNET_SYSERR; } break; default: GNUNET_break (0); return GNUNET_SYSERR; } return GNUNET_OK; } static void process_get (const char *hostname, uint16_t record_type, uint16_t request_id, struct GNUNET_SERVICE_Client *client) { if (GNUNET_NO == try_cache (hostname, record_type, request_id, client)) { int result = resolve_and_cache (hostname, record_type, request_id, client); GNUNET_assert (GNUNET_OK == result); } } /** * Handle GET-message. * * @param cls identification of the client * @param msg the actual message */ static void handle_get (void *cls, const struct GNUNET_RESOLVER_GetMessage *msg) { struct GNUNET_SERVICE_Client *client = cls; int direction; int af; uint16_t request_id; const char *hostname; direction = ntohl (msg->direction); af = ntohl (msg->af); request_id = ntohs (msg->id); if (GNUNET_NO == direction) { /* IP from hostname */ hostname = GNUNET_strdup ((const char *) &msg[1]); switch (af) { case AF_UNSPEC: { process_get (hostname, GNUNET_DNSPARSER_TYPE_ALL, request_id, client); break; } case AF_INET: { process_get (hostname, GNUNET_DNSPARSER_TYPE_A, request_id, client); break; } case AF_INET6: { process_get (hostname, GNUNET_DNSPARSER_TYPE_AAAA, request_id, client); break; } default: { GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "got invalid af: %d\n", af); GNUNET_assert (0); } } } else { /* hostname from IP */ hostname = make_reverse_hostname (&msg[1], af); process_get (hostname, GNUNET_DNSPARSER_TYPE_PTR, request_id, client); } GNUNET_free_non_null ((char *)hostname); GNUNET_SERVICE_client_continue (client); } static void shutdown_task (void *cls) { (void) cls; struct ResolveCache *pos; while (NULL != (pos = cache_head)) { GNUNET_CONTAINER_DLL_remove (cache_head, cache_tail, pos); free_cache_entry (pos); } GNUNET_DNSSTUB_stop (dnsstub_ctx); } static void init_cb (void *cls, const struct GNUNET_CONFIGURATION_Handle *cfg, struct GNUNET_SERVICE_Handle *sh) { (void) cfg; (void) sh; GNUNET_SCHEDULER_add_shutdown (&shutdown_task, cls); dnsstub_ctx = GNUNET_DNSSTUB_start (128); char **dns_servers; ssize_t num_dns_servers = lookup_dns_servers (&dns_servers); if (0 == num_dns_servers) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "no DNS server available. DNS resolution will not be possible.\n"); } for (int i = 0; i != num_dns_servers; i++) { int result = GNUNET_DNSSTUB_add_dns_ip (dnsstub_ctx, dns_servers[i]); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Adding DNS server '%s': %s\n", dns_servers[i], GNUNET_OK == result ? "success" : "failure"); GNUNET_free (dns_servers[i]); } GNUNET_free_non_null (dns_servers); } /** * Callback called when a client connects to the service. * * @param cls closure for the service, unused * @param c the new client that connected to the service * @param mq the message queue used to send messages to the client * @return @a c */ static void * connect_cb (void *cls, struct GNUNET_SERVICE_Client *c, struct GNUNET_MQ_Handle *mq) { (void) cls; (void) mq; return c; } /** * Callback called when a client disconnected from the service * * @param cls closure for the service * @param c the client that disconnected * @param internal_cls should be equal to @a c */ static void disconnect_cb (void *cls, struct GNUNET_SERVICE_Client *c, void *internal_cls) { (void) cls; struct ResolveCache *pos = cache_head; while (NULL != pos) { if (pos->client == c) { pos->client = NULL; } pos = pos->next; } GNUNET_assert (c == internal_cls); } /** * Define "main" method using service macro. */ GNUNET_SERVICE_MAIN ("resolver", GNUNET_SERVICE_OPTION_NONE, &init_cb, &connect_cb, &disconnect_cb, NULL, GNUNET_MQ_hd_var_size (get, GNUNET_MESSAGE_TYPE_RESOLVER_REQUEST, struct GNUNET_RESOLVER_GetMessage, NULL), GNUNET_MQ_handler_end ()); #if defined(LINUX) && defined(__GLIBC__) #include /** * MINIMIZE heap size (way below 128k) since this process doesn't need much. */ void __attribute__ ((constructor)) GNUNET_RESOLVER_memory_init () { mallopt (M_TRIM_THRESHOLD, 4 * 1024); mallopt (M_TOP_PAD, 1 * 1024); malloc_trim (0); } #endif /* end of gnunet-service-resolver.c */