Skip to content

Commit

Permalink
Improve reliability of network hotplug events
Browse files Browse the repository at this point in the history
  • Loading branch information
SimulPiscator committed Nov 25, 2023
1 parent 5ab8c27 commit 1eb3f1b
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 6 deletions.
2 changes: 1 addition & 1 deletion server/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Server::Server(int argc, char** argv)
{ "unix-socket", "", "listen on named unix socket", unixsocket },
{ "access-log", "", "HTTP access log, - for stdout", accesslog },
{ "hotplug", "true", "repeat scanner search on hotplug event", hotplug },
{ "network-hotplug", "false", "restart server on network change", networkhotplug },
{ "network-hotplug", "true", "restart server on network change", networkhotplug },
{ "mdns-announce", "true", "announce scanners via mDNS", announce },
{ "announce-secure", "false", "announce secure connection", announcesecure },
{ "web-interface", "true", "enable web interface", webinterface },
Expand Down
114 changes: 109 additions & 5 deletions zeroconf/networkhotplugnotifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.

#include "networkhotplugnotifier.h"

#include "web/httpserver.h"

#include <iostream>
#include <vector>
#include <set>
#include <thread>

#include <poll.h>
Expand All @@ -33,15 +35,59 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <linux/rtnetlink.h>
#endif
#include <net/if.h>
#include <ifaddrs.h>

#include <string.h>

namespace {
// Return true if a sorts smaller/less than b.
struct CompareAddresses
{
bool operator()(const HttpServer::Sockaddr& a, const HttpServer::Sockaddr& b)
{
if (a.sa.sa_family != b.sa.sa_family)
return a.sa.sa_family < b.sa.sa_family;
int cmp = 0;
switch (a.sa.sa_family) {
case AF_UNIX:
cmp = ::strcmp(a.un.sun_path, b.un.sun_path);
break;
case AF_INET:
cmp = ::memcmp(&a.in.sin_addr, &b.in.sin_addr, sizeof(a.in.sin_addr));
break;
case AF_INET6:
cmp = ::memcmp(&a.in6.sin6_addr, &b.in6.sin6_addr, sizeof(a.in6.sin6_addr));
break;
}
if (cmp < 0)
return true;
if (cmp > 0)
return false;

switch (a.sa.sa_family) {
case AF_UNIX:
return false;
case AF_INET:
cmp = ::memcmp(&a.in.sin_port, &b.in.sin_port, sizeof(a.in.sin_port));
break;
case AF_INET6:
cmp = ::memcmp(&a.in6.sin6_port, &b.in6.sin6_port, sizeof(a.in6.sin6_port));
break;
}
return cmp < 0;
}
};

}

struct NetworkHotplugNotifier::Private
{
std::thread mThread;
NetworkHotplugNotifier* mpNotifier;
int mPipeWriteFd, mPipeReadFd;

std::set<HttpServer::Sockaddr, CompareAddresses> mAddresses;

Private(NetworkHotplugNotifier* pNotifier)
: mpNotifier(pNotifier), mPipeWriteFd(-1), mPipeReadFd(-1)
{
Expand All @@ -65,6 +111,33 @@ struct NetworkHotplugNotifier::Private
::close(mPipeReadFd);
}

void initAddresses()
{
mAddresses.clear();
ifaddrs* pAddrs = nullptr;
int result = ::getifaddrs(&pAddrs);
if (result < 0) {
std::cerr << "Could not get addresses: " << errno << std::endl;
return;
}
for (ifaddrs* p = pAddrs; p != nullptr; p = p->ifa_next) {
HttpServer::Sockaddr address = {0};
if (p->ifa_addr) {
switch (p->ifa_addr->sa_family) {
case AF_INET:
::memcpy(&address, p->ifa_addr, sizeof(address.in));
break;
case AF_INET6:
::memcpy(&address, p->ifa_addr, sizeof(address.in6));
break;
}
}
if (address.sa.sa_family != AF_UNSPEC)
mAddresses.insert(address);
}
::freeifaddrs(pAddrs);
}

void hotplugThread()
{
int sock = ::socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
Expand All @@ -83,6 +156,8 @@ struct NetworkHotplugNotifier::Private
return;
}

initAddresses();

struct pollfd pfds[2] = {0};
pfds[0].fd = mPipeReadFd;
pfds[0].events = POLLIN;
Expand All @@ -103,11 +178,40 @@ struct NetworkHotplugNotifier::Private
if (data.n->nlmsg_flags & MSG_TRUNC)
continue;
while (NLMSG_OK(data.n, len) && (data.n->nlmsg_type != NLMSG_DONE)) {
if (data.n->nlmsg_type == RTM_NEWADDR)
mpNotifier->onHotplugEvent(addressArrived);
else if(data.n->nlmsg_type == RTM_DELADDR)
if (data.n->nlmsg_type == RTM_NEWADDR
|| data.n->nlmsg_type == RTM_DELADDR
|| data.n->nlmsg_type == RTM_GETADDR) {
struct ifaddrmsg* pIfa = static_cast<struct ifaddrmsg*>(NLMSG_DATA(data.n));
int ifalen = IFA_PAYLOAD(data.n);
struct rtattr* pRta = IFA_RTA(pIfa);
HttpServer::Sockaddr address = {0};
while (ifalen && RTA_OK(pRta, ifalen)) {
if (pIfa->ifa_family == AF_INET && pRta->rta_type == IFA_ADDRESS) {
address.in.sin_family = AF_INET;
address.in.sin_addr = *reinterpret_cast<in_addr*>(RTA_DATA(pRta));
}
else if (pIfa->ifa_family == AF_INET6 && pRta->rta_type == IFA_ADDRESS) {
address.in6.sin6_family = AF_INET6;
address.in6.sin6_addr = *reinterpret_cast<in6_addr*>(RTA_DATA(pRta));
}
RTA_NEXT(pRta, ifalen);
}
if (address.sa.sa_family != AF_UNSPEC) {
if (data.n->nlmsg_type == RTM_NEWADDR) {
if (mAddresses.find(address) == mAddresses.end()) {
mAddresses.insert(address);
std::clog << "New IP address: " << HttpServer::ipString(address) << std::endl;
mpNotifier->onHotplugEvent(addressArrived);
}
}
else if(data.n->nlmsg_type == RTM_DELADDR) {
mAddresses.erase(address);
std::clog << "IP address gone: " << HttpServer::ipString(address) << std::endl;
mpNotifier->onHotplugEvent(addressLeft);
NLMSG_NEXT(data.n, len);
}
}
}
NLMSG_NEXT(data.n, len);
}
}
}
Expand Down

0 comments on commit 1eb3f1b

Please sign in to comment.