/* This file is part of GNUnet. Copyright (C) 2012-2013 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 . SPDX-License-Identifier: AGPL3.0-or-later */ /** * @file gnunet-dns2gns.c * @brief DNS server that translates DNS requests to GNS * @author Christian Grothoff */ #include "platform.h" #include #include #include #include #include "gns.h" /** * Timeout for DNS requests. */ #define TIMEOUT GNUNET_TIME_UNIT_MINUTES /** * Data kept per request. */ struct Request { /** * Socket to use for sending the reply. */ struct GNUNET_NETWORK_Handle *lsock; /** * Destination address to use. */ const void *addr; /** * Initially, this is the DNS request, it will then be * converted to the DNS response. */ struct GNUNET_DNSPARSER_Packet *packet; /** * Our GNS request handle. */ struct GNUNET_GNS_LookupWithTldRequest *lookup; /** * Our DNS request handle */ struct GNUNET_DNSSTUB_RequestSocket *dns_lookup; /** * Task run on timeout or shutdown to clean up without * response. */ struct GNUNET_SCHEDULER_Task *timeout_task; /** * Original UDP request message. */ char *udp_msg; /** * Number of bytes in @e addr. */ size_t addr_len; /** * Number of bytes in @e udp_msg. */ size_t udp_msg_size; /** * ID of the original request. */ uint16_t original_request_id; }; /** * The address to bind to */ static in_addr_t address; /** * The IPv6 address to bind to */ static struct in6_addr address6; /** * Handle to GNS resolver. */ struct GNUNET_GNS_Handle *gns; /** * Stub resolver */ struct GNUNET_DNSSTUB_Context *dns_stub; /** * Listen socket for IPv4. */ static struct GNUNET_NETWORK_Handle *listen_socket4; /** * Listen socket for IPv6. */ static struct GNUNET_NETWORK_Handle *listen_socket6; /** * Task for IPv4 socket. */ static struct GNUNET_SCHEDULER_Task *t4; /** * Task for IPv6 socket. */ static struct GNUNET_SCHEDULER_Task *t6; /** * IP of DNS server */ static char *dns_ip; /** * UDP Port we listen on for inbound DNS requests. */ static unsigned int listen_port = 53; /** * Configuration to use. */ static const struct GNUNET_CONFIGURATION_Handle *cfg; /** * Task run on shutdown. Cleans up everything. * * @param cls unused */ static void do_shutdown (void *cls) { (void) cls; if (NULL != t4) { GNUNET_SCHEDULER_cancel (t4); t4 = NULL; } if (NULL != t6) { GNUNET_SCHEDULER_cancel (t6); t6 = NULL; } if (NULL != listen_socket4) { GNUNET_NETWORK_socket_close (listen_socket4); listen_socket4 = NULL; } if (NULL != listen_socket6) { GNUNET_NETWORK_socket_close (listen_socket6); listen_socket6 = NULL; } if (NULL != gns) { GNUNET_GNS_disconnect (gns); gns = NULL; } if (NULL != dns_stub) { GNUNET_DNSSTUB_stop (dns_stub); dns_stub = NULL; } } /** * Shuffle answers * Fisher-Yates (aka Knuth) Shuffle * * @param request context for the request (with answers) */ static void shuffle_answers (struct Request *request) { unsigned int idx = request->packet->num_answers; unsigned int r_idx; struct GNUNET_DNSPARSER_Record tmp_answer; while (0 != idx) { r_idx = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, request->packet->num_answers); idx--; tmp_answer = request->packet->answers[idx]; memcpy (&request->packet->answers[idx], &request->packet->answers[r_idx], sizeof (struct GNUNET_DNSPARSER_Record)); memcpy (&request->packet->answers[r_idx], &tmp_answer, sizeof (struct GNUNET_DNSPARSER_Record)); } } /** * Send the response for the given request and clean up. * * @param request context for the request. */ static void send_response (struct Request *request) { char *buf; size_t size; ssize_t sret; shuffle_answers (request); if (GNUNET_SYSERR == GNUNET_DNSPARSER_pack (request->packet, UINT16_MAX /* is this not too much? */, &buf, &size)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Failed to pack DNS response into UDP packet!\n")); } else { sret = GNUNET_NETWORK_socket_sendto (request->lsock, buf, size, request->addr, request->addr_len); if ((sret < 0) || (size != (size_t) sret)) GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "sendto"); GNUNET_free (buf); } GNUNET_SCHEDULER_cancel (request->timeout_task); GNUNET_DNSPARSER_free_packet (request->packet); GNUNET_free (request->udp_msg); GNUNET_free (request); } /** * Task run on timeout. Cleans up request. * * @param cls `struct Request *` of the request to clean up */ static void do_timeout (void *cls) { struct Request *request = cls; if (NULL != request->packet) GNUNET_DNSPARSER_free_packet (request->packet); if (NULL != request->lookup) GNUNET_GNS_lookup_with_tld_cancel (request->lookup); if (NULL != request->dns_lookup) GNUNET_DNSSTUB_resolve_cancel (request->dns_lookup); GNUNET_free (request->udp_msg); GNUNET_free (request); } /** * Iterator called on obtained result for a DNS lookup * * @param cls closure * @param dns the DNS udp payload * @param r size of the DNS payload */ static void dns_result_processor (void *cls, const struct GNUNET_TUN_DnsHeader *dns, size_t r) { struct Request *request = cls; if (NULL == dns) { /* DNSSTUB gave up, so we trigger timeout early */ GNUNET_SCHEDULER_cancel (request->timeout_task); do_timeout (request); return; } if (request->original_request_id != dns->id) { /* for a another query, ignore */ return; } request->packet = GNUNET_DNSPARSER_parse ((char *) dns, r); GNUNET_DNSSTUB_resolve_cancel (request->dns_lookup); send_response (request); } /** * Iterator called on obtained result for a GNS lookup. * * @param cls closure * @param was_gns #GNUNET_NO if the TLD is not configured for GNS * @param rd_count number of records in @a rd * @param rd the records in reply */ static void result_processor (void *cls, int was_gns, uint32_t rd_count, const struct GNUNET_GNSRECORD_Data *rd) { struct Request *request = cls; struct GNUNET_DNSPARSER_Packet *packet; struct GNUNET_DNSPARSER_Record rec; request->lookup = NULL; if (GNUNET_NO == was_gns) { /* TLD not configured for GNS, fall back to DNS */ GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Using DNS resolver IP `%s' to resolve `%s'\n", dns_ip, request->packet->queries[0].name); request->original_request_id = request->packet->id; GNUNET_DNSPARSER_free_packet (request->packet); request->packet = NULL; request->dns_lookup = GNUNET_DNSSTUB_resolve (dns_stub, request->udp_msg, request->udp_msg_size, &dns_result_processor, request); return; } packet = request->packet; packet->flags.query_or_response = 1; packet->flags.return_code = GNUNET_TUN_DNS_RETURN_CODE_NO_ERROR; packet->flags.checking_disabled = 0; packet->flags.authenticated_data = 1; packet->flags.zero = 0; packet->flags.recursion_available = 1; packet->flags.message_truncated = 0; packet->flags.authoritative_answer = 0; // packet->flags.opcode = GNUNET_TUN_DNS_OPCODE_STATUS; // ??? for (uint32_t i = 0; i < rd_count; i++) { rec.expiration_time.abs_value_us = rd[i].expiration_time; switch (rd[i].record_type) { case GNUNET_DNSPARSER_TYPE_A: GNUNET_assert (sizeof(struct in_addr) == rd[i].data_size); rec.name = GNUNET_strdup (packet->queries[0].name); rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; rec.type = GNUNET_DNSPARSER_TYPE_A; rec.data.raw.data = GNUNET_new (struct in_addr); GNUNET_memcpy (rec.data.raw.data, rd[i].data, rd[i].data_size); rec.data.raw.data_len = sizeof(struct in_addr); GNUNET_array_append (packet->answers, packet->num_answers, rec); break; case GNUNET_DNSPARSER_TYPE_AAAA: GNUNET_assert (sizeof(struct in6_addr) == rd[i].data_size); rec.name = GNUNET_strdup (packet->queries[0].name); rec.data.raw.data = GNUNET_new (struct in6_addr); rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; rec.type = GNUNET_DNSPARSER_TYPE_AAAA; GNUNET_memcpy (rec.data.raw.data, rd[i].data, rd[i].data_size); rec.data.raw.data_len = sizeof(struct in6_addr); GNUNET_array_append (packet->answers, packet->num_answers, rec); break; case GNUNET_DNSPARSER_TYPE_CNAME: rec.name = GNUNET_strdup (packet->queries[0].name); rec.data.hostname = GNUNET_strdup (rd[i].data); rec.dns_traffic_class = GNUNET_TUN_DNS_CLASS_INTERNET; rec.type = GNUNET_DNSPARSER_TYPE_CNAME; GNUNET_memcpy (rec.data.hostname, rd[i].data, rd[i].data_size); GNUNET_array_append (packet->answers, packet->num_answers, rec); break; default: /* skip */ break; } } send_response (request); } /** * Handle DNS request. * * @param lsock socket to use for sending the reply * @param addr address to use for sending the reply * @param addr_len number of bytes in @a addr * @param udp_msg DNS request payload * @param udp_msg_size number of bytes in @a udp_msg */ static void handle_request (struct GNUNET_NETWORK_Handle *lsock, const void *addr, size_t addr_len, const char *udp_msg, size_t udp_msg_size) { struct Request *request; struct GNUNET_DNSPARSER_Packet *packet; packet = GNUNET_DNSPARSER_parse (udp_msg, udp_msg_size); if (NULL == packet) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Cannot parse DNS request from %s\n"), GNUNET_a2s (addr, addr_len)); return; } GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received request for `%s' with flags %u, #answers %d, #auth %d, #additional %d\n", packet->queries[0].name, (unsigned int) packet->flags.query_or_response, (int) packet->num_answers, (int) packet->num_authority_records, (int) packet->num_additional_records); if ((0 != packet->flags.query_or_response) || (0 != packet->num_answers) || (0 != packet->num_authority_records)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Received malformed DNS request from %s\n"), GNUNET_a2s (addr, addr_len)); GNUNET_DNSPARSER_free_packet (packet); return; } if ((1 != packet->num_queries)) { GNUNET_log (GNUNET_ERROR_TYPE_WARNING, _ ("Received unsupported DNS request from %s\n"), GNUNET_a2s (addr, addr_len)); GNUNET_DNSPARSER_free_packet (packet); return; } request = GNUNET_malloc (sizeof(struct Request) + addr_len); request->lsock = lsock; request->packet = packet; request->addr = &request[1]; request->addr_len = addr_len; GNUNET_memcpy (&request[1], addr, addr_len); request->udp_msg_size = udp_msg_size; request->udp_msg = GNUNET_memdup (udp_msg, udp_msg_size); request->timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &do_timeout, request); GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Calling GNS on `%s'\n", packet->queries[0].name); request->lookup = GNUNET_GNS_lookup_with_tld (gns, packet->queries[0].name, packet->queries[0].type, GNUNET_GNS_LO_DEFAULT, &result_processor, request); } /** * Task to read IPv4 DNS packets. * * @param cls the 'listen_socket4' */ static void read_dns4 (void *cls) { struct sockaddr_in v4; socklen_t addrlen; ssize_t size; const struct GNUNET_SCHEDULER_TaskContext *tc; GNUNET_assert (listen_socket4 == cls); t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, listen_socket4, &read_dns4, listen_socket4); tc = GNUNET_SCHEDULER_get_task_context (); if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason)) return; /* shutdown? */ size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket4); if (0 > size) { GNUNET_break (0); return; /* read error!? */ } { char buf[size + 1]; ssize_t sret; addrlen = sizeof(v4); sret = GNUNET_NETWORK_socket_recvfrom (listen_socket4, buf, size + 1, (struct sockaddr *) &v4, &addrlen); if (0 > sret) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "recvfrom"); return; } GNUNET_break (size == sret); handle_request (listen_socket4, &v4, addrlen, buf, size); } } /** * Task to read IPv6 DNS packets. * * @param cls the 'listen_socket6' */ static void read_dns6 (void *cls) { struct sockaddr_in6 v6; socklen_t addrlen; ssize_t size; const struct GNUNET_SCHEDULER_TaskContext *tc; GNUNET_assert (listen_socket6 == cls); t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, listen_socket6, &read_dns6, listen_socket6); tc = GNUNET_SCHEDULER_get_task_context (); if (0 == (GNUNET_SCHEDULER_REASON_READ_READY & tc->reason)) return; /* shutdown? */ size = GNUNET_NETWORK_socket_recvfrom_amount (listen_socket6); if (0 > size) { GNUNET_break (0); return; /* read error!? */ } { char buf[size]; ssize_t sret; addrlen = sizeof(v6); sret = GNUNET_NETWORK_socket_recvfrom (listen_socket6, buf, size, (struct sockaddr *) &v6, &addrlen); if (0 > sret) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_WARNING, "recvfrom"); return; } GNUNET_break (size == sret); handle_request (listen_socket6, &v6, addrlen, buf, size); } } /** * Main function that will be run. * * @param cls closure * @param args remaining command-line arguments * @param cfgfile name of the configuration file used (for saving, can be NULL!) * @param c configuration */ static void run (void *cls, char *const *args, const char *cfgfile, const struct GNUNET_CONFIGURATION_Handle *c) { char *addr_str; (void) cls; (void) args; (void) cfgfile; cfg = c; if (NULL == dns_ip) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _ ("No DNS server specified!\n")); return; } GNUNET_SCHEDULER_add_shutdown (&do_shutdown, NULL); if (NULL == (gns = GNUNET_GNS_connect (cfg))) return; GNUNET_assert (NULL != (dns_stub = GNUNET_DNSSTUB_start (128))); if (GNUNET_OK != GNUNET_DNSSTUB_add_dns_ip (dns_stub, dns_ip)) { GNUNET_DNSSTUB_stop (dns_stub); GNUNET_GNS_disconnect (gns); gns = NULL; return; } /* Get address to bind to */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (c, "dns2gns", "BIND_TO", &addr_str)) { // No address specified GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Don't know what to bind to...\n"); GNUNET_free (addr_str); GNUNET_SCHEDULER_shutdown (); return; } if (1 != inet_pton (AF_INET, addr_str, &address)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to parse address %s\n", addr_str); GNUNET_free (addr_str); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_free (addr_str); /* Get address to bind to */ if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (c, "dns2gns", "BIND_TO6", &addr_str)) { // No address specified GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Don't know what to bind6 to...\n"); GNUNET_free (addr_str); GNUNET_SCHEDULER_shutdown (); return; } if (1 != inet_pton (AF_INET6, addr_str, &address6)) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Unable to parse IPv6 address %s\n", addr_str); GNUNET_free (addr_str); GNUNET_SCHEDULER_shutdown (); return; } GNUNET_free (addr_str); listen_socket4 = GNUNET_NETWORK_socket_create (PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (NULL != listen_socket4) { struct sockaddr_in v4; memset (&v4, 0, sizeof(v4)); v4.sin_family = AF_INET; v4.sin_addr.s_addr = address; #if HAVE_SOCKADDR_IN_SIN_LEN v4.sin_len = sizeof(v4); #endif v4.sin_port = htons (listen_port); if (GNUNET_OK != GNUNET_NETWORK_socket_bind (listen_socket4, (struct sockaddr *) &v4, sizeof(v4))) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind"); GNUNET_NETWORK_socket_close (listen_socket4); listen_socket4 = NULL; } } listen_socket6 = GNUNET_NETWORK_socket_create (PF_INET6, SOCK_DGRAM, IPPROTO_UDP); if (NULL != listen_socket6) { struct sockaddr_in6 v6; memset (&v6, 0, sizeof(v6)); v6.sin6_family = AF_INET6; v6.sin6_addr = address6; #if HAVE_SOCKADDR_IN_SIN_LEN v6.sin6_len = sizeof(v6); #endif v6.sin6_port = htons (listen_port); if (GNUNET_OK != GNUNET_NETWORK_socket_bind (listen_socket6, (struct sockaddr *) &v6, sizeof(v6))) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "bind"); GNUNET_NETWORK_socket_close (listen_socket6); listen_socket6 = NULL; } } if ((NULL == listen_socket4) && (NULL == listen_socket6)) { GNUNET_GNS_disconnect (gns); gns = NULL; GNUNET_DNSSTUB_stop (dns_stub); dns_stub = NULL; return; } if (NULL != listen_socket4) t4 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, listen_socket4, &read_dns4, listen_socket4); if (NULL != listen_socket6) t6 = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, listen_socket6, &read_dns6, listen_socket6); } /** * The main function for the dns2gns daemon. * * @param argc number of arguments from the command line * @param argv command line arguments * @return 0 ok, 1 on error */ int main (int argc, char *const *argv) { struct GNUNET_GETOPT_CommandLineOption options[] = { GNUNET_GETOPT_option_string ('d', "dns", "IP", gettext_noop ( "IP of recursive DNS resolver to use (required)"), &dns_ip), GNUNET_GETOPT_option_uint ('p', "port", "UDPPORT", gettext_noop ( "UDP port to listen on for inbound DNS requests; default: 2853"), &listen_port), GNUNET_GETOPT_OPTION_END }; int ret; if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) return 2; GNUNET_log_setup ("gnunet-dns2gns", "WARNING", NULL); ret = (GNUNET_OK == GNUNET_PROGRAM_run (argc, argv, "gnunet-dns2gns", _ ("GNUnet DNS-to-GNS proxy (a DNS server)"), options, &run, NULL)) ? 0 : 1; GNUNET_free_nz ((void *) argv); return ret; } /* end of gnunet-dns2gns.c */