From 99c447ff420aa352bf525d434ed9136b6b5e8b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim Rühsen?= 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 +/-ares 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-ares. --- README.checkout | 4 + configure.ac | 30 ++++++- doc/wget.texi | 21 +++++ src/build_info.c.in | 1 + src/host.c | 240 ++++++++++++++++++++++++++++++++++++++-------------- src/init.c | 18 ++++ src/main.c | 71 ++++++++++++++++ src/options.h | 5 ++ 8 files changed, 325 insertions(+), 65 deletions(-) diff --git a/README.checkout b/README.checkout index 3265c81..17b9e70 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]libares 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-ares. 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 d7bba63..42723cf 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(ares, AS_HELP_STRING([--with-ares], [enable support for c-ares DNS lookup.]), with_ares=$withval, with_ares=no) + dnl dnl Process features dnl @@ -744,6 +746,31 @@ AS_IF([test "X$enable_pcre" != "Xno"],[ ]) ]) +dnl +dnl Check for libares (resolver library) +dnl + +AS_IF([test "X$with_ares" == "Xyes"],[ + PKG_CHECK_MODULES([ARES], libcares, [ + CFLAGS="$ARES_CFLAGS $CFLAGS" + AC_CHECK_HEADER(ares.h, [ + LIBS="$ARES_LIBS $LIBS" + AC_DEFINE([HAVE_LIBARES], [1], [Define if libares is available.]) + RESOLVER_INFO="libares, --bind-dns-address and --dns-servers available" + ]) + ], [ + AC_CHECK_HEADER(ares.h, [ + AC_CHECK_LIB(ares, ares_set_local_ip4, [ + LIBS="-lares ${LIBS}" + AC_DEFINE([HAVE_LIBARES], 1, [Define if libares is available.]) + RESOLVER_INFO="libares, --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 efebc49..a11e78c 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. address@hidden bind DNS address address@hidden client DNS address address@hidden DNS IP address, client, DNS address@hidden address@hidden +[libares 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. address@hidden must be specified either as IPv4 or IPv6 address. +Wget needs to be built with libares for this option to be available. + address@hidden DNS server address@hidden DNS IP address, client, DNS address@hidden address@hidden +[libares only] +The given address(es) override the standard nameserver +addresses, e.g. as configured in /etc/resolv.conf. address@hidden may be specified either as IPv4 or IPv6 addresses, +comma-separated. +Wget needs to be built with libares 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 83b7664..765a982 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 +ares defined HAVE_LIBARES metalink defined HAVE_METALINK gpgme defined HAVE_GPGME diff --git a/src/host.c b/src/host.c index 219f89c..29796c7 100644 --- a/src/host.c +++ b/src/host.c @@ -649,6 +649,86 @@ cache_remove (const char *host) } } +#ifdef HAVE_LIBARES +#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) +{ + for (;;) + { + struct timeval *tvp, tv; + fd_set read_fds, write_fds; + int nfds; + + FD_ZERO (&read_fds); + FD_ZERO (&write_fds); + nfds = ares_fds (channel, &read_fds, &write_fds); + if (nfds == 0) + break; + + tvp = ares_timeout (channel, NULL, &tv); + select (nfds, &read_fds, &write_fds, NULL, tvp); + 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 +835,112 @@ lookup_host (const char *host, int flags) } #ifdef ENABLE_IPV6 - { - 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. */ +#ifdef HAVE_LIBARES + if (ares) + { + struct address_list *al4; + struct address_list *al6; + + 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_LIBARES + 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 48859aa..b3b568d 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_LIBARES + { "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_LIBARES + { "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_LIBARES +#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 4641008..27c2a77 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_LIBARES +#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_LIBARES + { "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_LIBARES + { "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_LIBARES + N_("\ + --dns-servers­DRESSES list of DNS servers to query (comma separated)\n"), + N_("\ + --bind-dns-address­DRESS 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_LIBARES + if (opt.bind_dns_address || opt.dns_servers) + { + if (ares_library_init (ARES_LIB_INIT_ALL)) + { + fprintf (stderr, _("Failed to init libares\n")); + exit (WGET_EXIT_GENERIC_ERROR); + } + + if (ares_init (&ares) != ARES_SUCCESS) + { + fprintf (stderr, _("Failed to init 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 5cd5fb1..540619d 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_LIBARES + char *bind_dns_address; + char *dns_servers; +#endif + char **domains; /* See host.c */ char **exclude_domains; bool dns_cache; /* whether we cache DNS lookups. */ -- 2.7.0