Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
Interface.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#include <filesystem>
10#include <random>
11
12#if qStroika_Platform_POSIX
13#include <arpa/inet.h>
14#include <ifaddrs.h>
15#include <net/if.h> // before <linux/...>, so <linux/if.h> defers to it: a second struct ifreq (vs LinkMonitor.cpp's) violates ODR (-Wodr)
16#include <net/if_arp.h>
17#include <netdb.h>
18#include <netinet/in.h>
19#include <sys/ioctl.h>
20#include <sys/socket.h>
21#include <unistd.h>
22#if qStroika_Platform_Linux
23#include <linux/ethtool.h>
24#include <linux/sockios.h>
25#include <linux/wireless.h>
26#elif qStroika_Platform_MacOS
27#include <net/if_dl.h>
28#include <net/route.h>
29#include <sys/sysctl.h>
30#endif
31#elif qStroika_Platform_Windows
32#include <WinSock2.h>
33
34#include <Windot11.h> // for DOT11_SSID struct
35#include <wlanapi.h>
36
37#include <Iphlpapi.h>
38#include <WS2tcpip.h>
39#include <netioapi.h>
40#endif
41
46#include "Stroika/Foundation/Containers/Collection.h"
47#include "Stroika/Foundation/Containers/KeyedCollection.h"
48#include "Stroika/Foundation/Containers/Mapping.h"
51#include "Stroika/Foundation/Execution/Exceptions.h"
53#if qStroika_Platform_Windows
54#include "Stroika/Foundation/../Foundation/Execution/Platform/Windows/Exception.h"
55#endif
59
60#include "Stroika/Foundation/IO/Network/ConnectionlessSocket.h"
62
63#include "Interface.h"
64
65using std::byte;
66
67using namespace Stroika::Foundation;
70using namespace Stroika::Foundation::Execution;
71using namespace Stroika::Foundation::Memory;
72using namespace Stroika::Foundation::IO;
75
76// Comment this in to turn on aggressive noisy DbgTrace in this module
77// #define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
78
79#if defined(_MSC_VER)
80// support use of Iphlpapi - but better to reference here than in lib entry of project file cuz
81// easier to see/modularize (and only pulled in if this module is referenced)
82#pragma comment(lib, "Iphlpapi.lib")
83#endif
84
85namespace {
86 // Windows uses '-' as separator, and linux ':'. Pick arbitrarily (more linux machines
87 // than windows, or soon will be)
88 auto PrintMacAddr_ (const uint8_t* macaddrBytes, [[maybe_unused]] const uint8_t* macaddrBytesEnd) -> String
89 {
90 Require (macaddrBytesEnd - macaddrBytes == 6);
92 (void)snprintf (buf, sizeof (buf), "%02x:%02x:%02x:%02x:%02x:%02x", macaddrBytes[0], macaddrBytes[1], macaddrBytes[2],
93 macaddrBytes[3], macaddrBytes[4], macaddrBytes[5]);
94 Assert (::strlen (buf) < std::size (buf)); // else we must patch in '\0' but I think snprintf always works here
95 return String{buf};
96 };
97}
98
99/*
100 ********************************************************************************
101 ********************** Interface::WirelessInfo *********************************
102 ********************************************************************************
103 */
105{
106 StringBuilder sb;
107 sb << "{"sv;
108 sb << "SSID: "sv << fSSID;
109 sb << ", state: "sv << fState;
110 sb << ", connectionMode: "sv << fConnectionMode;
111 sb << ", profileName: "sv << fProfileName;
112 sb << ", BSSType: "sv << fBSSType;
113 sb << ", MACAddress: "sv << fMACAddress;
114 sb << ", physicalConnectionType: "sv << fPhysicalConnectionType;
115 sb << ", signalQuality: "sv << fSignalQuality;
116 sb << ", securityEnabled: "sv << fSecurityEnabled;
117 sb << ", 8021XEnabled: "sv << f8021XEnabled;
118 sb << ", authAlgorithm: "sv << fAuthAlgorithm;
119 sb << ", cipher: "sv << fCipher;
120 sb << "}"sv;
121 return sb;
122}
123
124/*
125 ********************************************************************************
126 ******************************* Interface::Bindings ****************************
127 ********************************************************************************
128 */
129String Interface::Bindings::ToString () const
130{
131 StringBuilder sb;
132 sb << "{"sv;
133 sb << "boundAddressRanges: "sv << fAddressRanges;
134 sb << ", boundAddresses: "sv << fAddresses;
135 sb << "}"sv;
136 return sb;
137}
138/*
139 ********************************************************************************
140 *********************************** Interface **********************************
141 ********************************************************************************
142 */
144{
145 StringBuilder sb;
146 sb << "{"sv;
147 sb << "interfaceID: "sv << fInterfaceID;
148#if qStroika_Platform_POSIX
149 sb << ", interfaceName: "sv << GetInterfaceName ();
150#endif
151 sb << ", friendlyName: "sv << fFriendlyName;
152 if (fDescription) {
153 sb << ", description: "sv << *fDescription;
154 }
155 if (fNetworkGUID) {
156 sb << ", networkGUID: "sv << *fNetworkGUID;
157 }
158 if (fType) {
159 sb << ", type: "sv << *fType;
160 }
161 if (fIndex) {
162 sb << ", index: "sv << *fIndex;
163 }
164 if (fHardwareAddress) {
165 sb << ", hardwareAddress: "sv << *fHardwareAddress;
166 }
167 if (fTransmitSpeedBaud) {
168 sb << ", transmitSpeedBaud: "sv << *fTransmitSpeedBaud;
169 }
171 sb << ", receiveLinkSpeedBaud: "sv << *fReceiveLinkSpeedBaud;
172 }
173 if (fWirelessInfo) {
174 sb << ", wirelessInfo: "sv << fWirelessInfo;
175 }
176 sb << ", bindings: "sv << fBindings;
177
178 sb << ", gateways: "sv << fGateways;
179 sb << ", DNSServers: "sv << fDNSServers;
180 if (fStatus) {
181 sb << ", status: "sv << *fStatus;
182 }
183 sb << "}"sv;
184 return sb;
185}
186
187/*
188 ********************************************************************************
189 ************************** Network::GetInterfaces ******************************
190 ********************************************************************************
191 */
192#if qStroika_Platform_POSIX
193namespace {
194
195 // a getifaddrs () address, if it is an IPv4 or IPv6 one - copied into the right sockaddr type, so an IPv6 address is read whole
196 optional<InternetAddress> GetInternetAddress_ (const sockaddr* sa)
197 {
198 if (sa != nullptr and sa->sa_family == AF_INET) {
199 sockaddr_in a{};
200 ::memcpy (&a, sa, sizeof (a));
201 return InternetAddress{a.sin_addr};
202 }
203 if (sa != nullptr and sa->sa_family == AF_INET6) {
204 sockaddr_in6 a{};
205 ::memcpy (&a, sa, sizeof (a));
206 return InternetAddress{a.sin6_addr};
207 }
208 return nullopt;
209 }
210
211 // the prefix length a getifaddrs () netmask encodes - read as the ADDRESS's family: macOS can leave the netmask's own
212 // sa_family 0, and its sa_len short of the whole sockaddr (the trailing zero bytes left off)
213 optional<unsigned int> GetPrefixLength_ (const sockaddr* netmask, int family)
214 {
215 if (netmask == nullptr) {
216 return nullopt;
217 }
218 sockaddr_storage ss{};
219#if qStroika_Platform_MacOS
220 ::memcpy (&ss, netmask, min<size_t> (netmask->sa_len, sizeof (ss)));
221#else
222 ::memcpy (&ss, netmask, family == AF_INET ? sizeof (sockaddr_in) : sizeof (sockaddr_in6));
223#endif
224 ss.ss_family = static_cast<sa_family_t> (family);
225 optional<InternetAddress> mask = GetInternetAddress_ (reinterpret_cast<const sockaddr*> (&ss));
226 if (not mask) {
227 return nullopt;
228 }
229 unsigned int prefixLen{};
230 for (bool b : mask->As<vector<bool>> ()) {
231 if (not b) {
232 break;
233 }
234 ++prefixLen;
235 }
236 return prefixLen;
237 }
238
239#if qStroika_Platform_MacOS
240 // a getifaddrs () AF_LINK entry's 6-byte link-layer (MAC) address, if it has one. A sockaddr_dl is often longer than the
241 // struct (name and address can exceed sdl_data), so it is copied by sa_len into a buffer that holds all of it
242 optional<String> GetHardwareAddress_ (const sockaddr* sa)
243 {
244 if (sa == nullptr or sa->sa_family != AF_LINK) {
245 return nullopt;
246 }
247 alignas (sockaddr_dl) uint8_t buf[sizeof (sockaddr_storage)]{};
248 ::memcpy (buf, sa, min<size_t> (sa->sa_len, sizeof (buf)));
249 const sockaddr_dl* sdl = reinterpret_cast<const sockaddr_dl*> (buf);
250 if (sdl->sdl_alen != 6 or offsetof (sockaddr_dl, sdl_data) + sdl->sdl_nlen + 6 > sizeof (buf)) {
251 return nullopt;
252 }
253 const uint8_t* mac = reinterpret_cast<const uint8_t*> (LLADDR (sdl));
254 return PrintMacAddr_ (mac, mac + 6);
255 }
256#endif
257
258 // everything about one interface except its addresses - GetInterfaces_POSIX_ adds those, one per getifaddrs () entry
259#if qStroika_Platform_Linux or qStroika_Platform_MacOS
260 // each interface's IPv4 default gateway (its first, if it has several), by interface name - read once for a listing
262 {
264 try {
265#if qStroika_Platform_Linux
267 static const filesystem::path kFileName_{"/proc/net/route"};
268 /*
269 * EXAMPLE OUTPUT:
270 * cat /proc/net/route
271 * Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT
272 * eth0 00000000 010011AC 0003 0 0 0 00000000 0 0 0
273 * eth0 000011AC 00000000 0001 0 0 0 0000FFFF 0 0 0
274 */
275 // Note - /procfs files always unseekable
276 for (const Sequence<String>& line : reader.ReadMatrix (FileInputStream::New (kFileName_, IO::FileSystem::FileInputStream::eNotSeekable))) {
277 if (line.size () >= 3 and line[1] == "00000000"sv and not result.ContainsKey (line[0])) {
278 int tmp[4]{};
279 swscanf (line[2].As<wstring> ().c_str (), L"%02x%02x%02x%02x", &tmp[3], &tmp[2], &tmp[1], &tmp[0]);
280 result.Add (line[0], InternetAddress{static_cast<byte> (tmp[0]), static_cast<byte> (tmp[1]), static_cast<byte> (tmp[2]),
281 static_cast<byte> (tmp[3])});
282 }
283 }
284#elif qStroika_Platform_MacOS
285 // the kernel's routing table - the routes with a gateway - as netstat -rn shows it. (Not 'route get default', as until
286 // v3.0d25: a process per interface - most of a listing's time - finding only the primary default route, its gateway
287 // given as a DNS name to look up again.)
288 int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET, NET_RT_FLAGS, RTF_GATEWAY};
289 vector<byte> buf;
290 size_t len = 0;
291 for (int tries = 0;; ++tries) {
292 Execution::ThrowPOSIXErrNoIfNegative (::sysctl (mib, static_cast<u_int> (std::size (mib)), nullptr, &len, nullptr, 0));
293 buf.resize (len + len / 8 + 1024); // room for routes added meanwhile
294 len = buf.size ();
295 if (::sysctl (mib, static_cast<u_int> (std::size (mib)), buf.data (), &len, nullptr, 0) == 0) {
296 break;
297 }
298 if (errno != ENOMEM or tries == 3) {
300 }
301 }
302 // each route a routing message: a header, then its addresses - one per bit set in rtm_addrs, in RTAX_ order, each
303 // padded to 4 bytes. Copied out, never cast in place: they are packed (as LinkMonitor's are)
304 for (size_t offset = 0; offset + sizeof (rt_msghdr) <= len;) {
305 rt_msghdr rtm;
306 ::memcpy (&rtm, buf.data () + offset, sizeof (rtm));
307 if (rtm.rtm_msglen == 0) {
308 break;
309 }
310 size_t end = min<size_t> (offset + rtm.rtm_msglen, len);
311 optional<in_addr> dst;
312 optional<in_addr> gateway;
313 optional<in_addr> mask;
314 for (size_t i = 0, a = offset + sizeof (rtm); i < RTAX_MAX and a < end; ++i) {
315 if (not(rtm.rtm_addrs & (1 << i))) {
316 continue;
317 }
318 uint8_t saLen = static_cast<uint8_t> (buf[a]);
319 if (i == RTAX_DST or i == RTAX_GATEWAY or i == RTAX_NETMASK) {
320 // by sa_len - a netmask's is often short, its trailing zero bytes left off (or 0: all zero)
321 sockaddr_in sin{};
322 ::memcpy (&sin, buf.data () + a, min (min<size_t> (saLen, sizeof (sin)), end - a));
323 if (i == RTAX_NETMASK) {
324 mask = sin.sin_addr; // (its family is not always set)
325 }
326 else if (sin.sin_family == AF_INET) {
327 (i == RTAX_DST ? dst : gateway) = sin.sin_addr;
328 }
329 }
330 a += saLen > 0 ? (1 + ((saLen - 1) | (sizeof (uint32_t) - 1))) : sizeof (uint32_t);
331 }
332 // a default route: to 0.0.0.0, with a zero mask (not, say, a VPN's 0.0.0.0/1)
333 char name[IF_NAMESIZE]{};
334 if (dst and dst->s_addr == INADDR_ANY and not(rtm.rtm_flags & RTF_HOST) and (not mask or mask->s_addr == 0) and gateway and
335 ::if_indextoname (rtm.rtm_index, name) != nullptr) {
336 String interfaceName = String::FromNarrowSDKString (name);
337 if (not result.ContainsKey (interfaceName)) {
338 result.Add (interfaceName, InternetAddress{*gateway});
339 }
340 }
341 offset += rtm.rtm_msglen;
342 }
343#endif
344 }
345 catch (...) {
346 // lot's of reasons this could fail, including running WSL on Windows (2018-12-03)
347 }
348 return result;
349 }
350#endif
351
352 Interface GetInterfaces_POSIX_mkInterface_ (int sd, const char* name, unsigned int flags,
353 [[maybe_unused]] const Containers::Mapping<String, InternetAddress>& defaultGateways)
354 {
355 Interface newInterface;
356 newInterface.fInterfaceID = String::FromSDKString (name);
357 newInterface.fFriendlyName = newInterface.fInterfaceID; // not great - maybe find better name - but this will do for now...
358 if (unsigned int index = ::if_nametoindex (name); index != 0) {
359 newInterface.fIndex = index;
360 }
361#if qStroika_Platform_Linux
362 auto getWirelessFlag = [] (int sd, const char* name) -> bool {
363#if defined(SIOCGIWNAME)
364 iwreq pwrq{};
365 CString::Copy (pwrq.ifr_name, std::size (pwrq.ifr_name), name);
366 int r = ::ioctl (sd, SIOCGIWNAME, (char*)&pwrq);
367 return r == 0;
368#else
369 return false;
370#endif
371 };
372#endif
373 if (flags & IFF_LOOPBACK) {
374 newInterface.fType = Interface::Type::eLoopback;
375 }
376#if qStroika_Platform_Linux
377 else if (getWirelessFlag (sd, name)) {
378 newInterface.fType = Interface::Type::eWIFI;
379 }
380#endif
381 else {
382 // NYI
383 newInterface.fType = Interface::Type::eWiredEthernet; // WAG - not the right way to tell!
384 }
385
386#if qStroika_Platform_Linux
387 {
388 ifreq tmp{};
389 CString::Copy (tmp.ifr_name, std::size (tmp.ifr_name), name);
390 if (::ioctl (sd, SIOCGIFHWADDR, &tmp) == 0 and tmp.ifr_hwaddr.sa_family == ARPHRD_ETHER) {
391 newInterface.fHardwareAddress = PrintMacAddr_ (reinterpret_cast<const uint8_t*> (tmp.ifr_hwaddr.sa_data),
392 reinterpret_cast<const uint8_t*> (tmp.ifr_hwaddr.sa_data) + 6);
393 }
394 }
395#endif
396
397#if qStroika_Platform_Linux or qStroika_Platform_MacOS
398 if (optional<InternetAddress> gw = defaultGateways.Lookup (String::FromNarrowSDKString (name))) {
400 }
401#endif
402
403#if qStroika_Platform_Linux
404 auto getSpeed = [] (int sd, const char* name) -> optional<uint64_t> {
405 ifreq ifreq{};
406 CString::Copy (ifreq.ifr_name, std::size (ifreq.ifr_name), name);
407 ethtool_cmd edata{};
408 ifreq.ifr_data = reinterpret_cast<caddr_t> (&edata);
409 edata.cmd = ETHTOOL_GSET;
410 int r = ioctl (sd, SIOCETHTOOL, &ifreq);
411 if (r != 0) {
412#if USE_NOISY_TRACE_IN_THIS_MODULE_
413 DbgTrace ("No speed for interface {}, errno={}"_f, name, errno); // typicall errno=22{EINVAL} on linux
414#endif
415 return nullopt;
416 }
417 constexpr uint64_t kMegabit_ = 1000 * 1000;
418 switch (ethtool_cmd_speed (&edata)) {
419 case SPEED_10:
420 return 10 * kMegabit_;
421 case SPEED_100:
422 return 100 * kMegabit_;
423 case SPEED_1000:
424 return 1000 * kMegabit_;
425 case SPEED_2500:
426 return 2500 * kMegabit_;
427 case SPEED_10000:
428 return 10000 * kMegabit_;
429 default:
430 return nullopt;
431 }
432 };
433 newInterface.fTransmitSpeedBaud = getSpeed (sd, name);
434 newInterface.fReceiveLinkSpeedBaud = newInterface.fTransmitSpeedBaud;
435#endif
436
437 {
439 if (flags & IFF_RUNNING) {
440 // see https://stackoverflow.com/questions/11679514/what-is-the-difference-between-iff-up-and-iff-running for difference between IFF_UP and IFF_RUNNING
443 }
444 else {
445 // see if we can check if physical cable plugged in - https://stackoverflow.com/questions/808560/how-to-detect-the-physical-connected-state-of-a-network-cable-connector
446#if qStroika_Platform_Linux
447 auto checkCarrierKnownSet = [] (const char* id) -> bool {
448#if !USE_NOISY_TRACE_IN_THIS_MODULE_
449 Debug::TraceContextSuppressor suppressTraceInThisBlock; // needlessly noisy on linux systems (due to frequent throw), and more heat than light
450#endif
451 try {
452 auto fs = FileInputStream::New (filesystem::path{"/sys/class/net"} / id, IO::FileSystem::FileInputStream::eNotSeekable);
453 Memory::BLOB b = fs.ReadAll ();
454 if (b.size () >= 1 and b[0] == static_cast<byte> ('1')) {
455 return true;
456 }
457 }
458 catch (...) {
459 }
460 return false; // unknown if this fails
461 };
462 if (checkCarrierKnownSet (name)) {
464 }
465#endif
466 }
467 newInterface.fStatus = status;
468 }
469#if USE_NOISY_TRACE_IN_THIS_MODULE_
470 DbgTrace ("GetInterfaces_POSIX_mkInterface_ returns {}"_f, newInterface);
471#endif
472 return newInterface;
473 }
474
475 InterfacesByID GetInterfaces_POSIX_ ()
476 {
477 ifaddrs* ifa = nullptr;
478 if (::getifaddrs (&ifa) != 0) {
480 }
481 [[maybe_unused]] auto&& cleanupIfa = Execution::Finally ([ifa] () noexcept { ::freeifaddrs (ifa); });
482
483 int sd = ::socket (PF_INET, SOCK_STREAM, 0); // for the per-interface ioctls
484 Assert (sd >= 0);
485 [[maybe_unused]] auto&& cleanup = Execution::Finally ([sd] () noexcept { ::close (sd); });
486
487 /*
488 * getifaddrs () has one entry per (interface, address) - and for each interface one more, with no internet address
489 * (AF_PACKET on Linux, AF_LINK on macOS) - so an interface with no IP address is listed too.
490 *
491 * Not SIOCGIFCONF, which this used until v3.0d25: on Linux it reports IPv4 addresses only (so interfaces with
492 * no IPv4 address went missing); on macOS its records are packed (misaligned), each holds just a struct sockaddr
493 * (too small for an IPv6 address), and link-local addresses come with the scope id embedded in them.
494 */
496#if qStroika_Platform_Linux or qStroika_Platform_MacOS
497 GetDefaultGateways_ ();
498#else
499 {};
500#endif
501 InterfacesByID results;
502 for (const ifaddrs* p = ifa; p != nullptr; p = p->ifa_next) {
503#if USE_NOISY_TRACE_IN_THIS_MODULE_
504 DbgTrace ("interface: ifa_name={}; ifa_addr.sa_family = {}"_f, p->ifa_name, p->ifa_addr == nullptr ? -1 : p->ifa_addr->sa_family);
505#endif
506 // the interface the entry is for - on Linux, an IPv4 address with a label (eth0:1, from ifconfig or 'ip addr add ...
507 // label') comes named by its label, but belongs to its device: eth0 (a device's name cannot contain ':')
508 string deviceName{p->ifa_name};
509#if qStroika_Platform_Linux
510 if (size_t colon = deviceName.find (':'); colon != string::npos) {
511 deviceName.erase (colon);
512 }
513#endif
514 String interfaceName = String::FromSDKString (deviceName);
515 optional<Interface> prev = results.Lookup (interfaceName);
516 Interface newInterface = prev ? *prev : GetInterfaces_POSIX_mkInterface_ (sd, deviceName.c_str (), p->ifa_flags, defaultGateways);
517 if (optional<InternetAddress> ia = GetInternetAddress_ (p->ifa_addr)) {
518 newInterface.fBindings.fAddressRanges.Add (CIDR{*ia, GetPrefixLength_ (p->ifa_netmask, p->ifa_addr->sa_family)});
519 newInterface.fBindings.fAddresses.Add (*ia);
520 }
521#if qStroika_Platform_MacOS
522 // macOS has no SIOCGIFHWADDR (as Linux uses above): the hardware address is the interface's AF_LINK entry
523 if (optional<String> mac = GetHardwareAddress_ (p->ifa_addr)) {
524 newInterface.fHardwareAddress = mac;
525 }
526#endif
527 results.Add (newInterface);
528 }
529 return results;
530 }
531}
532#endif
533
534#if qStroika_Platform_Windows
535namespace {
536 /*
537 * This DLL is apparently only available if the Wireless Lan Service feature is installed
538 * (see https://forum.kodi.tv/showthread.php?tid=333721&pid=2751888#pid2751888)
539 * It is also missing from the docker image mcr.microsoft.com/windows/servercore:ltsc2019.
540 * So don't statically link since that caused the whole app to not load. Instead, load the DLL conditionally.
541 *
542 * \note switched to this helper class from #pragma comment(lib, "wlanapi.lib") in Stroika v2.1a6, to avoid dependency on
543 * that DLL (not met by simple docker containers).
544 */
545 struct WLANAPI_ {
546 WLANAPI_ ()
547 : fDLL{::LoadLibrary (_T ("wlanapi.dll"))}
548 {
549 Execution::ThrowIfNull (fDLL);
550 fWlanOpenHandle = (DWORD (WINAPI*) (_In_ DWORD dwClientVersion, _Reserved_ PVOID pReserved, _Out_ PDWORD pdwNegotiatedVersion,
551 _Out_ PHANDLE phClientHandle)) (::GetProcAddress (fDLL, "WlanOpenHandle"));
552 fWlanCloseHandle =
553 (DWORD (WINAPI*) (_In_ HANDLE hClientHandle, _Reserved_ PVOID pReserved)) (::GetProcAddress (fDLL, "WlanCloseHandle"));
554 fWlanFreeMemory = (DWORD (WINAPI*) (_In_ PVOID pMemory)) (::GetProcAddress (fDLL, "WlanFreeMemory"));
555 fWlanEnumInterfaces =
556 (DWORD (WINAPI*) (_In_ HANDLE hClientHandle, _Reserved_ PVOID pReserved,
557 _Outptr_ PWLAN_INTERFACE_INFO_LIST * ppInterfaceList)) (::GetProcAddress (fDLL, "WlanEnumInterfaces"));
558 fWlanQueryInterface =
559 (DWORD (WINAPI*) (_In_ HANDLE hClientHandle, _In_ CONST ::GUID * pInterfaceGuid, _In_ WLAN_INTF_OPCODE OpCode,
560 _Reserved_ PVOID pReserved, _Out_ PDWORD pdwDataSize, _Outptr_result_bytebuffer_ (*pdwDataSize) PVOID * ppData,
561 _Out_opt_ PWLAN_OPCODE_VALUE_TYPE pWlanOpcodeValueType)) (::GetProcAddress (fDLL, "WlanQueryInterface"));
562 }
563 WLANAPI_ (const WLANAPI_&) = delete;
564 ~WLANAPI_ ()
565 {
566 if (fDLL != nullptr) {
567 Verify (::FreeLibrary (fDLL));
568 }
569 }
570 HINSTANCE fDLL;
571 DWORD (WINAPI* fWlanOpenHandle)
572 (_In_ DWORD dwClientVersion, _Reserved_ PVOID pReserved, _Out_ PDWORD pdwNegotiatedVersion, _Out_ PHANDLE phClientHandle) = nullptr;
573 DWORD (WINAPI* fWlanCloseHandle)
574 (_In_ HANDLE hClientHandle, _Reserved_ PVOID pReserved) = nullptr;
575 DWORD (WINAPI* fWlanFreeMemory)
576 (_In_ PVOID pMemory) = nullptr;
577 DWORD (WINAPI* fWlanEnumInterfaces)
578 (_In_ HANDLE hClientHandle, _Reserved_ PVOID pReserved, _Outptr_ PWLAN_INTERFACE_INFO_LIST* ppInterfaceList) = nullptr;
579 DWORD (WINAPI* fWlanQueryInterface)
580 (_In_ HANDLE hClientHandle, _In_ CONST ::GUID* pInterfaceGuid, _In_ WLAN_INTF_OPCODE OpCode, _Reserved_ PVOID pReserved, _Out_ PDWORD pdwDataSize,
581 _Outptr_result_bytebuffer_ (*pdwDataSize) PVOID* ppData, _Out_opt_ PWLAN_OPCODE_VALUE_TYPE pWlanOpcodeValueType) = nullptr;
582 };
583 struct WirelessInfoPlus_ : Interface::WirelessInfo {
584 // extra info so can be patched into Interface
585 optional<uint64_t> fTransmitSpeedBaud;
586 optional<uint64_t> fReceiveLinkSpeedBaud;
587 };
588 Mapping<Common::GUID, WirelessInfoPlus_> GetInterfaces_Windows_WirelessInfo_ ()
589 {
590 using WirelessInfo = Interface::WirelessInfo;
592
593 static unique_ptr<WLANAPI_> sWlanAPI_;
594 static once_flag sOnceFlag_;
595 call_once (sOnceFlag_, [&] () { IgnoreExceptionsForCall (sWlanAPI_ = make_unique<WLANAPI_> ()) });
596
597 if (sWlanAPI_) {
598 HANDLE hClient = nullptr;
599 {
600 DWORD dwCurVersion = 0;
601 Execution::Platform::Windows::ThrowIfNotERROR_SUCCESS (sWlanAPI_->fWlanOpenHandle (2, NULL, &dwCurVersion, &hClient));
602 }
603 [[maybe_unused]] auto&& cleanup1 = Execution::Finally ([&] () noexcept {
604 if (hClient != nullptr) {
605 sWlanAPI_->fWlanCloseHandle (hClient, nullptr);
606 }
607 });
608
609 PWLAN_INTERFACE_INFO_LIST pIfList = nullptr;
610 Execution::Platform::Windows::ThrowIfNotERROR_SUCCESS (sWlanAPI_->fWlanEnumInterfaces (hClient, nullptr, &pIfList));
611 [[maybe_unused]] auto&& cleanup2 = Execution::Finally ([&] () noexcept {
612 if (pIfList != nullptr) {
613 sWlanAPI_->fWlanFreeMemory (pIfList);
614 }
615 });
616
617 //
618 // makes more sense for pConnectionInfo to be scoped inside loop, but the example docs in:
619 // https://docs.microsoft.com/en-us/windows/win32/api/wlanapi/nf-wlanapi-wlanqueryinterface
620 // keep it out here (so gets re-used in each QueryInterface call) and just deleted once
621 // at the end.
622 //
623 // Scoped inside the loop, we get ASAN errors under windows, so presume the docs / example was right
624 // any my intuitions wrong.
625 //
626 PWLAN_CONNECTION_ATTRIBUTES pConnectInfo{};
627 [[maybe_unused]] auto&& cleanup3 = Execution::Finally ([&] () noexcept {
628 if (pConnectInfo != nullptr) {
629 sWlanAPI_->fWlanFreeMemory (pConnectInfo);
630 }
631 });
632
633 for (DWORD i = 0; i < pIfList->dwNumberOfItems; ++i) {
634 PWLAN_INTERFACE_INFO pIfInfo = (WLAN_INTERFACE_INFO*)&pIfList->InterfaceInfo[i];
635 WirelessInfoPlus_ wInfo;
636 Common::GUID interfaceGUID{pIfInfo->InterfaceGuid};
637
638 auto mapState = [] (WLAN_INTERFACE_STATE s) -> WirelessInfo::State {
639 switch (s) {
640 case wlan_interface_state_not_ready:
641 return WirelessInfo::State::eNotReady;
642 case wlan_interface_state_connected:
643 return WirelessInfo::State::eConnected;
644 case wlan_interface_state_ad_hoc_network_formed:
645 return WirelessInfo::State::eAdHocNetworkFormed;
646 case wlan_interface_state_disconnecting:
647 return WirelessInfo::State::eDisconnecting;
648 case wlan_interface_state_disconnected:
649 return WirelessInfo::State::eDisconnected;
650 case wlan_interface_state_associating:
651 return WirelessInfo::State::eAssociating;
652 case wlan_interface_state_discovering:
653 return WirelessInfo::State::eDiscovering;
654 case wlan_interface_state_authenticating:
655 return WirelessInfo::State::eAuthenticating;
656 default:
657 DbgTrace ("Unknown state {}"_f, static_cast<int> (s));
658 return WirelessInfo::State::eUnknown;
659 }
660 };
661
662 wInfo.fState = mapState (pIfInfo->isState);
663
664 // If interface state is connected, call WlanQueryInterface
665 // to get current connection attributes
666 if (pIfInfo->isState == wlan_interface_state_connected) {
667 {
668 DWORD connectInfoSize = sizeof (WLAN_CONNECTION_ATTRIBUTES);
669 WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_invalid;
671 sWlanAPI_->fWlanQueryInterface (hClient, &pIfInfo->InterfaceGuid, wlan_intf_opcode_current_connection, nullptr,
672 &connectInfoSize, (PVOID*)&pConnectInfo, &opCode));
673 }
674
675 if (pConnectInfo->isState != pIfInfo->isState) {
676 DbgTrace ("Not sure how these can differ (except for race condition) - but if they do, maybe worth looking into"_f);
677 }
678
679 auto mapConnectionMode = [] (WLAN_CONNECTION_MODE s) -> WirelessInfo::ConnectionMode {
680 switch (s) {
681 case wlan_connection_mode_profile:
682 return WirelessInfo::ConnectionMode::eProfile;
683 case wlan_connection_mode_temporary_profile:
684 return WirelessInfo::ConnectionMode::eTemporaryProfile;
685 case wlan_connection_mode_discovery_secure:
686 return WirelessInfo::ConnectionMode::eDiscoverSecrure;
687 case wlan_connection_mode_discovery_unsecure:
688 return WirelessInfo::ConnectionMode::eDiscoverInsecure;
689 case wlan_connection_mode_auto:
690 return WirelessInfo::ConnectionMode::eAuto;
691 case wlan_connection_mode_invalid:
692 return WirelessInfo::ConnectionMode::eInvalid;
693 default:
694 DbgTrace ("Unknown connection mode {}"_f, static_cast<int> (s));
695 return WirelessInfo::ConnectionMode::eUnknown;
696 }
697 };
698 wInfo.fConnectionMode = mapConnectionMode (pConnectInfo->wlanConnectionMode);
699 wInfo.fProfileName = pConnectInfo->strProfileName;
700
701 //Association Attributes for this connection
702 if (pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength != 0) {
703 wInfo.fSSID = String::FromNarrowSDKString (
704 span{reinterpret_cast<const char*> (pConnectInfo->wlanAssociationAttributes.dot11Ssid.ucSSID),
705 pConnectInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength});
706 }
707
708 auto mapBSSType = [] (DOT11_BSS_TYPE s) -> WirelessInfo::BSSType {
709 switch (s) {
710 case dot11_BSS_type_infrastructure:
711 return WirelessInfo::BSSType::eInfrastructure;
712 case dot11_BSS_type_independent:
713 return WirelessInfo::BSSType::eIndependent;
714 case dot11_BSS_type_any:
715 return WirelessInfo::BSSType::eAny;
716 default:
717 DbgTrace ("Unknown BSS mode {}"_f, static_cast<int> (s));
718 return WirelessInfo::BSSType::eUnknown;
719 }
720 };
721 wInfo.fBSSType = mapBSSType (pConnectInfo->wlanAssociationAttributes.dot11BssType);
722
723 wInfo.fMACAddress = PrintMacAddr_ (std::begin (pConnectInfo->wlanAssociationAttributes.dot11Bssid),
724 std::end (pConnectInfo->wlanAssociationAttributes.dot11Bssid));
725
726 auto mapPhysicalConnectionType = [] (DOT11_PHY_TYPE s) -> WirelessInfo::PhysicalConnectionType {
727 switch (s) {
728 case dot11_phy_type_unknown:
729 return WirelessInfo::PhysicalConnectionType::eUnknown;
730 case dot11_phy_type_fhss:
731 return WirelessInfo::PhysicalConnectionType::eFHSS;
732 case dot11_phy_type_dsss:
733 return WirelessInfo::PhysicalConnectionType::eDSSS;
734 case dot11_phy_type_irbaseband:
735 return WirelessInfo::PhysicalConnectionType::eIRBaseBand;
736 case dot11_phy_type_ofdm:
737 return WirelessInfo::PhysicalConnectionType::e80211a;
738 case dot11_phy_type_hrdsss:
739 return WirelessInfo::PhysicalConnectionType::e80211b;
740 case dot11_phy_type_erp:
741 return WirelessInfo::PhysicalConnectionType::e80211g;
742 case dot11_phy_type_ht:
743 return WirelessInfo::PhysicalConnectionType::e80211n;
744 case dot11_phy_type_vht:
745 return WirelessInfo::PhysicalConnectionType::e80211ac;
746 case dot11_phy_type_dmg:
747 return WirelessInfo::PhysicalConnectionType::e80211ad;
748 case dot11_phy_type_he:
749 return WirelessInfo::PhysicalConnectionType::e80211ax;
750 default:
751 DbgTrace ("Unknown DOT11_PHY_TYPE {}"_f, static_cast<int> (s));
752 return WirelessInfo::PhysicalConnectionType::eUnknown;
753 }
754 };
755 wInfo.fPhysicalConnectionType = mapPhysicalConnectionType (pConnectInfo->wlanAssociationAttributes.dot11PhyType);
756
757 wInfo.fSignalQuality = pConnectInfo->wlanAssociationAttributes.wlanSignalQuality;
758
759 {
760 const ULONG kMagicFactor_{1000}; // empirical, so we get same answers from IP_ADAPTER_ADDRESSES_LH::TransmitLinkSpeed
761 wInfo.fTransmitSpeedBaud = pConnectInfo->wlanAssociationAttributes.ulTxRate * kMagicFactor_;
762 wInfo.fReceiveLinkSpeedBaud = pConnectInfo->wlanAssociationAttributes.ulRxRate * kMagicFactor_;
763 }
764
765 wInfo.fSecurityEnabled = pConnectInfo->wlanSecurityAttributes.bSecurityEnabled;
766 wInfo.f8021XEnabled = pConnectInfo->wlanSecurityAttributes.bOneXEnabled;
767 auto mapAuthAlgorithm = [] (DOT11_AUTH_ALGORITHM s) -> WirelessInfo::AuthAlgorithm {
768 switch (s) {
769 case DOT11_AUTH_ALGO_80211_OPEN:
770 return WirelessInfo::AuthAlgorithm::eOpen;
771 case DOT11_AUTH_ALGO_80211_SHARED_KEY:
772 return WirelessInfo::AuthAlgorithm::ePresharedKey;
773 case DOT11_AUTH_ALGO_WPA:
774 return WirelessInfo::AuthAlgorithm::eWPA;
775 case DOT11_AUTH_ALGO_WPA_PSK:
776 return WirelessInfo::AuthAlgorithm::eWPA_PSK;
777 case DOT11_AUTH_ALGO_WPA_NONE:
778 return WirelessInfo::AuthAlgorithm::eWPA_NONE;
779 case DOT11_AUTH_ALGO_RSNA:
780 return WirelessInfo::AuthAlgorithm::eRSNA;
781 case DOT11_AUTH_ALGO_RSNA_PSK:
782 return WirelessInfo::AuthAlgorithm::eRSNA_PSK;
783 default:
784 DbgTrace ("Unknown AuthAlgorithm {}"_f, static_cast<int> (s));
785 return WirelessInfo::AuthAlgorithm::eUnknown;
786 }
787 };
788 wInfo.fAuthAlgorithm = mapAuthAlgorithm (pConnectInfo->wlanSecurityAttributes.dot11AuthAlgorithm);
789
790 auto mapCipher = [] (DOT11_CIPHER_ALGORITHM s) -> String {
791 switch (s) {
792 case DOT11_CIPHER_ALGO_NONE:
793 return "None"sv;
794 case DOT11_CIPHER_ALGO_WEP40:
795 return "WEP-40"sv;
796 case DOT11_CIPHER_ALGO_TKIP:
797 return "TKIP"sv;
798 case DOT11_CIPHER_ALGO_CCMP:
799 return "CCMP"sv;
800 case DOT11_CIPHER_ALGO_WEP104:
801 return "WEP-104"sv;
802 case DOT11_CIPHER_ALGO_WEP:
803 return "WEP"sv;
804 default:
805 return "{}"_f(static_cast<int> (s));
806 }
807 };
808 wInfo.fCipher = mapCipher (pConnectInfo->wlanSecurityAttributes.dot11CipherAlgorithm);
809 results.Add (interfaceGUID, wInfo);
810 }
811 }
812 }
813
814 return results;
815 }
816
817 InterfacesByID GetInterfaces_Windows_ ()
818 {
820 try {
821 wirelessInfo2Merge = GetInterfaces_Windows_WirelessInfo_ ();
822 }
823 catch (const std::system_error& e) {
824 if (Execution::Platform::Windows::IsWin32Error (e.code (), ERROR_SERVICE_NOT_ACTIVE)) {
825 // this just means no wireless services active, so leave wirelessInfo2Merge empty
826 }
827 else {
828 // but other errors it makes sense to propagate
830 }
831 }
832 InterfacesByID results;
833 ULONG flags = GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_INCLUDE_GATEWAYS;
834 ULONG family = AF_UNSPEC; // Both IPv4 and IPv6 addresses
836 Again:
837 ULONG ulOutBufLen = static_cast<ULONG> (buf.GetSize ());
838 PIP_ADAPTER_ADDRESSES pAddresses = reinterpret_cast<PIP_ADAPTER_ADDRESSES> (buf.begin ());
839 // NB: we use GetAdapaterAddresses () instead of GetInterfaceInfo () so we get non-ipv4 addresses
840 //
841 // Note also: calling GetAdaptersAddresses () produces about 10 "Invalid parameter passed to C runtime function" lines in the debugger.
842 // @see https://developercommunity.visualstudio.com/content/problem/363323/getadaptersaddresses-invalid-parameter-passed-to-c.html
843 //
844 DWORD dwRetVal = ::GetAdaptersAddresses (family, flags, nullptr, pAddresses, &ulOutBufLen);
845 if (dwRetVal == NO_ERROR) {
846 for (PIP_ADAPTER_ADDRESSES currAddresses = pAddresses; currAddresses != nullptr; currAddresses = currAddresses->Next) {
847 String adapterName{String::FromNarrowSDKString (currAddresses->AdapterName)};
848 Interface newInterface = results.LookupValue (adapterName);
849 newInterface.fInterfaceID = adapterName;
850 newInterface.fFriendlyName = currAddresses->FriendlyName;
851 newInterface.fDescription = currAddresses->Description;
852
853 static constexpr Common::GUID kZeroGUID_{};
854 if (memcmp (&currAddresses->NetworkGuid, &kZeroGUID_, sizeof (kZeroGUID_)) != 0) {
855 newInterface.fNetworkGUID = currAddresses->NetworkGuid;
856 }
857 switch (currAddresses->IfType) {
858 case IF_TYPE_SOFTWARE_LOOPBACK:
859 newInterface.fType = Interface::Type::eLoopback;
860 break;
861 case IF_TYPE_PROP_VIRTUAL:
863 break;
864 case IF_TYPE_IEEE80211:
865 newInterface.fType = Interface::Type::eWIFI;
866 break;
867 case IF_TYPE_ETHERNET_CSMACD:
868 if (newInterface.fDescription->Contains ("VirtualBox Host-Only Ethernet Adapter"sv) or
869 newInterface.fDescription->Contains ("Hyper-V Virtual Ethernet Adapter"sv)) {
870 // a fairly good guess - not sure how to tell for sure
872 }
873 else {
874 newInterface.fType = Interface::Type::eWiredEthernet;
875 }
876 break;
877 default:
878 DbgTrace ("Treating unknown currAddresses->IfType = {} type as eOther"_f, currAddresses->IfType);
879 newInterface.fType = Interface::Type::eOther;
880 break;
881 }
882 if (currAddresses->TunnelType != TUNNEL_TYPE_NONE) {
883 if (newInterface.fType) {
884 DbgTrace ("overwriting type {} with tunneltype"_f, newInterface.fType);
885 }
886 newInterface.fType = Interface::Type::eTunnel;
887 }
888 switch (currAddresses->OperStatus) {
889 case IfOperStatusUp:
890 newInterface.fStatus = Memory::NullCoalesce (newInterface.fStatus) +
892 break;
893 case IfOperStatusDown:
894 newInterface.fStatus = Memory::NullCoalesce (newInterface.fStatus); // keep any existing status values, but don't leave unknown
895 break;
896 case IfOperStatusDormant:
897 case IfOperStatusLowerLayerDown:
898 // Not sure about either of these - based on docs in https://msdn.microsoft.com/en-us/library/windows/hardware/ff553790(v=vs.85).aspx - not super clear
899 newInterface.fStatus = Memory::NullCoalesce (newInterface.fStatus) + Set<Interface::Status> ({Interface::Status::eConnected});
900 break;
901 default:
902 // Don't know how to interpret the other status states
903 DbgTrace ("ignoring unrecognized status: {}"_f, static_cast<int> (currAddresses->OperStatus));
904 break;
905 }
906 for (PIP_ADAPTER_UNICAST_ADDRESS pu = currAddresses->FirstUnicastAddress; pu != nullptr; pu = pu->Next) {
907 SocketAddress sa{pu->Address};
908 if (sa.IsInternetAddress ()) {
909 newInterface.fBindings.fAddressRanges.Add (
910 pu->OnLinkPrefixLength == 255 ? sa.GetInternetAddress () : CIDR{sa.GetInternetAddress (), pu->OnLinkPrefixLength});
911 newInterface.fBindings.fAddresses.Add (sa.GetInternetAddress ());
912 }
913 }
914 for (PIP_ADAPTER_ANYCAST_ADDRESS pa = currAddresses->FirstAnycastAddress; pa != nullptr; pa = pa->Next) {
915 SocketAddress sa{pa->Address};
916 if (sa.IsInternetAddress ()) {
917 newInterface.fBindings.fAddressRanges.Add (sa.GetInternetAddress ());
918 newInterface.fBindings.fAddresses.Add (sa.GetInternetAddress ());
919 }
920 }
921 // not FirstMulticastAddress: those are the groups joined (by any process), not this interface's addresses
922 for (PIP_ADAPTER_GATEWAY_ADDRESS_LH pa = currAddresses->FirstGatewayAddress; pa != nullptr; pa = pa->Next) {
923 SocketAddress sa{pa->Address};
924 if (sa.IsInternetAddress ()) {
925 auto gws = newInterface.fGateways.value_or (Containers::Sequence<InternetAddress>{});
926 if (not gws.Contains (sa.GetInternetAddress ())) {
927 gws += sa.GetInternetAddress ();
928 newInterface.fGateways = gws;
929 }
930 }
931 }
932 for (PIP_ADAPTER_DNS_SERVER_ADDRESS_XP pa = currAddresses->FirstDnsServerAddress; pa != nullptr; pa = pa->Next) {
933 SocketAddress sa{pa->Address};
934 if (sa.IsInternetAddress ()) {
935 auto ds = newInterface.fDNSServers.value_or (Containers::Sequence<InternetAddress>{});
936 if (not ds.Contains (sa.GetInternetAddress ())) {
937 ds += sa.GetInternetAddress ();
938 newInterface.fDNSServers = ds;
939 }
940 }
941 }
942 if (currAddresses->IfIndex != 0) {
943 newInterface.fIndex = currAddresses->IfIndex;
944 }
945 else if (currAddresses->Ipv6IfIndex != 0) {
946 newInterface.fIndex = currAddresses->Ipv6IfIndex; // an interface with no IPv4
947 }
948 if (currAddresses->PhysicalAddressLength == 6) {
949 newInterface.fHardwareAddress = PrintMacAddr_ (currAddresses->PhysicalAddress, currAddresses->PhysicalAddress + 6);
950 }
951
952#if (NTDDI_VERSION >= NTDDI_WIN6)
953 newInterface.fTransmitSpeedBaud = currAddresses->TransmitLinkSpeed;
954 newInterface.fReceiveLinkSpeedBaud = currAddresses->ReceiveLinkSpeed;
955#endif
956
957 if (newInterface.fType == Interface::Type::eWIFI) {
958 if (auto owinfo = wirelessInfo2Merge.Lookup (newInterface.fInterfaceID)) {
959 newInterface.fWirelessInfo = *owinfo;
960 WeakAssert (not newInterface.fTransmitSpeedBaud.has_value () or newInterface.fTransmitSpeedBaud == owinfo->fTransmitSpeedBaud);
961 WeakAssert (not newInterface.fReceiveLinkSpeedBaud.has_value () or newInterface.fReceiveLinkSpeedBaud == owinfo->fReceiveLinkSpeedBaud);
962 newInterface.fTransmitSpeedBaud = Memory::NullCoalesce (newInterface.fTransmitSpeedBaud, owinfo->fTransmitSpeedBaud);
963 newInterface.fReceiveLinkSpeedBaud = Memory::NullCoalesce (newInterface.fReceiveLinkSpeedBaud, owinfo->fReceiveLinkSpeedBaud);
964 }
965 else {
966 // This happens for down/wifi-direct interfaces
967 // no biggie.
968 // DbgTrace ("Oops - didn't find wireless interface we should have: {}, avail-keys={}"_f, newInterface.fInterfaceID, wirelessInfo2Merge.Keys ());
969 }
970 }
971 else {
972 WeakAssert (not wirelessInfo2Merge.ContainsKey (newInterface.fInterfaceID));
973 }
974#if USE_NOISY_TRACE_IN_THIS_MODULE_
975 DbgTrace ("newInterface={}"_f, newInterface);
976#endif
977 results.Add (newInterface);
978 }
979 }
980 else if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
981 buf.GrowToSize_uninitialized (ulOutBufLen);
982 goto Again;
983 }
984 else if (dwRetVal == ERROR_NO_DATA) {
985 DbgTrace ("There are no network adapters enabled on the local system"_f);
986 }
987 else {
988 Execution::ThrowSystemErrNo (dwRetVal);
989 }
990
991#if USE_NOISY_TRACE_IN_THIS_MODULE_
992 DbgTrace ("returning {}"_f, results.MappedValues ());
993#endif
994 return results;
995 }
996}
997#endif
998
999/*
1000 ********************************************************************************
1001 ************************* Network::SystemInterfacesMgr *************************
1002 ********************************************************************************
1003 */
1005{
1006#if USE_NOISY_TRACE_IN_THIS_MODULE_
1007 Debug::TraceContextBumper ctx{"SystemInterfacesMgr::GetAll"};
1008#endif
1009#if qStroika_Platform_POSIX
1010 InterfacesByID results = GetInterfaces_POSIX_ ();
1011#elif qStroika_Platform_Windows
1012 InterfacesByID results = GetInterfaces_Windows_ ();
1013#else
1015#endif
1016#if USE_NOISY_TRACE_IN_THIS_MODULE_
1017 DbgTrace ("returning {}"_f, results);
1018#endif
1019 return results;
1020}
1021
1022optional<Interface> SystemInterfacesMgr::GetById (const Interface::SystemIDType& interfaceID)
1023{
1024 // @todo - a much more efficent implementation - maybe good enuf to use caller staleness cache with a few seconds staleness
1025 return GetAll ().Lookup (interfaceID);
1026}
1027
1029{
1030#if USE_NOISY_TRACE_IN_THIS_MODULE_
1031 Debug::TraceContextBumper ctx{"Network::GetContainingAddress"};
1032#endif
1033 // @todo - a much more efficent implementation - maybe good enuf to use caller staleness cache with a few seconds staleness
1034 for (const Interface& i : GetAll ()) {
1035 if (i.fBindings.fAddressRanges.Any ([&ia] (CIDR c) { return c.GetRange ().Contains (ia); })) {
1036#if USE_NOISY_TRACE_IN_THIS_MODULE_
1037 DbgTrace ("found interface {}"_f, i.fInterfaceID);
1038#endif
1039 return i;
1040 }
1041 }
1042#if USE_NOISY_TRACE_IN_THIS_MODULE_
1043 DbgTrace ("no interface contains {}"_f, ia);
1044#endif
1045 return nullopt;
1046}
1047
1048/*
1049 ********************************************************************************
1050 ********************** Network::GetLocalAddressToReach *************************
1051 ********************************************************************************
1052 */
1053optional<InternetAddress> Network::GetLocalAddressToReach (const SocketAddress& peer)
1054{
1055 Require (peer.IsInternetAddress ());
1056 // connect () needs a port (Windows refuses 0), but any will do: connecting a UDP socket sends nothing - it only looks up
1057 // the route, which fixes the local address. Keep an IPv6 peer's scope id (needed for a link-local one).
1058 SocketAddress usePeer = peer;
1059 if (peer.GetPort () == 0) {
1060 constexpr PortType kAnyPort_ = 9; // 'discard'
1061 if (peer.GetAddressFamily () == SocketAddress::INET6) {
1062 sockaddr_in6 withPort = SocketAddress{peer.GetInternetAddress (), kAnyPort_}.As<sockaddr_in6> ();
1063 withPort.sin6_scope_id = peer.As<sockaddr_in6> ().sin6_scope_id;
1064 usePeer = SocketAddress{withPort};
1065 }
1066 else {
1067 usePeer = SocketAddress{peer.GetInternetAddress (), kAnyPort_};
1068 }
1069 }
1070 try {
1072 sockaddr_storage ss = usePeer.As<sockaddr_storage> ();
1073 if (::connect (s.GetNativeSocket (), reinterpret_cast<const sockaddr*> (&ss), static_cast<int> (usePeer.GetRequiredSize ())) != 0) {
1074 return nullopt; // no route to peer
1075 }
1076 if (optional<SocketAddress> local = s.GetLocalAddress (); local and local->IsInternetAddress ()) {
1077 return local->GetInternetAddress ();
1078 }
1079 }
1080 catch (...) {
1081 DbgTrace ("GetLocalAddressToReach ({}) failed: {}"_f, peer, current_exception ());
1082 }
1083 return nullopt;
1084}
#define AssertNotImplemented()
Definition Assertions.h:404
#define WeakAssert(c)
A WeakAssert() is for things that aren't guaranteed to be true, but are overwhelmingly likely to be t...
Definition Assertions.h:441
#define Verify(c)
Definition Assertions.h:422
#define Stroika_ATTRIBUTE_INDETERMINATE
Stroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is in...
Definition StdCompat.h:428
#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
static String FromSDKString(const SDKChar *from)
Definition String.inl:449
static String FromNarrowSDKString(const char *from)
Definition String.inl:472
a cross between Mapping<KEY, T> and Collection<T> and Set<T>
nonvirtual value_type LookupValue(ArgByValueType< KeyType > key, ArgByValueType< value_type > defaultValue=value_type{}) const
nonvirtual void erase(ArgByValueType< value_type > item)
nonvirtual bool Add(ArgByValueType< value_type > item)
nonvirtual optional< value_type > Lookup(ArgByValueType< KeyType > key) const
A Mapping uniquely associates two elements: a key and a value (use Assocation to allow multiple value...
nonvirtual bool Add(ArgByValueType< key_type > key, ArgByValueType< mapped_type > newElt, AddReplaceMode addReplaceMode=AddReplaceMode::eAddReplaces)
Definition Mapping.inl:186
nonvirtual bool ContainsKey(ArgByValueType< key_type > key) const
Definition Mapping.inl:175
nonvirtual optional< mapped_type > Lookup(ArgByValueType< key_type > key) const
Definition Mapping.inl:142
A generalization of a vector: a container whose elements are keyed by the natural numbers.
Set<T> is a container of T, where once an item is added, additionally adds () do nothing.
nonvirtual void Add(ArgByValueType< value_type > item)
Definition Set.inl:138
This COULD be easily used to read CSV files, or tab-delimited files, for example.
nonvirtual optional< IO::Network::SocketAddress > GetLocalAddress() const
Definition Socket.inl:57
nonvirtual PlatformNativeHandle GetNativeSocket() const
Definition Socket.inl:52
nonvirtual InternetAddress GetInternetAddress() const
nonvirtual FamilyType GetAddressFamily() const
nonvirtual optional< Interface > GetById(const Interface::SystemIDType &interfaceID)
nonvirtual optional< Interface > GetContainingAddress(const InternetAddress &ia)
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
void ThrowPOSIXErrNo(errno_t errNo=errno)
treats argument errNo as a POSIX errno value, and calls ThrowError () with an error_code constructed ...
auto Finally(FUNCTION &&f) -> Private_::FinallySentry< FUNCTION >
Definition Finally.inl:31
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
optional< InternetAddress > GetLocalAddressToReach(const SocketAddress &peer)
This machine's address for reaching peer - the local address a datagram sent to peer would carry,...
optional< Containers::Set< Status > > fStatus
Definition Interface.h:370
optional< Containers::Sequence< InternetAddress > > fDNSServers
Definition Interface.h:339
optional< Containers::Sequence< InternetAddress > > fGateways
Definition Interface.h:334
optional< Common::GUID > fNetworkGUID
Definition Interface.h:135