diff --git a/third-party/rsutils/src/adapter-watcher.cpp b/third-party/rsutils/src/adapter-watcher.cpp index bb336886d05..58f306a012e 100644 --- a/third-party/rsutils/src/adapter-watcher.cpp +++ b/third-party/rsutils/src/adapter-watcher.cpp @@ -16,6 +16,13 @@ #pragma comment( lib, "iphlpapi.lib" ) #else + +#include +#include +#include +#include +#include + #endif @@ -30,9 +37,11 @@ class adapter_watcher_singleton #ifdef WIN32 HANDLE _done; - std::thread _th; #else + int _socket; + int _done; #endif + std::thread _th; public: public_signal callbacks; @@ -42,7 +51,7 @@ class adapter_watcher_singleton : _done( CreateEvent( nullptr /*security attrs*/, TRUE /*manual reset*/, FALSE /*init unset*/, nullptr /*no name*/ ) ) , _th( - [this]() + [this] { // We're going to wait on the following handles: HANDLE handles[2]; @@ -78,9 +87,81 @@ class adapter_watcher_singleton CancelIPChangeNotify( &overlap ); } ) #else - // https://manpages.ubuntu.com/manpages/oracular/en/man7/netlink.7.html // https://stackoverflow.com/questions/27008067/how-to-get-notified-about-network-interface-changes-with-netlist-and-rtmgrp-link - On Linux, it is done opening and reading on a special kind of socket.Documentation here.Some nice examples here. + : _socket( open_netlink() ) + , _done( eventfd(0, 0) ) + , _th( + [this] + { + // We're going to wait on the following handles: + LOG_DEBUG( "starting network adapter watcher" ); + + int const timeout = -1; // negative value = infinite + int const nfds = 2; + pollfd fds[nfds]; + memset( fds, 0, sizeof(fds) ); + fds[0].fd = _done; + fds[0].events = POLLIN; + fds[1].fd = _socket; + fds[1].events = POLLIN; + + while( true ) + { + fds[0].revents = 0; + fds[1].revents = 0; + auto rv = poll( fds, nfds, timeout ); + if( rv <= 0 ) + { + LOG_ERROR( "poll failed: " << rv ); + break; + } + + if( fds[0].revents != 0 ) + break; // we're done + + LOG_DEBUG( "network adapter changes detected!" ); + + char buf[4096]; + struct sockaddr_nl snl; + iovec iov = { buf, sizeof buf }; + msghdr msg = { (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0 }; + int status = recvmsg( _socket, &msg, 0 ); + if( status < 0 ) + { + // Socket non-blocking so bail out once we have read everything + if( errno == EWOULDBLOCK || errno == EAGAIN ) + continue; + + // Anything else is an error + LOG_DEBUG( "read_netlink: Error recvmsg: " << status ); + break; + } + if( status == 0 ) + LOG_DEBUG( "read_netlink: EOF" ); + + // We need to handle more than one message per recvmsg + for( auto h = (struct nlmsghdr *) buf; + NLMSG_OK( h, (unsigned int)status ); + h = NLMSG_NEXT( h, status ) ) + { + // Finish reading + if( h->nlmsg_type == NLMSG_DONE ) + continue; + + // Message is some kind of error + if( h->nlmsg_type == NLMSG_ERROR ) + { + LOG_DEBUG( "read_netlink: Message is an error - decode TBD" ); + continue; + } + } + + // Call our clients once for all the messages... outside the loop + callbacks.raise(); + } + + LOG_DEBUG( "exiting network adapter watcher" ); + } ) #endif { LOG_DEBUG( "network adapter watcher singleton is up" ); @@ -96,8 +177,39 @@ class adapter_watcher_singleton } CloseHandle( _done ); #else + if( _th.joinable() ) + { + if( write( _done, &_done, sizeof( &_done ) ) != sizeof( &_done ) ) + ; + _th.join(); + } + close( _done ); #endif } + +#ifndef WIN32 + static int open_netlink() + { + // NETLINK_ROUTE + // Receives routing and link updates and may be used to modify the routing tables + // (both IPv4 and IPv6), IP addresses, link parameters, neighbor setups, queueing + // disciplines, traffic classes, and packet classifiers. + // https://manpages.ubuntu.com/manpages/oracular/en/man7/netlink.7.html + int sock = socket( AF_NETLINK, SOCK_RAW, NETLINK_ROUTE ); + if( sock < 0 ) + return sock; + + struct sockaddr_nl addr; + memset( (void *)&addr, 0, sizeof( addr ) ); + addr.nl_family = AF_NETLINK; + addr.nl_pid = getpid(); + addr.nl_groups = RTMGRP_LINK|RTMGRP_IPV4_IFADDR|RTMGRP_IPV6_IFADDR; + if( bind( sock, (struct sockaddr *)&addr, sizeof( addr ) ) < 0 ) + return -1; + + return sock; + } +#endif }; static rsutils::shared_ptr_singleton< detail::adapter_watcher_singleton > the_adapter_watcher;