From 9097351727ad0df751467d6c56339c28bb0478a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ke=C5=BEl=C3=ADnek?= <103315902+Dakevid@users.noreply.github.com> Date: Tue, 23 Sep 2025 18:06:20 +0200 Subject: [PATCH] ListDetector: Add runtime configuration reloading --- modules/listDetector/src/listDetector.cpp | 5 ++ modules/listDetector/src/listDetector.hpp | 6 +++ modules/listDetector/src/main.cpp | 57 ++++++++++++++++++++++- modules/listDetector/src/rulesMatcher.cpp | 16 +++++++ modules/listDetector/src/rulesMatcher.hpp | 6 +++ modules/listDetector/tests/test.sh | 45 ++++++++++++++++++ 6 files changed, 133 insertions(+), 2 deletions(-) diff --git a/modules/listDetector/src/listDetector.cpp b/modules/listDetector/src/listDetector.cpp index dfbe252e..f4cea456 100644 --- a/modules/listDetector/src/listDetector.cpp +++ b/modules/listDetector/src/listDetector.cpp @@ -47,6 +47,11 @@ ListDetector::ListDetector(const ConfigParser* configParser, ListDetectorMode mo { } +void ListDetector::updateRules(const ConfigParser* configParser) +{ + m_rulesMatcher.updateRules(configParser); +} + bool ListDetector::matches(const Nemea::UnirecRecordView& unirecRecordView) { const bool match = m_rulesMatcher.anyOfRuleMatches(unirecRecordView); diff --git a/modules/listDetector/src/listDetector.hpp b/modules/listDetector/src/listDetector.hpp index 69d7d078..7d62860f 100644 --- a/modules/listDetector/src/listDetector.hpp +++ b/modules/listDetector/src/listDetector.hpp @@ -39,6 +39,12 @@ class ListDetector { */ explicit ListDetector(const ConfigParser* configParser, ListDetectorMode mode); + /** + * @brief Updates rules in the ListDetector using the provided ConfigParser. + * @param configParser Pointer to the ConfigParser providing new rules. + */ + void updateRules(const ConfigParser* configParser); + /** * @brief Checks if the given UnirecRecordView matches some rule from ListDetector. * @param unirecRecordView The Unirec record to check against the ListDetector. diff --git a/modules/listDetector/src/main.cpp b/modules/listDetector/src/main.cpp index 2ac53525..926fca9b 100644 --- a/modules/listDetector/src/main.cpp +++ b/modules/listDetector/src/main.cpp @@ -25,10 +25,12 @@ #include #include #include +#include using namespace Nemea; static std::atomic g_stopFlag(false); +static std::atomic g_rulesChanged(false); static void signalHandler(int signum) { @@ -72,6 +74,37 @@ static void processNextRecord( } } +/** + * @brief Monitor the rules file for changes and set a flag when it changes. + * + * This function periodically checks the last modification time of the specified rules file. + * If rules has changed, the function sets a flag to indicate that the rules need to be reloaded. + * + * @param rulesFilePath Path to the rules file to monitor. + * @param checkIntervalms Interval in milliseconds between checks. + */ +static void checkRulesFileChanges(const std::string& rulesFilePath, int checkIntervalms) +{ + auto logger = Nm::loggerGet("rulesFileWatcher"); + try { + auto lastModificationTime = std::filesystem::last_write_time(rulesFilePath); + + while (!g_stopFlag.load()) { + std::this_thread::sleep_for(std::chrono::milliseconds(checkIntervalms)); + auto currentModificationTime = std::filesystem::last_write_time(rulesFilePath); + if (currentModificationTime != lastModificationTime) { + logger->warn("Rules file changed, reloading..."); + lastModificationTime = currentModificationTime; + g_rulesChanged.store(true); + } + } + + } catch (std::exception& ex) { + logger->error(ex.what()); + g_stopFlag.store(true); + } +} + /** * @brief Process Unirec records based on the rules of listDetector. * @@ -88,12 +121,13 @@ static void processUnirecRecords( UnirecBidirectionalInterface& biInterface, ListDetector::ListDetector& listDetector) { - while (!g_stopFlag.load()) { + while (!g_stopFlag.load() && !g_rulesChanged.load()) { try { processNextRecord(biInterface, listDetector); } catch (FormatChangeException& ex) { handleFormatChange(biInterface); } catch (const EoFException& ex) { + g_stopFlag.store(true); break; } catch (const std::exception& ex) { throw; @@ -124,6 +158,11 @@ int main(int argc, char** argv) .required() .help("path where the appFs directory will be mounted") .default_value(std::string("")); + + program.add_argument("-ci", "--check-interval") + .help("interval in milliseconds for checking rules file changes. Default is 10000ms (10s). Negative value disable checking.") + .default_value(10000) + .scan<'i', int>(); } catch (std::exception& ex) { logger->error(ex.what()); return EXIT_FAILURE; @@ -190,7 +229,21 @@ int main(int argc, char** argv) auto listDetectorTelemetryDirectory = telemetryRootDirectory->addDir("listdetector"); listDetector.setTelemetryDirectory(listDetectorTelemetryDirectory); - processUnirecRecords(biInterface, listDetector); + if (program.get("--check-interval") >= 0) + { + std::thread rulesFileWatcherThread(checkRulesFileChanges, program.get("--rules"), program.get("--check-interval")); + rulesFileWatcherThread.detach(); + } + + while (!g_stopFlag.load()) + { + if(g_rulesChanged.load()) { + configParser= std::make_unique(program.get("--rules")); + listDetector.updateRules(configParser.get()); + g_rulesChanged.store(false); + } + processUnirecRecords(biInterface, listDetector); + } } catch (std::exception& ex) { logger->error(ex.what()); diff --git a/modules/listDetector/src/rulesMatcher.cpp b/modules/listDetector/src/rulesMatcher.cpp index 8c7216be..995ba012 100644 --- a/modules/listDetector/src/rulesMatcher.cpp +++ b/modules/listDetector/src/rulesMatcher.cpp @@ -26,6 +26,22 @@ RulesMatcher::RulesMatcher(const ConfigParser* configParser) noexcept m_fieldsMatcher = std::make_unique(m_rules); } +void RulesMatcher::updateRules(const ConfigParser* configParser) +{ + const std::string unirecTemplateDescription = configParser->getUnirecTemplateDescription(); + + RuleBuilder ruleBuilder(unirecTemplateDescription); + + m_rules.clear(); + for (const auto& ruleDescription : configParser->getRulesDescription()) { + auto rule = ruleBuilder.build(ruleDescription); + m_rules.emplace_back(rule); + } + + m_ipAddressFieldMatchers = ruleBuilder.getIpAddressFieldMatchers(); + m_fieldsMatcher = std::make_unique(m_rules); +} + std::vector RulesMatcher::getMatchingIpRulesMask(const Nemea::UnirecRecordView& unirecRecordView) { diff --git a/modules/listDetector/src/rulesMatcher.hpp b/modules/listDetector/src/rulesMatcher.hpp index a0a796d5..fcf25b73 100644 --- a/modules/listDetector/src/rulesMatcher.hpp +++ b/modules/listDetector/src/rulesMatcher.hpp @@ -24,6 +24,12 @@ class RulesMatcher { */ explicit RulesMatcher(const ConfigParser* configParser) noexcept; + /** + * @brief Updates rules in the RulesMatcher using the provided ConfigParser. + * @param configParser Pointer to the ConfigParser providing new rules. + */ + void updateRules(const ConfigParser* configParser); + /** * @brief Checks if some rule matches given Unirec view. * @param unirecRecordView The Unirec view to match. diff --git a/modules/listDetector/tests/test.sh b/modules/listDetector/tests/test.sh index 82a64523..83cefad0 100755 --- a/modules/listDetector/tests/test.sh +++ b/modules/listDetector/tests/test.sh @@ -23,6 +23,7 @@ set -e trap 'echo "Command \"$BASH_COMMAND\" failed!"; exit_with_error' ERR for input_file in $data_path/inputs/*; do index=$(echo "$input_file" | grep -o '[0-9]\+') + echo "Running test $index" res_file="/tmp/res" logger -i "u:listDetector" -w $res_file & @@ -58,5 +59,49 @@ for input_file in $data_path/inputs/*; do fi done +echo "Running test 24 + 25" + +res_file="/tmp/res" +logger -i "u:listDetector" -w $res_file & +logger_pid=$! +sleep 0.1 + +process_started $logger_pid + +touch /tmp/rules +cat "$data_path/rules/rule24.csv" > /tmp/rules + +$list_detector \ + -i "u:lr,u:listDetector" \ + -r "/tmp/rules" \ + -lm blacklist \ + -ci 100 & + +detector_pid=$! +sleep 0.1 +process_started $detector_pid + +logreplay -i "u:lr" -f "$data_path/inputs/input24.csv" 2>/dev/null -n +sleep 1 + +cat "$data_path/rules/rule25.csv" > /tmp/rules + +logreplay -i "u:lr" -f "$data_path/inputs/input25.csv" 2>/dev/null + +wait $detector_pid +wait $logger_pid + +cat "$data_path/results/res24.csv" "$data_path/results/res25.csv" > "/tmp/res_expected24_25.csv" + +if [ -f "$res_file" ]; then + if ! cmp -s "/tmp/res_expected24_25.csv" "$res_file"; then + echo "Files /tmp/res_expected24_25.csv and $res_file are not equal" + exit_with_error + fi +else + echo "File $res_file not found" + exit_with_error +fi + echo "All tests passed" exit 0