AuroraRuntime/Source/IO/Net/AuNetResolver.Unix.cpp

499 lines
13 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: AuNetResolver.Unix.cpp
Date: 2022-8-26
Author: Reece
***/
#include "Networking.hpp"
#include "AuNetResolver.Unix.hpp"
#include "AuNetEndpoint.hpp"
#include <Source/IO/Loop/LSSignalCatcher.Linux.hpp>
#include <Source/IO/Loop/LSEvent.hpp>
#if defined(AURORA_IS_LINUX_DERIVED)
#define RESOLVER_IS_VERY_FREETARDED
#endif
namespace Aurora::IO::Net
{
static AuList<AuWPtr<NetResolver>> tlsResolvers;
struct ThreadLocalCaughtCompletion : Loop::LSSignalCatcher
{
ThreadLocalCaughtCompletion(NetResolver *pParent);
virtual bool OnTrigger(AuUInt handle) override;
virtual const AuList<AuUInt> &GetHandles() override;
virtual bool Singular() override;
private:
AuList<AuUInt> handles_;
NetResolver *pParent_;
};
// this is beyond dumb and inefficient. i think glibc just creates a singular worker thread and resolves each request in a blocking manner with no mutliplexing
// TODO: we really need to make a general purpose AuAsync IO pool and generic resolver replacer/hooking interface for net services.
static auto GetGAIAsyncIOSignal()
{
return gRuntimeConfig.linuxConfig.uSignalGAIOWorkerThreadDone;
}
ThreadLocalCaughtCompletion::ThreadLocalCaughtCompletion(NetResolver *pParent) :
LSSignalCatcher({GetGAIAsyncIOSignal()}),
pParent_(pParent)
{
handles_ = {this->handle, AuStaticCast<AuLoop::LSEvent>(this->pParent_->pEvent)->GetHandle()};
}
const AuList<AuUInt> &ThreadLocalCaughtCompletion::GetHandles()
{
return this->handles_;
}
bool ThreadLocalCaughtCompletion::Singular()
{
return false;
}
bool ThreadLocalCaughtCompletion::OnTrigger(AuUInt handle)
{
if (this->pParent_->CheckAsync())
{
if (this->pParent_->pEvent)
{
this->pParent_->pEvent->Reset();
}
return true;
}
// TODO (Reece) urgent: HACK FIX ME
// Its a slow path so this hack shouldn't matter.
// We specifically optimize it by keeping the event latched until we
// specifically receive a poll update, in which case, reset the event,
// give the shitty glibc freetarded code a chance to catch up, then check.
// This way, we should never miss an update
#if 1
if (handle == handles_[1])
{
if (this->pParent_->pEvent)
{
this->pParent_->pEvent->Reset();
}
for (int i = 0; i < 10; i++)
{
AuThreading::ContextYield();
}
return this->pParent_->CheckAsync();
}
#endif
return false;
}
NetResolver::NetResolver(const AuSPtr<INetWorker> &pWorker) :
pWorker_(pWorker)
{
this->pEvent = AuLoop::NewLSEventSlow(false, false);
}
NetResolver::NetResolver(const AuSPtr<INetWorker> &pWorker,
const AuSPtr<AuAsync::PromiseCallback<AuList<IPAddress>, NetError>> &pCompletion) :
pCompletion_(pCompletion),
pWorker_(pWorker)
{
this->pEvent = AuLoop::NewLSEventSlow(false, false);
}
NetResolver::~NetResolver()
{
}
bool NetResolver::Start()
{
int iInfoEx { 0 };
AuMemset(&this->infoEx, 0, sizeof(infoEx));
if (this->bA && this->bAAAA)
{
infoEx.ai_family = AF_UNSPEC;
}
else if (this->bA && !this->bAAAA)
{
infoEx.ai_family = AF_INET;
}
else if (this->bAAAA)
{
infoEx.ai_family = AF_INET6;
}
else
{
return false;
}
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
if (!pgetaddrinfo_a)
{
return this->StartStandard();
}
sigevent event {0};
event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = GetGAIAsyncIOSignal();
//event._sigev_un._tid = gettid();
auto pQueue = AuMakeShared<ThreadLocalCaughtCompletion>(this);
if (!pQueue)
{
SysPushErrorNet("");
return false;
}
if (!pQueue->HasValidHandle())
{
SysPushErrorNet("");
return false;
}
this->UpdateTrigger(pQueue);
auto pBase = &this->gnuAsyncOperation;
pBase->ar_request = (addrinfo *)&infoEx;
pBase->ar_name = hostname.c_str();
if (!AuTryInsert(tlsResolvers, AuSharedFromThis()))
{
SysPushErrorMemory();
return false;
}
int iStatus = pgetaddrinfo_a(GAI_NOWAIT,
&pBase,
1,
&event);
switch (iStatus)
{
case EAI_AGAIN:
{
SysPushErrorNet("Low Resources | Failed to resolve");
this->uOsError = iStatus;
this->error_ = this->ToError();
return false;
}
case EAI_MEMORY:
{
SysPushErrorNet("Out of Memory | Failed to resolve");
this->uOsError = iStatus;
this->error_ = this->ToError();
return false;
}
case EAI_SYSTEM:
{
SysPushErrorNet("Invalid | Failed to resolve: {}", errno);
this->uOsError = errno;
this->error_ = this->ToError();
return false;
}
}
if (iStatus != 0)
{
SysPushErrorNet("{}", iStatus);
this->uOsError = iStatus;
this->error_ = this->ToError();
return false;
}
return this->BeginOperation(AuSharedFromThis(),
this->pWorker_);
#else
return this->StartStandard();
#endif
}
bool NetResolver::StartStandard()
{
auto pShared = this->SharedFromThis();
auto pThread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind([=]()
{
int iRet {};
addrinfo infoA { 0 };
addrinfo *pInfoRet {};
infoA.ai_family = infoEx.ai_family;
iRet = getaddrinfo(pShared->hostname.c_str(),
nullptr,
&infoA,
&pInfoRet);
if (iRet == 0)
{
auto pCurrent = pInfoRet;
while (pCurrent)
{
NetEndpoint endpoint;
AuMemcpy(endpoint.hint, pCurrent->ai_addr, pCurrent->ai_addrlen);
DeoptimizeEndpoint(endpoint);
if (!AuTryInsert(pShared->processedIps_, endpoint.ip))
{
pShared->bForceError_ = true;
break;
}
pCurrent = pCurrent->ai_next;
}
}
else
{
pShared->bForceError_ = true;
pShared->uOsError = iRet;
pShared->error_ = pShared->ToError();
}
if (pInfoRet)
{
freeaddrinfo(pInfoRet);
}
pShared->pEvent->Set();
})),
AuThreads::IThreadVectorsFunctional::OnExit_t{}),
"Legacy Posix DNS Request"
));
if (!pThread)
{
return false;
}
pThread->Run();
pThread->Detach();
this->UpdateTrigger(pShared->pEvent);
return this->BeginOperation(AuSharedFromThis(),
this->pWorker_);
}
void NetResolver::SetCompletion(const AuSPtr<AuAsync::PromiseCallback<AuList<IPAddress>, NetError>> &callback)
{
this->pCompletion_ = callback;
}
void NetResolver::OnOverlappedComplete()
{
for (auto itr = tlsResolvers.begin(); itr != tlsResolvers.end(); )
{
auto pResolver = (*itr).lock();
if (!pResolver)
{
itr = tlsResolvers.erase(itr);
continue;
}
if (pResolver.get() == this)
{
itr = tlsResolvers.erase(itr);
continue;
}
itr++;
}
if (AuExchange(this->bHasCompleted_, true))
{
return;
}
if (this->Unpack())
{
if (this->pCompletion_)
{
try
{
this->pCompletion_->OnSuccess((void *)&this->processedIps_);
}
catch (...)
{
SysPushErrorCatch();
}
}
}
else
{
if (this->pCompletion_)
{
try
{
this->error_ = ENetworkError::eAsyncError;
this->pCompletion_->OnFailure((void *)&this->error_);
}
catch (...)
{
SysPushErrorCatch();
}
}
}
}
void NetResolver::OnOverlappedFailure(const NetError &error)
{
this->error_ = error;
if (this->pCompletion_)
{
try
{
this->pCompletion_->OnFailure((void *)&this->error_);
}
catch (...)
{
SysPushErrorCatch();
}
}
this->bHasCompleted_ = true;
}
bool NetResolver::HasFinished()
{
return this->bHasCompleted_;
}
bool NetResolver::CheckAsync()
{
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
if (!pgai_error)
{
return !this->bForceError_;
}
int iStatus = pgai_error(&this->gnuAsyncOperation);
return ((iStatus == 0) ||
(iStatus == EAI_CANCELED));
#else
return !this->bForceError_;
#endif
}
void NetResolver::Cancel()
{
while (!this->HasComplete())
{
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
if (pgai_cancel)
{
int iStatus = pgai_cancel(&this->gnuAsyncOperation);
switch (iStatus)
{
case EAI_CANCELED:
case EAI_ALLDONE:
break;
case EAI_NOTCANCELED:
{
IOYield();
AuThreading::ContextYield();
continue;
}
}
}
else
{
IOYield();
AuThreading::ContextYield();
}
#else
IOYield();
AuThreading::ContextYield();
#endif
}
}
const NetError &NetResolver::GetError()
{
return this->error_;
}
bool NetResolver::Unpack()
{
if (!CheckAsync())
{
return false;
}
for (auto pItr = this->gnuAsyncOperation.ar_result; pItr; pItr = pItr->ai_next)
{
NetEndpoint endpoint;
AuMemcpy(endpoint.hint, pItr->ai_addr, pItr->ai_addrlen);
DeoptimizeEndpoint(endpoint);
if (AuExists(this->processedIps_, endpoint.ip))
{
continue;
}
if (!AuTryInsert(this->processedIps_, endpoint.ip))
{
return false;
}
}
freeaddrinfo(this->gnuAsyncOperation.ar_result);
return true;
}
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
static void Annoying(int)
{
for (const auto &gawad : tlsResolvers)
{
if (auto pResolver = gawad.lock())
{
pResolver->pEvent->Set();
}
}
}
#endif
void InitFreetardedResolver()
{
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
struct sigaction action =
{
.sa_handler = Annoying,
.sa_flags = SA_ONSTACK | SA_RESTART
};
::sigemptyset(&action.sa_mask);
::sigaction(GetGAIAsyncIOSignal(), &action, nullptr);
#endif
}
}