[+] Linux: missing AuNet adapter API

[*] Update README
This commit is contained in:
Reece Wilson 2022-12-17 13:57:00 +00:00
parent 1e4082c02f
commit 6f6be5c545
3 changed files with 614 additions and 136 deletions

268
README.md
View File

@ -1,6 +1,4 @@
## PREALPHA (in-dev, missing polish and APIs are volatile) ## PREALPHA
## Minimum viable product ETA: September 2022
## AuroraRuntime ## AuroraRuntime
The Aurora Runtime is a low level platform abstraction layer for modern cross-platform C++ The Aurora Runtime is a low level platform abstraction layer for modern cross-platform C++
@ -14,26 +12,30 @@ pipeline to get started.
## Features ## Features
- Reduced C++ standard template library dependence (^1) - Reduced C++ standard template library dependence despite requiring a modern-ish driver (^1) (portable when?)
- High performance threading and synchronization primitives (os userland sched optimized) - Logging; UTF-8 logger, common sink backends, formating interface
- Async even driven subsystem with high perf sync primitives - Debug and Telementry; asserts, panics, exception logging, demangling of symbols, more
- Abstract kernel file/net transaction, IPC, timer, semaphore, et al abstraction in the form of LoopQueues (eg, MacOS RunLoops) - Crypto ECC/[25519, P-384, P-256], [AES, RSA, X509], CBC[AES, Stinky3DES], HMAC, HashCash, BCrypt, [common digests]
- Asynchronous and synchronous IO (network, character, file, buffered, process, and io watcher)
- Optional event driven async programming paradigm
- Consoles; graphical and standard, file archives
- Logging; UTF-8 logger, common sink backends
- Debug and Telementry; asserts, exception logging, fio, nio backends
- Crypto ECC/[25519, P-384, P-256], [AES, RSA, X509], [common digests]
- Basic cmdline parsing from any module - Basic cmdline parsing from any module
- Exit and fatal save condition callbacks - Exit and fatal save condition callbacks
- IPC - Random; secure, user-seeded, and fast
- Network [WIP] - Hardware Info; memory and cpu info (including cpu feature bits, core topology, e-core awareness, and basic cache size)
- Random; secure and fast - Software stack information for retrieving kernel, version, brand, family, build string, etc
- Hardware Info; memory and cpu info (including features, topology, e-core, and cache info) - Compression (deflate, gzip, zstd, LZ4, bzip2, TODO: lzma, brotli)
- Software Stack Info (kernel, version, brand, build string, etc)
- FIO settings registry
- Compression
- Locale and encoding - Locale and encoding
- High performance threading and synchronization primitives (os userland sched optimized)
- Async subsystem backed by high performance sync primitives (cv loop) and hybrid switching into IO polling (think userland cv-backed promises + waitmultipleobjects)
- IO subsystem for standard cross-platform IO loop queues, IPC (mutex with auto-unlock, semaphores, full-duplex single-connection pipes, and shared memory), file (direct uncached access), and network
- Abstract kernel IO transactions, IPC objects, timers, semaphores, and others in the form of ILoopSources
- Common IO transaction interface for network, file, and handle async access (with workarounds for platform querks)
- IO processor for common network, pipe processing, and general work on any given thread (think of it as an io context)
- IO pipe processor for the processing of data when invoked by io transactions and other signalable interfaces
- Protocol stack concept, for implementing low-overhead IO stream processors, where data is streamed through layers of interceptors
- Builtin support for TLS and compression in the form of protocol stack interceptors
- Non-locking file system watchers with IO subsystem interoperability
- Process spawning with stream redirection backed by the IO subsystem
- Process memory management with IPC and file mapping
- FIO settings registry
- C++ utility templates and macros - C++ utility templates and macros
- Follows all strings are UTF-8 convention - Follows all strings are UTF-8 convention
@ -55,36 +57,28 @@ Discord: [Invite](https://discord.gg/XYjCGWWa4J)
| Platform | Support | | Platform | Support |
| ----------- | ------- | | ----------- | ------- |
| NT/Win32-like |✅ | | NT/Win7 | ⚠️ |
| NT/Win8.1+ | ✅ |
| NT/UWP | 🕖 | | NT/UWP | 🕖 |
| NT/GameOS|❌ | | NT/GameOS|❌ |
| Linux |🕖 | | Linux | |
| FreeBSD 9 | ❌ | | FreeBSD 9 | ❌ |
| FreeBSD 11 | ❌ | | FreeBSD 11 | ❌ |
| OpenBSD | ❌ |
| XNU/NS-like | ❌ | | XNU/NS-like | ❌ |
Win7: some apis are inherently limited before Win8.1.
For client applications, win7 should not be crippled.
## Performance ## Performance
Performance of each system should ideally be that of the best implementation on the platform, Performance of each system should ideally be that of the best implementation on the platform,
and no worse than the STL. Due to heavyweight requirements and spiral model defined objectives, and no worse than the STL. Due to heavyweight requirements and spiral model defined objectives,
a handful of portable C libraries have been brought into achieve compression, crypto, alloc, and a handful of unknown good industry standard libraries have been brought into achieve compression,
str formatting objectives using known good industry standard libraries. Footprint is expected to crypto, alloc, and formatting objectives. Footprint is expected to be on the heavier side for optimal
be on the heavier side for optimal performance (incl toll for C++ tradeoffs) and flexibility. performance (incl toll for C++ tradeoffs), usability, and flexibility.
Runtime as of 2022-02-01 *without the wxWidgets toolkit, with all compression libraries* on Windows LTSC 2019:
DLL Disk: 4.2MB \
Size Of Image: 0x50B000 (5MB) \
Real Commit Charge of a Console App: 9.7MB \
...the task manager lie: 3,168K (3.1MB -> less than our DLL) \
...HWInfo reports
[13:07:38] [Info] | RamInfo Private Allocation: 10215424/71987290112 (^1) \
[13:07:38] [Info] | RamInfo Address Space: 11669504/71987290112
^1 ...on LTSC 2019. Modern Windows 10 and 11 will return the exact task manager value of 3,168K (3.1MB).
Defer to benchmarks Defer to benchmarks
@ -201,9 +195,8 @@ resources.
## IO ## IO
The Aurora Runtime implements a multiple io wait loop sub-subsystem, file io, network io, The Aurora Runtime implements loop, file io, network io, and other sub-subsystems with
various adapters and connectors, io processors, io/character, io/buffered, and other such various adapters and connectors.
concepts to aid with writing low-level cross-platform IO.
An important note about texting encoding. Stdin, file encoding, text decoders, and other IO An important note about texting encoding. Stdin, file encoding, text decoders, and other IO
resources work with codepage UTF-8 as the internal encoding scheme. String overloads and resources work with codepage UTF-8 as the internal encoding scheme. String overloads and
@ -212,16 +205,22 @@ to read a BOM to translate any other arbitrary user generated text input to UTF-
### Loop ### Loop
The Aurora Runtime implements a kernel-scheduler optimized IO subsystem for managing GUIs, The Aurora Runtime implements a kernel-scheduler optimized IO loop subsystem for managing GUIs;
Network AIO, File AIO, IPC AIO, and thread synchronization objects through loop the loop subsystem. network, file, ipc AIO; and thread synchronization objects for when these waitables converse.
ILoopSource is an interface defined by the loop subsystem for IO objects with a signalable state. ILoopSource is an interface defined by the loop subsystem for IO objects with a signalable state.
Attached to an ILoopQueue, the ILoopQueue will provide wait-on and similar functionality; and Attached to an ILoopQueue, the ILoopQueue will provide wait-any/wait-all/is-signaled polling with
subscription notifications of signal state change. ILoopQueue's are thread-safe allowing for optional subscription functionality. Furthermore, ILoopQueues are thread-safe allowing for cross-thread
cross-thread or mid-wait work scheduling. Subscription notifications allow for optimized loop or mid-wait work scheduling (as in, the addition of new subscribers during sleep or callback).
source removal or no-action/non-removal replies from subscription implementer. In addition to
the synchronization provided by the ILoopQueue, the ILoopSource interface permits arbitrary It is possible to run loop queues like a poll object or with an arbitrary amount of optional
is-signaled-and-latch (TryLock) queries and timed-wait (WaitOn) calls on a per IO object basis. subscribers per loop source.
Subscription notifications allow for optimized loop source removal or no-action/non-removal replies
from subscription implementer. If you and all other subscribers want to evict the ILoopSource,
the source will be automatically removed from the ILoopQueue. If just you vote to evict, you will
no longer receive updates for the object, but the ILoopSource and other subscribers will remain.
### IPC ### IPC
@ -233,16 +232,28 @@ are used to implement IPC within the applications namespace/sandbox.
### FIO ### FIO
A simple file stream interface is provided by an Open function which accepts an Aurora path and A simple blocking file stream is provided by an open function given an Aurora path string and a
an advisory lock level. However, all such functions are blocking in face of platform specific file advisory lock level. This object can be used with AuProcess to map regions of the file into
asynchronous alternatives. An alternative `IAsyncFileStream` is provided to supply the user with the address map. However, everything about this object is blocking.
an supplier of `IAsyncTransaction`'s - an overlapped IO style interface for starting a read/write
transaction, registering an APC-like callback, requesting a loop subsystem waitable object, and An alternative asynchronous IAsyncFileStream interface is available which supplies IO transaction
clearing the request. AIO is backed by `io_submit` under Linux, POSIX AIO under BSD, and Overlapped objects for scheduling direct disk reads. One should be careful to note each platform has file AIO
IO under NT. A glibc approach of spamming threads akin to libuv and skipping the synchronization querks. For instance...
on completion step isn't our style. Instead, you are reliant on the native async capabilities of the
underlying operating system. Special consideration must be made for alignment, cached/uncached access, - Linux will block on most file systems if metadata has to be poked
and supported file systems. - FS support is limited (NT/NTFS > Linux/XFS > NT/xxxx (w/ caching) > Linux/EXT4 > unsupported)
- Read/Writes musts be made with respect to sector alignment
- Removing caching on Linux will mitigate blocking behaviour (~O_DIRECT blocking io_submit)
- ...but caching on Win32 is sometimes desirable
- Linux reads might be limited by max_sectors_kb or max_segments
For large block reads: \
bDirectIO = true; read directly from fs (recommend) \
offsets -> must align
When data is small enough for file caches to be useful: \
bDirectIO = !AuBuild::kIsNtDerived (recommend) \
offsets -> align for the highest denominator
Additional utility functions exist outside of the two file interfaces for: stat, directory iteration, Additional utility functions exist outside of the two file interfaces for: stat, directory iteration,
UTF-8 string reading and writing, blocking binary read/writes, and more. UTF-8 string reading and writing, blocking binary read/writes, and more.
@ -265,10 +276,16 @@ All string paths are simply expanded, similar to MSCRT's `fullpath` or UNIX's `r
| `\` | Agnostic Directory Splitter | | `\` | Agnostic Directory Splitter |
| `.` [SPLITTER] | Nothing | | `.` [SPLITTER] | Nothing |
### TLS
TLS client and partial server support is provided by protocol stack interceptors meaning that our implementation is no-socket.
It's possible to write into a buffered protocol stack using the provided stream writer, simulating data coming through a socket channel;
and to fetch the response/translated message using an end protocol piece to be supplied with the data, or using the provided stream reader to read the end
to read the end interceptors buffer once the protocol stack has been ticked.
### Resources ### Resources
The Aurora Runtime provides reports system, application, and user specific paths under the `Aurora::IO::FS` subsystem. The Aurora Runtime provides system, application, and user specific paths under the `Aurora::IO::FS` subsystem.
These include the users home directory, a per vendor sandboxed application user directory, a per vendor sandboxed application These include the users home directory, a per vendor sandboxed application user directory, a per vendor sandboxed application
all users directory, the user-installable program directory, the user's real home directory, and other such relevant paths. all users directory, the user-installable program directory, the user's real home directory, and other such relevant paths.
@ -285,44 +302,47 @@ The Aurora Runtime provides child process monitoring, asynchronous child stdin/o
## Locale ## Locale
Encoding and decoding of UTF-8, UTF-16, UTF-32, GBK, GB-2312, and SJIS is supported through platform provided decoders. Encoding and decoding of UTF-8, UTF-16, UTF-32, GBK, GB-2312, and SJIS is supported through OS provided decoders.
System localization information, including system codepage, country, and system language, is provided by the envrionment variables which System localization information, including system codepage, country, and system language, is provided by the available
are available, OS specific interfaces, or the user overload mechanism. envrionment variables, OS specific interfaces, or the overload mechanism.
## Memory ## Memory
### Allocator ### Allocator
Objects are allocated across API/Module boundaries. So long as the high level API design isn't horribly inefficient to an extent Objects are allocated across API/module boundaries using an ABI defined by Aurora Runtime.
that cache invalidation and indirect lookups are minimalized, object-heavy code can optimized. On modern hardware, legitmate indirect
branching versus short jumps aren't so expensive between modules in real world usage; and in combination with a fast enough allocator,
there is little reason why couldn't achieve reasonable OOP performance through a C-with-classes-like API.
As for allocation, we generally expect a dependence on Microsoft's mimalloc. Linking against the Aurora Runtime in the Aurora Such OOP design in contrast to header-everything is contingent on well-defined interface boundaries which make sense from a
Ecosystem will automatically replace global allocators with `Aurora::Memory`, which in turn, proxies any other suitable allocator perspective of optimizaton (primarily allocation and indirect branching) and usability.
interface with extended zero, array, and alignment respecting APIs. A suitably fast allocator, such as mimalloc, should reduce the
cost of the OOP design. Allocations are optimized by using a known good third party library, mimalloc. In the future, it should be possible to replace the
standard allocator; however, for now all allocations are routed through Aurora::Memory which is backed by mi_xxxx calls.
Mimalloc and/or other such slab and zone based allocators can and must be explored as an optimization for C++'s tendency to
arbitrary allocate memory. This should improve the performance of reasonbly written modules with OOP in mind.
Indirect branches are optimized by simply trying to design APIs that arent complete ass for humans and systems to work with.
### Memory Heap ### Memory Heap
Aurora provides a heap allocator for dividing up a large preallocated region of memory Aurora provides a heap allocator for dividing up a large preallocated region of memory.
Currently, we use a modified version of O(1) heap that provides a constant worst case allocation and deallocation
of any given request. Memory is provided by AuProcess for the requested allocation, but AuMemory might be unfortunately
hit during the initialization of a heap.
### Shared Pointers ### Shared Pointers
Memory objects, including shared pointers, and the object allocation model is defined by AuROXTL. The `AuSPtr` class template Memory objects, including shared pointers, and the object allocation model is defined by AuROXTL. The `AuSPtr` class template
is backed by the standard `std::shared_ptr`, extended by `#include <auROXTL/auMemoryModel.hpp>,` in the default configuration. is backed by the standard `std::shared_ptr`, extended by `#include <auROXTL/auMemoryModel.hpp>,` in the default configuration.
AuROXTL memory primitives, and most STL containers, are source compatible with the base STL classes, such that any Aurora The reason we wrap shared pointers is two folds
specific behaviour is lost during type reduction. 1) in code that doesn't need to be fast, we can simply hack in is valid checks on assignment or use an interface with panic branches
to handle null access conditions.
There are benefits of using the Aurora extended classes, include redefining null dereference on shared ptrs to throw 2) in the future, it may be desirable to use established AuEnableSharedOnThis, AuSharedFromThis, etc idoms to migrate existing code
an AU_THROW_STRING. Without support for native behaviour within the C++ driver, such features are rather expensive using the performace to an alternative non-std::shared_ptr backed system.
hacks we have available (outside of ripping the compiler apart to emit special debug info for hacky trap handlers). Arguably, it's an
experiment worth trying now that modern hardware can make up for software and microcode flaws; and architecture translation. Most users
probably wont even notice the performance loss, until it saves them from a hard crash and they realize dereferences are bloated.
This is default behaviour, and can be easily disabled or configured from within your ecosystem's AuroraConfiguration.h to globally
modify the behaviour and subsequent ABI of the AuSPtr's.
AuSPtr and friends are reducable and constructible from and from their std::shared_ptr/unique counterparts.
``` ```
Types: Types:
@ -342,34 +362,11 @@ Macros:
AuFunction<...> AuBindThis(This_t *::?, ...) AuFunction<...> AuBindThis(This_t *::?, ...)
``` ```
Defer to [auROXTL](https://git.reece.sx/AuroraSupport/auROXTL) for more information
### Note
Aurora provides a bring your own container and shared pointer model overloadable in your configuration header.<br>
User-overloadable type declerations and generic access utilities are defined under [auROXTL](https://git.reece.sx/AuroraSupport/auROXTL)
## Debug ## Debug
### Error Markers
SysPushError[EFailureCategory shorthand] can be used to include additional side-channel telemetry
information about the execution of a program. SysPushError_(...) takes a string format sequence and
a variadic sequence of substitute values - or no arguments whatsoever.
#### Example:
```cpp
IBufferedCharacterConsumer *BufferConsumerFromProviderNew(const AuSPtr<ICharacterProvider> &provider)
{
if (!provider)
{
SysPushErrorArg("Missing ICharacterProvider");
return {};
}
return _new BufferedCharacterConsumer(provider);
}
```
### Asserts ### Asserts
[TODO] [TODO]
@ -385,38 +382,46 @@ Debug and Release (debug and optimized ship-with-debug) assertions:
SysAssertDbg(AuFunction{}, "unexpected default function") SysAssertDbg(AuFunction{}, "unexpected default function")
``` ```
### Something went wrong ### Error stack
You should ensure AuDebug::CheckErrors() or a SysPushError-like function is called to ensure enchanced TLS-state telemetry is ```cpp
captured. AuDebug::PrintErrors() will print the the errors gathered by the debug subsystem for telemetry purposes. These may TEST(ErrorStack, A)
include the crts errno, the last reported posix return value, the last Win32 error code, and/or last reported microkernel error. {
AuErrorStack errors;
#### Example SysPushErrorIO("Something something IO error");
Try/Catch: SysPushErrorIO("Something something IO error 1");
``` SysPushErrorIO("Something something IO error 2 {}", "hello worlds");
ASSERT_TRUE(errors.HasCaptured());
ASSERT_TRUE(errors.HasMultipleOccurred());
ASSERT_TRUE(errors.FirstMessage()->pNextThreadMesage);
ASSERT_TRUE(errors.FirstMessage()->pNextThreadMesage->pNextThreadMesage);
AuLogDbg("{}, {}, {}",
*errors.ToString(),
errors.FirstMessage()->pNextThreadMesage->ToString(),
errors.FirstMessage()->pNextThreadMesage->pNextThreadMesage->ToString());
}
TEST(ErrorStack, B)
{
AuErrorStack errors;
try try
{ {
AU_THROW_FORMATTED("hello people {}", 23423423);
//throw "hello modern platforms";
} }
catch (...) catch (...)
{ {
SysPushErrorCatch(); // THIS IS NOT REQUIRED FOR EXCEPTION TELEMETRY }
ASSERT_TRUE(errors.HasCaptured());
AuLogDbg("{}", *errors.ToString());
} }
``` ```
[More](https://gitea.reece.sx/AuroraSupport/HelloAurora/src/branch/master/Tests/Public/19.%20Hello%20Debug/Main.cpp)
Windows System Error Messages: \
CRT:
```cpp
```
POSIX:
```cpp
```
## Binding ## Binding
Aurora Runtime provides C++ APIs; however, it should be noted that two libraries are used to extend interfaces and enums Aurora Runtime provides C++ APIs; however, it should be noted that two libraries are used to extend interfaces and enums
@ -448,9 +453,12 @@ can lead to some memory leaks.
## Aurora Async ## Aurora Async
The Aurora Runtime offers an optional asynchronous task driven model under the AuAsync namespace. Featuring promises, [TODO]
thread group pooling, functional-to-task wrapping, and task-completion callback-task-dispatch idioms built around 3 - Promises
concepts. - Thread Pool
- Optional shutdown on work exhaustion
- Wait for task[s] completion builtin to work objects
- IO subsystem interop
Example: Example:

View File

@ -9,6 +9,11 @@
#include "AuNetAdapter.hpp" #include "AuNetAdapter.hpp"
#include "AuNetEndpoint.hpp" #include "AuNetEndpoint.hpp"
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
namespace Aurora::IO::Net namespace Aurora::IO::Net
{ {
AuString NetAdapter::GetHostname() AuString NetAdapter::GetHostname()
@ -22,13 +27,478 @@ namespace Aurora::IO::Net
return name; return name;
} }
AuList<AuSPtr<INetAdapter>> NetAdapter::GetIPv4s() // Based on https://gist.github.com/Yawning/c70d804d4b8ae78cc698
struct NetlinkDevice
{
NetlinkDevice();
~NetlinkDevice();
bool Connect();
AuSInt Send(const void *pBuffer, size_t uLength);
AuSInt Recv(void *pBuffer, size_t uLength);
AuList<NetAdapter> GetAdaptersForFamily(sa_family_t family);
AuList<AuPair<int, NetEndpoint>> ReadRoutes(int index, int family, AuString &name);
void UpdateAdapterInfo(int family, NetAdapter &adapter);
int fd { -1 };
};
static NetlinkDevice gNetlinkDevice;
NetlinkDevice::NetlinkDevice()
{
}
NetlinkDevice::~NetlinkDevice()
{
if (this->fd != -1)
{
::close(this->fd);
}
}
bool NetlinkDevice::Connect()
{
sockaddr_nl nladdr {};
this->fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
if (this->fd < 0)
{
return false;
}
int flags = ::fcntl(this->fd, F_GETFL, 0);
if (flags != -1)
{
::fcntl(this->fd, F_SETFL, flags | FD_CLOEXEC);
}
nladdr.nl_family = AF_NETLINK;
if (::bind(this->fd, (struct sockaddr *) &nladdr, sizeof(nladdr)) != 0)
{
return false;
}
return true;
}
AuSInt NetlinkDevice::Send(const void *pBuffer, size_t uLength)
{
sockaddr_nl nladdr {};
msghdr msg {};
iovec vec {};
nladdr.nl_family = AF_NETLINK;
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
vec.iov_base = (char *)pBuffer;
vec.iov_len = uLength;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
return ::sendmsg(fd, &msg, 0);
}
AuSInt NetlinkDevice::Recv(void *pBuffer, size_t uLength)
{
sockaddr_nl nladdr {};
msghdr msg {};
iovec vec;
nladdr.nl_family = AF_NETLINK;
msg.msg_name = &nladdr;
msg.msg_namelen = sizeof(nladdr);
vec.iov_base = pBuffer;
vec.iov_len = uLength;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
auto ret = ::recvmsg(fd, &msg, 0);
if (msg.msg_flags & MSG_TRUNC)
{
return -1;
}
return ret;
}
struct addr_t
{
sa_family_t family;
union
{
in_addr in_addr;
in6_addr in6_addr;
} u;
};
AuList<NetAdapter> NetlinkDevice::GetAdaptersForFamily(sa_family_t family)
{
static const auto kRouteBufferSize = 12 * 1024;
struct nlmsghdr *hdr;
struct rtmsg *rt;
ssize_t len;
size_t reqlen;
reqlen = NLMSG_SPACE(sizeof(*rt));
auto pReq = AuMakeSharedArray<AuUInt8>(kRouteBufferSize);
hdr = (struct nlmsghdr *)pReq.get();
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*rt));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETROUTE;
rt = (struct rtmsg *)NLMSG_DATA(hdr);
rt->rtm_family = family;
rt->rtm_table = RT_TABLE_MAIN | RT_TABLE_LOCAL;
if (this->Send(pReq.get(), reqlen) < 0)
{
SysPushErrorIO("netlink error");
return {};
}
auto pResp = AuMakeSharedArray<AuUInt8>(kRouteBufferSize);
if (!pResp)
{
SysPushErrorMemory();
return {};
}
AuList<NetAdapter> ret;
while (1)
{
len = this->Recv(pResp.get(), kRouteBufferSize);
if (len < 0)
{
SysPushErrorIO("netlink error");
return {};
}
for (hdr = (struct nlmsghdr *)pResp.get();
NLMSG_OK(hdr, len);
hdr = NLMSG_NEXT(hdr, len))
{
NetAdapter adapter;
if (hdr->nlmsg_type == NLMSG_DONE)
{
return ret;
}
if (hdr->nlmsg_type == NLMSG_ERROR)
{ {
return {}; return {};
} }
AuList<AuSPtr<INetAdapter>> NetAdapter::GetIPv6s() rt = (struct rtmsg *)NLMSG_DATA(hdr);
auto attr = RTM_RTA(rt);
auto attrlen = RTM_PAYLOAD(hdr);
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen))
{
const size_t expectedAddressLength = (family == AF_INET) ? 4 : 16;
size_t dstlen = RTA_PAYLOAD(attr);
switch (attr->rta_type)
{
case RTA_GATEWAY:
case RTA_DST:
case RTA_SRC:
{
if (dstlen != expectedAddressLength)
{
continue;
}
NetEndpoint ep;
addr_t *address2 = (addr_t*)ep.hint;
address2->family = family;
AuMemcpy(&address2->u.in_addr, RTA_DATA(attr), dstlen);
DeoptimizeEndpoint(ep);
if (attr->rta_type == RTA_GATEWAY)
{
adapter.gateway = ep.ip;
}
if (attr->rta_type == RTA_DST)
{
adapter.address = ep.ip;
}
break;
}
case RTA_OIF:
{
adapter.index = *(int*)RTA_DATA(attr);
break;
}
default:
{
break;
}
}
}
ret.push_back(adapter);
}
}
return ret;
}
AuList<AuPair<int, NetEndpoint>> NetlinkDevice::ReadRoutes(int index, int family, AuString &name)
{
static const auto kAddrBufferSize = 12 * 1024;
AuList<AuPair<int, NetEndpoint>> ret;
nlmsghdr *hdr {};
ifaddrmsg *ifa {};
ssize_t len;
size_t reqlen;
/* Allocate space for the request. */
reqlen = NLMSG_SPACE(sizeof(*ifa));
auto pReq = AuMakeSharedArray<AuUInt8>(kAddrBufferSize);
hdr = (struct nlmsghdr *)pReq.get();
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
hdr->nlmsg_type = RTM_GETADDR;
ifa = (struct ifaddrmsg *)NLMSG_DATA(hdr);
ifa->ifa_family = family;
ifa->ifa_index = index;
if (this->Send(pReq.get(), reqlen) < 0)
{
SysPushErrorIO("netlink error");
return {};
}
auto pResp = AuMakeSharedArray<AuUInt8>(kAddrBufferSize);
if (!pResp)
{
SysPushErrorMemory();
return {};
}
while (1)
{
len = this->Recv(pResp.get(), kAddrBufferSize);
if (len < 0)
{
SysPushErrorIO("netlink error");
return {};
}
for (hdr = (struct nlmsghdr *)pResp.get(); NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len))
{
if (hdr->nlmsg_type == NLMSG_DONE)
{
return ret;
}
if (hdr->nlmsg_type == NLMSG_ERROR)
{
return {};
}
ifa = (struct ifaddrmsg *)NLMSG_DATA(hdr);
if ((ifa->ifa_family != family) ||
(ifa->ifa_index != index))
{
continue;
}
if (ifa->ifa_scope != RT_SCOPE_UNIVERSE)
{
continue;
}
auto attr = IFA_RTA(ifa);
auto attrlen = RTM_PAYLOAD(hdr);
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen))
{
size_t dstlen = RTA_PAYLOAD(attr);
const size_t addrlen = (family == AF_INET) ? sizeof(struct in_addr) : sizeof(struct in6_addr);
if (attr->rta_type == IFA_LABEL)
{
name = (const char *)RTA_DATA(attr);
}
else if (addrlen == dstlen)
{
NetEndpoint ep;
addr_t *address2 = (addr_t*)ep.hint;
address2->family = family;
AuMemcpy(&address2->u.in_addr, RTA_DATA(attr), addrlen);
DeoptimizeEndpoint(ep);
ret.push_back(AuMakePair(attr->rta_type, ep));
}
}
}
}
return ret;
}
void NetlinkDevice::UpdateAdapterInfo(int family, NetAdapter &adapter)
{
static const auto kAddrBufferSize = 12 * 1024;
nlmsghdr *hdr {};
ifinfomsg *ifa {};
auto reqlen = NLMSG_SPACE(sizeof(*ifa));
auto pReq = AuMakeSharedArray<AuUInt8>(kAddrBufferSize);
hdr = (struct nlmsghdr *)pReq.get();
hdr->nlmsg_len = NLMSG_LENGTH(sizeof(*ifa));
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
hdr->nlmsg_type = RTM_GETLINK;
ifa = (struct ifinfomsg *)NLMSG_DATA(hdr);
ifa->ifi_family = family;
ifa->ifi_index = adapter.index;
if (this->Send(pReq.get(), reqlen) < 0)
{
SysPushErrorIO("netlink error");
return;
}
auto pResp = AuMakeSharedArray<AuUInt8>(kAddrBufferSize);
if (!pResp)
{
SysPushErrorMemory();
return ;
}
while (1)
{
auto len = this->Recv(pResp.get(), kAddrBufferSize);
if (len < 0)
{
SysPushErrorIO("netlink error");
return;
}
for (hdr = (struct nlmsghdr *)pResp.get(); NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len))
{
if (hdr->nlmsg_type == NLMSG_DONE)
{
return;
}
if (hdr->nlmsg_type == NLMSG_ERROR)
{
return;
}
ifa = (struct ifinfomsg *)NLMSG_DATA(hdr);
if ((ifa->ifi_index != adapter.index))
{
continue;
}
auto attr = IFLA_RTA(ifa);
auto attrlen = hdr->nlmsg_len - NLMSG_LENGTH(sizeof(*ifa));
for (; RTA_OK(attr, attrlen); attr = RTA_NEXT(attr, attrlen)) {
size_t dstlen = RTA_PAYLOAD(attr);
const size_t addrlen = (family == AF_INET) ? sizeof(struct in_addr) : sizeof(struct in6_addr);
if (attr->rta_type == IFLA_IFNAME)
{
adapter.device = (const char *)RTA_DATA(attr);
}
// TODO: we can pull MTU and other stats from here.
}
}
}
}
static AuList<AuSPtr<INetAdapter>> GetForFamily(int family)
{
AuList<AuSPtr<INetAdapter>> ret;
if (gNetlinkDevice.fd == -1)
{
if (!gNetlinkDevice.Connect())
{ {
return {}; return {};
} }
} }
AuList<IPAddress> dups;
auto adapters = gNetlinkDevice.GetAdaptersForFamily(family);
for (auto &adapter : adapters)
{
gNetlinkDevice.UpdateAdapterInfo(family, adapter);
auto pAdapter = AuMakeShared<NetAdapter>(adapter);
if (!pAdapter)
{
SysPushErrorMemory();
return {};
}
bool bBreak = false;
auto ips = gNetlinkDevice.ReadRoutes(adapter.index, family, pAdapter->name);
for (const auto &[type, endpoint] : ips)
{
if (type == IFA_ANYCAST)
{
pAdapter->anycast = endpoint.ip;
}
if (type == IFA_ADDRESS)
{
bBreak |= AuExists(dups, endpoint.ip);
dups.push_back(endpoint.ip);
pAdapter->address = endpoint.ip;
}
if (type == IFA_BROADCAST)
{
pAdapter->broadcast = endpoint.ip;
}
}
if (bBreak)
{
continue;
}
ret.push_back(pAdapter);
}
return ret;
}
AuList<AuSPtr<INetAdapter>> NetAdapter::GetIPv4s()
{
return GetForFamily(AF_INET);
}
AuList<AuSPtr<INetAdapter>> NetAdapter::GetIPv6s()
{
return GetForFamily(AF_INET6);
}
}

View File

@ -56,7 +56,7 @@ namespace Aurora::IO::Net
return true; return true;
#endif #endif
#if defined(AURORA_PLATFORM_LINUX) #if defined(AURORA_PLATFORM_LINUX)
//return true; return true;
#endif #endif
return false; return false;
} }