Stroika Library 3.0d23x
 
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 <cstdio>
7
8#if qStroika_Foundation_Common_Platform_POSIX
9#include <arpa/inet.h>
10#include <net/if.h>
11#include <netdb.h>
12#include <netinet/in.h>
13#include <sys/ioctl.h>
14#include <sys/socket.h>
15#include <unistd.h>
16#if qStroika_Foundation_Common_Platform_Linux
17#include <linux/netlink.h>
18#include <linux/rtnetlink.h>
19#endif
20#elif qStroika_Foundation_Common_Platform_Windows
21#include <WinSock2.h>
22
23#include <WS2tcpip.h>
24
25#include <Iphlpapi.h>
26#include <netioapi.h>
27#endif
28
30#include "Stroika/Foundation/Containers/Collection.h"
31#include "Stroika/Foundation/Execution/Exceptions.h"
33#if qStroika_Foundation_Common_Platform_Windows
34#include "Platform/Windows/WinSock.h"
35#include "Stroika/Foundation/Execution/Platform/Windows/Exception.h"
36#endif
39
40#include "ConnectionlessSocket.h"
41
42#include "LinkMonitor.h"
43
44// Comment this in to turn on aggressive noisy DbgTrace in this module
45//#define USE_NOISY_TRACE_IN_THIS_MODULE_ 1
46
47using namespace Stroika::Foundation;
49using namespace Stroika::Foundation::Memory;
50using namespace Stroika::Foundation::IO;
52
53#if defined(_MSC_VER)
54// support use of Iphlpapi - but better to reference here than in lib entry of project file cuz
55// easiser to see/modularize (and only pulled in if this module is referenced)
56#pragma comment(lib, "Iphlpapi.lib")
57#endif
58
59#if 0
60// FOR POSIX DO SOMETHING LIKE THIS:
61#include <arpa/inet.h>
62#include <assert.h>
63#include <net/if.h>
64#include <netinet/in.h>
65#include <stdio.h>
66#include <string.h>
67#include <sys/ioctl.h>
68#include <sys/socket.h>
69#include <unistd.h>
70
71
72static const char* flags (int sd, const char* name)
73{
74 static char buf[1024];
75
76 static struct ifreq ifreq;
77 strcpy (ifreq.ifr_name, name);
78
79 int r = ioctl (sd, SIOCGIFFLAGS, (char*)&ifreq);
80 assert (r == 0);
81
82 int l = 0;
83#define FLAG(b) \
84 if (ifreq.ifr_flags & b) \
85 l += snprintf (buf + l, sizeof (buf) - l, #b " ")
86 FLAG (IFF_UP);
87 FLAG (IFF_BROADCAST);
88 FLAG (IFF_DEBUG);
89 FLAG (IFF_LOOPBACK);
90 FLAG (IFF_POINTOPOINT);
91 FLAG (IFF_RUNNING);
92 FLAG (IFF_NOARP);
93 FLAG (IFF_PROMISC);
94 FLAG (IFF_NOTRAILERS);
95 FLAG (IFF_ALLMULTI);
96 FLAG (IFF_MASTER);
97 FLAG (IFF_SLAVE);
98 FLAG (IFF_MULTICAST);
99 FLAG (IFF_PORTSEL);
100 FLAG (IFF_AUTOMEDIA);
101 FLAG (IFF_DYNAMIC);
102#undef FLAG
103
104 return buf;
105}
106
107int main (void)
108{
109 static struct ifreq ifreqs[32] {};
110 struct ifconf ifconf {};
111 ifconf.ifc_req = ifreqs;
112 ifconf.ifc_len = sizeof(ifreqs);
113
114 int sd = ::socket (PF_INET, SOCK_STREAM, 0);
115 assert (sd >= 0);
116
117 int r = ioctl (sd, SIOCGIFCONF, (char*)&ifconf);
118 assert (r == 0);
119
120 for (int i = 0; i < ifconf.ifc_len / sizeof(struct ifreq); ++i) {
121 printf ("%s: %s\n", ifreqs[i].ifr_name, inet_ntoa (((struct sockaddr_in*)&ifreqs[i].ifr_addr)->sin_addr));
122 printf (" flags: %s\n", flags (sd, ifreqs[i].ifr_name));
123 }
124
125 close (sd);
126
127 return 0;
128}
129#endif
130
131#if qStroika_Foundation_Common_Platform_Windows
132// /SEE THIS CODE FOR WINDOWS
133//http ://support.microsoft.com/default.aspx?scid=http://support.microsoft.com:80/support/kb/articles/Q129/3/15.asp&NoWebContent=1
134#endif
135
137{
138#if USE_NOISY_TRACE_IN_THIS_MODULE_
139 Debug::TraceContextBumper ctx{"IO::Network::GetPrimaryInternetAddress"};
140#endif
141/// HORRIBLY KLUDGY BAD IMPL!!!
142#if qStroika_Foundation_Common_Platform_Windows
143 IO::Network::Platform::Windows::WinSock::AssureStarted ();
144#if 0
145 DWORD TEST = GetComputerNameEx((COMPUTER_NAME_FORMAT)cnf, buffer, &dwSize))
146#endif
147 char ac[1024];
148 if (::gethostname (ac, sizeof (ac)) == SOCKET_ERROR) {
149 DbgTrace ("gethostname: err={}"_f, WSAGetLastError ());
150 return InternetAddress{};
151 }
152 Sequence<InternetAddress> allAddrs = DNS::kThe.GetHostAddresses (String::FromNarrowSDKString (ac));
153 Sequence<InternetAddress> allNotLocal = allAddrs.Where ([] (const InternetAddress& ia) { return not ia.IsLinkLocalAddress (); });
154 if (auto f = allNotLocal.First ()) {
155 return *f;
156 }
157 if (auto f = allAddrs.First ()) {
158 return *f;
159 }
160 return InternetAddress{};
161#elif qStroika_Foundation_Common_Platform_POSIX
162 auto getFlags = [] (int sd, const char* name) -> int {
163 struct ::ifreq ifreq{};
164 Characters::CString::Copy (ifreq.ifr_name, std::size (ifreq.ifr_name), name);
165 int r = ::ioctl (sd, SIOCGIFFLAGS, (char*)&ifreq);
166 // Since this is used only to filter the list of addresses, if we get an error, don't throw but
167 // return 0
168 if (r < 0) {
169 DbgTrace ("ioctl on getFlags returned {}, errno={}"_f, r, errno);
170 return 0;
171 }
172 Assert (r == 0);
173 return ifreq.ifr_flags;
174 };
175
176 struct ::ifreq ifreqs[32]{};
177 struct ::ifconf ifconf{};
178 ifconf.ifc_req = ifreqs;
179 ifconf.ifc_len = sizeof (ifreqs);
180
181 int sd = ::socket (PF_INET, SOCK_STREAM, 0);
182 Assert (sd >= 0);
183
184 [[maybe_unused]] int r = ::ioctl (sd, SIOCGIFCONF, (char*)&ifconf);
185 Assert (r == 0);
186
187 InternetAddress result;
188 for (int i = 0; i < ifconf.ifc_len / sizeof (struct ifreq); ++i) {
189 int flags = getFlags (sd, ifreqs[i].ifr_name);
190 if ((flags & IFF_UP) and (not(flags & IFF_LOOPBACK)) and (flags & IFF_RUNNING)) {
191 result = InternetAddress{((struct sockaddr_in*)&ifreqs[i].ifr_addr)->sin_addr};
192 break;
193 }
194 //printf ("%s: %s\n", ifreqs[i].ifr_name, inet_ntoa (((struct sockaddr_in*)&ifreqs[i].ifr_addr)->sin_addr));
195 //printf (" flags: %s\n", flags (sd, ifreqs[i].ifr_name));
196 }
197 ::close (sd);
198 return result;
199#endif
200}
201
202String Network::GetPrimaryNetworkDeviceMacAddress ()
203{
204#if USE_NOISY_TRACE_IN_THIS_MODULE_
205 Debug::TraceContextBumper ctx{"IO::Network::GetPrimaryNetworkDeviceMacAddress"};
206#endif
207 [[maybe_unused]] auto printMacAddr = [] (const uint8_t macaddrBytes[6]) -> String {
208 char buf[100]{};
209 (void)std::snprintf (buf, sizeof (buf), "%02x:%02x:%02x:%02x:%02x:%02x", macaddrBytes[0], macaddrBytes[1], macaddrBytes[2],
210 macaddrBytes[3], macaddrBytes[4], macaddrBytes[5]);
211 return String{buf};
212 };
213#if qStroika_Foundation_Common_Platform_Linux
214 // This counts on SIOCGIFHWADDR, which appears to be Linux specific
215 for (SocketAddress::FamilyType family : {SocketAddress::INET, SocketAddress::INET6}) {
216 ConnectionlessSocket::Ptr s = ConnectionlessSocket::New (family, Socket::DGRAM);
217
218 char buf[10 * 1024];
219 ifconf ifc;
220 ifc.ifc_len = sizeof (buf);
221 ifc.ifc_buf = buf;
222 Execution::ThrowPOSIXErrNoIfNegative (::ioctl (s.GetNativeSocket (), SIOCGIFCONF, &ifc));
223
224 const struct ifreq* const end = ifc.ifc_req + (ifc.ifc_len / sizeof (struct ifreq));
225 for (const ifreq* it = ifc.ifc_req; it != end; ++it) {
226 struct ifreq ifr{};
227 Characters::CString::Copy (ifr.ifr_name, std::size (ifr.ifr_name), it->ifr_name);
228 if (::ioctl (s.GetNativeSocket (), SIOCGIFFLAGS, &ifr) == 0) {
229 if (!(ifr.ifr_flags & IFF_LOOPBACK)) { // don't count loopback
230 if (::ioctl (s.GetNativeSocket (), SIOCGIFHWADDR, &ifr) == 0) {
231 return printMacAddr (reinterpret_cast<const uint8_t*> (ifr.ifr_hwaddr.sa_data));
232 }
233 }
234 }
235 }
236 }
237#elif qStroika_Foundation_Common_Platform_Windows
238 IP_ADAPTER_INFO adapterInfo[10];
239 DWORD dwBufLen = sizeof (adapterInfo);
240 Execution::Platform::Windows::ThrowIfNotERROR_SUCCESS (::GetAdaptersInfo (adapterInfo, &dwBufLen));
241 for (PIP_ADAPTER_INFO pi = adapterInfo; pi != nullptr; pi = pi->Next) {
242 // check attributes - IF TEST to see if good adaptoer
243 // @todo
244 return printMacAddr (pi->Address);
245 }
246#else
248#endif
249 return String{};
250}
251
252struct LinkMonitor::Rep_ {
253 void AddCallback (const Callback& callback)
254 {
255 fCallbacks_.Add (callback);
256 StartMonitorIfNeeded_ ();
257 }
258 void RemoveCallback (const Callback& callback)
259 {
260 fCallbacks_.Remove (callback);
261 // @todo - add some such StopMonitorIfNeeded_();
262 }
264#if qStroika_Foundation_Common_Platform_POSIX
265 Execution::Thread::Ptr fMonitorThread_;
266#endif
267#if qStroika_Foundation_Common_Platform_Windows
268 HANDLE fMonitorHandler_ = INVALID_HANDLE_VALUE;
269#endif
270
271 void SendNotifies (LinkChange lc, const String& linkName, const String& ipAddr)
272 {
273 for (const auto& cb : fCallbacks_) {
274 cb (lc, linkName, ipAddr);
275 }
276 }
277
278#if qStroika_Foundation_Common_Platform_Windows
279 // cannot use LAMBDA cuz we need WINAPI call convention
280 static void WINAPI CB_ (void* callerContext, PMIB_UNICASTIPADDRESS_ROW Address, MIB_NOTIFICATION_TYPE NotificationType)
281 {
282 Rep_* rep = reinterpret_cast<Rep_*> (callerContext);
283 if (Address != NULL) {
284 char ipAddrBuf[1024];
285 (void)snprintf (ipAddrBuf, std::size (ipAddrBuf), "%d.%d.%d.%d", Address->Address.Ipv4.sin_addr.s_net,
286 Address->Address.Ipv4.sin_addr.s_host, Address->Address.Ipv4.sin_addr.s_lh, Address->Address.Ipv4.sin_addr.s_impno);
287 LinkChange lc = (NotificationType == MibDeleteInstance) ? LinkChange::eRemoved : LinkChange::eAdded;
288 rep->SendNotifies (lc, String{}, String{ipAddrBuf});
289 }
290 }
291#endif
292
293 void StartMonitorIfNeeded_ ()
294 {
295#if qStroika_Foundation_Common_Platform_Linux
296 if (fMonitorThread_ == nullptr) {
297 // very slight race starting this but not worth worrying about
298 fMonitorThread_ = Execution::Thread::New ([this] () {
299 // for now - only handle adds, but removes SB easy too...
300
302 ConnectionlessSocket::New (static_cast<SocketAddress::FamilyType> (PF_NETLINK), Socket::RAW, NETLINK_ROUTE);
303
304 {
305 sockaddr_nl addr{};
306 addr.nl_family = AF_NETLINK;
307 addr.nl_groups = RTMGRP_IPV4_IFADDR;
308 Execution::ThrowPOSIXErrNoIfNegative (::bind (sock.GetNativeSocket (), (struct sockaddr*)&addr, sizeof (addr)));
309 }
310
311 //
312 /// @todo - PROBABLY REDO USING Socket::Recv () - but we have none right now!!!
313 // -- LGP 2014-01-23
314 //
315
316 int len;
317 char buffer[4096];
318 struct nlmsghdr* nlh;
319 nlh = (struct nlmsghdr*)buffer;
320 while ((len = ::recv (sock.GetNativeSocket (), nlh, 4096, 0)) > 0) {
321 while ((NLMSG_OK (nlh, len)) and (nlh->nlmsg_type != NLMSG_DONE)) {
322 if (nlh->nlmsg_type == RTM_NEWADDR) {
323 struct ifaddrmsg* ifa = (struct ifaddrmsg*)NLMSG_DATA (nlh);
324 struct rtattr* rth = IFA_RTA (ifa);
325 int rtl = IFA_PAYLOAD (nlh);
326 while (rtl and RTA_OK (rth, rtl)) {
327 if (rth->rta_type == IFA_LOCAL) {
328 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated\""); // macro uses 'register' - htons not deprecated
329 uint32_t ipaddr = htonl (*((uint32_t*)RTA_DATA (rth))); //NB no '::' cuz some systems use macro
330 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated\""); // macro uses 'register' - htons not deprecated
331 char name[IFNAMSIZ];
332 ::if_indextoname (ifa->ifa_index, name);
333 {
334 char ipAddrBuf[1024];
335 ::snprintf (ipAddrBuf, std::size (ipAddrBuf), "%d.%d.%d.%d", (ipaddr >> 24) & 0xff,
336 (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff);
337 SendNotifies (LinkChange::eAdded, String::FromNarrowSDKString (name), String{ipAddrBuf});
338 }
339 }
340 rth = RTA_NEXT (rth, rtl);
341 }
342 }
343 nlh = NLMSG_NEXT (nlh, len);
344 }
345 }
346 });
347 fMonitorThread_.SetThreadName ("Network LinkMonitor thread"sv);
348 fMonitorThread_.Start ();
349 }
350#elif qStroika_Foundation_Common_Platform_Windows
351 /*
352 * @todo Minor - but we maybe should be using NotifyIpInterfaceChange... - not sure we get stragiht up/down issues this
353 * way...
354 */
355 if (fMonitorHandler_ == INVALID_HANDLE_VALUE) {
356 Execution::Platform::Windows::ThrowIfNotERROR_SUCCESS (::NotifyUnicastIpAddressChange (AF_INET, &CB_, this, FALSE, &fMonitorHandler_));
357 }
358#else
360#endif
361 }
362
363 ~Rep_ ()
364 {
365#if qStroika_Foundation_Common_Platform_POSIX
366 Execution::Thread::SuppressInterruptionInContext suppressInterruption; // critical to wait til done cuz captures this
367 if (fMonitorThread_ != nullptr) {
368 fMonitorThread_.AbortAndWaitForDone ();
369 }
370#elif qStroika_Foundation_Common_Platform_Windows
371 if (fMonitorHandler_ != INVALID_HANDLE_VALUE) {
372 // @todo should check error result, but then do what?
373 // also - does this blcok until pending notifies done?
374 // assuming so!!!
375 ::CancelMibChangeNotify2 (fMonitorHandler_);
376 }
377#endif
378 }
379};
380
381/*
382 ********************************************************************************
383 ************************* IO::Network::LinkMonitor *****************************
384 ********************************************************************************
385 */
386LinkMonitor::LinkMonitor ()
387 : fRep_{Memory::MakeSharedPtr<Rep_> ()}
388{
389}
390
391void LinkMonitor::AddCallback (const Callback& callback)
392{
393 fRep_->AddCallback (callback);
394}
395
396void LinkMonitor::RemoveCallback (const Callback& callback)
397{
398 fRep_->RemoveCallback (callback);
399}
#define AssertNotImplemented()
Definition Assertions.h:401
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:309
String is like std::u32string, except it is much easier to use, often much more space efficient,...
Definition String.h:201
static String FromNarrowSDKString(const char *from)
Definition String.inl:470
A Collection<T> is a container to manage an un-ordered collection of items, without equality defined ...
A generalization of a vector: a container whose elements are keyed by the natural numbers.
nonvirtual optional< value_type > First() const
Definition Sequence.inl:403
nonvirtual RESULT_CONTAINER Where(INCLUDE_PREDICATE &&includeIfTrue) const
Thread::Ptr is a (unsynchronized) smart pointer referencing an internally synchronized std::thread ob...
Definition Thread.h:334
nonvirtual void SetThreadName(const Characters::String &threadName) const
Definition Thread.cpp:715
nonvirtual void AbortAndWaitForDone(Time::DurationSeconds timeout=Time::kInfinity) const
Abort () the thread, and then WaitForDone () - but if doesn't finish fast enough, send extra aborts (...
Definition Thread.inl:291
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)
Create a format-string (see std::wformat_string or Stroika FormatString, or python 'f' strings.
Ptr New(const function< void()> &fun2CallOnce, const optional< Characters::String > &name, const optional< Configuration > &configuration)
Definition Thread.cpp:960
INT_TYPE ThrowPOSIXErrNoIfNegative(INT_TYPE returnCode)
ConnectionlessSocket::Ptr New(SocketAddress::FamilyType family, Type socketKind, const optional< IPPROTO > &protocol=nullopt)
InternetAddress GetPrimaryInternetAddress()