4#include "Stroika/Foundation/StroikaPreComp.h"
10#if qStroika_Platform_POSIX
15#include <netinet/in.h>
17#include <sys/socket.h>
19#if qStroika_Platform_Linux
20#include <linux/netlink.h>
21#include <linux/rtnetlink.h>
22#elif qStroika_Platform_MacOS
26#elif qStroika_Platform_Windows
38#include "Stroika/Foundation/Containers/Collection.h"
39#include "Stroika/Foundation/Execution/Exceptions.h"
44#if qStroika_Platform_Windows
45#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
46#include "Stroika/Foundation/IO/Network/Platform/Windows/WinSock.h"
52#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
61using namespace Stroika::Foundation::Memory;
62using namespace Stroika::Foundation::IO;
68#pragma comment(lib, "Iphlpapi.lib")
76#include <netinet/in.h>
80#include <sys/socket.h>
84static const char* flags (
int sd,
const char* name)
86 static char buf[1024];
88 static struct ifreq ifreq;
89 strcpy (ifreq.ifr_name, name);
91 int r = ioctl (sd, SIOCGIFFLAGS, (
char*)&ifreq);
96 if (ifreq.ifr_flags & b) \
97 l += snprintf (buf + l, sizeof (buf) - l, #b " ")
102 FLAG (IFF_POINTOPOINT);
106 FLAG (IFF_NOTRAILERS);
110 FLAG (IFF_MULTICAST);
112 FLAG (IFF_AUTOMEDIA);
121 static struct ifreq ifreqs[32] {};
122 struct ifconf ifconf {};
123 ifconf.ifc_req = ifreqs;
124 ifconf.ifc_len =
sizeof(ifreqs);
126 int sd = ::socket (PF_INET, SOCK_STREAM, 0);
129 int r = ioctl (sd, SIOCGIFCONF, (
char*)&ifconf);
132 for (
int i = 0; i < ifconf.ifc_len /
sizeof(
struct ifreq); ++i) {
133 printf (
"%s: %s\n", ifreqs[i].ifr_name, inet_ntoa (((
struct sockaddr_in*)&ifreqs[i].ifr_addr)->sin_addr));
134 printf (
" flags: %s\n", flags (sd, ifreqs[i].ifr_name));
143#if qStroika_Platform_Windows
150#if USE_NOISY_TRACE_IN_THIS_MODULE_
154#if qStroika_Platform_Windows
155 IO::Network::Platform::Windows::WinSock::AssureStarted ();
157 DWORD TEST = GetComputerNameEx((COMPUTER_NAME_FORMAT)cnf, buffer, &dwSize))
160 if (::gethostname (ac,
sizeof (ac)) == SOCKET_ERROR) {
161 DbgTrace (
"gethostname: err={}"_f, WSAGetLastError ());
166 if (
auto f = allNotLocal.
First ()) {
169 if (
auto f = allAddrs.
First ()) {
173#elif qStroika_Platform_POSIX
176 ifaddrs* ifa =
nullptr;
177 if (::getifaddrs (&ifa) != 0) {
178 Execution::ThrowPOSIXErrNo ();
180 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([ifa] ()
noexcept { ::freeifaddrs (ifa); });
181 for (
const ifaddrs* p = ifa; p !=
nullptr; p = p->ifa_next) {
182 if (p->ifa_addr !=
nullptr and p->ifa_addr->sa_family == AF_INET and (p->ifa_flags & IFF_UP) and (p->ifa_flags & IFF_RUNNING) and
183 not(p->ifa_flags & IFF_LOOPBACK)) {
185 ::memcpy (&a, p->ifa_addr, sizeof (a));
193String Network::GetPrimaryNetworkDeviceMacAddress ()
195#if USE_NOISY_TRACE_IN_THIS_MODULE_
198 [[maybe_unused]]
auto printMacAddr = [] (
const uint8_t macaddrBytes[6]) ->
String {
200 (void)std::snprintf (buf,
sizeof (buf),
"%02x:%02x:%02x:%02x:%02x:%02x", macaddrBytes[0], macaddrBytes[1], macaddrBytes[2],
201 macaddrBytes[3], macaddrBytes[4], macaddrBytes[5]);
204#if qStroika_Platform_Linux
211 ifc.ifc_len =
sizeof (buf);
213 Execution::ThrowPOSIXErrNoIfNegative (::ioctl (s.
GetNativeSocket (), SIOCGIFCONF, &ifc));
215 const struct ifreq*
const end = ifc.ifc_req + (ifc.ifc_len /
sizeof (
struct ifreq));
216 for (
const ifreq* it = ifc.ifc_req; it != end; ++it) {
218 Characters::CString::Copy (ifr.ifr_name, std::size (ifr.ifr_name), it->ifr_name);
220 if (!(ifr.ifr_flags & IFF_LOOPBACK)) {
222 return printMacAddr (
reinterpret_cast<const uint8_t*
> (ifr.ifr_hwaddr.sa_data));
228#elif qStroika_Platform_Windows
234 ULONG bufLen =
static_cast<ULONG
> (adapterInfo.
GetSize () *
sizeof (IP_ADAPTER_INFO));
235 DWORD r = ::GetAdaptersInfo (adapterInfo.begin (), &bufLen);
236 if (r == ERROR_BUFFER_OVERFLOW) {
240 if (r == ERROR_NO_DATA) {
244 for (PIP_ADAPTER_INFO pi = adapterInfo.begin (); pi !=
nullptr; pi = pi->Next) {
245 if (pi->AddressLength == 6) {
246 return printMacAddr (pi->Address);
249#elif qStroika_Platform_MacOS
253 ifaddrs* ifa =
nullptr;
254 if (::getifaddrs (&ifa) != 0) {
255 Execution::ThrowPOSIXErrNo ();
257 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([ifa] ()
noexcept { ::freeifaddrs (ifa); });
258 for (
const ifaddrs* p = ifa; p !=
nullptr; p = p->ifa_next) {
259 if (p->ifa_addr !=
nullptr and p->ifa_addr->sa_family == AF_INET and not(p->ifa_flags & IFF_LOOPBACK)) {
260 for (
const ifaddrs* q = ifa; q !=
nullptr; q = q->ifa_next) {
261 if (q->ifa_addr !=
nullptr and q->ifa_addr->sa_family == AF_LINK and ::strcmp (q->ifa_name, p->ifa_name) == 0) {
262 alignas (sockaddr_dl) uint8_t buf[
sizeof (sockaddr_storage)]{};
263 ::memcpy (buf, q->ifa_addr, min<size_t> (q->ifa_addr->sa_len, sizeof (buf)));
264 const sockaddr_dl* sdl =
reinterpret_cast<const sockaddr_dl*
> (buf);
265 if (sdl->sdl_alen == 6 and offsetof (sockaddr_dl, sdl_data) + sdl->sdl_nlen + 6 <=
sizeof (buf)) {
266 return printMacAddr (
reinterpret_cast<const uint8_t*
> (LLADDR (sdl)));
282#if qStroika_Platform_Windows
284 String AdapterNameOf_ (const ::GUID& g)
287 (void)::snprintf (buf, std::size (buf),
"{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", g.Data1, g.Data2, g.Data3,
288 g.Data4[0], g.Data4[1], g.Data4[2], g.Data4[3], g.Data4[4], g.Data4[5], g.Data4[6], g.Data4[7]);
294 using IndexAndAddress_ = pair<unsigned int, InternetAddress>;
296#if qStroika_Platform_Windows
303#if qStroika_Platform_POSIX
307 in6_addr WithoutEmbeddedScope_ (in6_addr a)
309 if (a.s6_addr[0] == 0xfe and (a.s6_addr[1] & 0xc0) == 0x80) {
320 ifaddrs* ifa =
nullptr;
321 Execution::ThrowPOSIXErrNoIfNegative (::getifaddrs (&ifa));
322 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([ifa] ()
noexcept { ::freeifaddrs (ifa); });
323 for (
const ifaddrs* p = ifa; p !=
nullptr; p = p->ifa_next) {
324 if (p->ifa_addr ==
nullptr) {
327 string device{p->ifa_name};
328 if (
size_t colon = device.find (
':'); colon != string::npos) {
329 device.erase (colon);
331 unsigned int index = ::if_nametoindex (device.c_str ());
335 if (p->ifa_addr->sa_family == AF_INET) {
337 ::memcpy (&sin, p->ifa_addr, sizeof (sin));
340 else if (p->ifa_addr->sa_family == AF_INET6) {
342 ::memcpy (&sin6, p->ifa_addr, sizeof (sin6));
343 result.
Add (IndexAndAddress_{index,
InternetAddress{WithoutEmbeddedScope_ (sin6.sin6_addr)}});
363 static shared_ptr<Backend_> Get ()
365 [[maybe_unused]] lock_guard critSec{sMutex_};
366 if (shared_ptr<Backend_> b = sCurrent_.lock ()) {
369 shared_ptr<Backend_> b = Memory::MakeSharedPtr<Backend_> ();
375 Backend_ () =
default;
376 Backend_ (
const Backend_&) =
delete;
380 Require (not tNotifying_);
381#if qStroika_Platform_POSIX
383 if (fMonitorThread_ !=
nullptr) {
384 fMonitorThread_.AbortAndWaitForDone ();
386#elif qStroika_Platform_Windows
387 if (fMonitorHandler_ != INVALID_HANDLE_VALUE) {
391 ::CancelMibChangeNotify2 (fMonitorHandler_);
396 void Add (
const shared_ptr<Subscriber_>& s)
398 fSubscribers_.rwget ().rwref ().Add (s);
400 void Remove (
const shared_ptr<Subscriber_>& s)
402 fSubscribers_.rwget ().rwref ().Remove (s);
406#if qStroika_Platform_POSIX
410 if (c == LinkChange::eAdded ? known->
AddIf (IndexAndAddress_{index, a}) : known->RemoveIf (IndexAndAddress_{index, a})) {
411 char name[IF_NAMESIZE]{};
412 Notify_ (Event{.fChange = c,
413 .fInterfaceID = ::if_indextoname (index, name) ==
nullptr ?
String{} : String::FromNarrowSDKString (name),
419 void Notify_ (
const Event& e)
422 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([] ()
noexcept { tNotifying_ =
false; });
425 for (
const shared_ptr<Subscriber_>& sub : fSubscribers_.load ()) {
426 [[maybe_unused]] lock_guard critSec{sub->fCalling};
427 sub->fCallbacks.Call (e);
431#if qStroika_Platform_Windows
433 static void WINAPI CB_ (
void* callerContext, PMIB_UNICASTIPADDRESS_ROW Address, MIB_NOTIFICATION_TYPE NotificationType)
435 if (Address == NULL) {
439 Backend_* b =
reinterpret_cast<Backend_*
> (callerContext);
445 MIB_UNICASTIPADDRESS_ROW now{};
446 now.Address = Address->Address;
447 now.InterfaceLuid = Address->InterfaceLuid;
448 bool usable = NotificationType != MibDeleteInstance and ::GetUnicastIpAddressEntry (&now) == NO_ERROR and now.DadState == IpDadStatePreferred;
452 [[maybe_unused]] lock_guard critSec{b->fUsableMutex_};
453 changed = usable ? b->fUsable_.AddIf (IndexAndAddress_{Address->InterfaceIndex, a})
454 : b->fUsable_.RemoveIf (IndexAndAddress_{Address->InterfaceIndex, a});
458 b->Notify_ (Event{.fChange = usable ? LinkChange::eAdded : LinkChange::eRemoved,
459 .fInterfaceID = (::ConvertInterfaceLuidToGuid (&Address->InterfaceLuid, &guid) == NO_ERROR) ? AdapterNameOf_ (guid)
466 DbgTrace (
"LinkMonitor: {}"_f, current_exception ());
473#if qStroika_Platform_Linux
479 addr.nl_family = AF_NETLINK;
480 addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR;
481 Execution::ThrowPOSIXErrNoIfNegative (::bind (sock.
GetNativeSocket (), (
struct sockaddr*)&addr, sizeof (addr)));
487 alignas (nlmsghdr)
byte buffer[4096];
490 int len =
static_cast<int> (sock.
ReceiveFrom (span{buffer}, 0,
nullptr).size ());
494 for (nlmsghdr* nlh =
reinterpret_cast<nlmsghdr*
> (buffer); NLMSG_OK (nlh, len) and nlh->nlmsg_type != NLMSG_DONE;
495 nlh = NLMSG_NEXT (nlh, len)) {
496 if (nlh->nlmsg_type == RTM_NEWADDR or nlh->nlmsg_type == RTM_DELADDR) {
497 struct ifaddrmsg* ifa = (
struct ifaddrmsg*)NLMSG_DATA (nlh);
499 if (nlh->nlmsg_type == RTM_NEWADDR and (ifa->ifa_flags & (IFA_F_TENTATIVE | IFA_F_DADFAILED))) {
504 optional<InternetAddress> local;
505 optional<InternetAddress> address;
506 struct rtattr* rth = IFA_RTA (ifa);
507 int rtl = IFA_PAYLOAD (nlh);
508 for (; RTA_OK (rth, rtl); rth = RTA_NEXT (rth, rtl)) {
509 if (rth->rta_type == IFA_LOCAL or rth->rta_type == IFA_ADDRESS) {
510 optional<InternetAddress>& into = rth->rta_type == IFA_LOCAL ? local : address;
511 if (ifa->ifa_family == AF_INET and RTA_PAYLOAD (rth) >=
sizeof (in_addr)) {
513 ::memcpy (&a, RTA_DATA (rth),
sizeof (a));
516 else if (ifa->ifa_family == AF_INET6 and RTA_PAYLOAD (rth) >=
sizeof (in6_addr)) {
518 ::memcpy (&a, RTA_DATA (rth),
sizeof (a));
523 if (optional<InternetAddress> a = local ? local : address) {
524 Changed_ (&known, nlh->nlmsg_type == RTM_NEWADDR ? LinkChange::eAdded : LinkChange::eRemoved, ifa->ifa_index, *a);
530 fMonitorThread_.
SetThreadName (
"Network LinkMonitor thread"sv);
531 fMonitorThread_.Start ();
532#elif qStroika_Platform_Windows
539 PMIB_UNICASTIPADDRESS_TABLE table =
nullptr;
540 if (::GetUnicastIpAddressTable (AF_UNSPEC, &table) == NO_ERROR) {
541 [[maybe_unused]]
auto&& cleanup = Execution::Finally ([table] ()
noexcept { ::FreeMibTable (table); });
542 [[maybe_unused]] lock_guard critSec{fUsableMutex_};
543 for (ULONG i = 0; i < table->NumEntries; ++i) {
544 if (table->Table[i].DadState == IpDadStatePreferred) {
545 fUsable_.Add (IndexAndAddress_{table->Table[i].InterfaceIndex, AddressOf_ (table->Table[i].Address)});
549#elif qStroika_Platform_MacOS
559 size_t len = sock.
ReceiveFrom (span{buffer}, 0,
nullptr).size ();
565 for (
size_t offset = 0; offset +
sizeof (ifa_msghdr) <= len;) {
567 ::memcpy (&ifam, buffer + offset,
sizeof (ifam));
568 if (ifam.ifam_msglen == 0) {
571 size_t end = min<size_t> (offset + ifam.ifam_msglen, len);
572 if (ifam.ifam_type == RTM_NEWADDR or ifam.ifam_type == RTM_DELADDR) {
574 size_t a = offset +
sizeof (ifam);
575 for (
int i = 0; i < RTAX_MAX and a < end; ++i) {
576 if (not(ifam.ifam_addrs & (1 << i))) {
579 uint8_t saLen =
static_cast<uint8_t
> (buffer[a]);
580 if (i == RTAX_IFA and a + 2 <= end) {
581 LinkChange c = ifam.ifam_type == RTM_NEWADDR ? LinkChange::eAdded : LinkChange::eRemoved;
582 uint8_t family =
static_cast<uint8_t
> (buffer[a + 1]);
583 if (family == AF_INET and saLen >=
sizeof (sockaddr_in) and a +
sizeof (sockaddr_in) <= end) {
585 ::memcpy (&sin, buffer + a,
sizeof (sin));
588 else if (family == AF_INET6 and saLen >=
sizeof (sockaddr_in6) and a +
sizeof (sockaddr_in6) <= end) {
590 ::memcpy (&sin6, buffer + a,
sizeof (sin6));
591 Changed_ (&known, c, ifam.ifam_index,
InternetAddress{WithoutEmbeddedScope_ (sin6.sin6_addr)});
594 a += saLen > 0 ? (1 + ((saLen - 1) | (
sizeof (uint32_t) - 1))) : sizeof (uint32_t);
597 offset += ifam.ifam_msglen;
601 fMonitorThread_.
SetThreadName (
"Network LinkMonitor thread"sv);
602 fMonitorThread_.Start ();
609#if qStroika_Platform_POSIX
611#elif qStroika_Platform_Windows
612 HANDLE fMonitorHandler_ = INVALID_HANDLE_VALUE;
616 static inline mutex sMutex_;
617 static inline weak_ptr<Backend_> sCurrent_;
618 static inline thread_local bool tNotifying_{
false};
622struct LinkMonitor::Rep_ {
625 if (fBackend_ !=
nullptr) {
626 fBackend_->
Remove (fSubscriber_);
628 fSubscriber_->fCallbacks.RemoveAll ();
632 CallbackID AddCallback (
const Callback& callback)
635 [[maybe_unused]] lock_guard critSec{fBackendMutex_};
636 if (fBackend_ ==
nullptr) {
637 fBackend_ = Backend_::Get ();
638 fBackend_->Add (fSubscriber_);
641 return fSubscriber_->fCallbacks.Add (callback);
645 fSubscriber_->fCallbacks.Remove (callback);
647 const shared_ptr<Subscriber_> fSubscriber_{Memory::MakeSharedPtr<Subscriber_> ()};
648 mutex fBackendMutex_;
649 shared_ptr<Backend_> fBackend_;
668LinkMonitor::LinkMonitor ()
675 return fRep_->AddCallback (callback);
680 fRep_->RemoveCallback (callback);
#define AssertNotImplemented()
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
Similar to String, but intended to more efficiently construct a String. Mutable type (String is large...
String is like std::u32string, except it is much easier to use, often much more space efficient,...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< value_type > First() const
nonvirtual RESULT_CONTAINER Where(INCLUDE_PREDICATE &&includeIfTrue) const
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual bool AddIf(ArgByValueType< value_type > item)
nonvirtual void Remove(ArgByValueType< value_type > item)
Remove the item (given by value or iterator pointing to it) from the contain. The item MUST exist.
nonvirtual void Add(ArgByValueType< value_type > item)
The callbacks an object calls when something happens - each added, and removed by the ID Add gave it ...
Wrap any object with Synchronized<> and it can be used similarly to the base type,...
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
nonvirtual void SetThreadName(const Characters::String &threadName) const
nonvirtual span< byte > ReceiveFrom(span< byte > into, int flag, SocketAddress *fromAddress, Time::DurationSeconds timeout=Time::kInfinity) const
nonvirtual Sequence< InternetAddress > GetHostAddresses(const String &hostNameOrAddress) const
simple wrapper on GetHostEntry - looking up the hostname/ip address and returning the list of associa...
nonvirtual bool IsLinkLocalAddress() const
nonvirtual PlatformNativeHandle GetNativeSocket() const
FamilyType
Socket address family - also sometimes referred to as domain (argument to ::socket calls it domain)
Logically halfway between std::array and std::vector; Smart 'direct memory array' - which when needed...
nonvirtual void GrowToSize_uninitialized(size_t nElements)
same as GrowToSize (), except leaves newly created elements uninitialized (requires is_trivially_copy...
nonvirtual size_t GetSize() const noexcept
Create a format-string (see std::wformat_string or Stroika FormatString, or python 'f' strings) - a F...
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
InternetAddress GetPrimaryInternetAddress()
What a callback is told: an address added to (or removed from) an interface.
Interface::SystemIDType fInterfaceID
nonvirtual String ToString() const
nonvirtual CallbackID AddCallback(const Callback &callback)
Call callback with each change from when this returns (.
nonvirtual void RemoveCallback(CallbackID callback)
Once this returns, that callback is not running, and is not called again (.
Execution::CallbackRegistry< void(const Event &)>::ID CallbackID
Names a callback AddCallback added, for RemoveCallback.