diff --git a/CMakeLists.txt b/CMakeLists.txt index b7cd993..ccbdd43 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -80,6 +80,7 @@ add_executable(${PROJECT_NAME} imageformats/pngencoder.cpp zeroconf/mdnspublisher.cpp zeroconf/hotplugnotifier.cpp + zeroconf/networkhotplugnotifier.cpp ${ZEROCONF_FILES} ) if(CMAKE_SYSTEM_NAME STREQUAL FreeBSD) diff --git a/server/server.cpp b/server/server.cpp index 54918c1..cc47945 100644 --- a/server/server.cpp +++ b/server/server.cpp @@ -36,6 +36,7 @@ along with this program. If not, see . #include "purgethread.h" #include "basic/uuid.h" #include "zeroconf/hotplugnotifier.h" +#include "zeroconf/networkhotplugnotifier.h" #include "web/accessfile.h" extern const char* GIT_COMMIT_HASH; @@ -72,6 +73,26 @@ struct Notifier : HotplugNotifier } }; +struct NetworkNotifier : NetworkHotplugNotifier +{ + Server& server; + explicit NetworkNotifier(Server& s) + : server(s) + {} + void onHotplugEvent(Event ev) override + { + switch (ev) { + case addressArrived: + case addressLeft: + std::clog << "network hotplug event, reloading configuration" << std::endl; + server.terminate(SIGHUP); + break; + case other: + break; + } + } +}; + bool clientIsAirscan(const HttpServer::Request& req) { @@ -88,6 +109,7 @@ Server::Server(int argc, char** argv) , mDiscloseversion(true) , mLocalonly(true) , mHotplug(true) + , mNetworkhotplug(true) , mRandompaths(false) , mCompatiblepath(false) , mJobtimeout(0) @@ -95,8 +117,8 @@ Server::Server(int argc, char** argv) , mStartupTimeSeconds(0) , mDoRun(true) { - std::string port, interface, unixsocket, accesslog, hotplug, announce, - webinterface, resetoption, discloseversion, localonly, optionsfile, + std::string port, interface, unixsocket, accesslog, hotplug, networkhotplug, + announce, webinterface, resetoption, discloseversion, localonly, optionsfile, ignorelist, accessfile, randompaths, compatiblepath, debug, announcesecure, jobtimeout, purgeinterval; struct @@ -109,6 +131,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", "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 }, @@ -175,6 +198,7 @@ Server::Server(int argc, char** argv) sanecpp::log.rdbuf(std::clog.rdbuf()); mHotplug = (hotplug == "true"); + mNetworkhotplug = (networkhotplug == "true"); mAnnounce = (announce == "true"); mAnnouncesecure = (announcesecure == "true"); mWebinterface = (webinterface == "true"); @@ -240,6 +264,10 @@ Server::run() if (mHotplug) pNotifier = std::make_shared(*this); + std::shared_ptr pNetworkNotifier; + if (mNetworkhotplug) + pNetworkNotifier = std::make_shared(*this); + bool ok = false, done = false; do { if (unixSocket().empty()) { diff --git a/server/server.h b/server/server.h index af002b4..0af9e87 100644 --- a/server/server.h +++ b/server/server.h @@ -60,7 +60,7 @@ class Server : public HttpServer ScannerList mScanners; std::filebuf mLogfile; bool mAnnounce, mWebinterface, mResetoption, mDiscloseversion, - mLocalonly, mHotplug, mRandompaths, mCompatiblepath, mAnnouncesecure; + mLocalonly, mHotplug, mNetworkhotplug, mRandompaths, mCompatiblepath, mAnnouncesecure; std::string mOptionsfile, mAccessfile, mIgnorelist; int mJobtimeout, mPurgeinterval; float mStartupTimeSeconds; diff --git a/zeroconf/networkhotplugnotifier.cpp b/zeroconf/networkhotplugnotifier.cpp new file mode 100644 index 0000000..746033e --- /dev/null +++ b/zeroconf/networkhotplugnotifier.cpp @@ -0,0 +1,116 @@ +/* +AirSane Imaging Daemon +Copyright (C) 2018-2023 Simul Piscator + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#include "networkhotplugnotifier.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct NetworkHotplugNotifier::Private +{ + std::thread mThread; + NetworkHotplugNotifier* mpNotifier; + int mPipeWriteFd, mPipeReadFd; + + Private(NetworkHotplugNotifier* pNotifier) + : mpNotifier(pNotifier), mPipeWriteFd(-1), mPipeReadFd(-1) + { + int fds[2]; + if (::pipe(fds) < 0) { + std::cerr << "Could not create socket pair " << errno << std::endl; + return; + } + mPipeReadFd = fds[0]; + mPipeWriteFd = fds[1]; + mThread = std::thread([this]() { hotplugThread(); }); + } + + ~Private() + { + char c = '0'; + ::write(mPipeWriteFd, &c, 1); + mThread.join(); + ::close(mPipeWriteFd); + ::close(mPipeReadFd); + } + + void hotplugThread() + { + int sock = ::socket(PF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sock < 0) { + std::cerr << "Could not create netlink socket: " << errno << std::endl; + return; + } + + sockaddr_nl addr = {0}; + addr.nl_family = AF_NETLINK; + addr.nl_groups = RTMGRP_IPV4_IFADDR | RTMGRP_IPV6_IFADDR; + if (::bind(sock, reinterpret_cast(&addr), sizeof(addr)) < 0) { + std::cerr << "Could not bind netlink socket: " << errno << std::endl; + ::close(sock); + return; + } + + struct pollfd pfds[2] = {0}; + pfds[0].fd = mPipeReadFd; + pfds[0].events = POLLIN; + pfds[1].fd = sock; + pfds[1].events = POLLIN; + + char buffer[4096]; + bool done = false; + while (!done) { + int r = ::poll(pfds, sizeof(pfds)/sizeof(*pfds), -1); + if (r > 0 && pfds[0].revents) { + done = true; + } + else if (r > 0 && pfds[1].revents) { + int len = ::read(sock, buffer, sizeof(buffer)); + if (len > 0) { + union { const char* c; struct nlmsghdr* n; } data = { buffer }; + 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) + mpNotifier->onHotplugEvent(addressLeft); + NLMSG_NEXT(data.n, len); + } + } + } + } + } +}; + +NetworkHotplugNotifier::NetworkHotplugNotifier() + : p(new Private(this)) +{} + +NetworkHotplugNotifier::~NetworkHotplugNotifier() +{ + delete p; +} diff --git a/zeroconf/networkhotplugnotifier.h b/zeroconf/networkhotplugnotifier.h new file mode 100644 index 0000000..19a7f67 --- /dev/null +++ b/zeroconf/networkhotplugnotifier.h @@ -0,0 +1,45 @@ +/* +AirSane Imaging Daemon +Copyright (C) 2018-2023 Simul Piscator + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ + +#ifndef NETWORK_HOTPLUGNOTIFIER_H +#define NETWORK_HOTPLUGNOTIFIER_H + +class NetworkHotplugNotifier +{ + NetworkHotplugNotifier(const NetworkHotplugNotifier&) = delete; + NetworkHotplugNotifier& operator=(const NetworkHotplugNotifier&) = delete; + +public: + NetworkHotplugNotifier(); + virtual ~NetworkHotplugNotifier(); + +protected: + enum Event + { + other, + addressArrived, + addressLeft, + }; + virtual void onHotplugEvent(Event) {} + +private: + struct Private; + Private* p; +}; + +#endif // NETWORK_HOTPLUGNOTIFIER_H