privacore-open-source-searc.../GbDns.cpp
2018-08-07 15:44:56 +02:00

570 lines
13 KiB
C++

#include "GbDns.h"
#include "GbMutex.h"
#include "ScopedLock.h"
#include "Conf.h"
#include "Mem.h"
#include "utf8_fast.h"
#include "third-party/c-ares/ares.h"
#include "ip.h"
#include "GbThreadQueue.h"
#include "GbCache.h"
#include "Errno.h"
#include <arpa/nameser.h>
#include <netdb.h>
#include <vector>
#include <string>
#include <queue>
static ares_channel s_channel;
static pthread_t s_thread;
static std::atomic<bool> s_stop(false);
static std::atomic<bool> s_finalized(false);
static std::atomic<bool> s_pause(false);
static pthread_cond_t s_pauseCond = PTHREAD_COND_INITIALIZER;
static GbMutex s_pauseMtx;
static std::atomic<bool> s_wait(false);
static pthread_cond_t s_waitCond = PTHREAD_COND_INITIALIZER;
static GbMutex s_waitMtx;
static pthread_cond_t s_channelCond = PTHREAD_COND_INITIALIZER;
static GbMutex s_channelMtx;
static std::queue<struct DnsItem*> s_callbackQueue;
static GbMutex s_callbackQueueMtx;
static GbThreadQueue s_requestQueue;
static GbCache<std::string, GbDns::DnsResponse> s_cache;
static void a_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen);
static void ns_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen);
template<typename T>
class AresList {
public:
AresList();
~AresList();
T* getHead();
void append(T *item);
private:
T *m_head;
};
template<typename T> AresList<T>::AresList()
: m_head(NULL) {
}
template<typename T> AresList<T>::~AresList() {
while (m_head) {
T *node = m_head;
m_head = m_head->next;
mfree(node, sizeof(T), "ares-item");
}
}
template<typename T> T* AresList<T>::getHead() {
return m_head;
}
template<typename T> void AresList<T>::append(T *node) {
node->next = NULL;
if (m_head) {
T *last = m_head;
while (last->next) {
last = last->next;
}
last->next = node;
} else {
m_head = node;
}
}
static void* processing_thread(void *args) {
while (!s_stop) {
fd_set read_fds, write_fds;
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
int nfds;
{
ScopedLock sl(s_channelMtx);
nfds = ares_fds(s_channel, &read_fds, &write_fds);
if (nfds == 0) {
{
ScopedLock sl(s_waitMtx);
s_wait = true;
pthread_cond_signal(&s_waitCond);
}
// wait until new request comes in
pthread_cond_wait(&s_channelCond, &s_channelMtx.mtx);
s_wait = false;
continue;
}
}
timeval *tvp = NULL;
timeval tv;
{
ScopedLock sl(s_channelMtx);
tvp = ares_timeout(s_channel, NULL, &tv);
}
int count = select(nfds, &read_fds, &write_fds, NULL, tvp);
int status = errno;
if (count < 0 && status != EINVAL) {
logError("select fail: %d", status);
continue;
}
{
ScopedLock sl(s_channelMtx);
ares_process(s_channel, &read_fds, &write_fds);
}
}
return nullptr;
}
struct DnsItem {
enum RequestType {
request_type_a,
request_type_ns
};
DnsItem(RequestType reqType, const char *hostname, size_t hostnameLen,
void (*callback)(GbDns::DnsResponse *response, void *state), void *state)
: m_reqType(reqType)
, m_hostname(hostname, hostnameLen)
, m_callback(callback)
, m_state(state)
, m_response() {
}
RequestType m_reqType;
std::string m_hostname;
void (*m_callback)(GbDns::DnsResponse *response, void *state);
void *m_state;
GbDns::DnsResponse m_response;
};
static void processRequest(void *item) {
DnsItem *dnsItem = static_cast<DnsItem*>(item);
ares_callback callback;
int type;
switch (dnsItem->m_reqType) {
case DnsItem::request_type_a:
callback = a_callback;
type = T_A;
break;
case DnsItem::request_type_ns:
callback = ns_callback;
type = T_NS;
break;
}
{
ScopedLock sl(s_pauseMtx);
if (s_pause) {
pthread_cond_wait(&s_pauseCond, &s_pauseMtx.mtx);
}
}
ScopedLock sl(s_channelMtx);
ares_query(s_channel, dnsItem->m_hostname.c_str(), C_IN, type, callback, dnsItem);
pthread_cond_signal(&s_channelCond);
}
void GbDns::reinitializeSettings(void *state) {
initializeSettings(true);
}
bool GbDns::initializeSettings(bool reload) {
log(LOG_INFO, "dns: Initializing settings");
// setup dns servers
AresList<ares_addr_port_node> servers;
for (int i = 0; i < g_conf.m_numDns; ++i) {
ares_addr_port_node *server = (ares_addr_port_node*)mmalloc(sizeof(ares_addr_port_node), "ares-server");
if (server == NULL) {
logError("Unable allocate ares server");
return false;
}
server->addr.addr4.s_addr = g_conf.m_dnsIps[i];
server->family = AF_INET;
server->udp_port = g_conf.m_dnsPorts[i];
servers.append(server);
}
if (reload) {
s_pause = true;
// wait until pending items have been processed
ScopedLock sl(s_waitMtx);
while (!s_wait) {
pthread_cond_wait(&s_waitCond, &s_waitMtx.mtx);
}
}
{
ScopedLock sl(s_channelMtx);
if (ares_set_servers_ports(s_channel, servers.getHead()) != ARES_SUCCESS) {
logError("Unable to set ares server settings");
return false;
}
}
if (reload) {
s_pause = false;
ScopedLock sl(s_pauseMtx);
pthread_cond_signal(&s_pauseCond);
}
s_cache.configure(g_conf.m_dnsCacheMaxAge*1000, g_conf.m_dnsCacheSize, g_conf.m_logTraceDnsCache, "dns cache");
log(LOG_INFO, "dns: Done initializing settings");
return true;
}
bool GbDns::initialize() {
log(LOG_INFO, "dns: Initializing library");
if (ares_library_init(ARES_LIB_INIT_ALL) != ARES_SUCCESS) {
logError("Unable to init ares library");
return false;
}
if (ares_init(&s_channel) != ARES_SUCCESS) {
logError("Unable to init ares channel");
return false;
}
int optmask = ARES_OPT_FLAGS;
ares_options options;
memset(&options, 0, sizeof(options));
// don't default init servers (use null values from options)
optmask |= ARES_OPT_SERVERS;
// lookup from hostfile & dns servers
options.lookups = strdup("fb");
optmask |= ARES_OPT_LOOKUPS;
// round-robin selection of nameservers
optmask |= ARES_OPT_ROTATE;
if (ares_init_options(&s_channel, &options, optmask) != ARES_SUCCESS) {
logError("Unable to init ares options");
return false;
}
if (!initializeSettings()) {
return false;
}
// create processing thread
if (pthread_create(&s_thread, nullptr, processing_thread, nullptr) != 0) {
logError("Unable to create ares processing thread");
return false;
}
if (!s_requestQueue.initialize(processRequest, "process-dns")) {
logError("Unable to initialize request queue");
return false;
}
return true;
}
void GbDns::finalize() {
if (s_finalized) {
return;
}
log(LOG_INFO, "dns: Finalizing library");
s_finalized = true;
s_stop = true;
pthread_cond_broadcast(&s_channelCond);
pthread_join(s_thread, nullptr);
ares_destroy(s_channel);
ares_library_cleanup();
s_requestQueue.finalize();
}
GbDns::DnsResponse::DnsResponse()
: m_ips()
, m_nameservers()
, m_errno(0) {
}
static int convert_ares_errorno(int ares_errno) {
switch (ares_errno) {
// Server error codes (ARES_ENODATA indicates no relevant answer)
case ARES_ENODATA:
case ARES_ENOTFOUND:
return EDNSNOTFOUND;
case ARES_EFORMERR:
case ARES_ENOTIMP:
return EDNSBADREQUEST;
case ARES_ESERVFAIL:
return EDNSSERVFAIL;
case ARES_EREFUSED:
return EDNSREFUSED;
// Locally generated error codes
case ARES_EBADQUERY:
case ARES_EBADNAME:
case ARES_EBADFAMILY:
return EDNSBADREQUEST;
case ARES_EBADRESP:
case ARES_EBADSTR:
return EDNSBADRESPONSE;
case ARES_ECONNREFUSED:
case ARES_ETIMEOUT:
return EDNSTIMEDOUT;
// case ARES_EOF:
// break;
// case ARES_EFILE:
// break;
case ARES_ENOMEM:
return ENOMEM;
case ARES_EDESTRUCTION:
return ESHUTTINGDOWN;
// ares_getnameinfo error codes
case ARES_EBADFLAGS:
return EDNSBADREQUEST;
// ares_getaddrinfo error codes
case ARES_ENONAME:
case ARES_EBADHINTS:
return EDNSBADREQUEST;
// Uninitialized library error code
// case ARES_ENOTINITIALIZED:
// return EBADENGINEER;
// ares_library_init error codes
// case ARES_ELOADIPHLPAPI:
// case ARES_EADDRGETNETWORKPARAMS:
// return EBADENGINEER;
// More error codes
// case ARES_ECANCELLED:
// return ECANCELLED;
}
// defaults to no error code
return 0;
}
static void addToCallbackQueue(DnsItem *item) {
// add to cache
if (item->m_reqType == DnsItem::request_type_a) {
s_cache.insert(item->m_hostname, item->m_response);
} else if (item->m_reqType == DnsItem::request_type_ns) {
if (!item->m_response.m_nameservers.empty()) {
GbDns::DnsResponse response;
if (s_cache.lookup(item->m_hostname, &response)) {
// merge response
response.m_nameservers = item->m_response.m_nameservers;
s_cache.insert(item->m_hostname, response);
}
}
}
ScopedLock sl(s_callbackQueueMtx);
logTrace(g_conf.m_logTraceDns, "adding to callback queue item=%p", item);
s_callbackQueue.push(item);
}
static void a_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) {
logTrace(g_conf.m_logTraceDns, "BEGIN");
DnsItem *item = static_cast<DnsItem*>(arg);
if (status != ARES_SUCCESS) {
logTrace(g_conf.m_logTraceDns, "ares_error=%d(%s)", status, ares_strerror(status));
if (abuf == NULL) {
logTrace(g_conf.m_logTraceDns, "no abuf returned");
item->m_response.m_errno = convert_ares_errorno(status);
addToCallbackQueue(item);
logTrace(g_conf.m_logTraceDns, "END");
return;
}
}
hostent *host = nullptr;
int naddrttls = 5;
ares_addrttl addrttls[naddrttls];
status = ares_parse_a_reply_ext(abuf, alen, &host, addrttls, &naddrttls);
if (status == ARES_SUCCESS) {
for (int i = 0; i < naddrttls; ++i) {
char ipbuf[16];
logTrace(g_conf.m_logTraceDns, "ip=%s ttl=%d", iptoa(addrttls[i].ipaddr.s_addr, ipbuf), addrttls[i].ttl);
item->m_response.m_ips.push_back(addrttls[i].ipaddr.s_addr);
}
for (int i = 0; host->h_aliases[i] != NULL; ++i) {
logTrace(g_conf.m_logTraceDns, "ns[%d]='%s'", i, host->h_aliases[i]);
item->m_response.m_nameservers.push_back(host->h_aliases[i]);
}
ares_free_hostent(host);
} else {
logTrace(g_conf.m_logTraceDns, "ares_error=%d(%s)", status, ares_strerror(status));
item->m_response.m_errno = convert_ares_errorno(status);
}
addToCallbackQueue(item);
logTrace(g_conf.m_logTraceDns, "END");
}
bool GbDns::getARecord(const char *hostname, size_t hostnameLen, void (*callback)(GbDns::DnsResponse *response, void *state), void *state, GbDns::DnsResponse *response) {
logTrace(g_conf.m_logTraceDns, "BEGIN hostname='%.*s'", static_cast<int>(hostnameLen), hostname);
DnsItem *item = new DnsItem(DnsItem::request_type_a, hostname, hostnameLen, callback, state);
// check if hostname is ip
if (is_digit(item->m_hostname[0])) {
in_addr addr;
// hostname is ip. skip dns lookup
if (inet_pton(AF_INET, item->m_hostname.c_str(), &addr) == 1) {
response->m_ips.push_back(addr.s_addr);
delete item;
logTrace(g_conf.m_logTraceDns, "END. hostname is IP addr");
return true;
}
}
if (g_conf.m_useEtcHosts) {
ScopedLock sl(s_channelMtx);
hostent *host = nullptr;
if (ares_gethostbyname_file(s_channel, item->m_hostname.c_str(), AF_INET, &host) == ARES_SUCCESS) {
response->m_ips.push_back(((in_addr*)host->h_addr_list[0])->s_addr);
delete item;
ares_free_hostent(host);
logTrace(g_conf.m_logTraceDns, "END. hostname found in /etc/host");
return true;
}
}
// check cache
if (s_cache.lookup(item->m_hostname, response)) {
delete item;
logTrace(g_conf.m_logTraceDns, "END. hostname found in cache");
return true;
}
s_requestQueue.addItem(item);
logTrace(g_conf.m_logTraceDns, "END");
return false;
}
static void ns_callback(void *arg, int status, int timeouts, unsigned char *abuf, int alen) {
logTrace(g_conf.m_logTraceDns, "BEGIN");
DnsItem *item = static_cast<DnsItem*>(arg);
if (status != ARES_SUCCESS) {
logTrace(g_conf.m_logTraceDns, "ares_error='%s'", ares_strerror(status));
if (abuf == NULL) {
item->m_response.m_errno = convert_ares_errorno(status);
addToCallbackQueue(item);
logTrace(g_conf.m_logTraceDns, "END");
return;
}
}
hostent *host = nullptr;
status = ares_parse_ns_reply(abuf, alen, &host);
if (status == ARES_SUCCESS) {
for (int i = 0; host->h_aliases[i] != NULL; ++i) {
logTrace(g_conf.m_logTraceDns, "ns[%d]='%s'", i, host->h_aliases[i]);
item->m_response.m_nameservers.push_back(host->h_aliases[i]);
}
ares_free_hostent(host);
}
if (status != ARES_SUCCESS) {
logTrace(g_conf.m_logTraceDns, "ares_error=%d(%s)", status, ares_strerror(status));
if (status != ARES_EDESTRUCTION) {
item->m_response.m_errno = convert_ares_errorno(status);
addToCallbackQueue(item);
} else {
delete item;
}
logTrace(g_conf.m_logTraceDns, "END");
return;
}
addToCallbackQueue(item);
logTrace(g_conf.m_logTraceDns, "END");
}
void GbDns::getNSRecord(const char *hostname, size_t hostnameLen, void (*callback)(GbDns::DnsResponse *response, void *state), void *state) {
logTrace(g_conf.m_logTraceDns, "BEGIN hostname='%.*s'", static_cast<int>(hostnameLen), hostname);
DnsItem *item = new DnsItem(DnsItem::request_type_ns, hostname, hostnameLen, callback, state);
s_requestQueue.addItem(item);
logTrace(g_conf.m_logTraceDns, "END");
}
void GbDns::makeCallbacks() {
s_callbackQueueMtx.lock();
while (!s_callbackQueue.empty()) {
DnsItem *item = s_callbackQueue.front();
s_callbackQueueMtx.unlock();
logTrace(g_conf.m_logTraceDns, "processing callback queue item=%p", item);
item->m_callback(&(item->m_response), item->m_state);
logTrace(g_conf.m_logTraceDns, "removing callback queue item=%p", item);
delete item;
s_callbackQueueMtx.lock();
s_callbackQueue.pop();
}
s_callbackQueueMtx.unlock();
}