X-Git-Url: https://git.librecmc.org/?a=blobdiff_plain;f=src%2Fdns%2Fgnunet-service-dns.c;h=79beef83191d304621897166dbf755ab8a5714db;hb=2751b293778309bfb658d69b5a723aa90abc685a;hp=5dc6641e74d7c3758137230ee42c32baae01b064;hpb=40261ecc5bf242437f12cf91d035c138cf089a1e;p=oweals%2Fgnunet.git diff --git a/src/dns/gnunet-service-dns.c b/src/dns/gnunet-service-dns.c index 5dc6641e7..79beef831 100644 --- a/src/dns/gnunet-service-dns.c +++ b/src/dns/gnunet-service-dns.c @@ -20,19 +20,46 @@ /** * @file dns/gnunet-service-dns.c + * @brief service to intercept and modify DNS queries (and replies) of this system * @author Christian Grothoff + * + * For "secure" interaction with the legacy DNS system, we permit + * replies only to arrive within a 5s window (and they must match + * ports, IPs and request IDs). Furthermore, we let the OS pick a + * source port, opening up to 128 sockets per address family (IPv4 or + * IPv6). Those sockets are closed if they are not in use for 5s + * (which means they will be freshly randomized afterwards). For new + * requests, we pick a random slot in the array with 128 socket slots + * (and re-use an existing socket if the slot is still in use). Thus + * each request will be given one of 128 random source ports, and the + * 128 random source ports will also change "often" (less often if the + * system is very busy, each time if we are mostly idle). At the same + * time, the system will never use more than 256 UDP sockets. */ #include "platform.h" #include "gnunet_util_lib.h" +#include "gnunet_applications.h" #include "gnunet_constants.h" #include "gnunet_protocols.h" #include "gnunet_signatures.h" #include "dns.h" #include "gnunet_dns_service.h" +#include "gnunet_mesh_service.h" #include "gnunet_statistics_service.h" #include "gnunet_tun_lib.h" +/** + * Timeout for an external (Internet-DNS) DNS resolution + */ +#define REQUEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5) + +/** + * How many DNS sockets do we open at most at the same time? + * (technical socket maximum is this number x2 for IPv4+IPv6) + */ +#define DNS_SOCKET_MAX 128 + /** * Phases each request goes through. */ @@ -108,6 +135,34 @@ struct ClientRecord }; +/** + * UDP socket we are using for sending DNS requests to the Internet. + */ +struct 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; + + /** + * Task for reading from dnsout4 and dnsout6. + */ + GNUNET_SCHEDULER_TaskIdentifier read_task; + + /** + * When should this socket be closed? + */ + struct GNUNET_TIME_Absolute timeout; +}; + + /** * Entry we keep for each active request. */ @@ -126,6 +181,13 @@ struct RequestRecord */ char *payload; + /** + * Socket we are using to transmit this request (must match if we receive + * a response). Must NOT be freed as part of this request record (as it + * might be shared with other requests). + */ + struct GNUNET_NETWORK_Handle *dnsout; + /** * Source address of the original request (for sending response). */ @@ -136,6 +198,11 @@ struct RequestRecord */ struct sockaddr_storage dst_addr; + /** + * When should this request time out? + */ + struct GNUNET_TIME_Absolute timeout; + /** * ID of this request, also basis for hashing. Lowest 16 bit will * be our message ID when doing a global DNS request and our index @@ -162,32 +229,64 @@ struct RequestRecord /** - * The IPv4 UDP-Socket through which DNS-Resolves will be sent if they are not to be - * sent through gnunet. The port of this socket will not be hijacked. + * State we keep for each DNS tunnel that terminates at this node. */ -static struct GNUNET_NETWORK_Handle *dnsout4; +struct TunnelState +{ -/** - * The IPv6 UDP-Socket through which DNS-Resolves will be sent if they are not to be - * sent through gnunet. The port of this socket will not be hijacked. - */ -static struct GNUNET_NETWORK_Handle *dnsout6; + /** + * Associated MESH tunnel. + */ + struct GNUNET_MESH_Tunnel *tunnel; -/** - * Task for reading from dnsout4. - */ -static GNUNET_SCHEDULER_TaskIdentifier read4_task; + /** + * Active request for sending a reply. + */ + struct GNUNET_MESH_TransmitHandle *th; -/** - * Task for reading from dnsout6. - */ -static GNUNET_SCHEDULER_TaskIdentifier read6_task; + /** + * DNS reply ready for transmission. + */ + char *reply; + + /** + * Socket we are using to transmit this request (must match if we receive + * a response). Must NOT be freed as part of this request record (as it + * might be shared with other requests). + */ + struct GNUNET_NETWORK_Handle *dnsout; + + /** + * Address we sent the DNS request to. + */ + struct sockaddr_storage addr; + + /** + * When should this request time out? + */ + struct GNUNET_TIME_Absolute timeout; + + /** + * Number of bytes in 'addr'. + */ + socklen_t addrlen; + + /** + * Number of bytes in 'reply'. + */ + size_t reply_length; + + /** + * Original DNS request ID as used by the client. + */ + uint16_t original_id; + + /** + * DNS request ID that we used for forwarding. + */ + uint16_t my_id; +}; -/** - * The port bound to the socket dnsout (and/or dnsout6). We always (try) to bind - * both sockets to the same port. - */ -static uint16_t dnsoutport; /** * The configuration to use @@ -207,7 +306,7 @@ static struct GNUNET_HELPER_Handle *hijacker; /** * Command-line arguments we are giving to the hijacker process. */ -static char *helper_argv[8]; +static char *helper_argv[7]; /** * Head of DLL of clients we consult. @@ -229,11 +328,59 @@ static struct GNUNET_SERVER_NotificationContext *nc; */ static struct RequestRecord requests[UINT16_MAX + 1]; +/** + * Array of all open requests from tunnels. + */ +static struct TunnelState *tunnels[UINT16_MAX + 1]; + +/** + * Array of all open sockets for DNS requests. + */ +static struct RequestSocket sockets[DNS_SOCKET_MAX]; + /** * Generator for unique request IDs. */ static uint64_t request_id_gen; +/** + * IP address to use for the DNS server if we are a DNS exit service + * (for VPN via mesh); otherwise NULL. + */ +static char *dns_exit; + +/** + * Handle to the MESH service (for receiving DNS queries), or NULL + * if we are not a DNS exit. + */ +static struct GNUNET_MESH_Handle *mesh; + + +/** + * We're done with a RequestSocket, close it for now. + * + * @param rs request socket to clean up + */ +static void +cleanup_rs (struct 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 (GNUNET_SCHEDULER_NO_TASK != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = GNUNET_SCHEDULER_NO_TASK; + } +} + /** * We're done processing a DNS request, free associated memory. @@ -266,37 +413,70 @@ cleanup_task (void *cls GNUNET_UNUSED, GNUNET_HELPER_stop (hijacker); hijacker = NULL; - for (i=0;i<8;i++) + for (i=0;i<7;i++) GNUNET_free_non_null (helper_argv[i]); - if (NULL != dnsout4) - { - GNUNET_NETWORK_socket_close (dnsout4); - dnsout4 = NULL; - } - if (GNUNET_SCHEDULER_NO_TASK != read4_task) + for (i=0;i<=UINT16_MAX;i++) + cleanup_rr (&requests[i]); + GNUNET_SERVER_notification_context_destroy (nc); + nc = NULL; + if (stats != NULL) { - GNUNET_SCHEDULER_cancel (read4_task); - read4_task = GNUNET_SCHEDULER_NO_TASK; + GNUNET_STATISTICS_destroy (stats, GNUNET_NO); + stats = NULL; } - if (NULL != dnsout6) + if (NULL != dns_exit) { - GNUNET_NETWORK_socket_close (dnsout6); - dnsout6 = NULL; + GNUNET_free (dns_exit); + dns_exit = NULL; } - if (GNUNET_SCHEDULER_NO_TASK != read6_task) +} + + +/** + * 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) { - GNUNET_SCHEDULER_cancel (read6_task); - read6_task = GNUNET_SCHEDULER_NO_TASK; + 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; } - for (i=0;i<65536;i++) - cleanup_rr (&requests[i]); - GNUNET_SERVER_notification_context_destroy (nc); - nc = NULL; - if (stats != NULL) + sa->sa_family = af; + if (GNUNET_OK != GNUNET_NETWORK_socket_bind (ret, + sa, + alen)) { - GNUNET_STATISTICS_destroy (stats, GNUNET_YES); - stats = NULL; + 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; } @@ -310,8 +490,8 @@ request_done (struct RequestRecord *rr) { struct GNUNET_MessageHeader *hdr; size_t reply_len; - uint16_t spt; - uint16_t dpt; + uint16_t source_port; + uint16_t destination_port; GNUNET_array_grow (rr->client_wait_list, rr->client_wait_list_length, @@ -351,7 +531,8 @@ request_done (struct RequestRecord *rr) { char buf[reply_len]; size_t off; - uint32_t udp_crc_sum; + struct GNUNET_TUN_IPv4Header ip4; + struct GNUNET_TUN_IPv6Header ip6; /* first, GNUnet message header */ hdr = (struct GNUNET_MessageHeader*) buf; @@ -373,78 +554,38 @@ request_done (struct RequestRecord *rr) } /* now IP header */ - udp_crc_sum = 0; switch (rr->src_addr.ss_family) { case AF_INET: { struct sockaddr_in *src = (struct sockaddr_in *) &rr->src_addr; struct sockaddr_in *dst = (struct sockaddr_in *) &rr->dst_addr; - struct GNUNET_TUN_IPv4Header ip; - spt = dst->sin_port; - dpt = src->sin_port; - GNUNET_TUN_initialize_ipv4_header (&ip, + source_port = dst->sin_port; + destination_port = src->sin_port; + GNUNET_TUN_initialize_ipv4_header (&ip4, IPPROTO_UDP, reply_len - off - sizeof (struct GNUNET_TUN_IPv4Header), &dst->sin_addr, &src->sin_addr); - - - - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &ip.source_address, - sizeof (struct in_addr) * 2); - { - uint16_t tmp; - - tmp = htons (IPPROTO_UDP); - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &tmp, - sizeof (uint16_t)); - tmp = htons (rr->payload_length + sizeof (struct GNUNET_TUN_UdpHeader)); - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &tmp, - sizeof (uint16_t)); - } - memcpy (&buf[off], &ip, sizeof (ip)); - off += sizeof (ip); + memcpy (&buf[off], &ip4, sizeof (ip4)); + off += sizeof (ip4); } break; case AF_INET6: { struct sockaddr_in6 *src = (struct sockaddr_in6 *) &rr->src_addr; struct sockaddr_in6 *dst = (struct sockaddr_in6 *) &rr->dst_addr; - struct GNUNET_TUN_IPv6Header ip; - - spt = dst->sin6_port; - dpt = src->sin6_port; - ip.traffic_class_h = 0; - ip.version = 6; /* is there a named constant? I couldn't find one */ - ip.traffic_class_l = 0; - ip.flow_label = 0; - ip.payload_length = htons ((uint16_t) reply_len); - ip.next_header = IPPROTO_UDP; - ip.hop_limit = 255; /* or lower? */ - ip.source_address = dst->sin6_addr; - ip.destination_address = src->sin6_addr; - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &ip.source_address, - sizeof (struct in6_addr) * 2); - { - uint32_t tmp; - - tmp = htons (rr->payload_length + sizeof (struct GNUNET_TUN_UdpHeader)); - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &tmp, - sizeof (uint32_t)); - tmp = htons (IPPROTO_UDP); - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &tmp, - sizeof (uint32_t)); - } - memcpy (&buf[off], &ip, sizeof (ip)); - off += sizeof (ip); + + source_port = dst->sin6_port; + destination_port = src->sin6_port; + GNUNET_TUN_initialize_ipv6_header (&ip6, + IPPROTO_UDP, + reply_len - sizeof (struct GNUNET_TUN_IPv6Header), + &dst->sin6_addr, + &src->sin6_addr); + memcpy (&buf[off], &ip6, sizeof (ip6)); + off += sizeof (ip6); } break; default: @@ -455,20 +596,23 @@ request_done (struct RequestRecord *rr) { struct GNUNET_TUN_UdpHeader udp; - udp.spt = spt; - udp.dpt = dpt; + udp.source_port = source_port; + udp.destination_port = destination_port; udp.len = htons (reply_len - off); - udp.crc = 0; - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - &udp, - sizeof (udp)); - udp_crc_sum = GNUNET_CRYPTO_crc16_step (udp_crc_sum, - rr->payload, - rr->payload_length); - udp.crc = GNUNET_CRYPTO_crc16_finish (udp_crc_sum); + if (AF_INET == rr->src_addr.ss_family) + GNUNET_TUN_calculate_udp4_checksum (&ip4, + &udp, + rr->payload, + rr->payload_length); + else + GNUNET_TUN_calculate_udp6_checksum (&ip6, + &udp, + rr->payload, + rr->payload_length); memcpy (&buf[off], &udp, sizeof (udp)); off += sizeof (udp); } + /* now DNS payload */ { memcpy (&buf[off], rr->payload, rr->payload_length); @@ -522,6 +666,73 @@ send_request_to_client (struct RequestRecord *rr, } +/** + * Read a DNS response from the (unhindered) UDP-Socket + * + * @param cls socket to read from + * @param tc scheduler context (must be shutdown or read ready) + */ +static void +read_response (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc); + + +/** + * Get a socket of the specified address family to send out a + * UDP DNS request to the Internet. + * + * @param af desired address family + * @return NULL on error (given AF not "supported") + */ +static struct GNUNET_NETWORK_Handle * +get_request_socket (int af) +{ + struct RequestSocket *rs; + struct GNUNET_NETWORK_FDSet *rset; + static struct GNUNET_NETWORK_Handle *ret; + + rs = &sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, + DNS_SOCKET_MAX)]; + rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT); + switch (af) + { + case AF_INET: + if (NULL == rs->dnsout4) + rs->dnsout4 = open_socket (AF_INET); + ret = rs->dnsout4; + break; + case AF_INET6: + if (NULL == rs->dnsout6) + rs->dnsout6 = open_socket (AF_INET6); + ret = rs->dnsout6; + break; + default: + return NULL; + } + if (GNUNET_SCHEDULER_NO_TASK != rs->read_task) + { + GNUNET_SCHEDULER_cancel (rs->read_task); + rs->read_task = GNUNET_SCHEDULER_NO_TASK; + } + if ( (NULL == rs->dnsout4) && + (NULL == rs->dnsout6) ) + return NULL; + 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_SCHEDULER_NO_TASK, + REQUEST_TIMEOUT, + rset, + NULL, + &read_response, rs); + GNUNET_NETWORK_fdset_destroy (rset); + return ret; +} + + /** * A client has completed its processing for this * request. Move on. @@ -534,7 +745,6 @@ next_phase (struct RequestRecord *rr) struct ClientRecord *cr; int nz; unsigned int j; - struct GNUNET_NETWORK_Handle *dnsout; socklen_t salen; if (rr->phase == RP_DROP) @@ -582,35 +792,34 @@ next_phase (struct RequestRecord *rr) next_phase (rr); return; case RP_QUERY: - rr->phase = RP_INTERNET_DNS; switch (rr->dst_addr.ss_family) { case AF_INET: - dnsout = dnsout4; - salen = sizeof (struct GNUNET_TUN_IPv4Header); + salen = sizeof (struct sockaddr_in); break; case AF_INET6: - dnsout = dnsout6; - salen = sizeof (struct GNUNET_TUN_IPv6Header); + salen = sizeof (struct sockaddr_in6); break; default: - GNUNET_break (0); - cleanup_rr (rr); - return; + GNUNET_assert (0); } - if (NULL == dnsout) + + rr->phase = RP_INTERNET_DNS; + rr->dnsout = get_request_socket (rr->dst_addr.ss_family); + if (NULL == rr->dnsout) { GNUNET_STATISTICS_update (stats, - gettext_noop ("# DNS exit failed (address family not supported)"), + gettext_noop ("# DNS exit failed (failed to open socket)"), 1, GNUNET_NO); cleanup_rr (rr); return; } - GNUNET_NETWORK_socket_sendto (dnsout, + GNUNET_NETWORK_socket_sendto (rr->dnsout, rr->payload, rr->payload_length, (struct sockaddr*) &rr->dst_addr, salen); + rr->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT); return; case RP_INTERNET_DNS: rr->phase = RP_MODIFY; @@ -692,208 +901,200 @@ client_disconnect (void *cls, struct GNUNET_SERVER_Client *client) /** - * Read a DNS response from the (unhindered) UDP-Socket + * We got a reply from DNS for a request of a MESH tunnel. Send it + * via the tunnel (after changing the request ID back). * - * @param cls socket to read from - * @param tc scheduler context (must be shutdown or read ready) + * @param cls the 'struct TunnelState' + * @param size number of bytes available in buf + * @param buf where to copy the reply + * @return number of bytes written to buf */ -static void -read_response (void *cls, - const struct GNUNET_SCHEDULER_TaskContext *tc) +static size_t +transmit_reply_to_mesh (void *cls, + size_t size, + void *buf) { - struct GNUNET_NETWORK_Handle *dnsout = cls; - struct sockaddr_in addr4; - struct sockaddr_in6 addr6; - struct sockaddr *addr; - struct GNUNET_TUN_DnsHeader *dns; + struct TunnelState *ts = cls; + size_t off; + size_t ret; + char *cbuf = buf; + struct GNUNET_MessageHeader hdr; + struct GNUNET_TUN_DnsHeader dns; + + ts->th = NULL; + GNUNET_assert (ts->reply != NULL); + if (size == 0) + return 0; + ret = sizeof (struct GNUNET_MessageHeader) + ts->reply_length; + GNUNET_assert (ret <= size); + hdr.size = htons (ret); + hdr.type = htons (GNUNET_MESSAGE_TYPE_VPN_DNS_FROM_INTERNET); + memcpy (&dns, ts->reply, sizeof (dns)); + dns.id = ts->original_id; + off = 0; + memcpy (&cbuf[off], &hdr, sizeof (hdr)); + off += sizeof (hdr); + memcpy (&cbuf[off], &dns, sizeof (dns)); + off += sizeof (dns); + memcpy (&cbuf[off], &ts->reply[sizeof (dns)], ts->reply_length - sizeof (dns)); + off += ts->reply_length - sizeof (dns); + GNUNET_free (ts->reply); + ts->reply = NULL; + ts->reply_length = 0; + GNUNET_assert (ret == off); + return ret; +} + + +/** + * Actually do the reading of a DNS packet from our UDP socket and see + * if we have a valid, matching, pending request. + * + * @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_NETWORK_Handle *dnsout) +{ + struct sockaddr_storage addr; socklen_t addrlen; + struct GNUNET_TUN_DnsHeader *dns; struct RequestRecord *rr; + struct TunnelState *ts; ssize_t r; int len; - if (dnsout == dnsout4) - { - addrlen = sizeof (struct sockaddr_in); - addr = (struct sockaddr* ) &addr4; - read4_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, - dnsout, - &read_response, - dnsout); - } - else - { - addrlen = sizeof (struct sockaddr_in6); - addr = (struct sockaddr* ) &addr6; - read6_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, - dnsout, - &read_response, - dnsout); - } - if (0 != (tc->reason & GNUNET_SCHEDULER_REASON_SHUTDOWN)) - return; - #ifndef MINGW if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), FIONREAD, &len)) { /* conservative choice: */ - len = 65536; + len = UINT16_MAX; } #else /* port the code above? */ - len = 65536; + len = UINT16_MAX; #endif { unsigned char buf[len]; - memset (addr, 0, addrlen); + addrlen = sizeof (addr); + memset (&addr, 0, sizeof (addr)); r = GNUNET_NETWORK_socket_recvfrom (dnsout, buf, sizeof (buf), - addr, &addrlen); + (struct sockaddr*) &addr, &addrlen); if (-1 == r) { GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "recvfrom"); - return; + GNUNET_NETWORK_socket_close (dnsout); + return GNUNET_SYSERR; } if (sizeof (struct GNUNET_TUN_DnsHeader) > r) { GNUNET_log (GNUNET_ERROR_TYPE_ERROR, _("Received DNS response that is too small (%u bytes)"), r); - return; + return GNUNET_NO; } dns = (struct GNUNET_TUN_DnsHeader *) buf; + /* Handle case that this is a reply to a request from a MESH DNS tunnel */ + ts = tunnels[dns->id]; + if ( (NULL == ts) || + (ts->dnsout != dnsout) || + (addrlen != ts->addrlen) || + (0 != memcmp (&ts->addr, + &addr, + addrlen)) || + (0 == GNUNET_TIME_absolute_get_remaining (ts->timeout).rel_value) ) + ts = NULL; /* DNS responder address missmatch */ + if (NULL != ts) + { + tunnels[dns->id] = NULL; + GNUNET_free_non_null (ts->reply); + ts->reply = GNUNET_malloc (r); + ts->reply_length = r; + memcpy (ts->reply, dns, r); + if (ts->th != NULL) + GNUNET_MESH_notify_transmit_ready_cancel (ts->th); + ts->th = GNUNET_MESH_notify_transmit_ready (ts->tunnel, + GNUNET_NO, 0, + GNUNET_TIME_UNIT_FOREVER_REL, + NULL, + sizeof (struct GNUNET_MessageHeader) + r, + &transmit_reply_to_mesh, + ts); + } + /* Handle case that this is a reply to a local request (intercepted from TUN interface) */ rr = &requests[dns->id]; - if (rr->phase != RP_INTERNET_DNS) + if ( (rr->phase != RP_INTERNET_DNS) || + (rr->dnsout != dnsout) || + (0 != memcmp (&rr->dst_addr, + &addr, + addrlen)) || + (0 == GNUNET_TIME_absolute_get_remaining (rr->timeout).rel_value) ) { - /* unexpected / bogus reply */ - GNUNET_STATISTICS_update (stats, - gettext_noop ("# External DNS response discarded (no matching request)"), - 1, GNUNET_NO); - return; + if (NULL == ts) + { + /* unexpected / bogus reply */ + GNUNET_STATISTICS_update (stats, + gettext_noop ("# External DNS response discarded (no matching request)"), + 1, GNUNET_NO); + } + return GNUNET_NO; } GNUNET_free_non_null (rr->payload); - rr->payload = GNUNET_malloc (len); - memcpy (rr->payload, buf, len); - rr->payload_length = len; + rr->payload = GNUNET_malloc (r); + memcpy (rr->payload, buf, r); + rr->payload_length = r; next_phase (rr); } -} - - -/** - * Open source port for sending DNS request on IPv4. - * - * @return GNUNET_OK on success - */ -static int -open_port4 () -{ - struct sockaddr_in addr; - socklen_t addrlen; - - dnsout4 = GNUNET_NETWORK_socket_create (AF_INET, SOCK_DGRAM, 0); - if (dnsout4 == NULL) - return GNUNET_SYSERR; - - memset (&addr, 0, sizeof (struct sockaddr_in)); - addr.sin_family = AF_INET; - int err = GNUNET_NETWORK_socket_bind (dnsout4, - (struct sockaddr *) &addr, - sizeof (struct sockaddr_in)); - - if (err != GNUNET_OK) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Could not bind to any port: %s\n"), - STRERROR (errno)); - GNUNET_NETWORK_socket_close (dnsout4); - dnsout4 = NULL; - return GNUNET_SYSERR; - } - - /* Read the port we bound to */ - addrlen = sizeof (struct sockaddr_in); - if (0 != getsockname (GNUNET_NETWORK_get_fd (dnsout4), - (struct sockaddr *) &addr, - &addrlen)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Could not determine port I got: %s\n"), - STRERROR (errno)); - GNUNET_NETWORK_socket_close (dnsout4); - dnsout4 = NULL; - return GNUNET_SYSERR; - } - dnsoutport = htons (addr.sin_port); - - GNUNET_log (GNUNET_ERROR_TYPE_INFO, - _("GNUnet DNS will exit on source port %u\n"), - (unsigned int) dnsoutport); - read4_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, - dnsout4, - &read_response, dnsout4); return GNUNET_OK; } /** - * Open source port for sending DNS request on IPv6. Should be - * called AFTER open_port4. + * Read a DNS response from the (unhindered) UDP-Socket * - * @return GNUNET_OK on success - */ -static int -open_port6 () + * @param cls socket to read from + * @param tc scheduler context (must be shutdown or read ready) + */ +static void +read_response (void *cls, + const struct GNUNET_SCHEDULER_TaskContext *tc) { - struct sockaddr_in6 addr; - socklen_t addrlen; + struct RequestSocket *rs = cls; + struct GNUNET_NETWORK_FDSet *rset; - dnsout6 = GNUNET_NETWORK_socket_create (AF_INET6, SOCK_DGRAM, 0); - if (dnsout6 == NULL) + rs->read_task = GNUNET_SCHEDULER_NO_TASK; + if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY)) { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Could not create IPv6 socket: %s\n"), - STRERROR (errno)); - return GNUNET_SYSERR; - } - memset (&addr, 0, sizeof (struct sockaddr_in6)); - addr.sin6_family = AF_INET6; - addr.sin6_port = htons (dnsoutport); - int err = GNUNET_NETWORK_socket_bind (dnsout6, - (struct sockaddr *) &addr, - sizeof (struct sockaddr_in6)); - - if (err != GNUNET_OK) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Could not bind to port %u: %s\n"), - (unsigned int) dnsoutport, - STRERROR (errno)); - GNUNET_NETWORK_socket_close (dnsout6); - dnsout6 = NULL; - return GNUNET_SYSERR; - } - if (0 == dnsoutport) - { - addrlen = sizeof (struct sockaddr_in6); - if (0 != getsockname (GNUNET_NETWORK_get_fd (dnsout6), - (struct sockaddr *) &addr, - &addrlen)) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Could not determine port I got: %s\n"), - STRERROR (errno)); - GNUNET_NETWORK_socket_close (dnsout6); - dnsout6 = NULL; - return GNUNET_SYSERR; - } + /* timeout or shutdown */ + cleanup_rs (rs); + return; } - dnsoutport = htons (addr.sin6_port); - read6_task = GNUNET_SCHEDULER_add_read_net (GNUNET_TIME_UNIT_FOREVER_REL, - dnsout6, - &read_response, dnsout6); - return GNUNET_YES; + /* read and process ready sockets */ + if ((NULL != rs->dnsout4) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout4)) && + (GNUNET_SYSERR == do_dns_read (rs->dnsout4))) + rs->dnsout4 = NULL; + if ((NULL != rs->dnsout6) && + (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout6)) && + (GNUNET_SYSERR == do_dns_read (rs->dnsout6))) + rs->dnsout6 = NULL; + + /* re-schedule 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_SCHEDULER_NO_TASK, + GNUNET_TIME_absolute_get_remaining (rs->timeout), + rset, + NULL, + &read_response, rs); + GNUNET_NETWORK_fdset_destroy (rset); } @@ -1109,39 +1310,46 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client, /* setup new request */ rr->phase = RP_INIT; - if (ip4->version == 4) + switch (ntohs (tun->proto)) { - srca4 = (struct sockaddr_in*) &rr->src_addr; - dsta4 = (struct sockaddr_in*) &rr->dst_addr; - memset (srca4, 0, sizeof (struct sockaddr_in)); - memset (dsta4, 0, sizeof (struct sockaddr_in)); - srca4->sin_family = AF_INET; - dsta4->sin_family = AF_INET; - srca4->sin_addr = ip4->source_address; - dsta4->sin_addr = ip4->destination_address; - srca4->sin_port = udp->spt; - dsta4->sin_port = udp->dpt; + case ETH_P_IPV4: + { + srca4 = (struct sockaddr_in*) &rr->src_addr; + dsta4 = (struct sockaddr_in*) &rr->dst_addr; + memset (srca4, 0, sizeof (struct sockaddr_in)); + memset (dsta4, 0, sizeof (struct sockaddr_in)); + srca4->sin_family = AF_INET; + dsta4->sin_family = AF_INET; + srca4->sin_addr = ip4->source_address; + dsta4->sin_addr = ip4->destination_address; + srca4->sin_port = udp->source_port; + dsta4->sin_port = udp->destination_port; #if HAVE_SOCKADDR_IN_SIN_LEN - srca4->sin_len = sizeof (sizeof (struct sockaddr_in)); - dsta4->sin_len = sizeof (sizeof (struct sockaddr_in)); + srca4->sin_len = sizeof (struct sockaddr_in); + dsta4->sin_len = sizeof (struct sockaddr_in); #endif - } - else /* ipv6 */ - { - srca6 = (struct sockaddr_in6*) &rr->src_addr; - dsta6 = (struct sockaddr_in6*) &rr->dst_addr; - memset (srca6, 0, sizeof (struct sockaddr_in6)); - memset (dsta6, 0, sizeof (struct sockaddr_in6)); - srca6->sin6_family = AF_INET6; - dsta6->sin6_family = AF_INET6; - srca6->sin6_addr = ip6->source_address; - dsta6->sin6_addr = ip6->destination_address; - srca6->sin6_port = udp->spt; - dsta6->sin6_port = udp->dpt; + } + break; + case ETH_P_IPV6: + { + srca6 = (struct sockaddr_in6*) &rr->src_addr; + dsta6 = (struct sockaddr_in6*) &rr->dst_addr; + memset (srca6, 0, sizeof (struct sockaddr_in6)); + memset (dsta6, 0, sizeof (struct sockaddr_in6)); + srca6->sin6_family = AF_INET6; + dsta6->sin6_family = AF_INET6; + srca6->sin6_addr = ip6->source_address; + dsta6->sin6_addr = ip6->destination_address; + srca6->sin6_port = udp->source_port; + dsta6->sin6_port = udp->destination_port; #if HAVE_SOCKADDR_IN_SIN_LEN - srca6->sin6_len = sizeof (sizeof (struct sockaddr_in6)); - dsta6->sin6_len = sizeof (sizeof (struct sockaddr_in6)); + srca6->sin6_len = sizeof (struct sockaddr_in6); + dsta6->sin6_len = sizeof (struct sockaddr_in6); #endif + } + break; + default: + GNUNET_assert (0); } rr->payload = GNUNET_malloc (msize); rr->payload_length = msize; @@ -1157,6 +1365,150 @@ process_helper_messages (void *cls GNUNET_UNUSED, void *client, } +/** + * Process a request via mesh to perform a DNS query. + * + * @param cls closure, NULL + * @param tunnel connection to the other end + * @param tunnel_ctx pointer to our 'struct TunnelState *' + * @param sender who sent the message + * @param message the actual message + * @param atsi performance data for the connection + * @return GNUNET_OK to keep the connection open, + * GNUNET_SYSERR to close it (signal serious error) + */ +static int +receive_dns_request (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel, + void **tunnel_ctx, + const struct GNUNET_PeerIdentity *sender GNUNET_UNUSED, + const struct GNUNET_MessageHeader *message, + const struct GNUNET_ATS_Information *atsi GNUNET_UNUSED) +{ + struct TunnelState *ts = *tunnel_ctx; + const struct GNUNET_TUN_DnsHeader *dns; + size_t mlen = ntohs (message->size); + size_t dlen = mlen - sizeof (struct GNUNET_MessageHeader); + char buf[dlen]; + struct GNUNET_TUN_DnsHeader *dout; + struct sockaddr_in v4; + struct sockaddr_in6 v6; + struct sockaddr *so; + socklen_t salen; + + if (dlen < sizeof (struct GNUNET_TUN_DnsHeader)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + dns = (const struct GNUNET_TUN_DnsHeader *) &message[1]; + ts->original_id = dns->id; + if (tunnels[ts->my_id] == ts) + tunnels[ts->my_id] = NULL; + ts->my_id = (uint16_t) GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, + UINT16_MAX + 1); + tunnels[ts->my_id] = ts; + memcpy (buf, dns, dlen); + dout = (struct GNUNET_TUN_DnsHeader*) buf; + dout->id = ts->my_id; + memset (&v4, 0, sizeof (v4)); + memset (&v6, 0, sizeof (v6)); + if (1 == inet_pton (AF_INET, dns_exit, &v4.sin_addr)) + { + salen = sizeof (v4); + v4.sin_family = AF_INET; + v4.sin_port = htons (53); +#if HAVE_SOCKADDR_IN_SIN_LEN + v4.sin_len = (u_char) salen; +#endif + so = (struct sockaddr *) &v4; + ts->dnsout = get_request_socket (AF_INET); + } + else if (1 == inet_pton (AF_INET6, dns_exit, &v6.sin6_addr)) + { + salen = sizeof (v6); + v6.sin6_family = AF_INET6; + v6.sin6_port = htons (53); +#if HAVE_SOCKADDR_IN_SIN_LEN + v6.sin6_len = (u_char) salen; +#endif + so = (struct sockaddr *) &v6; + ts->dnsout = get_request_socket (AF_INET6); + } + else + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == ts->dnsout) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Configured DNS exit `%s' is not working / valid.\n"), + dns_exit); + return GNUNET_SYSERR; + } + memcpy (&ts->addr, + so, + salen); + ts->addrlen = salen; + GNUNET_NETWORK_socket_sendto (ts->dnsout, + buf, dlen, so, salen); + ts->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT); + return GNUNET_OK; +} + + +/** + * Callback from GNUNET_MESH for new tunnels. + * + * @param cls closure + * @param tunnel new handle to the tunnel + * @param initiator peer that started the tunnel + * @param ats performance information for the tunnel + * @return initial tunnel context for the tunnel + */ +static void * +accept_dns_tunnel (void *cls GNUNET_UNUSED, struct GNUNET_MESH_Tunnel *tunnel, + const struct GNUNET_PeerIdentity *initiator GNUNET_UNUSED, + const struct GNUNET_ATS_Information *ats GNUNET_UNUSED) +{ + struct TunnelState *ts = GNUNET_malloc (sizeof (struct TunnelState)); + + GNUNET_STATISTICS_update (stats, + gettext_noop ("# Inbound MESH tunnels created"), + 1, GNUNET_NO); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received inbound tunnel from `%s'\n", + GNUNET_i2s (initiator)); + ts->tunnel = tunnel; + return ts; +} + + +/** + * Function called by mesh whenever an inbound tunnel is destroyed. + * Should clean up any associated state. + * + * @param cls closure (set from GNUNET_MESH_connect) + * @param tunnel connection to the other end (henceforth invalid) + * @param tunnel_ctx place where local state associated + * with the tunnel is stored + */ +static void +destroy_dns_tunnel (void *cls GNUNET_UNUSED, + const struct GNUNET_MESH_Tunnel *tunnel, + void *tunnel_ctx) +{ + struct TunnelState *ts = tunnel_ctx; + + if (tunnels[ts->my_id] == ts) + tunnels[ts->my_id] = NULL; + if (NULL != ts->th) + GNUNET_MESH_notify_transmit_ready_cancel (ts->th); + GNUNET_free_non_null (ts->reply); + GNUNET_free (ts); +} + + /** * @param cls closure * @param server the initialized server @@ -1173,29 +1525,32 @@ run (void *cls, struct GNUNET_SERVER_Handle *server, {&handle_client_response, NULL, GNUNET_MESSAGE_TYPE_DNS_CLIENT_RESPONSE, 0}, {NULL, NULL, 0, 0} }; - char port_s[6]; char *ifc_name; char *ipv4addr; char *ipv4mask; char *ipv6addr; char *ipv6prefix; + struct in_addr dns_exit4; + struct in6_addr dns_exit6; cfg = cfg_; stats = GNUNET_STATISTICS_create ("dns", cfg); nc = GNUNET_SERVER_notification_context_create (server, 1); GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_FOREVER_REL, &cleanup_task, cls); - if (GNUNET_YES == - GNUNET_CONFIGURATION_get_value_yesno (cfg_, "dns", "PROVIDE_EXIT")) + if ( (GNUNET_YES == + GNUNET_CONFIGURATION_get_value_yesno (cfg_, "dns", "PROVIDE_EXIT")) && + ( (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, "dns", + "DNS_EXIT", + &dns_exit)) || + ( (1 != inet_pton (AF_INET, dns_exit, &dns_exit4)) && + (1 != inet_pton (AF_INET6, dns_exit, &dns_exit6)) ) ) ) { - if ( (GNUNET_OK != open_port4 ()) && - (GNUNET_OK != open_port6 ()) ) - { - GNUNET_log (GNUNET_ERROR_TYPE_ERROR, - _("Failed to open any port to provide DNS exit\n")); - GNUNET_SCHEDULER_shutdown (); - return; - } + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Configured to provide DNS exit, but no valid DNS server configured!\n")); + GNUNET_free_non_null (dns_exit); + dns_exit = NULL; } helper_argv[0] = GNUNET_strdup ("gnunet-dns"); @@ -1249,12 +1604,25 @@ run (void *cls, struct GNUNET_SERVER_Handle *server, return; } helper_argv[5] = ipv4mask; - GNUNET_snprintf (port_s, - sizeof (port_s), - "%u", - (unsigned int) dnsoutport); - helper_argv[6] = GNUNET_strdup (port_s); - helper_argv[7] = NULL; + helper_argv[6] = NULL; + + if (NULL != dns_exit) + { + static struct GNUNET_MESH_MessageHandler mesh_handlers[] = { + {&receive_dns_request, GNUNET_MESSAGE_TYPE_VPN_DNS_TO_INTERNET, 0}, + {NULL, 0, 0} + }; + static GNUNET_MESH_ApplicationType mesh_types[] = { + GNUNET_APPLICATION_TYPE_INTERNET_RESOLVER, + GNUNET_APPLICATION_TYPE_END + }; + mesh = GNUNET_MESH_connect (cfg, + 1, NULL, + &accept_dns_tunnel, + &destroy_dns_tunnel, + mesh_handlers, + mesh_types); + } hijacker = GNUNET_HELPER_start ("gnunet-helper-dns", helper_argv, &process_helper_messages, @@ -1280,4 +1648,4 @@ main (int argc, char *const *argv) } -/* end of gnunet-service-dns_new.c */ +/* end of gnunet-service-dns.c */