2 This file is part of GNUnet.
3 (C) 2012 Christian Grothoff (and other contributing authors)
5 GNUnet is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published
7 by the Free Software Foundation; either version 3, or (at your
8 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 General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with GNUnet; see the file COPYING. If not, write to the
17 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.
22 * @brief DNS stub resolver which sends DNS requests to an actual resolver
23 * @author Christian Grothoff
26 #include "gnunet_util_lib.h"
27 #include "gnunet_dnsstub_lib.h"
30 * Timeout for an external (Internet-DNS) DNS resolution
32 #define REQUEST_TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 5)
35 * How many DNS sockets do we open at most at the same time?
36 * (technical socket maximum is this number x2 for IPv4+IPv6)
38 #define DNS_SOCKET_MAX 128
42 * UDP socket we are using for sending DNS requests to the Internet.
44 struct GNUNET_DNSSTUB_RequestSocket
48 * UDP socket we use for this request for IPv4
50 struct GNUNET_NETWORK_Handle *dnsout4;
53 * UDP socket we use for this request for IPv6
55 struct GNUNET_NETWORK_Handle *dnsout6;
58 * Function to call with result.
60 GNUNET_DNSSTUB_ResultCallback rc;
68 * Task for reading from dnsout4 and dnsout6.
70 GNUNET_SCHEDULER_TaskIdentifier read_task;
73 * When should this request time out?
75 struct GNUNET_TIME_Absolute timeout;
78 * Address we sent the DNS request to.
80 struct sockaddr_storage addr;
83 * Number of bytes in 'addr'.
90 struct GNUNET_DNSSTUB_Context
94 * Array of all open sockets for DNS requests.
96 struct GNUNET_DNSSTUB_RequestSocket sockets[DNS_SOCKET_MAX];
99 * IP address to use for the DNS server if we are a DNS exit service
100 * (for VPN via mesh); otherwise NULL.
108 * We're done with a GNUNET_DNSSTUB_RequestSocket, close it for now.
110 * @param rs request socket to clean up
113 cleanup_rs (struct GNUNET_DNSSTUB_RequestSocket *rs)
115 if (NULL != rs->dnsout4)
117 GNUNET_NETWORK_socket_close (rs->dnsout4);
120 if (NULL != rs->dnsout6)
122 GNUNET_NETWORK_socket_close (rs->dnsout6);
125 if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
127 GNUNET_SCHEDULER_cancel (rs->read_task);
128 rs->read_task = GNUNET_SCHEDULER_NO_TASK;
134 * Open source port for sending DNS requests
136 * @param af AF_INET or AF_INET6
137 * @return GNUNET_OK on success
139 static struct GNUNET_NETWORK_Handle *
142 struct sockaddr_in a4;
143 struct sockaddr_in6 a6;
146 struct GNUNET_NETWORK_Handle *ret;
148 ret = GNUNET_NETWORK_socket_create (af, SOCK_DGRAM, 0);
154 memset (&a4, 0, alen = sizeof (struct sockaddr_in));
155 sa = (struct sockaddr *) &a4;
158 memset (&a6, 0, alen = sizeof (struct sockaddr_in6));
159 sa = (struct sockaddr *) &a6;
163 GNUNET_NETWORK_socket_close (ret);
167 if (GNUNET_OK != GNUNET_NETWORK_socket_bind (ret,
171 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
172 _("Could not bind to any port: %s\n"),
174 GNUNET_NETWORK_socket_close (ret);
182 * Read a DNS response from the (unhindered) UDP-Socket
184 * @param cls socket to read from
185 * @param tc scheduler context (must be shutdown or read ready)
188 read_response (void *cls,
189 const struct GNUNET_SCHEDULER_TaskContext *tc);
193 * Get a socket of the specified address family to send out a
194 * UDP DNS request to the Internet.
196 * @param af desired address family
197 * @return NULL on error (given AF not "supported")
199 static struct GNUNET_DNSSTUB_RequestSocket *
200 get_request_socket (struct GNUNET_DNSSTUB_Context *ctx,
203 struct GNUNET_DNSSTUB_RequestSocket *rs;
204 struct GNUNET_NETWORK_FDSet *rset;
206 rs = &ctx->sockets[GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
208 rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
212 if (NULL == rs->dnsout4)
213 rs->dnsout4 = open_socket (AF_INET);
216 if (NULL == rs->dnsout6)
217 rs->dnsout6 = open_socket (AF_INET6);
222 if (GNUNET_SCHEDULER_NO_TASK != rs->read_task)
224 GNUNET_SCHEDULER_cancel (rs->read_task);
225 rs->read_task = GNUNET_SCHEDULER_NO_TASK;
227 if ( (NULL == rs->dnsout4) &&
228 (NULL == rs->dnsout6) )
230 rset = GNUNET_NETWORK_fdset_create ();
231 if (NULL != rs->dnsout4)
232 GNUNET_NETWORK_fdset_set (rset, rs->dnsout4);
233 if (NULL != rs->dnsout6)
234 GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
235 rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
240 GNUNET_NETWORK_fdset_destroy (rset);
246 * Perform DNS resolution.
248 * @param ctx stub resolver to use
249 * @param af address family to use
250 * @param request DNS request to transmit
251 * @param request_len number of bytes in msg
252 * @param rc function to call with result
253 * @param rc_cls closure for 'rc'
254 * @return socket used for the request, NULL on error
256 struct GNUNET_DNSSTUB_RequestSocket *
257 GNUNET_DNSSTUB_resolve (struct GNUNET_DNSSTUB_Context *ctx,
258 const struct sockaddr *sa,
262 GNUNET_DNSSTUB_ResultCallback rc,
265 struct GNUNET_DNSSTUB_RequestSocket *rs;
266 struct GNUNET_NETWORK_Handle *ret;
270 if (NULL == (rs = get_request_socket (ctx, af)))
272 if (NULL != rs->dnsout4)
276 GNUNET_assert (NULL != ret);
279 GNUNET_NETWORK_socket_sendto (ret,
289 * Perform DNS resolution using our default IP from init.
291 * @param ctx stub resolver to use
292 * @param request DNS request to transmit
293 * @param request_len number of bytes in msg
294 * @param rc function to call with result
295 * @param rc_cls closure for 'rc'
296 * @return socket used for the request, NULL on error
298 struct GNUNET_DNSSTUB_RequestSocket *
299 GNUNET_DNSSTUB_resolve2 (struct GNUNET_DNSSTUB_Context *ctx,
302 GNUNET_DNSSTUB_ResultCallback rc,
306 struct sockaddr_in v4;
307 struct sockaddr_in6 v6;
310 struct GNUNET_NETWORK_Handle *dnsout;
311 struct GNUNET_DNSSTUB_RequestSocket *rs;
313 memset (&v4, 0, sizeof (v4));
314 memset (&v6, 0, sizeof (v6));
315 if (1 == inet_pton (AF_INET, ctx->dns_exit, &v4.sin_addr))
318 v4.sin_family = AF_INET;
319 v4.sin_port = htons (53);
320 #if HAVE_SOCKADDR_IN_SIN_LEN
321 v4.sin_len = (u_char) salen;
323 so = (struct sockaddr *) &v4;
326 else if (1 == inet_pton (AF_INET6, ctx->dns_exit, &v6.sin6_addr))
329 v6.sin6_family = AF_INET6;
330 v6.sin6_port = htons (53);
331 #if HAVE_SOCKADDR_IN_SIN_LEN
332 v6.sin6_len = (u_char) salen;
334 so = (struct sockaddr *) &v6;
342 if (NULL == (rs = get_request_socket (ctx, af)))
344 if (NULL != rs->dnsout4)
345 dnsout = rs->dnsout4;
347 dnsout = rs->dnsout6;
350 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
351 _("Configured DNS exit `%s' is not working / valid.\n"),
361 GNUNET_NETWORK_socket_sendto (dnsout,
363 request_len, so, salen);
364 rs->timeout = GNUNET_TIME_relative_to_absolute (REQUEST_TIMEOUT);
372 * Actually do the reading of a DNS packet from our UDP socket and see
373 * if we have a valid, matching, pending request.
375 * @param rs request socket with callback details
376 * @param dnsout socket to read from
377 * @return GNUNET_OK on success, GNUNET_NO on drop, GNUNET_SYSERR on IO-errors (closed socket)
380 do_dns_read (struct GNUNET_DNSSTUB_RequestSocket *rs,
381 struct GNUNET_NETWORK_Handle *dnsout)
383 struct sockaddr_storage addr;
385 struct GNUNET_TUN_DnsHeader *dns;
390 if (0 != ioctl (GNUNET_NETWORK_get_fd (dnsout), FIONREAD, &len))
392 /* conservative choice: */
396 /* port the code above? */
401 unsigned char buf[len] GNUNET_ALIGN;
403 addrlen = sizeof (addr);
404 memset (&addr, 0, sizeof (addr));
405 r = GNUNET_NETWORK_socket_recvfrom (dnsout,
407 (struct sockaddr*) &addr, &addrlen);
410 GNUNET_log_strerror (GNUNET_ERROR_TYPE_ERROR, "recvfrom");
411 GNUNET_NETWORK_socket_close (dnsout);
412 return GNUNET_SYSERR;
414 if (sizeof (struct GNUNET_TUN_DnsHeader) > r)
416 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
417 _("Received DNS response that is too small (%u bytes)"),
421 dns = (struct GNUNET_TUN_DnsHeader *) buf;
422 if ( (addrlen != rs->addrlen) ||
423 (0 != memcmp (&rs->addr,
426 (0 == GNUNET_TIME_absolute_get_remaining (rs->timeout).rel_value) )
438 * Read a DNS response from the (unhindered) UDP-Socket
440 * @param cls socket to read from
441 * @param tc scheduler context (must be shutdown or read ready)
444 read_response (void *cls,
445 const struct GNUNET_SCHEDULER_TaskContext *tc)
447 struct GNUNET_DNSSTUB_RequestSocket *rs = cls;
448 struct GNUNET_NETWORK_FDSet *rset;
450 rs->read_task = GNUNET_SCHEDULER_NO_TASK;
451 if (0 == (tc->reason & GNUNET_SCHEDULER_REASON_READ_READY))
453 /* timeout or shutdown */
457 /* read and process ready sockets */
458 if ((NULL != rs->dnsout4) &&
459 (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout4)) &&
460 (GNUNET_SYSERR == do_dns_read (rs, rs->dnsout4)))
462 if ((NULL != rs->dnsout6) &&
463 (GNUNET_NETWORK_fdset_isset (tc->read_ready, rs->dnsout6)) &&
464 (GNUNET_SYSERR == do_dns_read (rs, rs->dnsout6)))
467 /* re-schedule read task */
468 rset = GNUNET_NETWORK_fdset_create ();
469 if (NULL != rs->dnsout4)
470 GNUNET_NETWORK_fdset_set (rset, rs->dnsout4);
471 if (NULL != rs->dnsout6)
472 GNUNET_NETWORK_fdset_set (rset, rs->dnsout6);
473 rs->read_task = GNUNET_SCHEDULER_add_select (GNUNET_SCHEDULER_PRIORITY_DEFAULT,
474 GNUNET_TIME_absolute_get_remaining (rs->timeout),
478 GNUNET_NETWORK_fdset_destroy (rset);
484 * Start a DNS stub resolver.
486 * @param dns_ip target IP address to use
487 * @return NULL on error
489 struct GNUNET_DNSSTUB_Context *
490 GNUNET_DNSSTUB_start (const char *dns_ip)
492 struct GNUNET_DNSSTUB_Context *ctx;
494 ctx = GNUNET_malloc (sizeof (struct GNUNET_DNSSTUB_Context));
496 ctx->dns_exit = GNUNET_strdup (dns_ip);
502 * Cleanup DNSSTUB resolver.
504 * @param ctx stub resolver to clean up
507 GNUNET_DNSSTUB_stop (struct GNUNET_DNSSTUB_Context *ctx)
511 for (i=0;i<=UINT16_MAX;i++)
512 cleanup_rs (&ctx->sockets[i]);
513 if (NULL != ctx->dns_exit)
515 GNUNET_free (ctx->dns_exit);
516 ctx->dns_exit = NULL;
522 /* end of dnsstub.c */