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.
This commit is contained in:
Tim Rühsen 2016-03-20 13:42:47 +01:00
parent d7726f8a13
commit 76ef65b23c
8 changed files with 343 additions and 64 deletions

View File

@ -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/

View File

@ -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
])

View File

@ -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

View File

@ -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

View File

@ -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 <ares.h>
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

View File

@ -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 <ares.h>
{
extern ares_channel ares;
xfree (opt.bind_dns_address);
xfree (opt.dns_servers);
ares_destroy (ares);
ares_library_cleanup ();
}
#endif
#endif /* DEBUG_MALLOC */
}

View File

@ -86,6 +86,13 @@ as that of the covered work. */
struct iri dummy_iri;
#endif
#ifdef HAVE_LIBCARES
#include <ares.h>
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.

View File

@ -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. */