A C library for asynchronous DNS requests (grpc依赖)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

877 lines
23 KiB

/* MIT License
*
* Copyright (c) 1998, 2011, 2013 Massachusetts Institute of Technology
* Copyright (c) 2017 Christian Ammer
* Copyright (c) 2019 Andrew Selivanov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice (including the next
* paragraph) shall be included in all copies or substantial portions of the
* Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#ifdef HAVE_GETSERVBYNAME_R
# if !defined(GETSERVBYNAME_R_ARGS) || \
(GETSERVBYNAME_R_ARGS < 4) || (GETSERVBYNAME_R_ARGS > 6)
# error "you MUST specifiy a valid number of arguments for getservbyname_r"
# endif
#endif
#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
#include "ares_nameser.h"
#ifdef HAVE_STRINGS_H
#include <strings.h>
#endif
#include <assert.h>
#ifdef HAVE_LIMITS_H
#include <limits.h>
#endif
#include "ares.h"
#include "bitncmp.h"
#include "ares_private.h"
#include "ares_dns.h"
#ifdef WATT32
#undef WIN32
#endif
#ifdef WIN32
# include "ares_platform.h"
#endif
struct host_query
{
ares_channel channel;
char *name;
unsigned short port; /* in host order */
ares_addrinfo_callback callback;
void *arg;
struct ares_addrinfo_hints hints;
int sent_family; /* this family is what was is being used */
int timeouts; /* number of timeouts we saw for this request */
const char *remaining_lookups; /* types of lookup we need to perform ("fb" by
default, file and dns respectively) */
struct ares_addrinfo *ai; /* store results between lookups */
unsigned short qid_a; /* qid for A request */
unsigned short qid_aaaa; /* qid for AAAA request */
int remaining; /* number of DNS answers waiting for */
int next_domain; /* next search domain to try */
int nodata_cnt; /* Track nodata responses to possibly override final result */
};
static const struct ares_addrinfo_hints default_hints = {
0, /* ai_flags */
AF_UNSPEC, /* ai_family */
0, /* ai_socktype */
0, /* ai_protocol */
};
static const struct ares_addrinfo_cname empty_addrinfo_cname = {
INT_MAX, /* ttl */
NULL, /* alias */
NULL, /* name */
NULL, /* next */
};
static const struct ares_addrinfo_node empty_addrinfo_node = {
0, /* ai_ttl */
0, /* ai_flags */
0, /* ai_family */
0, /* ai_socktype */
0, /* ai_protocol */
0, /* ai_addrlen */
NULL, /* ai_addr */
NULL /* ai_next */
};
static const struct ares_addrinfo empty_addrinfo = {
NULL, /* cnames */
NULL, /* nodes */
NULL /* name */
};
/* forward declarations */
static void host_callback(void *arg, int status, int timeouts,
unsigned char *abuf, int alen);
static int as_is_first(const struct host_query *hquery);
static int as_is_only(const struct host_query* hquery);
static int next_dns_lookup(struct host_query *hquery);
static struct ares_addrinfo_cname *ares__malloc_addrinfo_cname(void)
{
struct ares_addrinfo_cname *cname = ares_malloc(sizeof(struct ares_addrinfo_cname));
if (!cname)
return NULL;
*cname = empty_addrinfo_cname;
return cname;
}
struct ares_addrinfo_cname *ares__append_addrinfo_cname(struct ares_addrinfo_cname **head)
{
struct ares_addrinfo_cname *tail = ares__malloc_addrinfo_cname();
struct ares_addrinfo_cname *last = *head;
if (!last)
{
*head = tail;
return tail;
}
while (last->next)
{
last = last->next;
}
last->next = tail;
return tail;
}
void ares__addrinfo_cat_cnames(struct ares_addrinfo_cname **head,
struct ares_addrinfo_cname *tail)
{
struct ares_addrinfo_cname *last = *head;
if (!last)
{
*head = tail;
return;
}
while (last->next)
{
last = last->next;
}
last->next = tail;
}
static struct ares_addrinfo *ares__malloc_addrinfo(void)
{
struct ares_addrinfo *ai = ares_malloc(sizeof(struct ares_addrinfo));
if (!ai)
return NULL;
*ai = empty_addrinfo;
return ai;
}
static struct ares_addrinfo_node *ares__malloc_addrinfo_node(void)
{
struct ares_addrinfo_node *node =
ares_malloc(sizeof(*node));
if (!node)
return NULL;
*node = empty_addrinfo_node;
return node;
}
/* Allocate new addrinfo and append to the tail. */
struct ares_addrinfo_node *ares__append_addrinfo_node(struct ares_addrinfo_node **head)
{
struct ares_addrinfo_node *tail = ares__malloc_addrinfo_node();
struct ares_addrinfo_node *last = *head;
if (!last)
{
*head = tail;
return tail;
}
while (last->ai_next)
{
last = last->ai_next;
}
last->ai_next = tail;
return tail;
}
void ares__addrinfo_cat_nodes(struct ares_addrinfo_node **head,
struct ares_addrinfo_node *tail)
{
struct ares_addrinfo_node *last = *head;
if (!last)
{
*head = tail;
return;
}
while (last->ai_next)
{
last = last->ai_next;
}
last->ai_next = tail;
}
/* Resolve service name into port number given in host byte order.
* If not resolved, return 0.
*/
static unsigned short lookup_service(const char *service, int flags)
{
const char *proto;
struct servent *sep;
#ifdef HAVE_GETSERVBYNAME_R
struct servent se;
char tmpbuf[4096];
#endif
if (service)
{
if (flags & ARES_NI_UDP)
proto = "udp";
else if (flags & ARES_NI_SCTP)
proto = "sctp";
else if (flags & ARES_NI_DCCP)
proto = "dccp";
else
proto = "tcp";
#ifdef HAVE_GETSERVBYNAME_R
memset(&se, 0, sizeof(se));
sep = &se;
memset(tmpbuf, 0, sizeof(tmpbuf));
#if GETSERVBYNAME_R_ARGS == 6
if (getservbyname_r(service, proto, &se, (void *)tmpbuf, sizeof(tmpbuf),
&sep) != 0)
sep = NULL; /* LCOV_EXCL_LINE: buffer large so this never fails */
#elif GETSERVBYNAME_R_ARGS == 5
sep =
getservbyname_r(service, proto, &se, (void *)tmpbuf, sizeof(tmpbuf));
#elif GETSERVBYNAME_R_ARGS == 4
if (getservbyname_r(service, proto, &se, (void *)tmpbuf) != 0)
sep = NULL;
#else
/* Lets just hope the OS uses TLS! */
sep = getservbyname(service, proto);
#endif
#else
/* Lets just hope the OS uses TLS! */
#if (defined(NETWARE) && !defined(__NOVELL_LIBC__))
sep = getservbyname(service, (char *)proto);
#else
sep = getservbyname(service, proto);
#endif
#endif
return (sep ? ntohs((unsigned short)sep->s_port) : 0);
}
return 0;
}
/* If the name looks like an IP address or an error occured,
* fake up a host entry, end the query immediately, and return true.
* Otherwise return false.
*/
static int fake_addrinfo(const char *name,
unsigned short port,
const struct ares_addrinfo_hints *hints,
struct ares_addrinfo *ai,
ares_addrinfo_callback callback,
void *arg)
{
struct ares_addrinfo_cname *cname;
int status = ARES_SUCCESS;
int result = 0;
int family = hints->ai_family;
if (family == AF_INET || family == AF_INET6 || family == AF_UNSPEC)
{
/* It only looks like an IP address if it's all numbers and dots. */
int numdots = 0, valid = 1;
const char *p;
for (p = name; *p; p++)
{
if (!ISDIGIT(*p) && *p != '.')
{
valid = 0;
break;
}
else if (*p == '.')
{
numdots++;
}
}
/* if we don't have 3 dots, it is illegal
* (although inet_pton doesn't think so).
*/
if (numdots != 3 || !valid)
result = 0;
else
{
struct in_addr addr4;
result = ares_inet_pton(AF_INET, name, &addr4) < 1 ? 0 : 1;
if (result)
{
status = ares_append_ai_node(AF_INET, port, 0, &addr4, &ai->nodes);
if (status != ARES_SUCCESS)
{
callback(arg, status, 0, NULL);
return 1;
}
}
}
}
if (!result && (family == AF_INET6 || family == AF_UNSPEC))
{
struct ares_in6_addr addr6;
result = ares_inet_pton(AF_INET6, name, &addr6) < 1 ? 0 : 1;
if (result)
{
status = ares_append_ai_node(AF_INET6, port, 0, &addr6, &ai->nodes);
if (status != ARES_SUCCESS)
{
callback(arg, status, 0, NULL);
return 1;
}
}
}
if (!result)
return 0;
if (hints->ai_flags & ARES_AI_CANONNAME)
{
cname = ares__append_addrinfo_cname(&ai->cnames);
if (!cname)
{
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return 1;
}
/* Duplicate the name, to avoid a constness violation. */
cname->name = ares_strdup(name);
if (!cname->name)
{
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return 1;
}
}
ai->nodes->ai_socktype = hints->ai_socktype;
ai->nodes->ai_protocol = hints->ai_protocol;
callback(arg, ARES_SUCCESS, 0, ai);
return 1;
}
static void end_hquery(struct host_query *hquery, int status)
{
struct ares_addrinfo_node sentinel;
struct ares_addrinfo_node *next;
if (status == ARES_SUCCESS)
{
if (!(hquery->hints.ai_flags & ARES_AI_NOSORT) && hquery->ai->nodes)
{
sentinel.ai_next = hquery->ai->nodes;
ares__sortaddrinfo(hquery->channel, &sentinel);
hquery->ai->nodes = sentinel.ai_next;
}
next = hquery->ai->nodes;
while (next)
{
next->ai_socktype = hquery->hints.ai_socktype;
next->ai_protocol = hquery->hints.ai_protocol;
next = next->ai_next;
}
}
else
{
/* Clean up what we have collected by so far. */
ares_freeaddrinfo(hquery->ai);
hquery->ai = NULL;
}
hquery->callback(hquery->arg, status, hquery->timeouts, hquery->ai);
ares_free(hquery->name);
ares_free(hquery);
}
static int is_localhost(const char *name)
{
/* RFC6761 6.3 says : The domain "localhost." and any names falling within ".localhost." */
size_t len;
if (name == NULL)
return 0;
if (strcmp(name, "localhost") == 0)
return 1;
len = strlen(name);
if (len < 10 /* strlen(".localhost") */)
return 0;
if (strcmp(name + (len - 10 /* strlen(".localhost") */), ".localhost") == 0)
return 1;
return 0;
}
static int file_lookup(struct host_query *hquery)
{
FILE *fp;
int error;
int status;
char *path_hosts = NULL;
if (hquery->hints.ai_flags & ARES_AI_ENVHOSTS)
{
path_hosts = ares_strdup(getenv("CARES_HOSTS"));
if (!path_hosts)
return ARES_ENOMEM;
}
if (hquery->channel->hosts_path)
{
path_hosts = ares_strdup(hquery->channel->hosts_path);
if (!path_hosts)
return ARES_ENOMEM;
}
if (!path_hosts)
{
#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 (RegOpenKeyExA(HKEY_LOCAL_MACHINE, WIN_NS_NT_KEY, 0, KEY_READ,
&hkeyHosts) == ERROR_SUCCESS)
{
DWORD dwLength = MAX_PATH;
RegQueryValueExA(hkeyHosts, DATABASEPATH, NULL, NULL, (LPBYTE)tmp,
&dwLength);
ExpandEnvironmentStringsA(tmp, PATH_HOSTS, MAX_PATH);
RegCloseKey(hkeyHosts);
}
}
else if (platform == WIN_9X)
GetWindowsDirectoryA(PATH_HOSTS, MAX_PATH);
else
return ARES_ENOTFOUND;
strcat(PATH_HOSTS, WIN_PATH_HOSTS);
#elif defined(WATT32)
const char *PATH_HOSTS = _w32_GetHostsFile();
if (!PATH_HOSTS)
return ARES_ENOTFOUND;
#endif
path_hosts = ares_strdup(PATH_HOSTS);
if (!path_hosts)
return ARES_ENOMEM;
}
fp = fopen(path_hosts, "r");
if (!fp)
{
error = ERRNO;
switch (error)
{
case ENOENT:
case ESRCH:
status = ARES_ENOTFOUND;
break;
default:
DEBUGF(fprintf(stderr, "fopen() failed with error: %d %s\n", error,
strerror(error)));
DEBUGF(fprintf(stderr, "Error opening file: %s\n", path_hosts));
status = ARES_EFILE;
break;
}
}
else
{
status = ares__readaddrinfo(fp, hquery->name, hquery->port, &hquery->hints, hquery->ai);
fclose(fp);
}
ares_free(path_hosts);
/* RFC6761 section 6.3 #3 states that "Name resolution APIs and libraries
* SHOULD recognize localhost names as special and SHOULD always return the
* IP loopback address for address queries".
* We will also ignore ALL errors when trying to resolve localhost, such
* as permissions errors reading /etc/hosts or a malformed /etc/hosts */
if (status != ARES_SUCCESS && is_localhost(hquery->name))
{
return ares__addrinfo_localhost(hquery->name, hquery->port,
&hquery->hints, hquery->ai);
}
return status;
}
static void next_lookup(struct host_query *hquery, int status)
{
switch (*hquery->remaining_lookups)
{
case 'b':
/* RFC6761 section 6.3 #3 says "Name resolution APIs SHOULD NOT send
* queries for localhost names to their configured caching DNS
* server(s)." */
if (!is_localhost(hquery->name))
{
/* DNS lookup */
if (next_dns_lookup(hquery))
break;
}
hquery->remaining_lookups++;
next_lookup(hquery, status);
break;
case 'f':
/* Host file lookup */
if (file_lookup(hquery) == ARES_SUCCESS)
{
end_hquery(hquery, ARES_SUCCESS);
break;
}
hquery->remaining_lookups++;
next_lookup(hquery, status);
break;
default:
/* No lookup left */
end_hquery(hquery, status);
break;
}
}
static void terminate_retries(struct host_query *hquery, unsigned short qid)
{
unsigned short term_qid = (qid == hquery->qid_a)?hquery->qid_aaaa:hquery->qid_a;
ares_channel channel = hquery->channel;
struct query *query = NULL;
/* No other outstanding queries, nothing to do */
if (!hquery->remaining)
return;
query = ares__htable_stvp_get_direct(channel->queries_by_qid, term_qid);
if (query == NULL)
return;
query->no_retries = 1;
}
static void host_callback(void *arg, int status, int timeouts,
unsigned char *abuf, int alen)
{
struct host_query *hquery = (struct host_query*)arg;
int addinfostatus = ARES_SUCCESS;
unsigned short qid = 0;
hquery->timeouts += timeouts;
hquery->remaining--;
if (status == ARES_SUCCESS) {
addinfostatus = ares__parse_into_addrinfo(abuf, alen, 1, hquery->port,
hquery->ai);
if (addinfostatus == ARES_SUCCESS && alen >= HFIXEDSZ) {
qid = DNS_HEADER_QID(abuf); /* Converts to host byte order */
terminate_retries(hquery, qid);
}
}
if (!hquery->remaining) {
if (addinfostatus != ARES_SUCCESS && addinfostatus != ARES_ENODATA) {
/* error in parsing result e.g. no memory */
if (addinfostatus == ARES_EBADRESP && hquery->ai->nodes) {
/* We got a bad response from server, but at least one query
* ended with ARES_SUCCESS */
end_hquery(hquery, ARES_SUCCESS);
} else {
end_hquery(hquery, addinfostatus);
}
} else if (hquery->ai->nodes) {
/* at least one query ended with ARES_SUCCESS */
end_hquery(hquery, ARES_SUCCESS);
} else if (status == ARES_EDESTRUCTION || status == ARES_ECANCELLED) {
/* must make sure we don't do next_lookup() on destroy or cancel */
end_hquery(hquery, status);
} else if (status == ARES_ENOTFOUND || status == ARES_ENODATA ||
addinfostatus == ARES_ENODATA) {
if (status == ARES_ENODATA || addinfostatus == ARES_ENODATA)
hquery->nodata_cnt++;
next_lookup(hquery, hquery->nodata_cnt?ARES_ENODATA:status);
} else {
end_hquery(hquery, status);
}
}
/* at this point we keep on waiting for the next query to finish */
}
void ares_getaddrinfo(ares_channel channel,
const char* name, const char* service,
const struct ares_addrinfo_hints* hints,
ares_addrinfo_callback callback, void* arg)
{
struct host_query *hquery;
unsigned short port = 0;
int family;
struct ares_addrinfo *ai;
char *alias_name = NULL;
int status;
if (!hints)
{
hints = &default_hints;
}
family = hints->ai_family;
/* Right now we only know how to look up Internet addresses
and unspec means try both basically. */
if (family != AF_INET &&
family != AF_INET6 &&
family != AF_UNSPEC)
{
callback(arg, ARES_ENOTIMP, 0, NULL);
return;
}
if (ares__is_onion_domain(name))
{
callback(arg, ARES_ENOTFOUND, 0, NULL);
return;
}
/* perform HOSTALIAS resolution (technically this function does some other
* things we are going to ignore) */
status = ares__single_domain(channel, name, &alias_name);
if (status != ARES_SUCCESS) {
callback(arg, status, 0, NULL);
return;
}
if (alias_name)
name = alias_name;
if (service)
{
if (hints->ai_flags & ARES_AI_NUMERICSERV)
{
unsigned long val;
errno = 0;
val = strtoul(service, NULL, 0);
if ((val == 0 && errno != 0) || val > 65535)
{
ares_free(alias_name);
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
port = (unsigned short)val;
}
else
{
port = lookup_service(service, 0);
if (!port)
{
unsigned long val;
errno = 0;
val = strtoul(service, NULL, 0);
if ((val == 0 && errno != 0) || val > 65535)
{
ares_free(alias_name);
callback(arg, ARES_ESERVICE, 0, NULL);
return;
}
port = (unsigned short)val;
}
}
}
ai = ares__malloc_addrinfo();
if (!ai)
{
ares_free(alias_name);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
if (fake_addrinfo(name, port, hints, ai, callback, arg))
{
ares_free(alias_name);
return;
}
/* Allocate and fill in the host query structure. */
hquery = ares_malloc(sizeof(*hquery));
if (!hquery)
{
ares_free(alias_name);
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
memset(hquery, 0, sizeof(*hquery));
hquery->name = ares_strdup(name);
ares_free(alias_name);
if (!hquery->name)
{
ares_free(hquery);
ares_freeaddrinfo(ai);
callback(arg, ARES_ENOMEM, 0, NULL);
return;
}
hquery->port = port;
hquery->channel = channel;
hquery->hints = *hints;
hquery->sent_family = -1; /* nothing is sent yet */
hquery->callback = callback;
hquery->arg = arg;
hquery->remaining_lookups = channel->lookups;
hquery->ai = ai;
hquery->next_domain = -1;
/* Start performing lookups according to channel->lookups. */
next_lookup(hquery, ARES_ECONNREFUSED /* initial error code */);
}
static int next_dns_lookup(struct host_query *hquery)
{
char *s = NULL;
int is_s_allocated = 0;
int status;
/* if next_domain == -1 and as_is_first is true, try hquery->name */
if (hquery->next_domain == -1)
{
if (as_is_first(hquery))
{
s = hquery->name;
}
hquery->next_domain = 0;
}
/* if as_is_first is false, try hquery->name at last */
if (!s && hquery->next_domain == hquery->channel->ndomains) {
if (!as_is_first(hquery))
{
s = hquery->name;
}
hquery->next_domain++;
}
if (!s && hquery->next_domain < hquery->channel->ndomains && !as_is_only(hquery))
{
status = ares__cat_domain(
hquery->name,
hquery->channel->domains[hquery->next_domain++],
&s);
if (status == ARES_SUCCESS)
{
is_s_allocated = 1;
}
}
if (s)
{
/* NOTE: hquery may be invalidated during the call to ares_query_qid(),
* so should not be referenced after this point */
switch (hquery->hints.ai_family)
{
case AF_INET:
hquery->remaining += 1;
ares_query_qid(hquery->channel, s, C_IN, T_A, host_callback, hquery,
&hquery->qid_a);
break;
case AF_INET6:
hquery->remaining += 1;
ares_query_qid(hquery->channel, s, C_IN, T_AAAA, host_callback,
hquery, &hquery->qid_aaaa);
break;
case AF_UNSPEC:
hquery->remaining += 2;
ares_query_qid(hquery->channel, s, C_IN, T_A, host_callback,
hquery, &hquery->qid_a);
ares_query_qid(hquery->channel, s, C_IN, T_AAAA, host_callback,
hquery, &hquery->qid_aaaa);
break;
default: break;
}
if (is_s_allocated)
{
ares_free(s);
}
return 1;
}
else
{
assert(!hquery->ai->nodes);
return 0;
}
}
static int as_is_first(const struct host_query* hquery)
{
char* p;
int ndots = 0;
size_t nname = hquery->name?strlen(hquery->name):0;
for (p = hquery->name; p && *p; p++)
{
if (*p == '.')
{
ndots++;
}
}
if (nname && hquery->name[nname-1] == '.')
{
/* prevent ARES_EBADNAME for valid FQDN, where ndots < channel->ndots */
return 1;
}
return ndots >= hquery->channel->ndots;
}
static int as_is_only(const struct host_query* hquery)
{
size_t nname = hquery->name?strlen(hquery->name):0;
if (nname && hquery->name[nname-1] == '.')
return 1;
return 0;
}