[*] Harden glibc resolver

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

View File

@ -5,14 +5,25 @@
Date: 2022-8-26
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 "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
// https://elixir.bootlin.com/glibc/glibc-2.40.9000/source/bits/types/sigevent_t.h#L8
// 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
namespace Aurora::IO::Net
@ -147,9 +158,17 @@ namespace Aurora::IO::Net
}
sigevent event {0};
#if defined(AURORA_PLATFORM_LINUX)
event.sigev_notify = SIGEV_SIGNAL;
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);
if (!pQueue)
@ -170,12 +189,6 @@ namespace Aurora::IO::Net
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,
@ -183,27 +196,27 @@ namespace Aurora::IO::Net
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;
}
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 this->StartStandard();
}
}
if (iStatus != 0)
@ -214,6 +227,13 @@ namespace Aurora::IO::Net
return false;
}
if (!AuTryInsert(tlsResolvers, AuSharedFromThis()))
{
SysPushErrorMemory();
this->Cancel();
return false;
}
return this->BeginOperation(AuSharedFromThis(),
this->pWorker_);
@ -468,13 +488,93 @@ namespace Aurora::IO::Net
#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. libresolv could, but using libresolv this early would suck.
// 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. Windows8+ can cancel, no matter what.
// On other POSIX systems, we could force term a thread, but we dont, and idk if we should.
// On Android, we might have android_res_cancel / android_getaddrinfofornetwork. We might not. We don't have android support in this file yet, or at all for that matter.
// On glibc, we have gai_cancel, that might work, might not.
// 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.
// this is fine for desktop clients cleaning up a subsystem. Might not be so great for mobiles or unstable connections.
// 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.
//
// Alternatively, we could use c-ares. That could provide a good extended resolver and provides cancelation. It's C89, run on Windows, runs on pretty much anything. It's completely nonblocking.
// Problem is, it doesn't get hooked by the users preferences properly. The caching sucks. It's not really the system resolver. We should use use this if we were porting to an embedded or a generic platform.
// freertos, freebsd, console ports, and MAYBE physically-portable musl targets are the only few platforms that can begin to justify this.
// [lowprio] Might pull it in for everybody to use later. We don't really need it until there's a demand for SRV & TXT & MX DNS resolution. It's a valid request.
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)
{
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();
}
}
}