2022-01-01 23:50:23 +01:00

191 lines
5.4 KiB
C++

/*
* IXDNSLookup.cpp
* Author: Benjamin Sergeant
* Copyright (c) 2018 Machine Zone, Inc. All rights reserved.
*/
//
// On Windows Universal Platform (uwp), gai_strerror defaults behavior is to
// returns wchar_t which is different from all other platforms. We want the non
// unicode version. See https://github.com/microsoft/vcpkg/pull/11030 We could
// do this in IXNetSystem.cpp but so far we are only using gai_strerror in here.
//
#ifdef _UNICODE
#undef _UNICODE
#endif
#ifdef UNICODE
#undef UNICODE
#endif
#include "IXDNSLookup.h"
#include "IXNetSystem.h"
#include <chrono>
#include <string.h>
#include <thread>
// mingw build quirks
#if defined(_WIN32) && defined(__GNUC__)
#define AI_NUMERICSERV NI_NUMERICSERV
#define AI_ADDRCONFIG LUP_ADDRCONFIG
#endif
namespace ix
{
const int64_t DNSLookup::kDefaultWait = 1; // ms
DNSLookup::DNSLookup(const std::string &hostname, int port, int64_t wait)
: _hostname(hostname), _port(port), _wait(wait), _res(nullptr), _done(false)
{
;
}
struct addrinfo *DNSLookup::getAddrInfo(const std::string &hostname,
int port,
std::string &errMsg)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
std::string sport = std::to_string(port);
struct addrinfo *res;
int getaddrinfo_result =
getaddrinfo(hostname.c_str(), sport.c_str(), &hints, &res);
if (getaddrinfo_result) {
errMsg = gai_strerror(getaddrinfo_result);
res = nullptr;
}
return res;
}
struct addrinfo *
DNSLookup::resolve(std::string &errMsg,
const CancellationRequest &isCancellationRequested,
bool cancellable)
{
return cancellable ? resolveCancellable(errMsg, isCancellationRequested)
: resolveUnCancellable(errMsg, isCancellationRequested);
}
void DNSLookup::release(struct addrinfo *addr) { freeaddrinfo(addr); }
struct addrinfo *DNSLookup::resolveUnCancellable(
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
errMsg = "no error";
// Maybe a cancellation request got in before the background thread
// terminated ?
if (isCancellationRequested()) {
errMsg = "cancellation requested";
return nullptr;
}
return getAddrInfo(_hostname, _port, errMsg);
}
struct addrinfo *DNSLookup::resolveCancellable(
std::string &errMsg,
const CancellationRequest &isCancellationRequested)
{
errMsg = "no error";
// Can only be called once, otherwise we would have to manage a pool
// of background thread which is overkill for our usage.
if (_done) {
return nullptr; // programming error, create a second DNSLookup instance
// if you need a second lookup.
}
//
// Good resource on thread forced termination
// https://www.bo-yang.net/2017/11/19/cpp-kill-detached-thread
//
auto ptr = shared_from_this();
std::weak_ptr<DNSLookup> self(ptr);
int port = _port;
std::string hostname(_hostname);
// We make the background thread doing the work a shared pointer
// instead of a member variable, because it can keep running when
// this object goes out of scope, in case of cancellation
auto t = std::make_shared<std::thread>(&DNSLookup::run,
this,
self,
hostname,
port);
t->detach();
while (!_done) {
// Wait for 1 milliseconds, to see if the bg thread has terminated.
// We do not use a condition variable to wait, as destroying this one
// if the bg thread is alive can cause undefined behavior.
std::this_thread::sleep_for(std::chrono::milliseconds(_wait));
// Were we cancelled ?
if (isCancellationRequested()) {
errMsg = "cancellation requested";
return nullptr;
}
}
// Maybe a cancellation request got in before the bg terminated ?
if (isCancellationRequested()) {
errMsg = "cancellation requested";
return nullptr;
}
errMsg = getErrMsg();
return getRes();
}
void DNSLookup::run(std::weak_ptr<DNSLookup> self,
std::string hostname,
int port) // thread runner
{
// We don't want to read or write into members variables of an object that
// could be gone, so we use temporary variables (res) or we pass in by copy
// everything that getAddrInfo needs to work.
std::string errMsg;
struct addrinfo *res = getAddrInfo(hostname, port, errMsg);
if (auto lock = self.lock()) {
// Copy result into the member variables
setRes(res);
setErrMsg(errMsg);
_done = true;
}
}
void DNSLookup::setErrMsg(const std::string &errMsg)
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
_errMsg = errMsg;
}
const std::string &DNSLookup::getErrMsg()
{
std::lock_guard<std::mutex> lock(_errMsgMutex);
return _errMsg;
}
void DNSLookup::setRes(struct addrinfo *addr)
{
std::lock_guard<std::mutex> lock(_resMutex);
_res = addr;
}
struct addrinfo *DNSLookup::getRes()
{
std::lock_guard<std::mutex> lock(_resMutex);
return _res;
}
} // namespace ix