From 76ef65b23cc8b315f92a5b51f135e4da0e51bd67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20R=C3=BChsen?= Date: Sun, 20 Mar 2016 13:42:47 +0100 Subject: [PATCH] Add options --bind-dns-address and --dns-servers * README.checkout: Add description for libares * configure.ac: Add check for libares * doc/wget.texi: Add docs for the new options * src/build_info.c.in: Add +/-cares for --version output * src/host.c: (merge_address_lists): New static function (address_list_from_hostent): New static function (wait_ares): New static function (callback): New static function (lookup_host): Add libares resolver code * src/init.c: Add new options, (cleanup): Add cleanup code * src/main.c: Add global libares channel variable (cmdline_option option_data): Add new options (print_help): Add short descriptions (main): Add libares init code * src/options.h (struct options): Add option members The new options allow to specify alternative DNS servers and an alternate packet route for the resolver packets. Wget has to built with libares, enabled at configure time by ./configure --with-cares. --- README.checkout | 4 + configure.ac | 30 +++++- doc/wget.texi | 21 ++++ src/build_info.c.in | 1 + src/host.c | 257 +++++++++++++++++++++++++++++++++----------- src/init.c | 18 ++++ src/main.c | 71 ++++++++++++ src/options.h | 5 + 8 files changed, 343 insertions(+), 64 deletions(-) diff --git a/README.checkout b/README.checkout index 3265c816..c8a111e0 100644 --- a/README.checkout +++ b/README.checkout @@ -99,6 +99,9 @@ Compiling From Repository Sources * [47]GnuPG with GPGME is used to verify GPG-signed Metalink resources. + * [48]libcares is needed to bind DNS resolving to a given IP address. + The command line options --dns-servers and --bind-dns-address are + only available when configured with --with-cares. For those who might be confused as to what to do once they check out the source code, considering configure and Makefile do not yet exist at @@ -207,3 +210,4 @@ References 45. https://www.python.org/ 46. https://launchpad.net/libmetalink 47. https://www.gnupg.org + 48. http://c-ares.haxx.se/ diff --git a/configure.ac b/configure.ac index d7bba63f..eb303f0f 100644 --- a/configure.ac +++ b/configure.ac @@ -72,7 +72,6 @@ dnl SSL: Configure SSL backend to use AC_ARG_WITH([ssl], [AS_HELP_STRING([--with-ssl={gnutls,openssl}], [specify SSL backend. GNU TLS is the default.])]) - dnl Zlib: Configure use of zlib for compression AC_ARG_WITH([zlib], [AS_HELP_STRING([--without-zlib], [disable zlib.])]) @@ -81,6 +80,9 @@ dnl Metalink: Configure use of the Metalink library AC_ARG_WITH([metalink], [AS_HELP_STRING([--with-metalink], [enable support for metalinks.])]) +dnl C-Ares: Configure use of the c-ares library for DNS lookup +AC_ARG_WITH(cares, AS_HELP_STRING([--with-cares], [enable support for C-Ares DNS lookup.]), with_cares=$withval, with_cares=no) + dnl dnl Process features dnl @@ -744,6 +746,31 @@ AS_IF([test "X$enable_pcre" != "Xno"],[ ]) ]) +dnl +dnl Check for libcares (resolver library) +dnl + +AS_IF([test "X$with_cares" == "Xyes"],[ + PKG_CHECK_MODULES([CARES], libcares, [ + CFLAGS="$CARES_CFLAGS $CFLAGS" + AC_CHECK_HEADER(ares.h, [ + LIBS="$CARES_LIBS $LIBS" + AC_DEFINE([HAVE_LIBCARES], [1], [Define if libcares is available.]) + RESOLVER_INFO="libcares, --bind-dns-address and --dns-servers available" + ]) + ], [ + AC_CHECK_HEADER(ares.h, [ + AC_CHECK_LIB(cares, ares_set_local_ip4, [ + LIBS="-lcares ${LIBS}" + AC_DEFINE([HAVE_LIBCARES], 1, [Define if libcares is available.]) + RESOLVER_INFO="libcares, --bind-dns-address and --dns-servers available" + ]) + ]) + ]) +], [ + RESOLVER_INFO="libc, --bind-dns-address and --dns-servers not available" +]) + dnl Needed by src/Makefile.am AM_CONDITIONAL([IRI_IS_ENABLED], [test "X$iri" != "Xno"]) @@ -778,5 +805,6 @@ AC_MSG_NOTICE([Summary of build options: Assertions: $ENABLE_ASSERTION Valgrind: $VALGRIND_INFO Metalink: $with_metalink + Resolver: $RESOLVER_INFO GPGME: $have_gpg ]) diff --git a/doc/wget.texi b/doc/wget.texi index efebc490..594a7027 100644 --- a/doc/wget.texi +++ b/doc/wget.texi @@ -571,6 +571,27 @@ the local machine. @var{ADDRESS} may be specified as a hostname or IP address. This option can be useful if your machine is bound to multiple IPs. +@cindex bind DNS address +@cindex client DNS address +@cindex DNS IP address, client, DNS +@item --bind-dns-address=@var{ADDRESS} +[libcares only] +This address overrides the route for DNS requests. If you ever need to +circumvent the standard settings from /etc/resolv.conf, this option together +with @samp{--dns-servers} is your friend. +@var{ADDRESS} must be specified either as IPv4 or IPv6 address. +Wget needs to be built with libcares for this option to be available. + +@cindex DNS server +@cindex DNS IP address, client, DNS +@item --dns-servers=@var{ADDRESSES} +[libcares only] +The given address(es) override the standard nameserver +addresses, e.g. as configured in /etc/resolv.conf. +@var{ADDRESSES} may be specified either as IPv4 or IPv6 addresses, +comma-separated. +Wget needs to be built with libcares for this option to be available. + @cindex retries @cindex tries @cindex number of tries diff --git a/src/build_info.c.in b/src/build_info.c.in index 83b7664e..c7493e9c 100644 --- a/src/build_info.c.in +++ b/src/build_info.c.in @@ -8,6 +8,7 @@ nls defined ENABLE_NLS ntlm defined ENABLE_NTLM opie defined ENABLE_OPIE psl defined HAVE_LIBPSL +cares defined HAVE_LIBCARES metalink defined HAVE_METALINK gpgme defined HAVE_GPGME diff --git a/src/host.c b/src/host.c index 219f89c1..d654feef 100644 --- a/src/host.c +++ b/src/host.c @@ -65,6 +65,7 @@ as that of the covered work. */ #include "host.h" #include "url.h" #include "hash.h" +#include "ptimer.h" #ifndef NO_ADDRESS # define NO_ADDRESS NO_DATA @@ -649,6 +650,104 @@ cache_remove (const char *host) } } +#ifdef HAVE_LIBCARES +#include +extern ares_channel ares; + +static struct address_list * +merge_address_lists (struct address_list *al1, struct address_list *al2) +{ + int count = al1->count + al2->count; + + /* merge al2 into al1 */ + al1->addresses = xrealloc (al1->addresses, sizeof (ip_address) * count); + memcpy (al1->addresses + al1->count, al2->addresses, sizeof (ip_address) * al2->count); + al1->count = count; + + address_list_delete (al2); + + return al1; +} + +static struct address_list * +address_list_from_hostent (struct hostent *host) +{ + int count, i; + struct address_list *al = xnew0 (struct address_list); + + for (count = 0; host->h_addr_list[count]; count++) + ; + + assert (count > 0); + + al->addresses = xnew_array (ip_address, count); + al->count = count; + al->refcount = 1; + + for (i = 0; i < count; i++) + { + ip_address *ip = &al->addresses[i]; + ip->family = host->h_addrtype; + memcpy (IP_INADDR_DATA (ip), host->h_addr_list[i], ip->family == AF_INET ? 4 : 16); + } + + return al; +} + +static void +wait_ares (ares_channel channel) +{ + struct ptimer *timer = NULL; + + if (opt.dns_timeout) + timer = ptimer_new (); + + for (;;) + { + struct timeval *tvp, tv; + fd_set read_fds, write_fds; + int nfds, rc; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + nfds = ares_fds (channel, &read_fds, &write_fds); + if (nfds == 0) + break; + + if (timer) + { + double max = opt.dns_timeout - ptimer_measure (timer); + + tv.tv_sec = (long) max; + tv.tv_usec = 1000000 * (max - (long) max); + tvp = ares_timeout (channel, &tv, &tv); + } + else + tvp = ares_timeout (channel, NULL, &tv); + + rc = select (nfds, &read_fds, &write_fds, NULL, tvp); + if (rc == 0 && timer && ptimer_measure (timer) >= opt.dns_timeout) + ares_cancel (channel); + else + ares_process (channel, &read_fds, &write_fds); + } +} + +static void +callback (void *arg, int status, int timeouts _GL_UNUSED, struct hostent *host) +{ + struct address_list **al = (struct address_list **) arg; + + if (!host || status != ARES_SUCCESS) + { + *al = NULL; + return; + } + + *al = address_list_from_hostent (host); +} +#endif + /* Look up HOST in DNS and return a list of IP addresses. This function caches its result so that, if the same host is passed @@ -755,80 +854,112 @@ lookup_host (const char *host, int flags) } #ifdef ENABLE_IPV6 - { - int err; - struct addrinfo hints, *res; +#ifdef HAVE_LIBCARES + if (ares) + { + struct address_list *al4; + struct address_list *al6; - xzero (hints); - hints.ai_socktype = SOCK_STREAM; - if (opt.ipv4_only) - hints.ai_family = AF_INET; - else if (opt.ipv6_only) - hints.ai_family = AF_INET6; - else - /* We tried using AI_ADDRCONFIG, but removed it because: it - misinterprets IPv6 loopbacks, it is broken on AIX 5.1, and - it's unneeded since we sort the addresses anyway. */ + if (opt.ipv4_only || !opt.ipv6_only) + ares_gethostbyname (ares, host, AF_INET, callback, &al4); + if (opt.ipv6_only || !opt.ipv4_only) + ares_gethostbyname (ares, host, AF_INET6, callback, &al6); + + wait_ares (ares); + + if (al4 && al6) + al = merge_address_lists (al4, al6); + else if (al4) + al = al4; + else + al = al6; + } + else +#endif + { + int err; + struct addrinfo hints, *res; + + xzero (hints); + hints.ai_socktype = SOCK_STREAM; + if (opt.ipv4_only) + hints.ai_family = AF_INET; + else if (opt.ipv6_only) + hints.ai_family = AF_INET6; + else + /* We tried using AI_ADDRCONFIG, but removed it because: it + misinterprets IPv6 loopbacks, it is broken on AIX 5.1, and + it's unneeded since we sort the addresses anyway. */ hints.ai_family = AF_UNSPEC; - if (flags & LH_BIND) - hints.ai_flags |= AI_PASSIVE; + if (flags & LH_BIND) + hints.ai_flags |= AI_PASSIVE; #ifdef AI_NUMERICHOST - if (numeric_address) - { - /* Where available, the AI_NUMERICHOST hint can prevent costly - access to DNS servers. */ - hints.ai_flags |= AI_NUMERICHOST; - timeout = 0; /* no timeout needed when "resolving" + if (numeric_address) + { + /* Where available, the AI_NUMERICHOST hint can prevent costly + access to DNS servers. */ + hints.ai_flags |= AI_NUMERICHOST; + timeout = 0; /* no timeout needed when "resolving" numeric hosts -- avoid setting up signal handlers and such. */ - } + } #endif - err = getaddrinfo_with_timeout (host, NULL, &hints, &res, timeout); - if (err != 0 || res == NULL) - { - if (!silent) - logprintf (LOG_VERBOSE, _("failed: %s.\n"), - err != EAI_SYSTEM ? gai_strerror (err) : strerror (errno)); - return NULL; - } - al = address_list_from_addrinfo (res); - freeaddrinfo (res); - if (!al) - { - logprintf (LOG_VERBOSE, - _("failed: No IPv4/IPv6 addresses for host.\n")); - return NULL; - } + err = getaddrinfo_with_timeout (host, NULL, &hints, &res, timeout); - /* Reorder addresses so that IPv4 ones (or IPv6 ones, as per - --prefer-family) come first. Sorting is stable so the order of - the addresses with the same family is undisturbed. */ - if (al->count > 1 && opt.prefer_family != prefer_none) - stable_sort (al->addresses, al->count, sizeof (ip_address), - opt.prefer_family == prefer_ipv4 - ? cmp_prefer_ipv4 : cmp_prefer_ipv6); - } + if (err != 0 || res == NULL) + { + if (!silent) + logprintf (LOG_VERBOSE, _ ("failed: %s.\n"), + err != EAI_SYSTEM ? gai_strerror (err) : strerror (errno)); + return NULL; + } + al = address_list_from_addrinfo (res); + freeaddrinfo (res); + } + + if (!al) + { + logprintf (LOG_VERBOSE, + _ ("failed: No IPv4/IPv6 addresses for host.\n")); + return NULL; + } + + /* Reorder addresses so that IPv4 ones (or IPv6 ones, as per + --prefer-family) come first. Sorting is stable so the order of + the addresses with the same family is undisturbed. */ + if (al->count > 1 && opt.prefer_family != prefer_none) + stable_sort (al->addresses, al->count, sizeof (ip_address), + opt.prefer_family == prefer_ipv4 + ? cmp_prefer_ipv4 : cmp_prefer_ipv6); #else /* not ENABLE_IPV6 */ - { - struct hostent *hptr = gethostbyname_with_timeout (host, timeout); - if (!hptr) - { - if (!silent) - { - if (errno != ETIMEDOUT) - logprintf (LOG_VERBOSE, _("failed: %s.\n"), - host_errstr (h_errno)); - else - logputs (LOG_VERBOSE, _("failed: timed out.\n")); - } - return NULL; - } - /* Do older systems have h_addr_list? */ - al = address_list_from_ipv4_addresses (hptr->h_addr_list); - } +#ifdef HAVE_LIBCARES + if (ares) + { + ares_gethostbyname (ares, host, AF_INET, callback, &al); + wait_ares (ares); + } + else +#endif + { + struct hostent *hptr = gethostbyname_with_timeout (host, timeout); + if (!hptr) + { + if (!silent) + { + if (errno != ETIMEDOUT) + logprintf (LOG_VERBOSE, _ ("failed: %s.\n"), + host_errstr (h_errno)); + else + logputs (LOG_VERBOSE, _ ("failed: timed out.\n")); + } + return NULL; + } + /* Do older systems have h_addr_list? */ + al = address_list_from_ipv4_addresses (hptr->h_addr_list); + } #endif /* not ENABLE_IPV6 */ /* Print the addresses determined by DNS lookup, but no more than diff --git a/src/init.c b/src/init.c index 48859aae..c6ee9cea 100644 --- a/src/init.c +++ b/src/init.c @@ -143,6 +143,9 @@ static const struct { { "backups", &opt.backups, cmd_number }, { "base", &opt.base_href, cmd_string }, { "bindaddress", &opt.bind_address, cmd_string }, +#ifdef HAVE_LIBCARES + { "binddnsaddress", &opt.bind_dns_address, cmd_string }, +#endif { "bodydata", &opt.body_data, cmd_string }, { "bodyfile", &opt.body_file, cmd_string }, #ifdef HAVE_SSL @@ -173,6 +176,9 @@ static const struct { { "dirprefix", &opt.dir_prefix, cmd_directory }, { "dirstruct", NULL, cmd_spec_dirstruct }, { "dnscache", &opt.dns_cache, cmd_boolean }, +#ifdef HAVE_LIBCARES + { "dnsservers", &opt.dns_servers, cmd_string }, +#endif { "dnstimeout", &opt.dns_timeout, cmd_time }, { "domains", &opt.domains, cmd_vector }, { "dotbytes", &opt.dot_bytes, cmd_bytes }, @@ -1922,6 +1928,18 @@ cleanup (void) xfree (opt.body_file); xfree (opt.rejected_log); +#ifdef HAVE_LIBCARES +#include + { + extern ares_channel ares; + + xfree (opt.bind_dns_address); + xfree (opt.dns_servers); + ares_destroy (ares); + ares_library_cleanup (); + } +#endif + #endif /* DEBUG_MALLOC */ } diff --git a/src/main.c b/src/main.c index 46410088..15b46ee6 100644 --- a/src/main.c +++ b/src/main.c @@ -86,6 +86,13 @@ as that of the covered work. */ struct iri dummy_iri; #endif +#ifdef HAVE_LIBCARES +#include +ares_channel ares; +#else +void *ares; +#endif + struct options opt; /* defined in version.c */ @@ -252,6 +259,9 @@ static struct cmdline_option option_data[] = { "backups", 0, OPT_BOOLEAN, "backups", -1 }, { "base", 'B', OPT_VALUE, "base", -1 }, { "bind-address", 0, OPT_VALUE, "bindaddress", -1 }, +#ifdef HAVE_LIBCARES + { "bind-dns-address", 0, OPT_VALUE, "binddnsaddress", -1 }, +#endif { "body-data", 0, OPT_VALUE, "bodydata", -1 }, { "body-file", 0, OPT_VALUE, "bodyfile", -1 }, { IF_SSL ("ca-certificate"), 0, OPT_VALUE, "cacertificate", -1 }, @@ -277,6 +287,9 @@ static struct cmdline_option option_data[] = { "directories", 0, OPT_BOOLEAN, "dirstruct", -1 }, { "directory-prefix", 'P', OPT_VALUE, "dirprefix", -1 }, { "dns-cache", 0, OPT_BOOLEAN, "dnscache", -1 }, +#ifdef HAVE_LIBCARES + { "dns-servers", 0, OPT_VALUE, "dnsservers", -1 }, +#endif { "dns-timeout", 0, OPT_VALUE, "dnstimeout", -1 }, { "domains", 'D', OPT_VALUE, "domains", -1 }, { "dont-remove-listing", 0, OPT__DONT_REMOVE_LISTING, NULL, no_argument }, @@ -627,6 +640,12 @@ Download:\n"), --spider don't download anything\n"), N_("\ -T, --timeout=SECONDS set all timeout values to SECONDS\n"), +#ifdef HAVE_LIBCARES + N_("\ + --dns-servers=ADDRESSES list of DNS servers to query (comma separated)\n"), + N_("\ + --bind-dns-address=ADDRESS bind DNS resolver to ADDRESS (hostname or IP) on local host\n"), +#endif N_("\ --dns-timeout=SECS set the DNS lookup timeout to SECS\n"), N_("\ @@ -1774,6 +1793,58 @@ only if outputting to a regular file.\n")); } } +#ifdef HAVE_LIBCARES + if (opt.bind_dns_address || opt.dns_servers) + { + if (ares_library_init (ARES_LIB_INIT_ALL)) + { + fprintf (stderr, _("Failed to init libcares\n")); + exit (WGET_EXIT_GENERIC_ERROR); + } + + if (ares_init (&ares) != ARES_SUCCESS) + { + fprintf (stderr, _("Failed to init c-ares channel\n")); + exit (WGET_EXIT_GENERIC_ERROR); + } + + if (opt.bind_dns_address) + { + struct in_addr a4; +#ifdef ENABLE_IPV6 + struct in6_addr a6; +#endif + + if (inet_pton (AF_INET, opt.bind_dns_address, &a4) == 1) + { + ares_set_local_ip4 (ares, ntohl (a4.s_addr)); + } +#ifdef ENABLE_IPV6 + else if (inet_pton (AF_INET6, opt.bind_dns_address, &a6) == 1) + { + ares_set_local_ip6 (ares, (unsigned char *) &a6); + } +#endif + else + { + fprintf (stderr, _("Failed to parse IP address '%s'\n"), opt.bind_dns_address); + exit (WGET_EXIT_GENERIC_ERROR); + } + } + + if (opt.dns_servers) + { + int result; + + if ((result = ares_set_servers_csv (ares, opt.dns_servers)) != ARES_SUCCESS) + { + fprintf (stderr, _("Failed to set DNS server(s) '%s' (%d)\n"), opt.dns_servers, result); + exit (WGET_EXIT_GENERIC_ERROR); + } + } + } +#endif + #ifdef __VMS /* Set global ODS5 flag according to the specified destination (if any), otherwise according to the current default device. diff --git a/src/options.h b/src/options.h index 5cd5fb11..b936f51f 100644 --- a/src/options.h +++ b/src/options.h @@ -99,6 +99,11 @@ struct options void *(*regex_compile_fun)(const char *); /* Function to compile a regex. */ bool (*regex_match_fun)(const void *, const char *); /* Function to match a string to a regex. */ +#ifdef HAVE_LIBCARES + char *bind_dns_address; + char *dns_servers; +#endif + char **domains; /* See host.c */ char **exclude_domains; bool dns_cache; /* whether we cache DNS lookups. */