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