From 7da98cf076e9c5101244dfbbf8c3ddff045d298e Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 25 Jun 2018 18:07:22 +0200 Subject: integrate dnsparser and dnsstub and tun with libgnunetutil --- src/util/dnsstub.c | 749 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 749 insertions(+) create mode 100644 src/util/dnsstub.c (limited to 'src/util/dnsstub.c') diff --git a/src/util/dnsstub.c b/src/util/dnsstub.c new file mode 100644 index 000000000..969ff7beb --- /dev/null +++ b/src/util/dnsstub.c @@ -0,0 +1,749 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2018 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 dns/dnsstub.c + * @brief DNS stub resolver which sends DNS requests to an actual resolver + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_tun_lib.h" +#include "gnunet_dnsstub_lib.h" + +/** + * Timeout for retrying DNS queries. + */ +#define DNS_RETRANSMIT_DELAY GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, 250) + + +/** + * DNS Server used for resolution. + */ +struct DnsServer; + + +/** + * UDP socket we are using for sending DNS requests to the Internet. + */ +struct GNUNET_DNSSTUB_RequestSocket +{ + + /** + * UDP socket we use for this request for IPv4 + */ + struct GNUNET_NETWORK_Handle *dnsout4; + + /** + * UDP socket we use for this request for IPv6 + */ + struct GNUNET_NETWORK_Handle *dnsout6; + + /** + * Function to call with result. + */ + GNUNET_DNSSTUB_ResultCallback rc; + + /** + * Closure for @e rc. + */ + void *rc_cls; + + /** + * Task for reading from dnsout4 and dnsout6. + */ + struct GNUNET_SCHEDULER_Task *read_task; + + /** + * Task for retrying transmission of the query. + */ + struct GNUNET_SCHEDULER_Task *retry_task; + + /** + * Next address we sent the DNS request to. + */ + struct DnsServer *ds_pos; + + /** + * Context this request executes in. + */ + struct GNUNET_DNSSTUB_Context *ctx; + + /** + * Query we sent to @e addr. + */ + void *request; + + /** + * Number of bytes in @a request. + */ + size_t request_len; + +}; + + +/** + * DNS Server used for resolution. + */ +struct DnsServer +{ + + /** + * Kept in a DLL. + */ + struct DnsServer *next; + + /** + * Kept in a DLL. + */ + struct DnsServer *prev; + + /** + * IP address of the DNS resolver. + */ + struct sockaddr_storage ss; +}; + + +/** + * Handle to the stub resolver. + */ +struct GNUNET_DNSSTUB_Context +{ + + /** + * Array of all open sockets for DNS requests. + */ + struct GNUNET_DNSSTUB_RequestSocket *sockets; + + /** + * DLL of DNS resolvers we use. + */ + struct DnsServer *dns_head; + + /** + * DLL of DNS resolvers we use. + */ + struct DnsServer *dns_tail; + + /** + * How frequently do we retry requests? + */ + struct GNUNET_TIME_Relative retry_freq; + + /** + * Length of @e sockets array. + */ + unsigned int num_sockets; + +}; + + +/** + * We're done with a `struct GNUNET_DNSSTUB_RequestSocket`, close it for now. + * + * @param rs request socket to clean up + */ +static void +cleanup_rs (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + if (NULL != rs->dnsout4) + { + GNUNET_NETWORK_socket_close (rs->dnsout4); + rs->dnsout4 = NULL; + } + if (NULL != rs->dnsout6) + { + GNUNET_NETWORK_socket_close (rs->dnsout6); + rs->dnsout6 = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->request) + { + GNUNET_free (rs->request); + rs->request = NULL; + } +} + + +/** + * Open source port for sending DNS requests + * + * @param af AF_INET or AF_INET6 + * @return #GNUNET_OK on success + */ +static struct GNUNET_NETWORK_Handle * +open_socket (int af) +{ + struct sockaddr_in a4; + struct sockaddr_in6 a6; + struct sockaddr *sa; + socklen_t alen; + struct GNUNET_NETWORK_Handle *ret; + + ret = GNUNET_NETWORK_socket_create (af, SOCK_DGRAM, 0); + if (NULL == ret) + return NULL; + switch (af) + { + case AF_INET: + memset (&a4, 0, alen = sizeof (struct sockaddr_in)); + sa = (struct sockaddr *) &a4; + break; + case AF_INET6: + memset (&a6, 0, alen = sizeof (struct sockaddr_in6)); + sa = (struct sockaddr *) &a6; + break; + default: + GNUNET_break (0); + GNUNET_NETWORK_socket_close (ret); + return NULL; + } + sa->sa_family = af; + if (GNUNET_OK != GNUNET_NETWORK_socket_bind (ret, + sa, + alen)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Could not bind to any port: %s\n"), + STRERROR (errno)); + GNUNET_NETWORK_socket_close (ret); + return NULL; + } + return ret; +} + + +/** + * Get a socket of the specified address family to send out a + * UDP DNS request to the Internet. + * + * @param ctx the DNSSTUB context + * @return NULL on error + */ +static struct GNUNET_DNSSTUB_RequestSocket * +get_request_socket (struct GNUNET_DNSSTUB_Context *ctx) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs; + + for (unsigned int i=0;i<256;i++) + { + rs = &ctx->sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, + ctx->num_sockets)]; + if (NULL == rs->rc) + break; + } + if (NULL != rs->rc) + { + /* signal "failure" */ + rs->rc (rs->rc_cls, + NULL, + 0); + rs->rc = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->request) + { + GNUNET_free (rs->request); + rs->request = NULL; + } + rs->ctx = ctx; + return rs; +} + + +/** + * Actually do the reading of a DNS packet from our UDP socket and see + * if we have a valid, matching, pending request. + * + * @param rs request socket with callback details + * @param dnsout socket to read from + * @return #GNUNET_OK on success, #GNUNET_NO on drop, #GNUNET_SYSERR on IO-errors (closed socket) + */ +static int +do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs, + struct GNUNET_NETWORK_Handle *dnsout) +{ + struct GNUNET_DNSSTUB_Context *ctx = rs->ctx; + ssize_t r; + int len; + +#ifndef MINGW + if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), + FIONREAD, + &len)) + { + /* conservative choice: */ + len = UINT16_MAX; + } +#else + /* port the code above? */ + len = UINT16_MAX; +#endif + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Receiving %d byte DNS reply\n", + len); + { + unsigned char buf[len] GNUNET_ALIGN; + int found; + struct sockaddr_storage addr; + socklen_t addrlen; + struct GNUNET_TUN_DnsHeader *dns; + + addrlen = sizeof (addr); + memset (&addr, + 0, + sizeof (addr)); + r = GNUNET_NETWORK_socket_recvfrom (dnsout, + buf, + sizeof (buf), + (struct sockaddr*) &addr, + &addrlen); + if (-1 == r) + { + GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, + "recvfrom"); + GNUNET_NETWORK_socket_close (dnsout); + return GNUNET_SYSERR; + } + found = GNUNET_NO; + for (struct DnsServer *ds = ctx->dns_head; NULL != ds; ds = ds->next) + { + if (0 == memcmp (&addr, + &ds->ss, + GNUNET_MIN (sizeof (struct sockaddr_storage), + addrlen))) + { + found = GNUNET_YES; + break; + } + } + if (GNUNET_NO == found) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received DNS response from server we never asked (ignored)"); + return GNUNET_NO; + } + if (sizeof (struct GNUNET_TUN_DnsHeader) > r) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Received DNS response that is too small (%u bytes)"), + (unsigned int) r); + return GNUNET_NO; + } + dns = (struct GNUNET_TUN_DnsHeader *) buf; + if (NULL == rs->rc) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Request timeout or cancelled; ignoring reply\n"); + return GNUNET_NO; + } + rs->rc (rs->rc_cls, + dns, + r); + } + return GNUNET_OK; +} + + +/** + * Read a DNS response from the (unhindered) UDP-Socket + * + * @param cls socket to read from + */ +static void +read_response (void *cls); + + +/** + * Schedule #read_response() task for @a rs. + * + * @param rs request to schedule read operation for + */ +static void +schedule_read (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + struct GNUNET_NETWORK_FDSet *rset; + + if (NULL != rs->read_task) + GNUNET_SCHEDULER_cancel (rs->read_task); + rset = GNUNET_NETWORK_fdset_create (); + if (NULL != rs->dnsout4) + GNUNET_NETWORK_fdset_set (rset, + rs->dnsout4); + if (NULL != rs->dnsout6) + GNUNET_NETWORK_fdset_set (rset, + rs->dnsout6); + rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT, + GNUNET_TIME_UNIT_FOREVER_REL, + rset, + NULL, + &read_response, + rs); + GNUNET_NETWORK_fdset_destroy (rset); +} + + +/** + * Read a DNS response from the (unhindered) UDP-Socket + * + * @param cls `struct GNUNET_DNSSTUB_RequestSocket` to read from + */ +static void +read_response (void *cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs = cls; + const struct GNUNET_SCHEDULER_TaskContext *tc; + + rs->read_task = NULL; + tc = GNUNET_SCHEDULER_get_task_context (); + /* read and process ready sockets */ + if ( (NULL != rs->dnsout4) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + rs->dnsout4)) && + (GNUNET_SYSERR == + do_dns_read (rs, + rs->dnsout4)) ) + rs->dnsout4 = NULL; + if ( (NULL != rs->dnsout6) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, + rs->dnsout6)) && + (GNUNET_SYSERR == + do_dns_read (rs, + rs->dnsout6)) ) + rs->dnsout6 = NULL; + /* re-schedule read task */ + schedule_read (rs); +} + + +/** + * Task to (re)transmit the DNS query, possibly repeatedly until + * we succeed. + * + * @param cls our `struct GNUNET_DNSSTUB_RequestSocket *` + */ +static void +transmit_query (void *cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs = cls; + struct GNUNET_DNSSTUB_Context *ctx = rs->ctx; + const struct sockaddr *sa; + socklen_t salen; + struct DnsServer *ds; + struct GNUNET_NETWORK_Handle *dnsout; + + rs->retry_task = GNUNET_SCHEDULER_add_delayed (ctx->retry_freq, + &transmit_query, + rs); + ds = rs->ds_pos; + rs->ds_pos = ds->next; + if (NULL == rs->ds_pos) + rs->ds_pos = ctx->dns_head; + GNUNET_assert (NULL != ds); + dnsout = NULL; + switch (ds->ss.ss_family) + { + case AF_INET: + if (NULL == rs->dnsout4) + rs->dnsout4 = open_socket (AF_INET); + dnsout = rs->dnsout4; + sa = (const struct sockaddr *) &ds->ss; + salen = sizeof (struct sockaddr_in); + break; + case AF_INET6: + if (NULL == rs->dnsout6) + rs->dnsout6 = open_socket (AF_INET6); + dnsout = rs->dnsout6; + sa = (const struct sockaddr *) &ds->ss; + salen = sizeof (struct sockaddr_in6); + break; + default: + return; + } + if (NULL == dnsout) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Unable to use configure DNS server, skipping\n"); + return; + } + if (GNUNET_SYSERR == + GNUNET_NETWORK_socket_sendto (dnsout, + rs->request, + rs->request_len, + sa, + salen)) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Failed to send DNS request to %s: %s\n"), + GNUNET_a2s (sa, + salen), + STRERROR (errno)); + else + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + _("Sent DNS request to %s\n"), + GNUNET_a2s (sa, + salen)); + schedule_read (rs); +} + + +/** + * Perform DNS resolution using our default IP from init. + * + * @param ctx stub resolver to use + * @param request DNS request to transmit + * @param request_len number of bytes in msg + * @param rc function to call with result + * @param rc_cls closure for 'rc' + * @return socket used for the request, NULL on error + */ +struct GNUNET_DNSSTUB_RequestSocket * +GNUNET_DNSSTUB_resolve (struct GNUNET_DNSSTUB_Context *ctx, + const void *request, + size_t request_len, + GNUNET_DNSSTUB_ResultCallback rc, + void *rc_cls) +{ + struct GNUNET_DNSSTUB_RequestSocket *rs; + + if (NULL == ctx->dns_head) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No DNS server configured for resolution\n"); + return NULL; + } + if (NULL == (rs = get_request_socket (ctx))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "No request socket available for DNS resolution\n"); + return NULL; + } + rs->ds_pos = ctx->dns_head; + rs->rc = rc; + rs->rc_cls = rc_cls; + rs->request = GNUNET_memdup (request, + request_len); + rs->request_len = request_len; + rs->retry_task = GNUNET_SCHEDULER_add_now (&transmit_query, + rs); + return rs; +} + + +/** + * Cancel DNS resolution. + * + * @param rs resolution to cancel + */ +void +GNUNET_DNSSTUB_resolve_cancel (struct GNUNET_DNSSTUB_RequestSocket *rs) +{ + rs->rc = NULL; + if (NULL != rs->retry_task) + { + GNUNET_SCHEDULER_cancel (rs->retry_task); + rs->retry_task = NULL; + } + if (NULL != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = NULL; + } +} + + +/** + * Start a DNS stub resolver. + * + * @param num_sockets how many sockets should we open + * in parallel for DNS queries for this stub? + * @return NULL on error + */ +struct GNUNET_DNSSTUB_Context * +GNUNET_DNSSTUB_start (unsigned int num_sockets) +{ + struct GNUNET_DNSSTUB_Context *ctx; + + if (0 == num_sockets) + { + GNUNET_break (0); + return NULL; + } + ctx = GNUNET_new (struct GNUNET_DNSSTUB_Context); + ctx->num_sockets = num_sockets; + ctx->sockets = GNUNET_new_array (num_sockets, + struct GNUNET_DNSSTUB_RequestSocket); + ctx->retry_freq = DNS_RETRANSMIT_DELAY; + return ctx; +} + + +/** + * Add nameserver for use by the DNSSTUB. We will use + * all provided nameservers for resolution (round-robin). + * + * @param ctx resolver context to modify + * @param dns_ip target IP address to use (as string) + * @return #GNUNET_OK on success + */ +int +GNUNET_DNSSTUB_add_dns_ip (struct GNUNET_DNSSTUB_Context *ctx, + const char *dns_ip) +{ + struct DnsServer *ds; + struct in_addr i4; + struct in6_addr i6; + + ds = GNUNET_new (struct DnsServer); + if (1 == inet_pton (AF_INET, + dns_ip, + &i4)) + { + struct sockaddr_in *s4 = (struct sockaddr_in *) &ds->ss; + + s4->sin_family = AF_INET; + s4->sin_port = htons (53); + s4->sin_addr = i4; +#if HAVE_SOCKADDR_IN_SIN_LEN + s4->sin_len = (u_char) sizeof (struct sockaddr_in); +#endif + } + else if (1 == inet_pton (AF_INET6, + dns_ip, + &i6)) + { + struct sockaddr_in6 *s6 = (struct sockaddr_in6 *) &ds->ss; + + s6->sin6_family = AF_INET6; + s6->sin6_port = htons (53); + s6->sin6_addr = i6; +#if HAVE_SOCKADDR_IN_SIN_LEN + s6->sin6_len = (u_char) sizeof (struct sockaddr_in6); +#endif + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Malformed IP address `%s' for DNS server\n", + dns_ip); + GNUNET_free (ds); + return GNUNET_SYSERR; + } + GNUNET_CONTAINER_DLL_insert (ctx->dns_head, + ctx->dns_tail, + ds); + return GNUNET_OK; +} + + +/** + * Add nameserver for use by the DNSSTUB. We will use + * all provided nameservers for resolution (round-robin). + * + * @param ctx resolver context to modify + * @param sa socket address of DNS resolver to use + * @return #GNUNET_OK on success + */ +int +GNUNET_DNSSTUB_add_dns_sa (struct GNUNET_DNSSTUB_Context *ctx, + const struct sockaddr *sa) +{ + struct DnsServer *ds; + + ds = GNUNET_new (struct DnsServer); + switch (sa->sa_family) + { + case AF_INET: + GNUNET_memcpy (&ds->ss, + sa, + sizeof (struct sockaddr_in)); + break; + case AF_INET6: + GNUNET_memcpy (&ds->ss, + sa, + sizeof (struct sockaddr_in6)); + break; + default: + GNUNET_break (0); + GNUNET_free (ds); + return GNUNET_SYSERR; + } + GNUNET_CONTAINER_DLL_insert (ctx->dns_head, + ctx->dns_tail, + ds); + return GNUNET_OK; +} + + +/** + * How long should we try requests before timing out? + * Only effective for requests issued after this call. + * + * @param ctx resolver context to modify + * @param retry_freq how long to wait between retries + */ +void +GNUNET_DNSSTUB_set_retry (struct GNUNET_DNSSTUB_Context *ctx, + struct GNUNET_TIME_Relative retry_freq) +{ + ctx->retry_freq = retry_freq; +} + + +/** + * Cleanup DNSSTUB resolver. + * + * @param ctx stub resolver to clean up + */ +void +GNUNET_DNSSTUB_stop (struct GNUNET_DNSSTUB_Context *ctx) +{ + struct DnsServer *ds; + + while (NULL != (ds = ctx->dns_head)) + { + GNUNET_CONTAINER_DLL_remove (ctx->dns_head, + ctx->dns_tail, + ds); + GNUNET_free (ds); + } + for (unsigned int i=0;inum_sockets;i++) + cleanup_rs (&ctx->sockets[i]); + GNUNET_free (ctx->sockets); + GNUNET_free (ctx); +} + + +/* end of dnsstub.c */ -- cgit v1.2.3