1190 lines
38 KiB
Diff
1190 lines
38 KiB
Diff
|
From 63ceb27adae045aa7e137663253d98060b5f05be Mon Sep 17 00:00:00 2001
|
||
|
From: Christian Ammer <chrie.ammer@gmail.com>
|
||
|
Date: Tue, 6 Nov 2018 14:47:05 +0100
|
||
|
Subject: [PATCH 01/10] Add initial implementation for ares_getaddrinfo (#112)
|
||
|
|
||
|
Initial implementation for ares_getaddrinfo(). It is NOT compliant with RFC6724, though
|
||
|
it is expected to come closer to conformance prior to the next release.
|
||
|
|
||
|
Features not supported include sorted addresses and honoring of service and hints
|
||
|
parameters.
|
||
|
|
||
|
Implementation by: Christian Ammer (@ChristianAmmer)
|
||
|
---
|
||
|
Makefile.inc | 12 +-
|
||
|
ares.h | 22 +++
|
||
|
ares_freeaddrinfo.3 | 35 ++++
|
||
|
ares_getaddrinfo.3 | 114 +++++++++++
|
||
|
ares_getaddrinfo.c | 465 +++++++++++++++++++++++++++++++++++++++++++++
|
||
|
ares_parse_a_reply.c | 38 ++++
|
||
|
ares_private.h | 4 +
|
||
|
ares_search.c | 12 +-
|
||
|
test/Makefile.inc | 5 +-
|
||
|
test/ares-test-ai.cc | 0
|
||
|
test/ares-test-ai.h | 32 ++++
|
||
|
test/ares-test-internal.cc | 8 +-
|
||
|
test/ares-test-mock-ai.cc | 154 +++++++++++++++
|
||
|
test/ares-test.cc | 11 ++
|
||
|
test/ares-test.h | 2 +
|
||
|
15 files changed, 898 insertions(+), 16 deletions(-)
|
||
|
create mode 100644 ares_freeaddrinfo.3
|
||
|
create mode 100644 ares_getaddrinfo.3
|
||
|
create mode 100644 ares_getaddrinfo.c
|
||
|
create mode 100644 test/ares-test-ai.cc
|
||
|
create mode 100644 test/ares-test-ai.h
|
||
|
create mode 100644 test/ares-test-mock-ai.cc
|
||
|
|
||
|
diff --git a/Makefile.inc b/Makefile.inc
|
||
|
index 30e0046..381cc75 100644
|
||
|
--- a/Makefile.inc
|
||
|
+++ b/Makefile.inc
|
||
|
@@ -48,7 +48,8 @@ CSOURCES = ares__close_sockets.c \
|
||
|
bitncmp.c \
|
||
|
inet_net_pton.c \
|
||
|
inet_ntop.c \
|
||
|
- windows_port.c
|
||
|
+ windows_port.c \
|
||
|
+ ares_getaddrinfo.c
|
||
|
|
||
|
HHEADERS = ares.h \
|
||
|
ares_android.h \
|
||
|
@@ -129,7 +130,8 @@ MANPAGES = ares_cancel.3 \
|
||
|
ares_set_sortlist.3 \
|
||
|
ares_strerror.3 \
|
||
|
ares_timeout.3 \
|
||
|
- ares_version.3
|
||
|
+ ares_version.3 \
|
||
|
+ ares_getaddrinfo.3
|
||
|
|
||
|
HTMLPAGES = ares_cancel.html \
|
||
|
ares_create_query.html \
|
||
|
@@ -184,7 +186,8 @@ HTMLPAGES = ares_cancel.html \
|
||
|
ares_set_sortlist.html \
|
||
|
ares_strerror.html \
|
||
|
ares_timeout.html \
|
||
|
- ares_version.html
|
||
|
+ ares_version.html \
|
||
|
+ ares_getaddrinfo.html
|
||
|
|
||
|
PDFPAGES = ares_cancel.pdf \
|
||
|
ares_create_query.pdf \
|
||
|
@@ -239,7 +242,8 @@ PDFPAGES = ares_cancel.pdf \
|
||
|
ares_set_sortlist.pdf \
|
||
|
ares_strerror.pdf \
|
||
|
ares_timeout.pdf \
|
||
|
- ares_version.pdf
|
||
|
+ ares_version.pdf \
|
||
|
+ ares_getaddrinfo.pdf
|
||
|
|
||
|
SAMPLESOURCES = ares_getopt.c \
|
||
|
ares_nowarn.c \
|
||
|
diff --git a/ares.h b/ares.h
|
||
|
index 06f60b3..99e3e0b 100644
|
||
|
--- a/ares.h
|
||
|
+++ b/ares.h
|
||
|
@@ -278,6 +278,7 @@ struct hostent;
|
||
|
struct timeval;
|
||
|
struct sockaddr;
|
||
|
struct ares_channeldata;
|
||
|
+struct ares_addrinfo;
|
||
|
|
||
|
typedef struct ares_channeldata *ares_channel;
|
||
|
|
||
|
@@ -306,6 +307,10 @@ typedef int (*ares_sock_config_callback)(ares_socket_t socket_fd,
|
||
|
int type,
|
||
|
void *data);
|
||
|
|
||
|
+typedef void (*ares_addr_callback)(void *arg,
|
||
|
+ int status,
|
||
|
+ struct ares_addrinfo *res);
|
||
|
+
|
||
|
CARES_EXTERN int ares_library_init(int flags);
|
||
|
|
||
|
CARES_EXTERN int ares_library_init_mem(int flags,
|
||
|
@@ -369,6 +374,12 @@ CARES_EXTERN void ares_set_socket_configure_callback(ares_channel channel,
|
||
|
CARES_EXTERN int ares_set_sortlist(ares_channel channel,
|
||
|
const char *sortstr);
|
||
|
|
||
|
+CARES_EXTERN void ares_getaddrinfo(ares_channel channel,
|
||
|
+ const char* node, const char* service,
|
||
|
+ const struct ares_addrinfo* hints,
|
||
|
+ ares_addr_callback callback, void* arg);
|
||
|
+CARES_EXTERN void ares_freeaddrinfo(struct ares_addrinfo* ai);
|
||
|
+
|
||
|
/*
|
||
|
* Virtual function set to have user-managed socket IO.
|
||
|
* Note that all functions need to be defined, and when
|
||
|
@@ -558,6 +569,17 @@ struct ares_soa_reply {
|
||
|
unsigned int minttl;
|
||
|
};
|
||
|
|
||
|
+struct ares_addrinfo {
|
||
|
+ int ai_flags;
|
||
|
+ int ai_family;
|
||
|
+ int ai_socktype;
|
||
|
+ int ai_protocol;
|
||
|
+ ares_socklen_t ai_addrlen;
|
||
|
+ char *ai_canonname;
|
||
|
+ struct sockaddr *ai_addr;
|
||
|
+ struct ares_addrinfo *ai_next;
|
||
|
+};
|
||
|
+
|
||
|
/*
|
||
|
** Parse the buffer, starting at *abuf and of length alen bytes, previously
|
||
|
** obtained from an ares_search call. Put the results in *host, if nonnull.
|
||
|
diff --git a/ares_freeaddrinfo.3 b/ares_freeaddrinfo.3
|
||
|
new file mode 100644
|
||
|
index 0000000..8143299
|
||
|
--- /dev/null
|
||
|
+++ b/ares_freeaddrinfo.3
|
||
|
@@ -0,0 +1,35 @@
|
||
|
+.\"
|
||
|
+.\" Copyright 1998 by the Massachusetts Institute of Technology.
|
||
|
+.\"
|
||
|
+.\" Permission to use, copy, modify, and distribute this
|
||
|
+.\" software and its documentation for any purpose and without
|
||
|
+.\" fee is hereby granted, provided that the above copyright
|
||
|
+.\" notice appear in all copies and that both that copyright
|
||
|
+.\" notice and this permission notice appear in supporting
|
||
|
+.\" documentation, and that the name of M.I.T. not be used in
|
||
|
+.\" advertising or publicity pertaining to distribution of the
|
||
|
+.\" software without specific, written prior permission.
|
||
|
+.\" M.I.T. makes no representations about the suitability of
|
||
|
+.\" this software for any purpose. It is provided "as is"
|
||
|
+.\" without express or implied warranty.
|
||
|
+.\"
|
||
|
+.TH ARES_FREEADDRINFO 3 "31 October 2018"
|
||
|
+.SH NAME
|
||
|
+ares_freeaddrinfo \- Free addrinfo structure allocated by ares functions
|
||
|
+.SH SYNOPSIS
|
||
|
+.nf
|
||
|
+.B #include <ares.h>
|
||
|
+.PP
|
||
|
+.B void ares_freeaddrinfo(struct addrinfo *\fIai\fP)
|
||
|
+.fi
|
||
|
+.SH DESCRIPTION
|
||
|
+The
|
||
|
+.B ares_freeaddrinfo
|
||
|
+function frees a
|
||
|
+.B struct addrinfo
|
||
|
+returned in \fIresult\fP of
|
||
|
+.B ares_addr_callback
|
||
|
+.SH SEE ALSO
|
||
|
+.BR ares_getaddrinfo (3),
|
||
|
+.SH AUTHOR
|
||
|
+Christian Ammer
|
||
|
diff --git a/ares_getaddrinfo.3 b/ares_getaddrinfo.3
|
||
|
new file mode 100644
|
||
|
index 0000000..42a43fc
|
||
|
--- /dev/null
|
||
|
+++ b/ares_getaddrinfo.3
|
||
|
@@ -0,0 +1,114 @@
|
||
|
+.\"
|
||
|
+.\" Copyright 1998 by the Massachusetts Institute of Technology.
|
||
|
+.\"
|
||
|
+.\" Permission to use, copy, modify, and distribute this
|
||
|
+.\" software and its documentation for any purpose and without
|
||
|
+.\" fee is hereby granted, provided that the above copyright
|
||
|
+.\" notice appear in all copies and that both that copyright
|
||
|
+.\" notice and this permission notice appear in supporting
|
||
|
+.\" documentation, and that the name of M.I.T. not be used in
|
||
|
+.\" advertising or publicity pertaining to distribution of the
|
||
|
+.\" software without specific, written prior permission.
|
||
|
+.\" M.I.T. makes no representations about the suitability of
|
||
|
+.\" this software for any purpose. It is provided "as is"
|
||
|
+.\" without express or implied warranty.
|
||
|
+.\"
|
||
|
+.TH ARES_GETADDRINFO 3 "4 November 2018"
|
||
|
+.SH NAME
|
||
|
+ares_getaddrinfo \- Initiate a host query by name
|
||
|
+.SH SYNOPSIS
|
||
|
+.nf
|
||
|
+.B #include <ares.h>
|
||
|
+.PP
|
||
|
+.B typedef void (*ares_addr_callback)(void *\fIarg\fP, int \fIstatus\fP,
|
||
|
+.B struct ares_addrinfo *\fIresult\fP)
|
||
|
+.PP
|
||
|
+.B void ares_getaddrinfo(ares_channel \fIchannel\fP, const char *\fIname\fP,
|
||
|
+.B const char* \fIservice\fP, const struct ares_addrinfo *\fIhints\fP,
|
||
|
+.B ares_addr_callback \fIcallback\fP, void *\fIarg\fP)
|
||
|
+.PP
|
||
|
+.B struct ares_addrinfo {
|
||
|
+.B int \fIai_flags\fP;
|
||
|
+.B int \fIai_family\fP;
|
||
|
+.B int \fIai_socktype\fP;
|
||
|
+.B int \fIai_protocol\fP;
|
||
|
+.B ares_socklen_t \fIai_addrlen\fP;
|
||
|
+.B char \fI*ai_canonname\fP;
|
||
|
+.B struct sockaddr \fI*ai_addr\fP;
|
||
|
+.B struct ares_addrinfo \fI*ai_next\fP;
|
||
|
+.B };
|
||
|
+.fi
|
||
|
+.SH DESCRIPTION
|
||
|
+The
|
||
|
+.B ares_getaddrinfo
|
||
|
+function initiates a host query by name on the name service channel
|
||
|
+identified by
|
||
|
+.IR channel .
|
||
|
+The parameter
|
||
|
+.I name
|
||
|
+gives the hostname as a NUL-terminated C string, and
|
||
|
+.I hints->ai_family
|
||
|
+gives the desired type of address for the resulting addrinfo result list.
|
||
|
+The parameter
|
||
|
+.I service
|
||
|
+and the other properties from the
|
||
|
+.I hints
|
||
|
+parameter are ignored. When the
|
||
|
+query is complete or has failed, the ares library will invoke \fIcallback\fP.
|
||
|
+Completion or failure of the query may happen immediately, or may happen
|
||
|
+during a later call to \fIares_process(3)\fP, \fIares_destroy(3)\fP or
|
||
|
+\fIares_cancel(3)\fP.
|
||
|
+.PP
|
||
|
+The callback argument
|
||
|
+.I arg
|
||
|
+is copied from the
|
||
|
+.B ares_getaddrinfo
|
||
|
+argument
|
||
|
+.IR arg .
|
||
|
+The callback argument
|
||
|
+.I status
|
||
|
+indicates whether the query succeeded and, if not, how it failed. It
|
||
|
+may have any of the following values:
|
||
|
+.TP 19
|
||
|
+.B ARES_SUCCESS
|
||
|
+The host lookup completed successfully.
|
||
|
+.TP 19
|
||
|
+.B ARES_ENOTIMP
|
||
|
+The ares library does not know how to find addresses of type
|
||
|
+.IR family .
|
||
|
+.TP 19
|
||
|
+.B ARES_ENOTFOUND
|
||
|
+The name
|
||
|
+.I name
|
||
|
+was not found.
|
||
|
+.TP 19
|
||
|
+.B ARES_ENOMEM
|
||
|
+Memory was exhausted.
|
||
|
+.TP 19
|
||
|
+.B ARES_ECANCELLED
|
||
|
+The query was cancelled.
|
||
|
+.TP 19
|
||
|
+.B ARES_EDESTRUCTION
|
||
|
+The name service channel
|
||
|
+.I channel
|
||
|
+is being destroyed; the query will not be completed.
|
||
|
+.PP
|
||
|
+On successful completion of the query, the callback argument
|
||
|
+.I result
|
||
|
+points to a
|
||
|
+.B struct addrinfo
|
||
|
+which is a linked list, where each item contains family and address of
|
||
|
+the requested name. The list is not sorted. The reserved memory has to be
|
||
|
+deleted by
|
||
|
+.B ares_freeaddrinfo.
|
||
|
+.SH SEE ALSO
|
||
|
+.BR ares_freeaddrinfo (3)
|
||
|
+.SH AUTHOR
|
||
|
+Christian Ammer
|
||
|
+.SH CAVEATS
|
||
|
+This function is under development. It only supports a minimum feature set
|
||
|
+of the function
|
||
|
+.B getaddrinfo
|
||
|
+defined in RFC-3493. It also does not support the destination address selection
|
||
|
+algorithm defined in RFC-6724.
|
||
|
+.br
|
||
|
diff --git a/ares_getaddrinfo.c b/ares_getaddrinfo.c
|
||
|
new file mode 100644
|
||
|
index 0000000..be936ff
|
||
|
--- /dev/null
|
||
|
+++ b/ares_getaddrinfo.c
|
||
|
@@ -0,0 +1,465 @@
|
||
|
+
|
||
|
+/* Copyright 1998, 2011, 2013 by the Massachusetts Institute of Technology.
|
||
|
+ * Copyright (C) 2017 - 2018 by Christian Ammer
|
||
|
+ *
|
||
|
+ * Permission to use, copy, modify, and distribute this
|
||
|
+ * software and its documentation for any purpose and without
|
||
|
+ * fee is hereby granted, provided that the above copyright
|
||
|
+ * notice appear in all copies and that both that copyright
|
||
|
+ * notice and this permission notice appear in supporting
|
||
|
+ * documentation, and that the name of M.I.T. not be used in
|
||
|
+ * advertising or publicity pertaining to distribution of the
|
||
|
+ * software without specific, written prior permission.
|
||
|
+ * M.I.T. makes no representations about the suitability of
|
||
|
+ * this software for any purpose. It is provided "as is"
|
||
|
+ * without express or implied warranty.
|
||
|
+ */
|
||
|
+
|
||
|
+#include "ares_setup.h"
|
||
|
+
|
||
|
+#ifdef HAVE_NETINET_IN_H
|
||
|
+# include <netinet/in.h>
|
||
|
+#endif
|
||
|
+#ifdef HAVE_NETDB_H
|
||
|
+# include <netdb.h>
|
||
|
+#endif
|
||
|
+#ifdef HAVE_ARPA_INET_H
|
||
|
+# include <arpa/inet.h>
|
||
|
+#endif
|
||
|
+#ifdef HAVE_ARPA_NAMESER_H
|
||
|
+# include <arpa/nameser.h>
|
||
|
+#else
|
||
|
+# include "nameser.h"
|
||
|
+#endif
|
||
|
+#ifdef HAVE_ARPA_NAMESER_COMPAT_H
|
||
|
+# include <arpa/nameser_compat.h>
|
||
|
+#endif
|
||
|
+
|
||
|
+#ifdef HAVE_STRINGS_H
|
||
|
+#include <strings.h>
|
||
|
+#endif
|
||
|
+#include <assert.h>
|
||
|
+
|
||
|
+#include "ares.h"
|
||
|
+#include "bitncmp.h"
|
||
|
+#include "ares_private.h"
|
||
|
+
|
||
|
+#ifdef WATT32
|
||
|
+#undef WIN32
|
||
|
+#endif
|
||
|
+#ifdef WIN32
|
||
|
+# include "ares_platform.h"
|
||
|
+#endif
|
||
|
+
|
||
|
+struct host_query {
|
||
|
+ /* Arguments passed to ares_getaddrinfo */
|
||
|
+ ares_channel channel;
|
||
|
+ char *name;
|
||
|
+ ares_addr_callback callback;
|
||
|
+ void *arg;
|
||
|
+ int sent_family; /* this family is what was is being used */
|
||
|
+ int ai_family; /* this family is what is asked for in the API */
|
||
|
+ int timeouts; /* number of timeouts we saw for this request */
|
||
|
+ int next_domain; /* next search domain to try */
|
||
|
+ int single_domain; /* do not check other domains */
|
||
|
+ int status;
|
||
|
+ int remaining;
|
||
|
+ struct ares_addrinfo* ai;
|
||
|
+};
|
||
|
+
|
||
|
+static void host_callback(void *arg, int status, int timeouts,
|
||
|
+ unsigned char *abuf, int alen);
|
||
|
+static void end_hquery(struct host_query *hquery, int status);
|
||
|
+static int file_lookup(const char *name, int family,
|
||
|
+ struct ares_addrinfo **ai);
|
||
|
+static void sort_addresses(struct hostent *host,
|
||
|
+ const struct apattern *sortlist, int nsort);
|
||
|
+static void sort6_addresses(struct hostent *host,
|
||
|
+ const struct apattern *sortlist, int nsort);
|
||
|
+static int get_address_index(const struct in_addr *addr,
|
||
|
+ const struct apattern *sortlist, int nsort);
|
||
|
+static int get6_address_index(const struct ares_in6_addr *addr,
|
||
|
+ const struct apattern *sortlist, int nsort);
|
||
|
+static int as_is_first(const struct host_query *hquery);
|
||
|
+static void add_to_addrinfo(struct ares_addrinfo** ai,
|
||
|
+ const struct hostent* host);
|
||
|
+static void next_dns_lookup(struct host_query *hquery);
|
||
|
+static int is_implemented(const int family);
|
||
|
+
|
||
|
+
|
||
|
+void ares_getaddrinfo(ares_channel channel,
|
||
|
+ const char* node, const char* service,
|
||
|
+ const struct ares_addrinfo* hints,
|
||
|
+ ares_addr_callback callback, void* arg) {
|
||
|
+ struct host_query *hquery;
|
||
|
+ char *single = NULL;
|
||
|
+ int ai_family;
|
||
|
+
|
||
|
+ ai_family = hints ? hints->ai_family : AF_UNSPEC;
|
||
|
+ if (!is_implemented(ai_family)) {
|
||
|
+ callback(arg, ARES_ENOTIMP, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+
|
||
|
+ /* Allocate and fill in the host query structure. */
|
||
|
+ hquery = ares_malloc(sizeof(struct host_query));
|
||
|
+ if (!hquery) {
|
||
|
+ callback(arg, ARES_ENOMEM, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ hquery->ai = NULL;
|
||
|
+ hquery->channel = channel;
|
||
|
+ hquery->name = single != NULL ? single : ares_strdup(node);
|
||
|
+ hquery->single_domain = single != NULL;
|
||
|
+ hquery->ai_family = ai_family;
|
||
|
+ hquery->sent_family = -1; /* nothing is sent yet */
|
||
|
+ if (!hquery->name) {
|
||
|
+ ares_free(hquery);
|
||
|
+ callback(arg, ARES_ENOMEM, NULL);
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ hquery->callback = callback;
|
||
|
+ hquery->arg = arg;
|
||
|
+ hquery->timeouts = 0;
|
||
|
+ hquery->next_domain = 0;
|
||
|
+ hquery->remaining = ai_family == AF_UNSPEC ? 2 : 1;
|
||
|
+
|
||
|
+ /* Host file lookup */
|
||
|
+ if (file_lookup(hquery->name, ai_family, &hquery->ai) == ARES_SUCCESS) {
|
||
|
+ end_hquery(hquery, ARES_SUCCESS);
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ next_dns_lookup(hquery);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+void ares_freeaddrinfo(struct ares_addrinfo* ai) {
|
||
|
+ struct ares_addrinfo* ai_free;
|
||
|
+ while (ai) {
|
||
|
+ ai_free = ai;
|
||
|
+ ai = ai->ai_next;
|
||
|
+ ares_free(ai_free->ai_addr);
|
||
|
+ ares_free(ai_free);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static int is_implemented(const int family) {
|
||
|
+ return
|
||
|
+ family == AF_INET ||
|
||
|
+ family == AF_INET6 ||
|
||
|
+ family == AF_UNSPEC;
|
||
|
+}
|
||
|
+
|
||
|
+static int file_lookup(const char *name, int family, struct ares_addrinfo **ai) {
|
||
|
+ FILE *fp;
|
||
|
+ char **alias;
|
||
|
+ int status;
|
||
|
+ int error;
|
||
|
+ struct hostent *host = NULL;
|
||
|
+
|
||
|
+#ifdef WIN32
|
||
|
+ char PATH_HOSTS[MAX_PATH];
|
||
|
+ win_platform platform;
|
||
|
+
|
||
|
+ PATH_HOSTS[0] = '\0';
|
||
|
+
|
||
|
+ platform = ares__getplatform();
|
||
|
+
|
||
|
+ if (platform == WIN_NT) {
|
||
|
+ char tmp[MAX_PATH];
|
||
|
+ HKEY hkeyHosts;
|
||
|
+
|
||
|
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
|
||
|
+ &hkeyHosts) == ERROR_SUCCESS) {
|
||
|
+ DWORD dwLength = MAX_PATH;
|
||
|
+ RegQueryValueEx(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
|
||
|
+ &dwLength);
|
||
|
+ ExpandEnvironmentStrings(tmp, PATH_HOSTS, MAX_PATH);
|
||
|
+ RegCloseKey(hkeyHosts);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else if (platform == WIN_9X) {
|
||
|
+ GetWindowsDirectory(PATH_HOSTS, MAX_PATH);
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ return ARES_ENOTFOUND;
|
||
|
+ }
|
||
|
+
|
||
|
+ strcat(PATH_HOSTS, WIN_PATH_HOSTS);
|
||
|
+
|
||
|
+#elif defined(WATT32)
|
||
|
+ extern const char *_w32_GetHostsFile (void);
|
||
|
+ const char *PATH_HOSTS = _w32_GetHostsFile();
|
||
|
+
|
||
|
+ if (!PATH_HOSTS) {
|
||
|
+ return ARES_ENOTFOUND;
|
||
|
+ }
|
||
|
+#endif
|
||
|
+
|
||
|
+ fp = fopen(PATH_HOSTS, "r");
|
||
|
+ if (!fp) {
|
||
|
+ error = ERRNO;
|
||
|
+ switch(error) {
|
||
|
+ case ENOENT:
|
||
|
+ case ESRCH:
|
||
|
+ return ARES_ENOTFOUND;
|
||
|
+ default:
|
||
|
+ DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n",
|
||
|
+ error, strerror(error)));
|
||
|
+ DEBUGF(fprintf(stderr, "Error opening file: %s\n",
|
||
|
+ PATH_HOSTS));
|
||
|
+ host = NULL;
|
||
|
+ return ARES_EFILE;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ status = ARES_ENOTFOUND;
|
||
|
+ while (ares__get_hostent(fp, family, &host) == ARES_SUCCESS) {
|
||
|
+ if (strcasecmp(host->h_name, name) == 0) {
|
||
|
+ add_to_addrinfo(ai, host);
|
||
|
+ status = ARES_SUCCESS;
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ for (alias = host->h_aliases; *alias; alias++) {
|
||
|
+ if (strcasecmp(*alias, name) == 0) {
|
||
|
+ add_to_addrinfo(ai, host);
|
||
|
+ status = ARES_SUCCESS;
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+ ares_free_hostent(host);
|
||
|
+ }
|
||
|
+ fclose(fp);
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+static void add_to_addrinfo(struct ares_addrinfo** ai,
|
||
|
+ const struct hostent* host) {
|
||
|
+ static const struct ares_addrinfo EmptyAddrinfo;
|
||
|
+ struct ares_addrinfo* next_ai;
|
||
|
+ char** p;
|
||
|
+ if (!host || (host->h_addrtype != AF_INET && host->h_addrtype != AF_INET6)) {
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ for (p = host->h_addr_list; *p; ++p) {
|
||
|
+ next_ai = ares_malloc(sizeof(struct ares_addrinfo));
|
||
|
+ *next_ai = EmptyAddrinfo;
|
||
|
+ if (*ai) {
|
||
|
+ (*ai)->ai_next = next_ai;
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ *ai = next_ai;
|
||
|
+ }
|
||
|
+ if (host->h_addrtype == AF_INET) {
|
||
|
+ next_ai->ai_protocol = IPPROTO_UDP;
|
||
|
+ next_ai->ai_family = AF_INET;
|
||
|
+ next_ai->ai_addr = ares_malloc(sizeof(struct sockaddr_in));
|
||
|
+ memcpy(&((struct sockaddr_in*)(next_ai->ai_addr))->sin_addr, *p,
|
||
|
+ host->h_length);
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ next_ai->ai_protocol = IPPROTO_UDP;
|
||
|
+ next_ai->ai_family = AF_INET6;
|
||
|
+ next_ai->ai_addr = ares_malloc(sizeof(struct sockaddr_in6));
|
||
|
+ memcpy(&((struct sockaddr_in6*)(next_ai->ai_addr))->sin6_addr, *p,
|
||
|
+ host->h_length);
|
||
|
+ }
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void next_dns_lookup(struct host_query *hquery) {
|
||
|
+ char *s = NULL;
|
||
|
+ int is_s_allocated = 0;
|
||
|
+ int status;
|
||
|
+
|
||
|
+ if (( as_is_first(hquery) && hquery->next_domain == 0) ||
|
||
|
+ (!as_is_first(hquery) && hquery->next_domain ==
|
||
|
+ hquery->channel->ndomains)) {
|
||
|
+ s = hquery->name;
|
||
|
+ }
|
||
|
+
|
||
|
+ if (!s && hquery->next_domain < hquery->channel->ndomains) {
|
||
|
+ status = ares__cat_domain(
|
||
|
+ hquery->name,
|
||
|
+ hquery->channel->domains[hquery->next_domain++],
|
||
|
+ &s);
|
||
|
+ if (status == ARES_SUCCESS) {
|
||
|
+ is_s_allocated = 1;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ if (s) {
|
||
|
+ if (hquery->ai_family == AF_INET || hquery->ai_family == AF_UNSPEC) {
|
||
|
+ ares_query(hquery->channel, s, C_IN, T_A, host_callback, hquery);
|
||
|
+ }
|
||
|
+ if (hquery->ai_family == AF_INET6 || hquery->ai_family == AF_UNSPEC) {
|
||
|
+ ares_query(hquery->channel, s, C_IN, T_AAAA, host_callback, hquery);
|
||
|
+ }
|
||
|
+ if (is_s_allocated) {
|
||
|
+ ares_free(s);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ assert(!hquery->ai);
|
||
|
+ end_hquery(hquery, ARES_ENOTFOUND);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void end_hquery(struct host_query *hquery, int status) {
|
||
|
+ if (hquery->ai) {
|
||
|
+ hquery->callback(hquery->arg, status, hquery->ai);
|
||
|
+ }
|
||
|
+ ares_free(hquery->name);
|
||
|
+ ares_free(hquery);
|
||
|
+}
|
||
|
+
|
||
|
+static void host_callback(void *arg, int status, int timeouts,
|
||
|
+ unsigned char *abuf, int alen) {
|
||
|
+ struct host_query *hquery = (struct host_query*)arg;
|
||
|
+ ares_channel channel = hquery->channel;
|
||
|
+ struct hostent *host = NULL;
|
||
|
+ int qtype;
|
||
|
+ hquery->timeouts += timeouts;
|
||
|
+
|
||
|
+ if (status == ARES_SUCCESS) {
|
||
|
+ status = ares__parse_qtype_reply(abuf, alen, &qtype);
|
||
|
+ if (status == ARES_SUCCESS && qtype == T_A) {
|
||
|
+ /* Can ares_parse_a_reply be unsuccessful (after parse_qtype) */
|
||
|
+ ares_parse_a_reply(abuf, alen, &host, NULL, NULL);
|
||
|
+ if (host && channel->nsort) {
|
||
|
+ sort_addresses(host, channel->sortlist, channel->nsort);
|
||
|
+ }
|
||
|
+ add_to_addrinfo(&hquery->ai, host);
|
||
|
+ ares_free_hostent(host);
|
||
|
+ if (!--hquery->remaining) {
|
||
|
+ end_hquery(hquery, ARES_SUCCESS);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else if (status == ARES_SUCCESS && qtype == T_AAAA) {
|
||
|
+ /* Can ares_parse_a_reply be unsuccessful (after parse_qtype) */
|
||
|
+ ares_parse_aaaa_reply(abuf, alen, &host, NULL, NULL);
|
||
|
+ if (host && channel->nsort) {
|
||
|
+ sort6_addresses(host, channel->sortlist, channel->nsort);
|
||
|
+ }
|
||
|
+ add_to_addrinfo(&hquery->ai, host);
|
||
|
+ ares_free_hostent(host);
|
||
|
+ if (!--hquery->remaining) {
|
||
|
+ end_hquery(hquery, ARES_SUCCESS);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ assert(!hquery->ai);
|
||
|
+ end_hquery(hquery, status);
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ next_dns_lookup(hquery);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void sort_addresses(struct hostent *host,
|
||
|
+ const struct apattern *sortlist, int nsort) {
|
||
|
+ struct in_addr a1, a2;
|
||
|
+ int i1, i2, ind1, ind2;
|
||
|
+
|
||
|
+ /* This is a simple insertion sort, not optimized at all. i1 walks
|
||
|
+ * through the address list, with the loop invariant that everything
|
||
|
+ * to the left of i1 is sorted. In the loop body, the value at i1 is moved
|
||
|
+ * back through the list (via i2) until it is in sorted order.
|
||
|
+ */
|
||
|
+ for (i1 = 0; host->h_addr_list[i1]; i1++) {
|
||
|
+ memcpy(&a1, host->h_addr_list[i1], sizeof(struct in_addr));
|
||
|
+ ind1 = get_address_index(&a1, sortlist, nsort);
|
||
|
+ for (i2 = i1 - 1; i2 >= 0; i2--) {
|
||
|
+ memcpy(&a2, host->h_addr_list[i2], sizeof(struct in_addr));
|
||
|
+ ind2 = get_address_index(&a2, sortlist, nsort);
|
||
|
+ if (ind2 <= ind1) {
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ memcpy(host->h_addr_list[i2 + 1], &a2, sizeof(struct in_addr));
|
||
|
+ }
|
||
|
+ memcpy(host->h_addr_list[i2 + 1], &a1, sizeof(struct in_addr));
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+/* Find the first entry in sortlist which matches addr. Return nsort
|
||
|
+ * if none of them match.
|
||
|
+ */
|
||
|
+static int get_address_index(const struct in_addr *addr,
|
||
|
+ const struct apattern *sortlist,
|
||
|
+ int nsort) {
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < nsort; i++) {
|
||
|
+ if (sortlist[i].family != AF_INET) {
|
||
|
+ continue;
|
||
|
+ }
|
||
|
+ if (sortlist[i].type == PATTERN_MASK) {
|
||
|
+ if ((addr->s_addr & sortlist[i].mask.addr4.s_addr) ==
|
||
|
+ sortlist[i].addrV4.s_addr) {
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ else {
|
||
|
+ if (!ares__bitncmp(&addr->s_addr, &sortlist[i].addrV4.s_addr,
|
||
|
+ sortlist[i].mask.bits)) {
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return i;
|
||
|
+}
|
||
|
+
|
||
|
+static void sort6_addresses(struct hostent *host,
|
||
|
+ const struct apattern *sortlist, int nsort) {
|
||
|
+ struct ares_in6_addr a1, a2;
|
||
|
+ int i1, i2, ind1, ind2;
|
||
|
+
|
||
|
+ /* This is a simple insertion sort, not optimized at all. i1 walks
|
||
|
+ * through the address list, with the loop invariant that everything
|
||
|
+ * to the left of i1 is sorted. In the loop body, the value at i1 is moved
|
||
|
+ * back through the list (via i2) until it is in sorted order.
|
||
|
+ */
|
||
|
+ for (i1 = 0; host->h_addr_list[i1]; i1++) {
|
||
|
+ memcpy(&a1, host->h_addr_list[i1], sizeof(struct ares_in6_addr));
|
||
|
+ ind1 = get6_address_index(&a1, sortlist, nsort);
|
||
|
+ for (i2 = i1 - 1; i2 >= 0; i2--) {
|
||
|
+ memcpy(&a2, host->h_addr_list[i2], sizeof(struct ares_in6_addr));
|
||
|
+ ind2 = get6_address_index(&a2, sortlist, nsort);
|
||
|
+ if (ind2 <= ind1) {
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ memcpy(host->h_addr_list[i2 + 1], &a2, sizeof(struct ares_in6_addr));
|
||
|
+ }
|
||
|
+ memcpy(host->h_addr_list[i2 + 1], &a1, sizeof(struct ares_in6_addr));
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+/* Find the first entry in sortlist which matches addr. Return nsort
|
||
|
+ * if none of them match.
|
||
|
+ */
|
||
|
+static int get6_address_index(const struct ares_in6_addr *addr,
|
||
|
+ const struct apattern *sortlist,
|
||
|
+ int nsort) {
|
||
|
+ int i;
|
||
|
+
|
||
|
+ for (i = 0; i < nsort; i++) {
|
||
|
+ if (sortlist[i].family != AF_INET6)
|
||
|
+ continue;
|
||
|
+ if (!ares__bitncmp(addr, &sortlist[i].addrV6, sortlist[i].mask.bits))
|
||
|
+ break;
|
||
|
+ }
|
||
|
+ return i;
|
||
|
+}
|
||
|
+
|
||
|
+static int as_is_first(const struct host_query* hquery) {
|
||
|
+ char* p;
|
||
|
+ int ndots = 0;
|
||
|
+ for (p = hquery->name; *p; p++) {
|
||
|
+ if (*p == '.') {
|
||
|
+ ndots++;
|
||
|
+ }
|
||
|
+ }
|
||
|
+ return ndots >= hquery->channel->ndots;
|
||
|
+}
|
||
|
+
|
||
|
diff --git a/ares_parse_a_reply.c b/ares_parse_a_reply.c
|
||
|
index 0422bd3..4fb6d14 100644
|
||
|
--- a/ares_parse_a_reply.c
|
||
|
+++ b/ares_parse_a_reply.c
|
||
|
@@ -262,3 +262,41 @@ int ares_parse_a_reply(const unsigned char *abuf, int alen,
|
||
|
ares_free(hostname);
|
||
|
return status;
|
||
|
}
|
||
|
+
|
||
|
+/* returned size includes terminating 0. */
|
||
|
+static long encoded_name_size(const unsigned char *begin, const unsigned char *end)
|
||
|
+{
|
||
|
+ const unsigned char* it = begin;
|
||
|
+ for (; *it && it != end; ++it);
|
||
|
+ return it == end ? -1 : (long)((it + 1) - begin);
|
||
|
+}
|
||
|
+
|
||
|
+int ares__parse_qtype_reply(const unsigned char* abuf, int alen, int* qtype)
|
||
|
+{
|
||
|
+ unsigned int qdcount, ancount;
|
||
|
+ const unsigned char* aptr;
|
||
|
+ long len;
|
||
|
+
|
||
|
+ /* Give up if abuf doesn't have room for a header. */
|
||
|
+ if (alen < HFIXEDSZ)
|
||
|
+ return ARES_EBADRESP;
|
||
|
+
|
||
|
+ /* Fetch the question and answer count from the header. */
|
||
|
+ qdcount = DNS_HEADER_QDCOUNT(abuf);
|
||
|
+ ancount = DNS_HEADER_ANCOUNT(abuf);
|
||
|
+ if (qdcount != 1)
|
||
|
+ return ARES_EBADRESP;
|
||
|
+
|
||
|
+ /* Expand the name from the question, and skip past the question. */
|
||
|
+ aptr = abuf + HFIXEDSZ;
|
||
|
+ len = encoded_name_size(aptr, abuf + alen);
|
||
|
+ if (len == -1)
|
||
|
+ return ARES_EBADRESP;
|
||
|
+ if (aptr + len + QFIXEDSZ > abuf + alen)
|
||
|
+ return ARES_EBADRESP;
|
||
|
+ aptr += len;
|
||
|
+ if (!ancount)
|
||
|
+ return ARES_ENODATA;
|
||
|
+ *qtype = DNS__16BIT(aptr);
|
||
|
+ return ARES_SUCCESS;
|
||
|
+}
|
||
|
diff --git a/ares_private.h b/ares_private.h
|
||
|
index 1990f69..8e16256 100644
|
||
|
--- a/ares_private.h
|
||
|
+++ b/ares_private.h
|
||
|
@@ -355,6 +355,10 @@ int ares__expand_name_for_response(const unsigned char *encoded,
|
||
|
char **s, long *enclen);
|
||
|
void ares__init_servers_state(ares_channel channel);
|
||
|
void ares__destroy_servers_state(ares_channel channel);
|
||
|
+int ares__parse_qtype_reply(const unsigned char* abuf, int alen, int* qtype);
|
||
|
+int ares__single_domain(ares_channel channel, const char *name, char **s);
|
||
|
+int ares__cat_domain(const char *name, const char *domain, char **s);
|
||
|
+
|
||
|
#if 0 /* Not used */
|
||
|
long ares__tvdiff(struct timeval t1, struct timeval t2);
|
||
|
#endif
|
||
|
diff --git a/ares_search.c b/ares_search.c
|
||
|
index 001c348..c4b0424 100644
|
||
|
--- a/ares_search.c
|
||
|
+++ b/ares_search.c
|
||
|
@@ -43,8 +43,6 @@ static void search_callback(void *arg, int status, int timeouts,
|
||
|
unsigned char *abuf, int alen);
|
||
|
static void end_squery(struct search_query *squery, int status,
|
||
|
unsigned char *abuf, int alen);
|
||
|
-static int cat_domain(const char *name, const char *domain, char **s);
|
||
|
-STATIC_TESTABLE int single_domain(ares_channel channel, const char *name, char **s);
|
||
|
|
||
|
void ares_search(ares_channel channel, const char *name, int dnsclass,
|
||
|
int type, ares_callback callback, void *arg)
|
||
|
@@ -64,7 +62,7 @@ void ares_search(ares_channel channel, const char *name, int dnsclass,
|
||
|
/* If name only yields one domain to search, then we don't have
|
||
|
* to keep extra state, so just do an ares_query().
|
||
|
*/
|
||
|
- status = single_domain(channel, name, &s);
|
||
|
+ status = ares__single_domain(channel, name, &s);
|
||
|
if (status != ARES_SUCCESS)
|
||
|
{
|
||
|
callback(arg, status, 0, NULL, 0);
|
||
|
@@ -126,7 +124,7 @@ void ares_search(ares_channel channel, const char *name, int dnsclass,
|
||
|
/* Try the name as-is last; start with the first search domain. */
|
||
|
squery->next_domain = 1;
|
||
|
squery->trying_as_is = 0;
|
||
|
- status = cat_domain(name, channel->domains[0], &s);
|
||
|
+ status = ares__cat_domain(name, channel->domains[0], &s);
|
||
|
if (status == ARES_SUCCESS)
|
||
|
{
|
||
|
ares_query(channel, s, dnsclass, type, search_callback, squery);
|
||
|
@@ -174,7 +172,7 @@ static void search_callback(void *arg, int status, int timeouts,
|
||
|
if (squery->next_domain < channel->ndomains)
|
||
|
{
|
||
|
/* Try the next domain. */
|
||
|
- status = cat_domain(squery->name,
|
||
|
+ status = ares__cat_domain(squery->name,
|
||
|
channel->domains[squery->next_domain], &s);
|
||
|
if (status != ARES_SUCCESS)
|
||
|
end_squery(squery, status, NULL, 0);
|
||
|
@@ -213,7 +211,7 @@ static void end_squery(struct search_query *squery, int status,
|
||
|
}
|
||
|
|
||
|
/* Concatenate two domains. */
|
||
|
-static int cat_domain(const char *name, const char *domain, char **s)
|
||
|
+int ares__cat_domain(const char *name, const char *domain, char **s)
|
||
|
{
|
||
|
size_t nlen = strlen(name);
|
||
|
size_t dlen = strlen(domain);
|
||
|
@@ -232,7 +230,7 @@ static int cat_domain(const char *name, const char *domain, char **s)
|
||
|
* the string we should query, in an allocated buffer. If not, set *s
|
||
|
* to NULL.
|
||
|
*/
|
||
|
-STATIC_TESTABLE int single_domain(ares_channel channel, const char *name, char **s)
|
||
|
+int ares__single_domain(ares_channel channel, const char *name, char **s)
|
||
|
{
|
||
|
size_t len = strlen(name);
|
||
|
const char *hostaliases;
|
||
|
diff --git a/test/Makefile.inc b/test/Makefile.inc
|
||
|
index b25f2e2..7952b4c 100644
|
||
|
--- a/test/Makefile.inc
|
||
|
+++ b/test/Makefile.inc
|
||
|
@@ -1,6 +1,7 @@
|
||
|
TESTSOURCES = ares-test-main.cc \
|
||
|
ares-test-init.cc \
|
||
|
ares-test.cc \
|
||
|
+ ares-test-ai.cc \
|
||
|
ares-test-ns.cc \
|
||
|
ares-test-parse.cc \
|
||
|
ares-test-parse-a.cc \
|
||
|
@@ -14,12 +15,14 @@ TESTSOURCES = ares-test-main.cc \
|
||
|
ares-test-parse-txt.cc \
|
||
|
ares-test-misc.cc \
|
||
|
ares-test-mock.cc \
|
||
|
+ ares-test-mock-ai.cc \
|
||
|
ares-test-internal.cc \
|
||
|
dns-proto.cc \
|
||
|
dns-proto-test.cc
|
||
|
|
||
|
TESTHEADERS = ares-test.h \
|
||
|
- dns-proto.h
|
||
|
+ dns-proto.h \
|
||
|
+ ares-test-ai.h
|
||
|
|
||
|
FUZZSOURCES = ares-test-fuzz.c \
|
||
|
ares-fuzz.c
|
||
|
diff --git a/test/ares-test-ai.cc b/test/ares-test-ai.cc
|
||
|
new file mode 100644
|
||
|
index 0000000..e69de29
|
||
|
diff --git a/test/ares-test-ai.h b/test/ares-test-ai.h
|
||
|
new file mode 100644
|
||
|
index 0000000..e4c4403
|
||
|
--- /dev/null
|
||
|
+++ b/test/ares-test-ai.h
|
||
|
@@ -0,0 +1,32 @@
|
||
|
+#ifndef ARES_TEST_AI_H
|
||
|
+#define ARES_TEST_AI_H
|
||
|
+
|
||
|
+#include <utility>
|
||
|
+#include "gtest/gtest.h"
|
||
|
+#include "gmock/gmock.h"
|
||
|
+#include "ares-test.h"
|
||
|
+
|
||
|
+namespace ares {
|
||
|
+namespace test {
|
||
|
+
|
||
|
+class MockChannelTestAI
|
||
|
+ : public MockChannelOptsTest,
|
||
|
+ public ::testing::WithParamInterface< std::pair<int, bool> > {
|
||
|
+ public:
|
||
|
+ MockChannelTestAI() : MockChannelOptsTest(1, GetParam().first, GetParam().second, nullptr, 0) {}
|
||
|
+};
|
||
|
+
|
||
|
+// Structure that describes the result of an ares_addr_callback invocation.
|
||
|
+struct AIResult {
|
||
|
+ // Whether the callback has been invoked.
|
||
|
+ bool done;
|
||
|
+ // Explicitly provided result information.
|
||
|
+ int status;
|
||
|
+ // Contents of the ares_addrinfo structure, if provided.
|
||
|
+ struct ares_addrinfo* airesult;
|
||
|
+};
|
||
|
+
|
||
|
+}
|
||
|
+}
|
||
|
+
|
||
|
+#endif
|
||
|
diff --git a/test/ares-test-internal.cc b/test/ares-test-internal.cc
|
||
|
index a021033..a2d31c5 100644
|
||
|
--- a/test/ares-test-internal.cc
|
||
|
+++ b/test/ares-test-internal.cc
|
||
|
@@ -383,23 +383,23 @@ TEST_F(LibraryTest, Striendstr) {
|
||
|
const char *str = "plugh";
|
||
|
EXPECT_NE(nullptr, ares_striendstr(str, str));
|
||
|
}
|
||
|
-extern "C" int single_domain(ares_channel, const char*, char**);
|
||
|
+extern "C" int ares__single_domain(ares_channel, const char*, char**);
|
||
|
TEST_F(DefaultChannelTest, SingleDomain) {
|
||
|
TempFile aliases("www www.google.com\n");
|
||
|
EnvValue with_env("HOSTALIASES", aliases.filename());
|
||
|
|
||
|
SetAllocSizeFail(128);
|
||
|
char *ptr = nullptr;
|
||
|
- EXPECT_EQ(ARES_ENOMEM, single_domain(channel_, "www", &ptr));
|
||
|
+ EXPECT_EQ(ARES_ENOMEM, ares__single_domain(channel_, "www", &ptr));
|
||
|
|
||
|
channel_->flags |= ARES_FLAG_NOSEARCH|ARES_FLAG_NOALIASES;
|
||
|
- EXPECT_EQ(ARES_SUCCESS, single_domain(channel_, "www", &ptr));
|
||
|
+ EXPECT_EQ(ARES_SUCCESS, ares__single_domain(channel_, "www", &ptr));
|
||
|
EXPECT_EQ("www", std::string(ptr));
|
||
|
ares_free(ptr);
|
||
|
ptr = nullptr;
|
||
|
|
||
|
SetAllocFail(1);
|
||
|
- EXPECT_EQ(ARES_ENOMEM, single_domain(channel_, "www", &ptr));
|
||
|
+ EXPECT_EQ(ARES_ENOMEM, ares__single_domain(channel_, "www", &ptr));
|
||
|
EXPECT_EQ(nullptr, ptr);
|
||
|
}
|
||
|
#endif
|
||
|
diff --git a/test/ares-test-mock-ai.cc b/test/ares-test-mock-ai.cc
|
||
|
new file mode 100644
|
||
|
index 0000000..8ba1611
|
||
|
--- /dev/null
|
||
|
+++ b/test/ares-test-mock-ai.cc
|
||
|
@@ -0,0 +1,154 @@
|
||
|
+#include "ares-test-ai.h"
|
||
|
+#include "dns-proto.h"
|
||
|
+
|
||
|
+#ifdef HAVE_NETDB_H
|
||
|
+#include <netdb.h>
|
||
|
+#endif
|
||
|
+
|
||
|
+#include <arpa/inet.h>
|
||
|
+#include <sstream>
|
||
|
+#include <vector>
|
||
|
+
|
||
|
+using testing::InvokeWithoutArgs;
|
||
|
+using testing::DoAll;
|
||
|
+
|
||
|
+namespace ares {
|
||
|
+namespace test {
|
||
|
+
|
||
|
+MATCHER_P(IncludesNumAddresses, n, "") {
|
||
|
+ int cnt = 0;
|
||
|
+ for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next)
|
||
|
+ cnt++;
|
||
|
+ return n == cnt;
|
||
|
+}
|
||
|
+
|
||
|
+MATCHER_P(IncludesV4Address, address, "") {
|
||
|
+ in_addr addressnum = {};
|
||
|
+ if (!inet_pton(AF_INET, address, &addressnum))
|
||
|
+ return false; // wrong number format?
|
||
|
+ for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next) {
|
||
|
+ if (ai->ai_family != AF_INET)
|
||
|
+ continue;
|
||
|
+ if (reinterpret_cast<sockaddr_in*>(ai->ai_addr)->sin_addr.s_addr ==
|
||
|
+ addressnum.s_addr)
|
||
|
+ return true; // found
|
||
|
+ }
|
||
|
+ return false;
|
||
|
+}
|
||
|
+
|
||
|
+MATCHER_P(IncludesV6Address, address, "") {
|
||
|
+ in6_addr addressnum = {};
|
||
|
+ if (!inet_pton(AF_INET6, address, &addressnum)) {
|
||
|
+ return false; // wrong number format?
|
||
|
+ }
|
||
|
+ for (const ares_addrinfo* ai = arg; ai != NULL; ai = ai->ai_next) {
|
||
|
+ if (ai->ai_family != AF_INET6)
|
||
|
+ continue;
|
||
|
+ if (!memcmp(
|
||
|
+ reinterpret_cast<sockaddr_in6*>(ai->ai_addr)->sin6_addr.s6_addr,
|
||
|
+ addressnum.s6_addr, sizeof(addressnum.s6_addr)))
|
||
|
+ return true; // found
|
||
|
+ }
|
||
|
+ return false;
|
||
|
+}
|
||
|
+
|
||
|
+TEST_P(MockChannelTestAI, FamilyV6) {
|
||
|
+ DNSPacket rsp6;
|
||
|
+ rsp6.set_response().set_aa()
|
||
|
+ .add_question(new DNSQuestion("example.com", ns_t_aaaa))
|
||
|
+ .add_answer(new DNSAaaaRR("example.com", 100,
|
||
|
+ {0x21, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03}));
|
||
|
+ ON_CALL(server_, OnRequest("example.com", ns_t_aaaa))
|
||
|
+ .WillByDefault(SetReply(&server_, &rsp6));
|
||
|
+ AIResult result;
|
||
|
+ struct ares_addrinfo hints = {};
|
||
|
+ hints.ai_family = AF_INET6;
|
||
|
+ ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
|
||
|
+ AICallback, &result);
|
||
|
+ Process();
|
||
|
+ EXPECT_TRUE(result.done);
|
||
|
+ EXPECT_EQ(result.status, ARES_SUCCESS);
|
||
|
+ EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
|
||
|
+ ares_freeaddrinfo(result.airesult);
|
||
|
+}
|
||
|
+
|
||
|
+
|
||
|
+TEST_P(MockChannelTestAI, FamilyV4) {
|
||
|
+ DNSPacket rsp4;
|
||
|
+ rsp4.set_response().set_aa()
|
||
|
+ .add_question(new DNSQuestion("example.com", ns_t_a))
|
||
|
+ .add_answer(new DNSARR("example.com", 100, {2, 3, 4, 5}));
|
||
|
+ ON_CALL(server_, OnRequest("example.com", ns_t_a))
|
||
|
+ .WillByDefault(SetReply(&server_, &rsp4));
|
||
|
+ AIResult result = {};
|
||
|
+ struct ares_addrinfo hints = {};
|
||
|
+ hints.ai_family = AF_INET;
|
||
|
+ ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
|
||
|
+ AICallback, &result);
|
||
|
+ Process();
|
||
|
+ EXPECT_TRUE(result.done);
|
||
|
+ EXPECT_EQ(result.status, ARES_SUCCESS);
|
||
|
+ EXPECT_THAT(result.airesult, IncludesNumAddresses(1));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
|
||
|
+ ares_freeaddrinfo(result.airesult);
|
||
|
+}
|
||
|
+
|
||
|
+TEST_P(MockChannelTestAI, FamilyV4_MultipleAddresses) {
|
||
|
+ DNSPacket rsp4;
|
||
|
+ rsp4.set_response().set_aa()
|
||
|
+ .add_question(new DNSQuestion("example.com", ns_t_a))
|
||
|
+ .add_answer(new DNSARR("example.com", 100, {2, 3, 4, 5}))
|
||
|
+ .add_answer(new DNSARR("example.com", 100, {7, 8, 9, 0}));
|
||
|
+ ON_CALL(server_, OnRequest("example.com", ns_t_a))
|
||
|
+ .WillByDefault(SetReply(&server_, &rsp4));
|
||
|
+ AIResult result = {};
|
||
|
+ struct ares_addrinfo hints = {};
|
||
|
+ hints.ai_family = AF_INET;
|
||
|
+ ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
|
||
|
+ AICallback, &result);
|
||
|
+ Process();
|
||
|
+ EXPECT_TRUE(result.done);
|
||
|
+ EXPECT_EQ(result.status, ARES_SUCCESS);
|
||
|
+ EXPECT_THAT(result.airesult, IncludesNumAddresses(2));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV4Address("7.8.9.0"));
|
||
|
+ ares_freeaddrinfo(result.airesult);
|
||
|
+}
|
||
|
+
|
||
|
+TEST_P(MockChannelTestAI, FamilyUnspecified) {
|
||
|
+ DNSPacket rsp6;
|
||
|
+ rsp6.set_response().set_aa()
|
||
|
+ .add_question(new DNSQuestion("example.com", ns_t_aaaa))
|
||
|
+ .add_answer(new DNSAaaaRR("example.com", 100,
|
||
|
+ {0x21, 0x21, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||
|
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03}));
|
||
|
+ ON_CALL(server_, OnRequest("example.com", ns_t_aaaa))
|
||
|
+ .WillByDefault(SetReply(&server_, &rsp6));
|
||
|
+ DNSPacket rsp4;
|
||
|
+ rsp4.set_response().set_aa()
|
||
|
+ .add_question(new DNSQuestion("example.com", ns_t_a))
|
||
|
+ .add_answer(new DNSARR("example.com", 100, {2, 3, 4, 5}));
|
||
|
+ ON_CALL(server_, OnRequest("example.com", ns_t_a))
|
||
|
+ .WillByDefault(SetReply(&server_, &rsp4));
|
||
|
+ AIResult result;
|
||
|
+ struct ares_addrinfo hints = {};
|
||
|
+ hints.ai_family = AF_UNSPEC;
|
||
|
+ ares_getaddrinfo(channel_, "example.com.", NULL, &hints,
|
||
|
+ AICallback, &result);
|
||
|
+ Process();
|
||
|
+ EXPECT_TRUE(result.done);
|
||
|
+ EXPECT_EQ(result.status, ARES_SUCCESS);
|
||
|
+ EXPECT_THAT(result.airesult, IncludesNumAddresses(2));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV4Address("2.3.4.5"));
|
||
|
+ EXPECT_THAT(result.airesult, IncludesV6Address("2121:0000:0000:0000:0000:0000:0000:0303"));
|
||
|
+ ares_freeaddrinfo(result.airesult);
|
||
|
+}
|
||
|
+
|
||
|
+INSTANTIATE_TEST_CASE_P(AddressFamilies, MockChannelTestAI,
|
||
|
+ ::testing::Values(std::make_pair<int, bool>(AF_INET, false)));
|
||
|
+
|
||
|
+
|
||
|
+} // namespace test
|
||
|
+} // namespace ares
|
||
|
diff --git a/test/ares-test.cc b/test/ares-test.cc
|
||
|
index 594320d..7776548 100644
|
||
|
--- a/test/ares-test.cc
|
||
|
+++ b/test/ares-test.cc
|
||
|
@@ -1,4 +1,5 @@
|
||
|
#include "ares-test.h"
|
||
|
+#include "ares-test-ai.h"
|
||
|
#include "dns-proto.h"
|
||
|
|
||
|
// Include ares internal files for DNS protocol details
|
||
|
@@ -564,6 +565,16 @@ void HostCallback(void *data, int status, int timeouts,
|
||
|
if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
|
||
|
}
|
||
|
|
||
|
+void AICallback(void *data, int status,
|
||
|
+ struct ares_addrinfo *res) {
|
||
|
+ EXPECT_NE(nullptr, data);
|
||
|
+ AIResult* result = reinterpret_cast<AIResult*>(data);
|
||
|
+ result->done = true;
|
||
|
+ result->status = status;
|
||
|
+ result->airesult = res;
|
||
|
+ //if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
|
||
|
+}
|
||
|
+
|
||
|
std::ostream& operator<<(std::ostream& os, const SearchResult& result) {
|
||
|
os << '{';
|
||
|
if (result.done_) {
|
||
|
diff --git a/test/ares-test.h b/test/ares-test.h
|
||
|
index abcc508..03e15ec 100644
|
||
|
--- a/test/ares-test.h
|
||
|
+++ b/test/ares-test.h
|
||
|
@@ -287,6 +287,8 @@ void SearchCallback(void *data, int status, int timeouts,
|
||
|
unsigned char *abuf, int alen);
|
||
|
void NameInfoCallback(void *data, int status, int timeouts,
|
||
|
char *node, char *service);
|
||
|
+void AICallback(void *data, int status,
|
||
|
+ struct ares_addrinfo *res);
|
||
|
|
||
|
// Retrieve the name servers used by a channel.
|
||
|
std::vector<std::string> GetNameServers(ares_channel channel);
|
||
|
--
|
||
|
2.16.4
|
||
|
|