[*] Harden glibc resolver

This commit is contained in:
Reece Wilson 2024-09-29 21:39:39 +01:00
parent 6e9e962c84
commit e63fb1c996

View File

@ -5,14 +5,25 @@
Date: 2022-8-26 Date: 2022-8-26
Author: Reece Author: Reece
***/ ***/
#if defined(AURORA_IS_LINUX_DERIVED)
#define RESOLVER_IS_VERY_FREETARDED
// Compilers compiled for linux should define this for us, including GCC.
#if !defined(_GNU_SOURCE)
#define _GNU_SOURCE
#endif
//Defines: __USE_GNU
#include <features.h>
#endif
#include "Networking.hpp" #include "Networking.hpp"
#include "AuNetResolver.Unix.hpp" #include "AuNetResolver.Unix.hpp"
#include "AuNetEndpoint.hpp" #include "AuNetEndpoint.hpp"
#include <Source/IO/Loop/LSSignalCatcher.Linux.hpp> #include <Source/IO/Loop/LSSignalCatcher.Linux.hpp>
#include <Source/IO/Loop/LSEvent.hpp> #include <Source/IO/Loop/LSEvent.hpp>
#if defined(AURORA_IS_LINUX_DERIVED) // https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/bits/types/sigevent_t.h#L8
#define RESOLVER_IS_VERY_FREETARDED // https://elixir.bootlin.com/musl/v1.2.5/source/include/signal.h#L190
#if defined(__USE_GNU)
#define sigev_notify_thread_id _sigev_un._tid
#endif #endif
namespace Aurora::IO::Net namespace Aurora::IO::Net
@ -147,9 +158,17 @@ namespace Aurora::IO::Net
} }
sigevent event {0}; sigevent event {0};
#if defined(AURORA_PLATFORM_LINUX)
event.sigev_notify = SIGEV_SIGNAL; event.sigev_notify = SIGEV_SIGNAL;
event.sigev_signo = GetGAIAsyncIOSignal(); event.sigev_signo = GetGAIAsyncIOSignal();
//event._sigev_un._tid = gettid(); event.sigev_notify_thread_id = gettid();
// pid_t caller_pid = sig->sigev_notify == SIGEV_SIGNAL ? getpid() : 0;
// not required for glibc resolv?
#else
// TODO: setup threaded callback:
return this->StartStandard();
#endif
auto pQueue = AuMakeShared<ThreadLocalCaughtCompletion>(this); auto pQueue = AuMakeShared<ThreadLocalCaughtCompletion>(this);
if (!pQueue) if (!pQueue)
@ -170,12 +189,6 @@ namespace Aurora::IO::Net
pBase->ar_request = (addrinfo *)&infoEx; pBase->ar_request = (addrinfo *)&infoEx;
pBase->ar_name = hostname.c_str(); pBase->ar_name = hostname.c_str();
if (!AuTryInsert(tlsResolvers, AuSharedFromThis()))
{
SysPushErrorMemory();
return false;
}
int iStatus = pgetaddrinfo_a(GAI_NOWAIT, int iStatus = pgetaddrinfo_a(GAI_NOWAIT,
&pBase, &pBase,
1, 1,
@ -183,27 +196,27 @@ namespace Aurora::IO::Net
switch (iStatus) switch (iStatus)
{ {
case EAI_AGAIN: case EAI_AGAIN:
{ {
SysPushErrorNet("Low Resources | Failed to resolve"); SysPushErrorNet("Low Resources | Failed to resolve");
this->uOsError = iStatus; this->uOsError = iStatus;
this->error_ = this->ToError(); this->error_ = this->ToError();
return false; return false;
} }
case EAI_MEMORY: case EAI_MEMORY:
{ {
SysPushErrorNet("Out of Memory | Failed to resolve"); SysPushErrorNet("Out of Memory | Failed to resolve");
this->uOsError = iStatus; this->uOsError = iStatus;
this->error_ = this->ToError(); this->error_ = this->ToError();
return false; return false;
} }
case EAI_SYSTEM: case EAI_SYSTEM:
{ {
SysPushErrorNet("Invalid | Failed to resolve: {}", errno); SysPushErrorNet("Invalid | Failed to resolve: {}", errno);
this->uOsError = errno; this->uOsError = errno;
this->error_ = this->ToError(); this->error_ = this->ToError();
return false; return this->StartStandard();
} }
} }
if (iStatus != 0) if (iStatus != 0)
@ -214,6 +227,13 @@ namespace Aurora::IO::Net
return false; return false;
} }
if (!AuTryInsert(tlsResolvers, AuSharedFromThis()))
{
SysPushErrorMemory();
this->Cancel();
return false;
}
return this->BeginOperation(AuSharedFromThis(), return this->BeginOperation(AuSharedFromThis(),
this->pWorker_); this->pWorker_);
@ -468,13 +488,86 @@ namespace Aurora::IO::Net
#if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS) #if defined(AURORA_HAS_GLIBC_RESOLVER_PROXY_STUBS)
// Our name-resolution signaling options for glibc / GAHHNUUU-EEB-SNCCEE are:
// 1) [gahnuu IO] Wait forever for glibc to spawn a thread with their own pthreads; or
// 2) [gahnuu IO] Use their interface for sending a signal instead. Noting that Linux-likes can
// handle sigev_notify_thread_id notifications, via a signalqueue and sigevent field.
// 3) [posix] Just call getaddrinfo in a thread pool ourselves (or just not and create a thread each time. same for winxp.)
//
// Noting that GAHHNUUU-SNCCEE resolv NSS modules are the only hope of standard Linux-like applications of getting DoH,
// alternative plugin-based DNS services, and cached results on their system.
// musl and other embedded drivers are hopeless.
//
// No matter the POSIX implementation type, it'll match WinXP - 11 with the Windows Name Resolution stack under Windows Sockets 2.
// Canceling? Windows XP, Vista and 7 doesn't even have GetAddrInfoExCancel. glibc says, na, not yet because its not in posix yet.
// Canceling, but can we? Ok, we can sort of cancel on all platforms. On old Windows, we could force term a thread, but we dont, and idk if we should.
// On other POSIX systems, we could force term a thread, but we dont, and idk if we should.
// On glibc, we have gai_cancel, that might work.
// On glibc, posix, and Windows 7-XP, we will probably find ourselves spinning in a yield loop until the DNS request has been resolved or properly canceled.
// Caching? Yea probably.
// IPv6? Yea, if the platform vendor supports it.
// User config? Yea, if the platform vendor supports it.
// Native async? Maybe a thread pool if we're lucky. Otherwise we can just spawn a thread and get the same results
//
// On the plus side, libc abstraction of getaddrinfo in async form (glibc) or our own hack of spawn a thread (hello ::StartStandard),
// we always guarantee the system has a good async getaddrinfo;
// the interface we just established has the ability for the user to configure it; and
// the system will probably be handling our DNS caching in a global platform standard.
//
// On alternative libcs and platforms:
// >Musl will just have to write a good internal DNS library with plugin routing, caching, and/or async. Not my problem.
// >Android has its' own DNS stack based on ISCs lib, with caching. No async expected.
// >XNU has its' own dns stack based on ISCs lib (or at least they did. old libresolv.). No async expected.
// We can expect forks of ISCs library with IPv6 support with cached entries, somewhere; or similar features under getaddrinfo.
//
// On Linux:
// >Most Linux users will be using their own local version of glibc.
// >DNS can be configured how the user wants.
// >systemd-resolv and friends should take care of system local cache conctrol.
// >and yes that does mean implicit integration with systemd
// (systemd-resolve via the NSS module 'nss-resolve' gets served under our users of getaddrinfo [also incl the call under glibc aio] ).
//
// We assume best case glibc/signal on linux glibc targets.
// glibcs async resolve just uses getaddrinfo under the hood, with an actual threadpool. the only question which is faster: pthread spawn or a linux sigqueue?
// Knowing we can jut rely on getaddrinfo no matter what, we just spawn a detached thread, if we need async on other platforms with the same expected feature set.
// getaddrinfo may suck, but it's the only minimum interface we can provide and expect.
//
// So far as providing a common async abstraction for the expected platform provider is concerned, this should be the right thing.
//
// We could always build our own resolver on top of this and:
// >platform("k.root-servers.net") ?? (EU RIPE NCC, headquartered in Amsterdam. probably won't care for german or american lower level judges having a moment. )
// >193.0.14.129 ?? 2001:7fd::1 ??
// >platform("g.root-servers.net") ?? (US DoD glow plant. the good thing: EU plebs and activists be damned, they will not touch DNS results without a vaild US federal court order and 15 layers of bureaucracy. bc DOD feds.)
// >192.112.36.4 ?? 2001:500:12::d0d
// for the most globally standard, consistent, and censorship-free results.
// The downsides:
// No caching (TODO: registry with sqlite?)
// Probably wont be privacy friendly.
// Not all servers with be authenticated or encrypted.
// You could implement a nonstandard DNS server that's always recursive, cached, and encrypted, *but* then you probably need to worry about user consent and extra costs.
static void Annoying(int) static void Annoying(int)
{ {
// We probably do not need SA_NODEFER or iterator protection.
//
// Under Linux, we can target a specific posix processes with a signal properly.
// Under other systems, we cant. On these systems, we shouldn't expect SA_NODEFER to exist.
// Let's assume we have proper signal queues. On this note...
//
// if (--*waitlist->counterp == 0)
// __gai_notify_only (waitlist->sigevp, waitlist->caller_pid);
// https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/resolv/gai_misc.c#L337
// https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/resolv/gai_notify.c#L124-L128
// https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/sysdeps/unix/sysv/linux/gai_sigqueue.c#L31
// I think glibc is trying to protect us against this as well? I don't even know. I doubt they know either.
// They correctly pass around the pid, and then claim they need to because of a linux bug? what the actual fuck.
// Average gahnnuuu project. beri quality code.
for (const auto &gawad : tlsResolvers) for (const auto &gawad : tlsResolvers)
{ {
if (auto pResolver = gawad.lock()) if (auto pResolver = AuTryLockMemoryType(gawad))
{ {
pResolver->pEvent->Set(); // Under almost all POSIX systems, this will result in a blocking write
pResolver->pEvent->Set();
} }
} }
} }