diff --git a/daemon/CMakeLists.txt b/daemon/CMakeLists.txt index 2390458..06712b1 100644 --- a/daemon/CMakeLists.txt +++ b/daemon/CMakeLists.txt @@ -11,6 +11,8 @@ option(WITH_AVAHI "Include mDNS support via Avahi" OFF) option(FAKE_DRIVER "Use fake driver instead of RAVENNA" OFF) set(CMAKE_CXX_STANDARD 17) +option(WITH_SYSTEMD "Include systemd notify and watchdog support" OFF) + # ravena lkm _should_ be provided by the CLI. Nonetheless, we should be able # to find it in system dirs too... if (NOT RAVENNNA_ALSA_LKM_DIR) @@ -54,3 +56,9 @@ if(WITH_AVAHI) include_directories(aes67-daemon ${AVAHI_INCLUDE_DIRS}) target_link_libraries(aes67-daemon ${AVAHI_LIBRARIES}) endif() + +if(WITH_SYSTEMD) + MESSAGE(STATUS "WITH_SYSTEMD") + add_definitions(-D_USE_SYSTEMD_) + target_link_libraries(aes67-daemon systemd) +endif() diff --git a/daemon/main.cpp b/daemon/main.cpp index b5a99da..98039ec 100644 --- a/daemon/main.cpp +++ b/daemon/main.cpp @@ -31,6 +31,10 @@ #include "rtsp_server.hpp" #include "session_manager.hpp" +#ifdef _USE_SYSTEMD_ +#include +#endif + namespace po = boost::program_options; namespace postyle = boost::program_options::command_line_style; namespace logging = boost::log; @@ -64,6 +68,11 @@ int main(int argc, char* argv[]) { int unix_style = postyle::unix_style | postyle::short_allow_next; bool driver_restart(true); +#ifdef _USE_SYSTEMD_ + // with which interval we should pet the dog + uint64_t current_watchdog_usec; +#endif + po::variables_map vm; try { po::store(po::command_line_parser(argc, argv) @@ -98,6 +107,17 @@ int main(int argc, char* argv[]) { std::string filename = vm["config"].as(); +#ifdef _USE_SYSTEMD_ + sd_watchdog_enabled(0, ¤t_watchdog_usec); + + if (current_watchdog_usec > 0) { + // Inform systemd that if we're not petting the dog in 5s we're bust. + sd_notify(0, "WATCHDOG_USEC=5000000"); + + current_watchdog_usec = 5000000; + } +#endif + while (!is_terminated() && rc == EXIT_SUCCESS) { /* load configuration from file */ auto config = Config::parse(filename, driver_restart); @@ -112,6 +132,11 @@ int main(int argc, char* argv[]) { log_init(*config); if (config->get_ip_addr_str().empty()) { +#ifdef _USE_SYSTEMD_ + if (current_watchdog_usec > 0) + sd_notify(0, "WATCHDOG=1"); + sd_notify(0, "STATUS=no IP address, waiting ..."); +#endif BOOST_LOG_TRIVIAL(info) << "main:: no IP address, waiting ..."; std::this_thread::sleep_for(std::chrono::seconds(1)); continue; @@ -159,7 +184,22 @@ int main(int argc, char* argv[]) { session_manager->load_status(); BOOST_LOG_TRIVIAL(debug) << "main:: init done, entering loop..."; + +#ifdef _USE_SYSTEMD_ + // To be able to use sd_notify at all have to set service NotifyAccess + // (e.g. to main) + sd_notify(0, "READY=1"); // If service Type=notify the service is only + // considered ready once we send this (this is + // independent of watchdog capability) + sd_notify(0, "STATUS=Working"); +#endif + while (!is_terminated()) { +#ifdef _USE_SYSTEMD_ + if (current_watchdog_usec > 0) + sd_notify(0, "WATCHDOG=1"); +#endif + auto [ip_addr, ip_str] = get_interface_ip(config->get_interface_name()); if (config->get_ip_addr_str() != ip_str) { BOOST_LOG_TRIVIAL(warning) @@ -175,6 +215,15 @@ int main(int argc, char* argv[]) { std::this_thread::sleep_for(std::chrono::seconds(1)); } +#ifdef _USE_SYSTEMD_ + if (is_terminated()) { + sd_notify(0, "STOPPING=1"); + sd_notify(0, "STATUS=Stopping"); + } else { + sd_notify(0, "RELOADING=1"); + sd_notify(0, "STATUS=Restarting"); + } +#endif /* save session status to file */ session_manager->save_status(); diff --git a/systemd/aes67-daemon.conf b/systemd/aes67-daemon.conf new file mode 100644 index 0000000..12ff58e --- /dev/null +++ b/systemd/aes67-daemon.conf @@ -0,0 +1,2 @@ +#Type Name ID GECOS Home directory Shell +u aes67-daemon - "AES67 daemon user" diff --git a/systemd/aes67-daemon.service b/systemd/aes67-daemon.service new file mode 100644 index 0000000..35c4128 --- /dev/null +++ b/systemd/aes67-daemon.service @@ -0,0 +1,60 @@ +[Unit] +Description=AES67 daemon service +Before=multi-user.target +After=network.target + +[Service] +Type=notify +# Will be adjusted by service during startup +WatchdogSec=10 + +# Run as separate user created via sysusers.d +User=aes67-daemon + +ExecStart=/usr/local/bin/aes67-daemon + +# Security filters. +CapabilityBoundingSet= +DevicePolicy=closed +LockPersonality=yes +MemoryDenyWriteExecute=yes +NoNewPrivileges=yes +PrivateDevices=yes +PrivateMounts=yes +PrivateTmp=yes +PrivateUsers=yes +# interface::get_mac_from_arp_cache() reads from /proc/net/arp +ProcSubset=all +ProtectClock=yes +ProtectControlGroups=yes +ProtectHome=yes +ProtectHostname=yes +ProtectKernelLogs=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectProc=invisible +ProtectSystem=strict +RemoveIPC=yes +RestrictAddressFamilies=AF_INET AF_NETLINK AF_UNIX +RestrictNamespaces=yes +RestrictRealtime=yes +RestrictSUIDSGID=yes +SystemCallArchitectures=native +SystemCallFilter=~@clock +SystemCallFilter=~@cpu-emulation +SystemCallFilter=~@debug +SystemCallFilter=~@module +SystemCallFilter=~@mount +SystemCallFilter=~@obsolete +SystemCallFilter=~@privileged +SystemCallFilter=~@raw-io +SystemCallFilter=~@reboot +SystemCallFilter=~@resources +SystemCallFilter=~@swap +UMask=077 +# Paths matching daemon.conf +ReadWritePaths=/etc/daemon.conf +ReadWritePaths=/etc/status.json + +[Install] +WantedBy=multi-user.target