svxlink/src/async/cpp/AsyncCppDnsLookupWorker.cpp

560 lines
16 KiB
C++

/**
@file AsyncCppDnsLookupWorker.cpp
@brief Contains a class to execute DNS queries in the Posix environment
@author Tobias Blomberg
@date 2003-04-17
This file contains a class for executing DNS quries in the Cpp variant of
the async environment. This class should never be used directly. It is
used by Async::CppApplication to execute DNS queries.
\verbatim
Async - A library for programming event driven applications
Copyright (C) 2003-2022 Tobias Blomberg
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
\endverbatim
*/
/****************************************************************************
*
* System Includes
*
****************************************************************************/
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netdb.h>
#include <cassert>
#include <cstring>
/****************************************************************************
*
* Project Includes
*
****************************************************************************/
#include <AsyncDnsLookup.h>
/****************************************************************************
*
* Local Includes
*
****************************************************************************/
#include "AsyncCppDnsLookupWorker.h"
/****************************************************************************
*
* Namespaces to use
*
****************************************************************************/
using namespace Async;
/****************************************************************************
*
* Defines & typedefs
*
****************************************************************************/
/****************************************************************************
*
* Local class definitions
*
****************************************************************************/
/****************************************************************************
*
* Prototypes
*
****************************************************************************/
/****************************************************************************
*
* Exported Global Variables
*
****************************************************************************/
/****************************************************************************
*
* Local Global Variables
*
****************************************************************************/
/****************************************************************************
*
* Public member functions
*
****************************************************************************/
CppDnsLookupWorker::CppDnsLookupWorker(const DnsLookup& dns)
: DnsLookupWorker(dns)
{
m_notifier_watch.activity.connect(
sigc::mem_fun(*this, &CppDnsLookupWorker::notificationReceived));
} /* CppDnsLookupWorker::CppDnsLookupWorker */
CppDnsLookupWorker::~CppDnsLookupWorker(void)
{
abortLookup();
} /* CppDnsLookupWorker::~CppDnsLookupWorker */
DnsLookupWorker& CppDnsLookupWorker::operator=(DnsLookupWorker&& other_base)
{
this->DnsLookupWorker::operator=(std::move(other_base));
auto& other = static_cast<CppDnsLookupWorker&>(other_base);
abortLookup();
m_result = std::move(other.m_result);
assert(!other.m_result.valid());
m_notifier_watch = std::move(other.m_notifier_watch);
return *this;
} /* CppDnsLookupWorker::operator=(DnsLookupWorker&&) */
/****************************************************************************
*
* Protected member functions
*
****************************************************************************/
bool CppDnsLookupWorker::doLookup(void)
{
// A lookup is already running
if (m_result.valid())
{
return true;
}
setLookupFailed(false);
int fd[2];
if (pipe(fd) != 0)
{
char errbuf[256];
strerror_r(errno, errbuf, sizeof(errbuf));
std::cerr << "*** ERROR: Could not create pipe: " << errbuf << std::endl;
setLookupFailed();
return false;
}
m_notifier_watch.setFd(fd[0], FdWatch::FD_WATCH_RD);
m_notifier_watch.setEnabled(true);
ThreadContext ctx;
ctx.label = dns().label();
ctx.type = dns().type();
ctx.notifier_wr = fd[1];
ctx.anslen = 0;
ctx.thread_cerr.clear();
m_result = std::move(std::async(std::launch::async, workerFunc,
std::move(ctx)));
return true;
} /* CppDnsLookupWorker::doLookup */
void CppDnsLookupWorker::abortLookup(void)
{
if (m_result.valid())
{
const ThreadContext& ctx(m_result.get());
if (ctx.addrinfo != nullptr)
{
freeaddrinfo(ctx.addrinfo);
}
//m_result = std::move(std::future<ThreadContext>());
}
int fd = m_notifier_watch.fd();
if (fd >= 0)
{
m_notifier_watch.setFd(-1, FdWatch::FD_WATCH_RD);
close(fd);
}
} /* CppDnsLookupWorker::abortLookup */
/****************************************************************************
*
* Private member functions
*
****************************************************************************/
/*
*----------------------------------------------------------------------------
* Method: CppDnsLookupWorker::workerFunc
* Purpose: This is the function that do the actual DNS lookup. It is
* started as a separate thread since res_nsearch is a
* blocking function.
* Input: ctx - A context containing query and result parameters
* Output: The answer and anslen variables in the ThreadContext will be
* filled in with the lookup result. The context will be returned
* from this function.
* Author: Tobias Blomberg
* Created: 2021-07-14
* Remarks:
* Bugs:
*----------------------------------------------------------------------------
*/
CppDnsLookupWorker::ThreadContext CppDnsLookupWorker::workerFunc(
CppDnsLookupWorker::ThreadContext ctx)
{
std::ostream& th_cerr = ctx.thread_cerr;
int qtype = 0;
switch (ctx.type)
{
case DnsLookup::Type::A:
{
struct addrinfo hints = {0};
hints.ai_family = AF_INET;
int ret = getaddrinfo(ctx.label.c_str(), NULL, &hints, &ctx.addrinfo);
if (ret != 0)
{
th_cerr << "*** WARNING[getaddrinfo]: Could not look up host \""
<< ctx.label << "\": " << gai_strerror(ret) << std::endl;
}
else if (ctx.addrinfo == nullptr)
{
th_cerr << "*** WARNING[getaddrinfo]: No address info returned "
"for host \"" << ctx.label << "\"" << std::endl;
}
break;
}
case DnsLookup::Type::PTR:
{
IpAddress ip_addr;
size_t arpa_domain_pos = ctx.label.find(".in-addr.arpa");
if (arpa_domain_pos != std::string::npos)
{
ip_addr.setIpFromString(ctx.label.substr(0, arpa_domain_pos));
struct in_addr addr = ip_addr.ip4Addr();
addr.s_addr = htonl(addr.s_addr);
ip_addr.setIp(addr);
}
else
{
ip_addr.setIpFromString(ctx.label);
}
if (!ip_addr.isEmpty())
{
struct sockaddr_in in_addr = {0};
in_addr.sin_family = AF_INET;
in_addr.sin_addr = ip_addr.ip4Addr();
int ret = getnameinfo(reinterpret_cast<struct sockaddr*>(&in_addr),
sizeof(in_addr),
ctx.host, sizeof(ctx.host),
NULL, 0, NI_NAMEREQD);
if (ret != 0)
{
th_cerr << "*** WARNING[getnameinfo]: Could not look up IP \""
<< ctx.label << "\": " << gai_strerror(ret) << std::endl;
}
}
else
{
th_cerr << "*** WARNING: Failed to parse PTR label \""
<< ctx.label << "\"" << std::endl;
}
break;
}
case DnsLookup::Type::CNAME:
qtype = ns_t_cname;
break;
case DnsLookup::Type::SRV:
qtype = ns_t_srv;
break;
default:
assert(0);
}
if (qtype != 0)
{
struct __res_state state;
int ret = res_ninit(&state);
if (ret != -1)
{
state.options = RES_DEFAULT;
const char *dname = ctx.label.c_str();
ctx.anslen = res_nsearch(&state, dname, ns_c_in, qtype,
ctx.answer, NS_PACKETSZ);
if (ctx.anslen == -1)
{
th_cerr << "*** ERROR: Name resolver failure -- res_nsearch: "
<< hstrerror(h_errno) << std::endl;
}
// FIXME: Valgrind complain about leaked memory in the resolver library
// when a lookup fails. It seems to be a one time leak though so it
// does not grow with every failed lookup. But even so, it seems
// that res_close is not cleaning up properly.
// Glibc 2.33-18 on Fedora 34.
res_nclose(&state);
}
else
{
th_cerr << "*** ERROR: Name resolver failure -- res_ninit: "
<< hstrerror(h_errno) << std::endl;
}
}
close(ctx.notifier_wr);
ctx.notifier_wr = -1;
return std::move(ctx);
} /* CppDnsLookupWorker::workerFunc */
/*
*----------------------------------------------------------------------------
* Method: CppDnsLookupWorker::notificationReceived
* Purpose: When the DNS lookup thread is done, this function will be
* called to parse the result and notify the user that an answer
* is available.
* Input: w - The file watch object (notification pipe)
* Output: None
* Author: Tobias Blomberg
* Created: 2005-04-12
* Remarks:
* Bugs:
*----------------------------------------------------------------------------
*/
void CppDnsLookupWorker::notificationReceived(FdWatch *w)
{
char buf[1];
int cnt = read(w->fd(), buf, sizeof(buf));
assert(cnt == 0);
close(w->fd());
w->setFd(-1, FdWatch::FD_WATCH_RD);
const ThreadContext& ctx(m_result.get());
const std::string& thread_errstr = ctx.thread_cerr.str();
if (!thread_errstr.empty())
{
std::cerr << thread_errstr;
setLookupFailed();
}
if (ctx.type == DnsResourceRecord::Type::A)
{
if (ctx.addrinfo != nullptr)
{
struct addrinfo *entry;
std::vector<IpAddress> the_addresses;
for (entry = ctx.addrinfo; entry != 0; entry = entry->ai_next)
{
IpAddress ip_addr(
reinterpret_cast<struct sockaddr_in*>(entry->ai_addr)->sin_addr);
//std::cout << "### ai_family=" << entry->ai_family
// << " ai_socktype=" << entry->ai_socktype
// << " ai_protocol=" << entry->ai_protocol
// << " ip=" << ip_addr << std::endl;
if (find(the_addresses.begin(), the_addresses.end(), ip_addr) ==
the_addresses.end())
{
the_addresses.push_back(ip_addr);
addResourceRecord(
new DnsResourceRecordA(ctx.label, 0, ip_addr));
}
}
freeaddrinfo(ctx.addrinfo);
}
}
else if (ctx.type == DnsResourceRecord::Type::PTR)
{
if (ctx.host[0] != '\0')
{
addResourceRecord(
new DnsResourceRecordPTR(ctx.label, 0, ctx.host));
}
}
else
{
if (ctx.anslen == -1)
{
workerDone();
return;
}
char errbuf[256];
ns_msg msg;
int ret = ns_initparse(ctx.answer, ctx.anslen, &msg);
if (ret == -1)
{
strerror_r(ret, errbuf, sizeof(errbuf));
std::cerr << "*** WARNING: ns_initparse failed: " << errbuf << std::endl;
setLookupFailed();
workerDone();
return;
}
uint16_t msg_cnt = ns_msg_count(msg, ns_s_an);
if (msg_cnt == 0)
{
setLookupFailed();
}
for (uint16_t rrnum=0; rrnum<msg_cnt; ++rrnum)
{
ns_rr rr;
ret = ns_parserr(&msg, ns_s_an, rrnum, &rr);
if (ret == -1)
{
strerror_r(errno, errbuf, sizeof(errbuf));
std::cerr << "*** WARNING: DNS lookup failure in ns_parserr: "
<< errbuf << std::endl;
setLookupFailed();
continue;
}
const char *name = ns_rr_name(rr);
uint16_t rr_class = ns_rr_class(rr);
if (rr_class != ns_c_in)
{
std::cerr << "*** WARNING: Wrong RR class in DNS answer: "
<< rr_class << std::endl;
setLookupFailed();
continue;
}
uint32_t ttl = ns_rr_ttl(rr);
uint16_t type = ns_rr_type(rr);
const unsigned char *cp = ns_rr_rdata(rr);
switch (type)
{
case ns_t_a:
{
struct in_addr in_addr;
uint32_t ip = ns_get32(cp);
in_addr.s_addr = ntohl(ip);
addResourceRecord(
new DnsResourceRecordA(name, ttl, IpAddress(in_addr)));
break;
}
case ns_t_ptr:
{
char exp_dn[NS_MAXDNAME+1];
ret = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), cp,
exp_dn, NS_MAXDNAME);
if (ret == -1)
{
strerror_r(errno, errbuf, sizeof(errbuf));
std::cerr << "*** WARNING: DNS lookup failure in "
"ns_name_uncompress: " << errbuf << std::endl;
setLookupFailed();
continue;
}
size_t exp_dn_len = strlen(exp_dn);
exp_dn[exp_dn_len] = '.';
exp_dn[exp_dn_len+1] = 0;
addResourceRecord(new DnsResourceRecordPTR(name, ttl, exp_dn));
break;
}
case ns_t_cname:
{
char exp_dn[NS_MAXDNAME+1];
ret = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), cp,
exp_dn, NS_MAXDNAME);
if (ret == -1)
{
strerror_r(errno, errbuf, sizeof(errbuf));
std::cerr << "*** WARNING: DNS lookup failure in "
"ns_name_uncompress" << errbuf << std::endl;
setLookupFailed();
continue;
}
size_t exp_dn_len = strlen(exp_dn);
exp_dn[exp_dn_len] = '.';
exp_dn[exp_dn_len+1] = 0;
addResourceRecord(new DnsResourceRecordCNAME(name, ttl, exp_dn));
break;
}
case ns_t_srv:
{
unsigned int prio = ns_get16(cp);
cp += NS_INT16SZ;
unsigned int weight = ns_get16(cp);
cp += NS_INT16SZ;
unsigned int port = ns_get16(cp);
cp += NS_INT16SZ;
char exp_dn[NS_MAXDNAME+1];
ret = ns_name_uncompress(ns_msg_base(msg), ns_msg_end(msg), cp,
exp_dn, NS_MAXDNAME);
if (ret == -1)
{
strerror_r(errno, errbuf, sizeof(errbuf));
std::cerr << "*** WARNING: DNS lookup failure in "
"ns_name_uncompress: " << errbuf << std::endl;
setLookupFailed();
continue;
}
size_t exp_dn_len = strlen(exp_dn);
exp_dn[exp_dn_len] = '.';
exp_dn[exp_dn_len+1] = 0;
addResourceRecord(
new DnsResourceRecordSRV(name, ttl, prio, weight, port, exp_dn));
break;
}
default:
std::cerr << "*** WARNING: Unsupported RR type, " << type
<< ", received in DNS query for " << name << std::endl;
setLookupFailed();
break;
}
}
}
workerDone();
} /* CppDnsLookupWorker::notificationReceived */
/*
* This file has not been truncated
*/