/** @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 #include #include #include #include #include #include #include #include /**************************************************************************** * * Project Includes * ****************************************************************************/ #include /**************************************************************************** * * 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(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()); } 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(&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 the_addresses; for (entry = ctx.addrinfo; entry != 0; entry = entry->ai_next) { IpAddress ip_addr( reinterpret_cast(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