From 63ceb27adae045aa7e137663253d98060b5f05be Mon Sep 17 00:00:00 2001 From: Christian Ammer 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 +.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 +.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 +#endif +#ifdef HAVE_NETDB_H +# include +#endif +#ifdef HAVE_ARPA_INET_H +# include +#endif +#ifdef HAVE_ARPA_NAMESER_H +# include +#else +# include "nameser.h" +#endif +#ifdef HAVE_ARPA_NAMESER_COMPAT_H +# include +#endif + +#ifdef HAVE_STRINGS_H +#include +#endif +#include + +#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 +#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 > { + 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 +#endif + +#include +#include +#include + +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(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(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(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(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 GetNameServers(ares_channel channel); -- 2.16.4