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.

842 lines
24 KiB

/*
* Copyright (C) Brad House
*
* 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.
*
* SPDX-License-Identifier: MIT
*/
#include "ares_setup.h"
#include "ares.h"
#include "ares_nameser.h"
#include "ares-test.h"
#include "ares-test-ai.h"
#include "dns-proto.h"
// Include ares internal files for DNS protocol details
#include "ares_dns.h"
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_NETINET_TCP_H
#include <netinet/tcp.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <functional>
#include <sstream>
#ifdef WIN32
#define BYTE_CAST (char *)
#define mkdir_(d, p) mkdir(d)
#else
#define BYTE_CAST
#define mkdir_(d, p) mkdir(d, p)
#endif
namespace ares {
namespace test {
bool verbose = false;
static constexpr unsigned short dynamic_port = 0;
unsigned short mock_port = dynamic_port;
const std::vector<int> both_families = {AF_INET, AF_INET6};
const std::vector<int> ipv4_family = {AF_INET};
const std::vector<int> ipv6_family = {AF_INET6};
const std::vector<std::pair<int, bool>> both_families_both_modes = {
std::make_pair<int, bool>(AF_INET, false),
std::make_pair<int, bool>(AF_INET, true),
std::make_pair<int, bool>(AF_INET6, false),
std::make_pair<int, bool>(AF_INET6, true)
};
const std::vector<std::pair<int, bool>> ipv4_family_both_modes = {
std::make_pair<int, bool>(AF_INET, false),
std::make_pair<int, bool>(AF_INET, true)
};
const std::vector<std::pair<int, bool>> ipv6_family_both_modes = {
std::make_pair<int, bool>(AF_INET6, false),
std::make_pair<int, bool>(AF_INET6, true)
};
// Which parameters to use in tests
std::vector<int> families = both_families;
std::vector<std::pair<int, bool>> families_modes = both_families_both_modes;
unsigned long long LibraryTest::fails_ = 0;
std::map<size_t, int> LibraryTest::size_fails_;
`ares_channel` -> `ares_channel_t *`: don't bury the pointer (#595) `ares_channel` is defined as `typedef struct ares_channeldata *ares_channel;`. The problem with this, is it embeds the pointer into the typedef, which means an `ares_channel` can never be declared as `const` as if you write `const ares_channel channel`, that expands to `struct ares_channeldata * const ares_channel` and not `const struct ares_channeldata *channel`. We will now typedef `ares_channel_t` as `typedef struct ares_channeldata ares_channel_t;`, so if you write `const ares_channel_t *channel`, it properly expands to `const struct ares_channeldata *channel`. We are maintaining the old typedef for API compatibility with existing integrations, and due to typedef expansion this should not even cause any compiler warnings for existing code. There are no ABI implications with this change. I could be convinced to keep existing public functions as `ares_channel` if a sufficient argument exists, but internally we really need make this change for modern best practices. This change will allow us to internally use `const ares_channel_t *` where appropriate. Whether or not we decide to change any public interfaces to use `const` may require further discussion on if there might be ABI implications (I don't think so, but I'm also not 100% sure what a compiler internally does with `const` when emitting machine code ... I think more likely ABI implications would occur going the opposite direction). FYI, This PR was done via a combination of sed and clang-format, the only manual code change was the addition of the new typedef, and a couple doc fixes :) Fix By: Brad House (@bradh352)
1 year ago
void ProcessWork(ares_channel_t *channel,
std::function<std::set<ares_socket_t>()> get_extrafds,
std::function<void(ares_socket_t)> process_extra) {
int nfds, count;
fd_set readers, writers;
struct timeval tv;
while (true) {
// Retrieve the set of file descriptors that the library wants us to monitor.
FD_ZERO(&readers);
FD_ZERO(&writers);
nfds = ares_fds(channel, &readers, &writers);
if (nfds == 0) // no work left to do in the library
return;
// Add in the extra FDs if present.
std::set<ares_socket_t> extrafds = get_extrafds();
for (ares_socket_t extrafd : extrafds) {
FD_SET(extrafd, &readers);
if (extrafd >= nfds) {
nfds = (int)extrafd + 1;
}
}
/* If ares_timeout returns NULL, it means there are no requests in queue,
* so we can break out */
if (ares_timeout(channel, NULL, &tv) == NULL)
return;
count = select(nfds, &readers, &writers, nullptr, &tv);
if (count < 0) {
fprintf(stderr, "select() failed, errno %d\n", errno);
return;
}
// Let the library process any activity.
ares_process(channel, &readers, &writers);
// Let the provided callback process any activity on the extra FD.
for (ares_socket_t extrafd : extrafds) {
if (FD_ISSET(extrafd, &readers)) {
process_extra(extrafd);
}
}
}
}
// static
void LibraryTest::SetAllocFail(int nth) {
assert(nth > 0);
assert(nth <= (int)(8 * sizeof(fails_)));
fails_ |= (1LL << (nth - 1));
}
// static
void LibraryTest::SetAllocSizeFail(size_t size) {
size_fails_[size]++;
}
// static
void LibraryTest::ClearFails() {
fails_ = 0;
size_fails_.clear();
}
// static
bool LibraryTest::ShouldAllocFail(size_t size) {
bool fail = (fails_ & 0x01);
fails_ >>= 1;
if (size_fails_[size] > 0) {
size_fails_[size]--;
fail = true;
}
return fail;
}
// static
void* LibraryTest::amalloc(size_t size) {
if (ShouldAllocFail(size) || size == 0) {
if (verbose) std::cerr << "Failing malloc(" << size << ") request" << std::endl;
return nullptr;
} else {
return malloc(size);
}
}
// static
void* LibraryTest::arealloc(void *ptr, size_t size) {
if (ShouldAllocFail(size)) {
if (verbose) std::cerr << "Failing realloc(" << ptr << ", " << size << ") request" << std::endl;
return nullptr;
} else {
return realloc(ptr, size);
}
}
// static
void LibraryTest::afree(void *ptr) {
free(ptr);
}
std::set<ares_socket_t> NoExtraFDs() {
return std::set<ares_socket_t>();
}
void DefaultChannelTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
Replace hosts parser, add caching capabilities (#591) HOSTS FILE PROCESSING OVERVIEW ============================== The hosts file on the system contains static entries to be processed locally rather than querying the nameserver. Each row is an IP address followed by a list of space delimited hostnames that match the ip address. This is used for both forward and reverse lookups. We are caching the entire parsed hosts file for performance reasons. Some files may be quite sizable and as per Issue #458 can approach 1/2MB in size, and the parse overhead on a rapid succession of queries can be quite large. The entries are stored in forwards and backwards hashtables so we can get O(1) performance on lookup. The file is cached until the file modification timestamp changes (or 60s if there is no implemented stat() capability). The hosts file processing is quite unique. It has to merge all related hosts and ips into a single entry due to file formatting requirements. For instance take the below: ``` 127.0.0.1 localhost.localdomain localhost ::1 localhost.localdomain localhost 192.168.1.1 host.example.com host 192.168.1.5 host.example.com host 2620:1234::1 host.example.com host6.example.com host6 host ``` This will yield 2 entries. 1) ips: `127.0.0.1,::1` hosts: `localhost.localdomain,localhost` 2) ips: `192.168.1.1,192.168.1.5,2620:1234::1` hosts: `host.example.com,host,host6.example.com,host6` It could be argued that if searching for `192.168.1.1` that the `host6` hostnames should not be returned, but this implementation will return them since they are related (both ips have the fqdn of host.example.com). It is unlikely this will matter in the real world. Fix By: Brad House (@bradh352)
1 year ago
void FileChannelTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
void DefaultChannelModeTest::Process() {
ProcessWork(channel_, NoExtraFDs, nullptr);
}
MockServer::MockServer(int family, unsigned short port)
: udpport_(port), tcpport_(port), qid_(-1) {
// Create a TCP socket to receive data on.
tcp_data_ = NULL;
tcp_data_len_ = 0;
tcpfd_ = socket(family, SOCK_STREAM, 0);
EXPECT_NE(ARES_SOCKET_BAD, tcpfd_);
int optval = 1;
setsockopt(tcpfd_, SOL_SOCKET, SO_REUSEADDR,
BYTE_CAST &optval , sizeof(int));
// Send TCP data right away.
setsockopt(tcpfd_, IPPROTO_TCP, TCP_NODELAY,
BYTE_CAST &optval , sizeof(int));
// Create a UDP socket to receive data on.
udpfd_ = socket(family, SOCK_DGRAM, 0);
EXPECT_NE(ARES_SOCKET_BAD, udpfd_);
// Bind the sockets to the given port.
if (family == AF_INET) {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(tcpport_);
int tcprc = bind(tcpfd_, (struct sockaddr*)&addr, sizeof(addr));
EXPECT_EQ(0, tcprc) << "Failed to bind AF_INET to TCP port " << tcpport_;
addr.sin_port = htons(udpport_);
int udprc = bind(udpfd_, (struct sockaddr*)&addr, sizeof(addr));
EXPECT_EQ(0, udprc) << "Failed to bind AF_INET to UDP port " << udpport_;
// retrieve system-assigned port
if (udpport_ == dynamic_port) {
ares_socklen_t len = sizeof(addr);
auto result = getsockname(udpfd_, (struct sockaddr*)&addr, &len);
EXPECT_EQ(0, result);
udpport_ = ntohs(addr.sin_port);
EXPECT_NE(dynamic_port, udpport_);
}
if (tcpport_ == dynamic_port) {
ares_socklen_t len = sizeof(addr);
auto result = getsockname(tcpfd_, (struct sockaddr*)&addr, &len);
EXPECT_EQ(0, result);
tcpport_ = ntohs(addr.sin_port);
EXPECT_NE(dynamic_port, tcpport_);
}
} else {
EXPECT_EQ(AF_INET6, family);
struct sockaddr_in6 addr;
memset(&addr, 0, sizeof(addr));
addr.sin6_family = AF_INET6;
memset(&addr.sin6_addr, 0, sizeof(addr.sin6_addr)); // in6addr_any
addr.sin6_port = htons(tcpport_);
int tcprc = bind(tcpfd_, (struct sockaddr*)&addr, sizeof(addr));
EXPECT_EQ(0, tcprc) << "Failed to bind AF_INET6 to TCP port " << tcpport_;
addr.sin6_port = htons(udpport_);
int udprc = bind(udpfd_, (struct sockaddr*)&addr, sizeof(addr));
EXPECT_EQ(0, udprc) << "Failed to bind AF_INET6 to UDP port " << udpport_;
// retrieve system-assigned port
if (udpport_ == dynamic_port) {
ares_socklen_t len = sizeof(addr);
auto result = getsockname(udpfd_, (struct sockaddr*)&addr, &len);
EXPECT_EQ(0, result);
udpport_ = ntohs(addr.sin6_port);
EXPECT_NE(dynamic_port, udpport_);
}
if (tcpport_ == dynamic_port) {
ares_socklen_t len = sizeof(addr);
auto result = getsockname(tcpfd_, (struct sockaddr*)&addr, &len);
EXPECT_EQ(0, result);
tcpport_ = ntohs(addr.sin6_port);
EXPECT_NE(dynamic_port, tcpport_);
}
}
if (verbose) std::cerr << "Configured "
<< (family == AF_INET ? "IPv4" : "IPv6")
<< " mock server with TCP socket " << tcpfd_
<< " on port " << tcpport_
<< " and UDP socket " << udpfd_
<< " on port " << udpport_ << std::endl;
// For TCP, also need to listen for connections.
EXPECT_EQ(0, listen(tcpfd_, 5)) << "Failed to listen for TCP connections";
}
MockServer::~MockServer() {
for (ares_socket_t fd : connfds_) {
sclose(fd);
}
sclose(tcpfd_);
sclose(udpfd_);
free(tcp_data_);
}
void MockServer::ProcessPacket(ares_socket_t fd, struct sockaddr_storage *addr, ares_socklen_t addrlen,
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
byte *data, int len) {
// Assume the packet is a well-formed DNS request and extract the request
// details.
if (len < NS_HFIXEDSZ) {
std::cerr << "Packet too short (" << len << ")" << std::endl;
return;
}
int qid = DNS_HEADER_QID(data);
if (DNS_HEADER_QR(data) != 0) {
std::cerr << "Not a request" << std::endl;
return;
}
if (DNS_HEADER_OPCODE(data) != O_QUERY) {
std::cerr << "Not a query (opcode " << DNS_HEADER_OPCODE(data)
<< ")" << std::endl;
return;
}
if (DNS_HEADER_QDCOUNT(data) != 1) {
std::cerr << "Unexpected question count (" << DNS_HEADER_QDCOUNT(data)
<< ")" << std::endl;
return;
}
byte* question = data + 12;
int qlen = len - 12;
char *name = nullptr;
long enclen;
ares_expand_name(question, data, len, &name, &enclen);
if (!name) {
std::cerr << "Failed to retrieve name" << std::endl;
return;
}
qlen -= enclen;
question += enclen;
std::string namestr(name);
ares_free_string(name);
if (qlen < 4) {
std::cerr << "Unexpected question size (" << qlen
<< " bytes after name)" << std::endl;
return;
}
if (DNS_QUESTION_CLASS(question) != C_IN) {
std::cerr << "Unexpected question class (" << DNS_QUESTION_CLASS(question)
<< ")" << std::endl;
return;
}
int rrtype = DNS_QUESTION_TYPE(question);
if (verbose) {
std::vector<byte> req(data, data + len);
std::cerr << "received " << (fd == udpfd_ ? "UDP" : "TCP") << " request " << PacketToString(req)
<< " on port " << (fd == udpfd_ ? udpport_ : tcpport_) << std::endl;
std::cerr << "ProcessRequest(" << qid << ", '" << namestr
<< "', " << RRTypeToString(rrtype) << ")" << std::endl;
}
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
ProcessRequest(fd, addr, addrlen, qid, namestr, rrtype);
}
void MockServer::ProcessFD(ares_socket_t fd) {
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
if (fd != tcpfd_ && fd != udpfd_ && connfds_.find(fd) == connfds_.end()) {
// Not one of our FDs.
return;
}
if (fd == tcpfd_) {
ares_socket_t connfd = accept(tcpfd_, NULL, NULL);
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
if (connfd < 0) {
std::cerr << "Error accepting connection on fd " << fd << std::endl;
} else {
connfds_.insert(connfd);
}
return;
}
// Activity on a data-bearing file descriptor.
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
byte buffer[2048];
int len = recvfrom(fd, BYTE_CAST buffer, sizeof(buffer), 0,
(struct sockaddr *)&addr, &addrlen);
if (fd != udpfd_) {
if (len == 0) {
connfds_.erase(std::find(connfds_.begin(), connfds_.end(), fd));
sclose(fd);
free(tcp_data_);
tcp_data_ = NULL;
tcp_data_len_ = 0;
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
return;
}
tcp_data_ = (unsigned char *)realloc(tcp_data_, tcp_data_len_ + (size_t)len);
memcpy(tcp_data_ + tcp_data_len_, buffer, (size_t)len);
tcp_data_len_ += (size_t)len;
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
/* TCP might aggregate the various requests into a single packet, so we
* need to split */
while (tcp_data_len_ > 2) {
size_t tcplen = ((size_t)tcp_data_[0] << 8) + (size_t)tcp_data_[1];
if (tcp_data_len_ - 2 < tcplen)
break;
ProcessPacket(fd, &addr, addrlen, tcp_data_ + 2, (int)tcplen);
/* strip off processed data if connection not terminated */
if (tcp_data_ != NULL) {
memmove(tcp_data_, tcp_data_ + tcplen + 2, tcp_data_len_ - 2 - tcplen);
tcp_data_len_ -= 2 + tcplen;
}
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
}
} else {
/* UDP is always a single packet */
ProcessPacket(fd, &addr, addrlen, buffer, len);
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
}
}
std::set<ares_socket_t> MockServer::fds() const {
std::set<ares_socket_t> result = connfds_;
result.insert(tcpfd_);
result.insert(udpfd_);
return result;
}
void MockServer::ProcessRequest(ares_socket_t fd, struct sockaddr_storage* addr, ares_socklen_t addrlen,
int qid, const std::string& name, int rrtype) {
// Before processing, let gMock know the request is happening.
OnRequest(name, rrtype);
if (reply_.size() == 0) {
return;
}
// Make a local copy of the current pending reply.
std::vector<byte> reply = reply_;
if (qid_ >= 0) {
// Use the explicitly specified query ID.
qid = qid_;
}
if (reply.size() >= 2) {
// Overwrite the query ID if space to do so.
reply[0] = (byte)((qid >> 8) & 0xff);
reply[1] = (byte)(qid & 0xff);
}
if (verbose) std::cerr << "sending reply " << PacketToString(reply)
<< " on port " << ((fd == udpfd_) ? udpport_ : tcpport_) << std::endl;
// Prefix with 2-byte length if TCP.
if (fd != udpfd_) {
int len = (int)reply.size();
std::vector<byte> vlen = {(byte)((len & 0xFF00) >> 8), (byte)(len & 0xFF)};
reply.insert(reply.begin(), vlen.begin(), vlen.end());
// Also, don't bother with the destination address.
addr = nullptr;
addrlen = 0;
}
int rc = sendto(fd, BYTE_CAST reply.data(), reply.size(), 0,
(struct sockaddr *)addr, addrlen);
if (rc < static_cast<int>(reply.size())) {
std::cerr << "Failed to send full reply, rc=" << rc << std::endl;
}
}
// static
MockChannelOptsTest::NiceMockServers MockChannelOptsTest::BuildServers(int count, int family, unsigned short base_port) {
NiceMockServers servers;
assert(count > 0);
for (unsigned short ii = 0; ii < count; ii++) {
unsigned short port = base_port == dynamic_port ? dynamic_port : base_port + ii;
std::unique_ptr<NiceMockServer> server(new NiceMockServer(family, port));
servers.push_back(std::move(server));
}
return servers;
}
MockChannelOptsTest::MockChannelOptsTest(int count,
int family,
bool force_tcp,
struct ares_options* givenopts,
int optmask)
: servers_(BuildServers(count, family, mock_port)),
server_(*servers_[0].get()), channel_(nullptr) {
// Set up channel options.
struct ares_options opts;
if (givenopts) {
memcpy(&opts, givenopts, sizeof(opts));
} else {
memset(&opts, 0, sizeof(opts));
}
// Point the library at the first mock server by default (overridden below).
opts.udp_port = server_.udpport();
optmask |= ARES_OPT_UDP_PORT;
opts.tcp_port = server_.tcpport();
optmask |= ARES_OPT_TCP_PORT;
// If not already overridden, set short-ish timeouts.
if (!(optmask & (ARES_OPT_TIMEOUTMS|ARES_OPT_TIMEOUT))) {
opts.timeout = 1500;
optmask |= ARES_OPT_TIMEOUTMS;
}
// If not already overridden, set 3 retries.
if (!(optmask & ARES_OPT_TRIES)) {
opts.tries = 3;
optmask |= ARES_OPT_TRIES;
}
// If not already overridden, set search domains.
const char *domains[3] = {"first.com", "second.org", "third.gov"};
if (!(optmask & ARES_OPT_DOMAINS)) {
opts.ndomains = 3;
opts.domains = (char**)domains;
optmask |= ARES_OPT_DOMAINS;
}
if (force_tcp) {
opts.flags |= ARES_FLAG_USEVC;
optmask |= ARES_OPT_FLAGS;
}
EXPECT_EQ(ARES_SUCCESS, ares_init_options(&channel_, &opts, optmask));
EXPECT_NE(nullptr, channel_);
// Set up servers after construction so we can set individual ports
struct ares_addr_port_node* prev = nullptr;
struct ares_addr_port_node* first = nullptr;
for (const auto& server : servers_) {
struct ares_addr_port_node* node = (struct ares_addr_port_node*)malloc(sizeof(*node));
if (prev) {
prev->next = node;
} else {
first = node;
}
node->next = nullptr;
node->family = family;
node->udp_port = server->udpport();
node->tcp_port = server->tcpport();
if (family == AF_INET) {
node->addr.addr4.s_addr = htonl(0x7F000001);
} else {
memset(&node->addr.addr6, 0, sizeof(node->addr.addr6));
node->addr.addr6._S6_un._S6_u8[15] = 1;
}
prev = node;
}
EXPECT_EQ(ARES_SUCCESS, ares_set_servers_ports(channel_, first));
while (first) {
prev = first;
first = first->next;
free(prev);
}
if (verbose) {
std::cerr << "Configured library with servers:";
std::cerr << GetNameServers(channel_);
std::cerr << std::endl;
}
}
MockChannelOptsTest::~MockChannelOptsTest() {
if (channel_) {
ares_destroy(channel_);
}
channel_ = nullptr;
}
std::set<ares_socket_t> MockChannelOptsTest::fds() const {
std::set<ares_socket_t> fds;
for (const auto& server : servers_) {
std::set<ares_socket_t> serverfds = server->fds();
fds.insert(serverfds.begin(), serverfds.end());
}
return fds;
}
void MockChannelOptsTest::ProcessFD(ares_socket_t fd) {
for (auto& server : servers_) {
server->ProcessFD(fd);
}
}
void MockChannelOptsTest::Process() {
using namespace std::placeholders;
ProcessWork(channel_,
std::bind(&MockChannelOptsTest::fds, this),
std::bind(&MockChannelOptsTest::ProcessFD, this, _1));
}
std::ostream& operator<<(std::ostream& os, const HostResult& result) {
os << '{';
if (result.done_) {
os << StatusToString(result.status_);
if (result.host_.addrtype_ != -1) {
os << " " << result.host_;
} else {
os << ", (no hostent)";
}
} else {
os << "(incomplete)";
}
os << '}';
return os;
}
HostEnt::HostEnt(const struct hostent *hostent) : addrtype_(-1) {
if (!hostent)
return;
if (hostent->h_name)
name_ = hostent->h_name;
if (hostent->h_aliases) {
char** palias = hostent->h_aliases;
while (*palias != nullptr) {
aliases_.push_back(*palias);
palias++;
}
}
addrtype_ = hostent->h_addrtype;
if (hostent->h_addr_list) {
char** paddr = hostent->h_addr_list;
while (*paddr != nullptr) {
std::string addr = AddressToString(*paddr, hostent->h_length);
addrs_.push_back(addr);
paddr++;
}
}
}
std::ostream& operator<<(std::ostream& os, const HostEnt& host) {
os << "{'";
if (host.name_.length() > 0) {
os << host.name_;
}
os << "' aliases=[";
for (size_t ii = 0; ii < host.aliases_.size(); ii++) {
if (ii > 0) os << ", ";
os << host.aliases_[ii];
}
os << "] ";
os << "addrs=[";
for (size_t ii = 0; ii < host.addrs_.size(); ii++) {
if (ii > 0) os << ", ";
os << host.addrs_[ii];
}
os << "]";
os << '}';
return os;
}
void HostCallback(void *data, int status, int timeouts,
struct hostent *hostent) {
EXPECT_NE(nullptr, data);
if (data == nullptr)
return;
HostResult* result = reinterpret_cast<HostResult*>(data);
result->done_ = true;
result->status_ = status;
result->timeouts_ = timeouts;
Reimplement ares_gethostbyname() by wrapping ares_getaddrinfo() (#428) ares_gethostbyname() and ares_getaddrinfo() do a lot of similar things, however ares_getaddrinfo() has some desirable behaviors that should be imported into ares_gethostbyname(). For one, it sorts the address lists for the most likely to succeed based on the current system routes. Next, when AF_UNSPEC is specified, it properly handles search lists instead of first searching all of AF_INET6 then AF_INET, since ares_gethostbyname() searches in parallel. Therefore, this PR should also resolve the issues attempted in #94. A few things this PR does: 1. ares_parse_a_reply() and ares_parse_aaaa_reply() had very similar code to translate struct ares_addrinfo into a struct hostent as well as into struct ares_addrttl/ares_addr6ttl this has been split out into helper functions of ares__addrinfo2hostent() and ares__addrinfo2addrttl() to prevent this duplicative code. 2. ares_getaddrinfo() was apparently never honoring HOSTALIASES, and this was discovered once ares_gethostbyname() was turned into a wrapper, the affected test cases started failing. 3. A slight API modification to save the query hostname into struct ares_addrinfo as the last element of name. Since this is the last element, and all user-level instances of struct ares_addrinfo are allocated internally by c-ares, this is not an ABI-breaking change nor would it impact any API compatibility. This was needed since struct hostent has an h_name element. 4. Test Framework: MockServer tests via TCP would fail if more than 1 request was received at a time which is common when ares_getaddrinfo() queries for both A and AAAA records simultaneously. Infact, this was a long standing issue in which the ares_getaddrinfo() test were bypassing TCP alltogether. This has been corrected, the message is now processed in a loop. 5. Some tests had to be updated for overall correctness as they were invalid but somehow passing prior to this change. Change By: Brad House (@bradh352)
3 years ago
if (hostent)
result->host_ = HostEnt(hostent);
if (verbose) std::cerr << "HostCallback(" << *result << ")" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const AddrInfoResult& result) {
os << '{';
if (result.done_ && result.ai_) {
os << StatusToString(result.status_) << " " << result.ai_;
} else {
os << "(incomplete)";
}
os << '}';
return os;
}
std::ostream& operator<<(std::ostream& os, const AddrInfo& ai) {
os << '{';
if (ai == nullptr) {
os << "nullptr}";
return os;
}
struct ares_addrinfo_cname *next_cname = ai->cnames;
while(next_cname) {
if(next_cname->alias) {
os << next_cname->alias << "->";
}
if(next_cname->name) {
os << next_cname->name;
}
if((next_cname = next_cname->next))
os << ", ";
else
os << " ";
}
struct ares_addrinfo_node *next = ai->nodes;
while(next) {
//if(next->ai_canonname) {
//os << "'" << next->ai_canonname << "' ";
//}
unsigned short port = 0;
os << "addr=[";
if(next->ai_family == AF_INET) {
sockaddr_in* sin = (sockaddr_in*)next->ai_addr;
port = ntohs(sin->sin_port);
os << AddressToString(&sin->sin_addr, 4);
}
else if (next->ai_family == AF_INET6) {
sockaddr_in6* sin = (sockaddr_in6*)next->ai_addr;
port = ntohs(sin->sin6_port);
os << "[" << AddressToString(&sin->sin6_addr, 16) << "]";
}
else
os << "unknown family";
if(port) {
os << ":" << port;
}
os << "]";
if((next = next->ai_next))
os << ", ";
}
os << '}';
return os;
}
void AddrInfoCallback(void *data, int status, int timeouts,
struct ares_addrinfo *ai) {
EXPECT_NE(nullptr, data);
AddrInfoResult* result = reinterpret_cast<AddrInfoResult*>(data);
result->done_ = true;
result->status_ = status;
result->timeouts_= timeouts;
Replace hosts parser, add caching capabilities (#591) HOSTS FILE PROCESSING OVERVIEW ============================== The hosts file on the system contains static entries to be processed locally rather than querying the nameserver. Each row is an IP address followed by a list of space delimited hostnames that match the ip address. This is used for both forward and reverse lookups. We are caching the entire parsed hosts file for performance reasons. Some files may be quite sizable and as per Issue #458 can approach 1/2MB in size, and the parse overhead on a rapid succession of queries can be quite large. The entries are stored in forwards and backwards hashtables so we can get O(1) performance on lookup. The file is cached until the file modification timestamp changes (or 60s if there is no implemented stat() capability). The hosts file processing is quite unique. It has to merge all related hosts and ips into a single entry due to file formatting requirements. For instance take the below: ``` 127.0.0.1 localhost.localdomain localhost ::1 localhost.localdomain localhost 192.168.1.1 host.example.com host 192.168.1.5 host.example.com host 2620:1234::1 host.example.com host6.example.com host6 host ``` This will yield 2 entries. 1) ips: `127.0.0.1,::1` hosts: `localhost.localdomain,localhost` 2) ips: `192.168.1.1,192.168.1.5,2620:1234::1` hosts: `host.example.com,host,host6.example.com,host6` It could be argued that if searching for `192.168.1.1` that the `host6` hostnames should not be returned, but this implementation will return them since they are related (both ips have the fqdn of host.example.com). It is unlikely this will matter in the real world. Fix By: Brad House (@bradh352)
1 year ago
if (ai)
result->ai_ = AddrInfo(ai);
if (verbose) std::cerr << "AddrInfoCallback(" << *result << ")" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const SearchResult& result) {
os << '{';
if (result.done_) {
os << StatusToString(result.status_) << " " << PacketToString(result.data_);
} else {
os << "(incomplete)";
}
os << '}';
return os;
}
void SearchCallback(void *data, int status, int timeouts,
unsigned char *abuf, int alen) {
EXPECT_NE(nullptr, data);
SearchResult* result = reinterpret_cast<SearchResult*>(data);
result->done_ = true;
result->status_ = status;
result->timeouts_ = timeouts;
result->data_.assign(abuf, abuf + alen);
if (verbose) std::cerr << "SearchCallback(" << *result << ")" << std::endl;
}
std::ostream& operator<<(std::ostream& os, const NameInfoResult& result) {
os << '{';
if (result.done_) {
os << StatusToString(result.status_) << " " << result.node_ << " " << result.service_;
} else {
os << "(incomplete)";
}
os << '}';
return os;
}
void NameInfoCallback(void *data, int status, int timeouts,
char *node, char *service) {
EXPECT_NE(nullptr, data);
NameInfoResult* result = reinterpret_cast<NameInfoResult*>(data);
result->done_ = true;
result->status_ = status;
result->timeouts_ = timeouts;
result->node_ = std::string(node ? node : "");
result->service_ = std::string(service ? service : "");
if (verbose) std::cerr << "NameInfoCallback(" << *result << ")" << std::endl;
}
std::string GetNameServers(ares_channel_t *channel) {
char *csv = ares_get_servers_csv(channel);
EXPECT_NE((char *)NULL, csv);
std::string servers(csv);
ares_free_string(csv);
return servers;
}
TransientDir::TransientDir(const std::string& dirname) : dirname_(dirname) {
if (mkdir_(dirname_.c_str(), 0755) != 0) {
std::cerr << "Failed to create subdirectory '" << dirname_ << "'" << std::endl;
}
}
TransientDir::~TransientDir() {
rmdir(dirname_.c_str());
}
TransientFile::TransientFile(const std::string& filename,
const std::string& contents)
: filename_(filename) {
FILE *f = fopen(filename.c_str(), "w");
if (f == nullptr) {
std::cerr << "Error: failed to create '" << filename << "'" << std::endl;
return;
}
int rc = fwrite(contents.data(), 1, contents.size(), f);
if (rc != (int)contents.size()) {
std::cerr << "Error: failed to write contents of '" << filename << "'" << std::endl;
}
fclose(f);
}
TransientFile::~TransientFile() {
unlink(filename_.c_str());
}
std::string TempNam(const char *dir, const char *prefix) {
char *p = tempnam(dir, prefix);
std::string result(p);
free(p);
return result;
}
TempFile::TempFile(const std::string& contents)
: TransientFile(TempNam(nullptr, "ares"), contents) {
}
`ares_channel` -> `ares_channel_t *`: don't bury the pointer (#595) `ares_channel` is defined as `typedef struct ares_channeldata *ares_channel;`. The problem with this, is it embeds the pointer into the typedef, which means an `ares_channel` can never be declared as `const` as if you write `const ares_channel channel`, that expands to `struct ares_channeldata * const ares_channel` and not `const struct ares_channeldata *channel`. We will now typedef `ares_channel_t` as `typedef struct ares_channeldata ares_channel_t;`, so if you write `const ares_channel_t *channel`, it properly expands to `const struct ares_channeldata *channel`. We are maintaining the old typedef for API compatibility with existing integrations, and due to typedef expansion this should not even cause any compiler warnings for existing code. There are no ABI implications with this change. I could be convinced to keep existing public functions as `ares_channel` if a sufficient argument exists, but internally we really need make this change for modern best practices. This change will allow us to internally use `const ares_channel_t *` where appropriate. Whether or not we decide to change any public interfaces to use `const` may require further discussion on if there might be ABI implications (I don't think so, but I'm also not 100% sure what a compiler internally does with `const` when emitting machine code ... I think more likely ABI implications would occur going the opposite direction). FYI, This PR was done via a combination of sed and clang-format, the only manual code change was the addition of the new typedef, and a couple doc fixes :) Fix By: Brad House (@bradh352)
1 year ago
VirtualizeIO::VirtualizeIO(ares_channel_t *c)
: channel_(c)
{
ares_set_socket_functions(channel_, &default_functions, 0);
}
VirtualizeIO::~VirtualizeIO() {
ares_set_socket_functions(channel_, 0, 0);
}
} // namespace test
} // namespace ares