Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
LinkMonitor.cpp
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include "Stroika/Foundation/StroikaPreComp.h"
5
6#include <cstddef>
7#include <cstdio>
8#include <cstring>
9
10#if qStroika_Platform_POSIX
11#include <arpa/inet.h>
12#include <ifaddrs.h>
13#include <net/if.h>
14#include <netdb.h>
15#include <netinet/in.h>
16#include <sys/ioctl.h>
17#include <sys/socket.h>
18#include <unistd.h>
19#if qStroika_Platform_Linux
20#include <linux/netlink.h>
21#include <linux/rtnetlink.h>
22#elif qStroika_Platform_MacOS
23#include <net/if_dl.h>
24#include <net/route.h>
25#endif
26#elif qStroika_Platform_Windows
27#include <WinSock2.h>
28
29#include <WS2tcpip.h>
30
31#include <Iphlpapi.h>
32#include <netioapi.h>
33#endif
34
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"
47#endif
51
52#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
53
54#include "LinkMonitor.h"
55
56// Comment this in to turn on aggressive noisy DbgTrace in this module
57//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
58
59using namespace Stroika::Foundation;
61using namespace Stroika::Foundation::Memory;
62using namespace Stroika::Foundation::IO;
64
65#if defined(_MSC_VER)
66// support use of Iphlpapi - but better to reference here than in lib entry of project file cuz
67// easiser to see/modularize (and only pulled in if this module is referenced)
68#pragma comment(lib, "Iphlpapi.lib")
69#endif
70
71#if 0
72// FOR POSIX DO SOMETHING LIKE THIS:
73#include <arpa/inet.h>
74#include <assert.h>
75#include <net/if.h>
76#include <netinet/in.h>
77#include <stdio.h>
78#include <string.h>
79#include <sys/ioctl.h>
80#include <sys/socket.h>
81#include <unistd.h>
82
83
84static const char* flags (int sd, const char* name)
85{
86 static char buf[1024];
87
88 static struct ifreq ifreq;
89 strcpy (ifreq.ifr_name, name);
90
91 int r = ioctl (sd, SIOCGIFFLAGS, (char*)&ifreq);
92 assert (r == 0);
93
94 int l = 0;
95#define FLAG(b) \
96 if (ifreq.ifr_flags & b) \
97 l += snprintf (buf + l, sizeof (buf) - l, #b " ")
98 FLAG (IFF_UP);
99 FLAG (IFF_BROADCAST);
100 FLAG (IFF_DEBUG);
101 FLAG (IFF_LOOPBACK);
102 FLAG (IFF_POINTOPOINT);
103 FLAG (IFF_RUNNING);
104 FLAG (IFF_NOARP);
105 FLAG (IFF_PROMISC);
106 FLAG (IFF_NOTRAILERS);
107 FLAG (IFF_ALLMULTI);
108 FLAG (IFF_MASTER);
109 FLAG (IFF_SLAVE);
110 FLAG (IFF_MULTICAST);
111 FLAG (IFF_PORTSEL);
112 FLAG (IFF_AUTOMEDIA);
113 FLAG (IFF_DYNAMIC);
114#undef FLAG
115
116 return buf;
117}
118
119int main (void)
120{
121 static struct ifreq ifreqs[32] {};
122 struct ifconf ifconf {};
123 ifconf.ifc_req = ifreqs;
124 ifconf.ifc_len = sizeof(ifreqs);
125
126 int sd = ::socket (PF_INET, SOCK_STREAM, 0);
127 assert (sd >= 0);
128
129 int r = ioctl (sd, SIOCGIFCONF, (char*)&ifconf);
130 assert (r == 0);
131
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));
135 }
136
137 close (sd);
138
139 return 0;
140}
141#endif
142
143#if qStroika_Platform_Windows
144// /SEE THIS CODE FOR WINDOWS
145//http ://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q129/3/15.asp&NoWebContent=1
146#endif
147
149{
150#if USE_NOISY_TRACE_IN_THIS_MODULE_
151 Debug::TraceContextBumper ctx{"IO::Network::GetPrimaryInternetAddress"};
152#endif
153/// HORRIBLY KLUDGY BAD IMPL!!!
154#if qStroika_Platform_Windows
155 IO::Network::Platform::Windows::WinSock::AssureStarted ();
156#if 0
157 DWORD TEST = GetComputerNameEx((COMPUTER_NAME_FORMAT)cnf, buffer, &dwSize))
158#endif
159 char ac[1024];
160 if (::gethostname (ac, sizeof (ac)) == SOCKET_ERROR) {
161 DbgTrace ("gethostname: err={}"_f, WSAGetLastError ());
162 return InternetAddress{};
163 }
164 Sequence<InternetAddress> allAddrs = DNS::kThe.GetHostAddresses (String::FromNarrowSDKString (ac));
165 Sequence<InternetAddress> allNotLocal = allAddrs.Where ([] (const InternetAddress& ia) { return not ia.IsLinkLocalAddress (); });
166 if (auto f = allNotLocal.First ()) {
167 return *f;
168 }
169 if (auto f = allAddrs.First ()) {
170 return *f;
171 }
172 return InternetAddress{};
173#elif qStroika_Platform_POSIX
174 // the first IPv4 address of an interface that is up, running and not loopback (getifaddrs, not SIOCGIFCONF - whose
175 // records macOS packs to varying lengths, starting with AF_LINK ones, so they cannot be indexed as an array)
176 ifaddrs* ifa = nullptr;
177 if (::getifaddrs (&ifa) != 0) {
178 Execution::ThrowPOSIXErrNo ();
179 }
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)) {
184 sockaddr_in a{};
185 ::memcpy (&a, p->ifa_addr, sizeof (a));
186 return InternetAddress{a.sin_addr};
187 }
188 }
189 return InternetAddress{};
190#endif
191}
192
193String Network::GetPrimaryNetworkDeviceMacAddress ()
194{
195#if USE_NOISY_TRACE_IN_THIS_MODULE_
196 Debug::TraceContextBumper ctx{"IO::Network::GetPrimaryNetworkDeviceMacAddress"};
197#endif
198 [[maybe_unused]] auto printMacAddr = [] (const uint8_t macaddrBytes[6]) -> String {
199 char buf[100]{};
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]);
202 return String{buf};
203 };
204#if qStroika_Platform_Linux
205 // This counts on SIOCGIFHWADDR, which appears to be Linux specific
206 for (SocketAddress::FamilyType family : {SocketAddress::INET, SocketAddress::INET6}) {
207 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (family, Socket::DGRAM);
208
209 char buf[10 * 1024];
210 ifconf ifc;
211 ifc.ifc_len = sizeof (buf);
212 ifc.ifc_buf = buf;
213 Execution::ThrowPOSIXErrNoIfNegative (::ioctl (s.GetNativeSocket (), SIOCGIFCONF, &ifc));
214
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) {
217 struct ifreq ifr{};
218 Characters::CString::Copy (ifr.ifr_name, std::size (ifr.ifr_name), it->ifr_name);
219 if (::ioctl (s.GetNativeSocket (), SIOCGIFFLAGS, &ifr) == 0) {
220 if (!(ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
221 if (::ioctl (s.GetNativeSocket (), SIOCGIFHWADDR, &ifr) == 0) {
222 return printMacAddr (reinterpret_cast<const uint8_t*> (ifr.ifr_hwaddr.sa_data));
223 }
224 }
225 }
226 }
227 }
228#elif qStroika_Platform_Windows
229 // GetAdaptersInfo () fills a caller-sized buffer, and says how big it must be when it overflows (a fixed array of 10 threw
230 // ERROR_BUFFER_OVERFLOW on any machine with more adapters, which Hyper-V, WSL and VPNs make common). So grow to that and
231 // retry - as GetInterfaces_Windows_ does for GetAdaptersAddresses () - which allocates only if it outgrows the stack buffer.
233Again:
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) {
237 adapterInfo.GrowToSize_uninitialized (bufLen / sizeof (IP_ADAPTER_INFO) + 1);
238 goto Again;
239 }
240 if (r == ERROR_NO_DATA) {
241 return String{}; // no adapters
242 }
244 for (PIP_ADAPTER_INFO pi = adapterInfo.begin (); pi != nullptr; pi = pi->Next) {
245 if (pi->AddressLength == 6) {
246 return printMacAddr (pi->Address);
247 }
248 }
249#elif qStroika_Platform_MacOS
250 // As the Linux code above: the hardware address of the first non-loopback interface with an IPv4 address. macOS has no
251 // SIOCGIFHWADDR - each interface's link-layer address is its getifaddrs () AF_LINK entry (a sockaddr_dl, often longer than
252 // the struct, so copied by sa_len)
253 ifaddrs* ifa = nullptr;
254 if (::getifaddrs (&ifa) != 0) {
255 Execution::ThrowPOSIXErrNo ();
256 }
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)));
267 }
268 }
269 }
270 }
271 }
272#else
274#endif
275 return String{};
276}
277
278namespace {
279 using Event = LinkMonitor::Event;
280 using LinkChange = LinkMonitor::LinkChange;
281
282#if qStroika_Platform_Windows
283 // a GUID as IP_ADAPTER_ADDRESSES::AdapterName spells it - so as Interface::fInterfaceID: upper case, in braces
284 String AdapterNameOf_ (const ::GUID& g)
285 {
286 char buf[64];
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]);
289 return String{buf};
290 }
291#endif
292
293 // an address on an interface - the interface by its index, as the OS's messages name it
294 using IndexAndAddress_ = pair<unsigned int, InternetAddress>;
295
296#if qStroika_Platform_Windows
297 InternetAddress AddressOf_ (const SOCKADDR_INET& a)
298 {
299 return a.si_family == AF_INET6 ? InternetAddress{a.Ipv6.sin6_addr} : InternetAddress{a.Ipv4.sin_addr};
300 }
301#endif
302
303#if qStroika_Platform_POSIX
304
305 // BSD's kernel puts the interface's index in bytes 2-3 of a link-local IPv6 address it hands out (KAME's 'embedded scope');
306 // the address itself has 0 there. (As of macOS 26.5 getifaddrs () clears them itself; a routing message may not.)
307 in6_addr WithoutEmbeddedScope_ (in6_addr a)
308 {
309 if (a.s6_addr[0] == 0xfe and (a.s6_addr[1] & 0xc0) == 0x80) {
310 a.s6_addr[2] = 0;
311 a.s6_addr[3] = 0;
312 }
313 return a;
314 }
315
316 // the addresses on the interfaces now - so one reported again (its lifetime renewed, say) is not taken for one added
317 Containers::Set<IndexAndAddress_> CurrentAddresses_ ()
318 {
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) {
325 continue;
326 }
327 string device{p->ifa_name};
328 if (size_t colon = device.find (':'); colon != string::npos) {
329 device.erase (colon); // a Linux address label (eth0:1): the address is its device's
330 }
331 unsigned int index = ::if_nametoindex (device.c_str ());
332 if (index == 0) {
333 continue;
334 }
335 if (p->ifa_addr->sa_family == AF_INET) {
336 sockaddr_in sin;
337 ::memcpy (&sin, p->ifa_addr, sizeof (sin));
338 result.Add (IndexAndAddress_{index, InternetAddress{sin.sin_addr}});
339 }
340 else if (p->ifa_addr->sa_family == AF_INET6) {
341 sockaddr_in6 sin6;
342 ::memcpy (&sin6, p->ifa_addr, sizeof (sin6));
343 result.Add (IndexAndAddress_{index, InternetAddress{WithoutEmbeddedScope_ (sin6.sin6_addr)}});
344 }
345 }
346 return result;
347 }
348#endif
349
350 // what one LinkMonitor has registered
351 struct Subscriber_ {
352 // held while its callbacks are called, and only then - so they run one at a time, though on Windows the OS's
353 // notifications may come on more than one of its threads (nothing else takes it, so it cannot deadlock with a callback)
354 mutex fCalling;
355 Execution::CallbackRegistry<void (const Event&)> fCallbacks;
356 };
357
358 // The one watcher of the OS's address changes, shared by every LinkMonitor with a callback: on Linux and macOS a thread
359 // (reading a netlink or routing socket), on Windows a NotifyUnicastIpAddressChange registration. Started by the first
360 // LinkMonitor to add a callback, and stopped when the last such goes - so it never outlives them (as no Stroika thread may
361 // outlive main).
362 struct Backend_ {
363 static shared_ptr<Backend_> Get ()
364 {
365 [[maybe_unused]] lock_guard critSec{sMutex_};
366 if (shared_ptr<Backend_> b = sCurrent_.lock ()) {
367 return b;
368 }
369 shared_ptr<Backend_> b = Memory::MakeSharedPtr<Backend_> ();
370 b->Start_ ();
371 sCurrent_ = b;
372 return b;
373 }
374
375 Backend_ () = default;
376 Backend_ (const Backend_&) = delete;
377 ~Backend_ ()
378 {
379 // the last LinkMonitor destroyed from within one of its callbacks: this cannot stop (and wait for) what is calling it
380 Require (not tNotifying_);
381#if qStroika_Platform_POSIX
382 Execution::Thread::SuppressInterruptionInContext suppressInterruption; // critical to wait til done cuz captures this
383 if (fMonitorThread_ != nullptr) {
384 fMonitorThread_.AbortAndWaitForDone ();
385 }
386#elif qStroika_Platform_Windows
387 if (fMonitorHandler_ != INVALID_HANDLE_VALUE) {
388 // @todo should check error result, but then do what?
389 // also - does this blcok until pending notifies done?
390 // assuming so!!!
391 ::CancelMibChangeNotify2 (fMonitorHandler_);
392 }
393#endif
394 }
395
396 void Add (const shared_ptr<Subscriber_>& s)
397 {
398 fSubscribers_.rwget ().rwref ().Add (s);
399 }
400 void Remove (const shared_ptr<Subscriber_>& s)
401 {
402 fSubscribers_.rwget ().rwref ().Remove (s);
403 }
404
405 private:
406#if qStroika_Platform_POSIX
407 // on the monitor thread: an address added or removed - reported only if it really was (known: the addresses there)
408 void Changed_ (Containers::Set<IndexAndAddress_>* known, LinkChange c, unsigned int index, const InternetAddress& a)
409 {
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),
414 .fAddress = a});
415 }
416 }
417#endif
418
419 void Notify_ (const Event& e)
420 {
421 tNotifying_ = true;
422 [[maybe_unused]] auto&& cleanup = Execution::Finally ([] () noexcept { tNotifying_ = false; });
423 // from a copy - a callback may add or remove LinkMonitors (and its CallbackRegistry lets it add or remove callbacks,
424 // calls none removed meanwhile, and logs and ignores a callback's exception, which would stop the notifications)
425 for (const shared_ptr<Subscriber_>& sub : fSubscribers_.load ()) {
426 [[maybe_unused]] lock_guard critSec{sub->fCalling};
427 sub->fCallbacks.Call (e);
428 }
429 }
430
431#if qStroika_Platform_Windows
432 // cannot use LAMBDA cuz we need WINAPI call convention
433 static void WINAPI CB_ (void* callerContext, PMIB_UNICASTIPADDRESS_ROW Address, MIB_NOTIFICATION_TYPE NotificationType)
434 {
435 if (Address == NULL) {
436 return; // MibInitialNotification (not asked for)
437 }
438 try {
439 Backend_* b = reinterpret_cast<Backend_*> (callerContext);
440 // Whether the address is usable (its DAD state Preferred) is what counts - not its being added or deleted:
441 // Windows keeps an address whose network goes (Wi-Fi off, within its DHCP lease), marking it Deprecated, and
442 // Preferred again when the network is back, telling of each only as a MibParameterNotification. (And it adds an
443 // address Tentative, while it checks it is no duplicate.) The row given holds only the keys, so its state is
444 // looked up.
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;
449 InternetAddress a = AddressOf_ (Address->Address);
450 bool changed;
451 {
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});
455 }
456 if (changed) {
457 ::GUID guid{};
458 b->Notify_ (Event{.fChange = usable ? LinkChange::eAdded : LinkChange::eRemoved,
459 .fInterfaceID = (::ConvertInterfaceLuidToGuid (&Address->InterfaceLuid, &guid) == NO_ERROR) ? AdapterNameOf_ (guid)
460 : String{},
461 .fAddress = a});
462 }
463 }
464 catch (...) {
465 // nothing may escape to the OS's thread
466 DbgTrace ("LinkMonitor: {}"_f, current_exception ());
467 }
468 }
469#endif
470
471 void Start_ ()
472 {
473#if qStroika_Platform_Linux
474 // the socket made (and bound) here, not on the thread: so a change from when AddCallback () returns is seen, and an
475 // OS that cannot tell (some containers) makes AddCallback () throw - rather than the thread silently ending
476 ConnectionlessSocket::Ptr sock = ConnectionlessSocket::New (static_cast<SocketAddress::FamilyType> (PF_NETLINK), Socket::RAW, NETLINK_ROUTE);
477 {
478 sockaddr_nl addr{};
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)));
482 }
483 // (listed after the socket is bound, so no change is missed between)
484 fMonitorThread_ = Execution::Thread::New ([this, sock, known = CurrentAddresses_ ()] () mutable {
485 // wait here, not in ReceiveFrom: with no timeout, that can miss an Abort () that comes just before it blocks
487 alignas (nlmsghdr) byte buffer[4096];
488 while (true) {
489 (void)ready.Wait ();
490 int len = static_cast<int> (sock.ReceiveFrom (span{buffer}, 0, nullptr).size ());
491 if (len <= 0) {
492 break;
493 }
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);
498 // one still checking it is no duplicate (IPv6) is not usable yet: reported when that is done
499 if (nlh->nlmsg_type == RTM_NEWADDR and (ifa->ifa_flags & (IFA_F_TENTATIVE | IFA_F_DADFAILED))) {
500 continue;
501 }
502 // IFA_LOCAL is this end's address where there is a peer (IFA_ADDRESS then being the peer's); IPv6
503 // gives only IFA_ADDRESS
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)) {
512 in_addr a;
513 ::memcpy (&a, RTA_DATA (rth), sizeof (a));
514 into = InternetAddress{a};
515 }
516 else if (ifa->ifa_family == AF_INET6 and RTA_PAYLOAD (rth) >= sizeof (in6_addr)) {
517 in6_addr a;
518 ::memcpy (&a, RTA_DATA (rth), sizeof (a));
519 into = InternetAddress{a};
520 }
521 }
522 }
523 if (optional<InternetAddress> a = local ? local : address) {
524 Changed_ (&known, nlh->nlmsg_type == RTM_NEWADDR ? LinkChange::eAdded : LinkChange::eRemoved, ifa->ifa_index, *a);
525 }
526 }
527 }
528 }
529 });
530 fMonitorThread_.SetThreadName ("Network LinkMonitor thread"sv);
531 fMonitorThread_.Start ();
532#elif qStroika_Platform_Windows
533 /*
534 * @todo Minor - but we maybe should be using NotifyIpInterfaceChange... - not sure we get stragiht up/down issues this
535 * way...
536 */
537 Execution::Platform::Windows::ThrowIfNotERROR_SUCCESS (::NotifyUnicastIpAddressChange (AF_UNSPEC, &CB_, this, FALSE, &fMonitorHandler_));
538 // the addresses usable now (listed after registering, so no change is missed between) - so only a change is reported
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)});
546 }
547 }
548 }
549#elif qStroika_Platform_MacOS
550 // as the Linux netlink loop above (the socket made, and the addresses listed, here too), via the BSD routing socket:
551 // each address added or removed (RTM_NEWADDR, RTM_DELADDR)
552 ConnectionlessSocket::Ptr sock = ConnectionlessSocket::New (static_cast<SocketAddress::FamilyType> (PF_ROUTE), Socket::RAW, AF_UNSPEC);
553 fMonitorThread_ = Execution::Thread::New ([this, sock, known = CurrentAddresses_ ()] () mutable {
554 // wait here, not in ReceiveFrom: with no timeout, that can miss an Abort () that comes just before it blocks
556 byte buffer[4096];
557 while (true) {
558 (void)ready.Wait ();
559 size_t len = sock.ReceiveFrom (span{buffer}, 0, nullptr).size ();
560 if (len == 0) {
561 break;
562 }
563 // Every routing message starts {u_short msglen; u_char version; u_char type}. Copied out, never cast in
564 // place: like SIOCGIFCONF's records, these are packed
565 for (size_t offset = 0; offset + sizeof (ifa_msghdr) <= len;) {
566 ifa_msghdr ifam;
567 ::memcpy (&ifam, buffer + offset, sizeof (ifam));
568 if (ifam.ifam_msglen == 0) {
569 break;
570 }
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) {
573 // its addresses follow the header: one per bit set in ifam_addrs, in RTAX_ order, each padded to 4 bytes
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))) {
577 continue;
578 }
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]); // a BSD sockaddr: {sa_len, sa_family, ...}
583 if (family == AF_INET and saLen >= sizeof (sockaddr_in) and a + sizeof (sockaddr_in) <= end) {
584 sockaddr_in sin;
585 ::memcpy (&sin, buffer + a, sizeof (sin));
586 Changed_ (&known, c, ifam.ifam_index, InternetAddress{sin.sin_addr});
587 }
588 else if (family == AF_INET6 and saLen >= sizeof (sockaddr_in6) and a + sizeof (sockaddr_in6) <= end) {
589 sockaddr_in6 sin6;
590 ::memcpy (&sin6, buffer + a, sizeof (sin6));
591 Changed_ (&known, c, ifam.ifam_index, InternetAddress{WithoutEmbeddedScope_ (sin6.sin6_addr)});
592 }
593 }
594 a += saLen > 0 ? (1 + ((saLen - 1) | (sizeof (uint32_t) - 1))) : sizeof (uint32_t);
595 }
596 }
597 offset += ifam.ifam_msglen;
598 }
599 }
600 });
601 fMonitorThread_.SetThreadName ("Network LinkMonitor thread"sv);
602 fMonitorThread_.Start ();
603#else
605#endif
606 }
607
609#if qStroika_Platform_POSIX
610 Execution::Thread::Ptr fMonitorThread_;
611#elif qStroika_Platform_Windows
612 HANDLE fMonitorHandler_ = INVALID_HANDLE_VALUE;
613 mutex fUsableMutex_; // the OS's callbacks may come on more than one of its threads
614 Containers::Set<IndexAndAddress_> fUsable_; // the addresses usable now (DAD state Preferred)
615#endif
616 static inline mutex sMutex_; // guards sCurrent_
617 static inline weak_ptr<Backend_> sCurrent_;
618 static inline thread_local bool tNotifying_{false};
619 };
620}
621
622struct LinkMonitor::Rep_ {
623 ~Rep_ ()
624 {
625 if (fBackend_ != nullptr) {
626 fBackend_->Remove (fSubscriber_); // no more notifications to it
627 // and none still running - unless here, a callback destroying its own LinkMonitor: then it calls none of the rest
628 fSubscriber_->fCallbacks.RemoveAll ();
629 }
630 // then fBackend_ goes - so it stops, if this was the last LinkMonitor using it
631 }
632 CallbackID AddCallback (const Callback& callback)
633 {
634 {
635 [[maybe_unused]] lock_guard critSec{fBackendMutex_};
636 if (fBackend_ == nullptr) {
637 fBackend_ = Backend_::Get (); // (throws if the OS cannot tell: so nothing is added)
638 fBackend_->Add (fSubscriber_);
639 }
640 }
641 return fSubscriber_->fCallbacks.Add (callback);
642 }
643 void RemoveCallback (CallbackID callback)
644 {
645 fSubscriber_->fCallbacks.Remove (callback); // (waits for it, if running on another thread)
646 }
647 const shared_ptr<Subscriber_> fSubscriber_{Memory::MakeSharedPtr<Subscriber_> ()};
648 mutex fBackendMutex_;
649 shared_ptr<Backend_> fBackend_; // from the first AddCallback; guarded by fBackendMutex_
650};
651
652/*
653 ********************************************************************************
654 ************************* IO::Network::LinkMonitor *****************************
655 ********************************************************************************
656 */
658{
660 sb << "{"sv;
661 sb << "change: "sv << fChange;
662 sb << ", interfaceID: "sv << fInterfaceID;
663 sb << ", address: "sv << fAddress;
664 sb << "}"sv;
665 return sb;
666}
667
668LinkMonitor::LinkMonitor ()
669 : fRep_{Memory::MakeSharedPtr<Rep_> ()}
670{
671}
672
673auto LinkMonitor::AddCallback (const Callback& callback) -> CallbackID
674{
675 return fRep_->AddCallback (callback);
676}
677
679{
680 fRep_->RemoveCallback (callback);
681}
#define AssertNotImplemented()
Definition Assertions.h:404
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...
#define DbgTrace
Definition Trace.h:327
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,...
Definition String.h:201
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< value_type > First() const
Definition Sequence.inl:749
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)
Definition Set.inl:143
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.
Definition Set.inl:217
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
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...
Definition Thread.h:357
nonvirtual void SetThreadName(const Characters::String &threadName) const
Definition Thread.cpp:627
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...
Definition DNS.cpp:238
nonvirtual PlatformNativeHandle GetNativeSocket() const
Definition Socket.inl:52
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)
Definition Thread.cpp:887
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
InternetAddress GetPrimaryInternetAddress()