From 025f49a5fd667afb8d672d3639f8fc806d0a3928 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B0=D1=88=D0=B5=20=D0=98=D0=BC=D1=8F?= Date: Tue, 7 Oct 2025 18:40:54 +0400 Subject: [PATCH] feat(core): add LocalPingScanner for subnet host discovery --- src/core/local_ping_scanner.cpp | 103 ++++++++++++++++++++++++++++++++ src/core/local_ping_scanner.hpp | 26 ++++++++ 2 files changed, 129 insertions(+) create mode 100644 src/core/local_ping_scanner.cpp create mode 100644 src/core/local_ping_scanner.hpp diff --git a/src/core/local_ping_scanner.cpp b/src/core/local_ping_scanner.cpp new file mode 100644 index 0000000..d71a2bf --- /dev/null +++ b/src/core/local_ping_scanner.cpp @@ -0,0 +1,103 @@ +#include "local_ping_scanner.hpp" +#include "logger.hpp" +#include +#include +#include + +LocalPingScanner::LocalPingScanner( + const std::string &baseIp, int prefixLen, int timeoutMs +) + : baseIp_(baseIp) + , prefixLen_(prefixLen) + , timeoutMs_(timeoutMs) +{ + if (prefixLen_ < 0 || prefixLen_ > 32) { + Logger::error("Invalid prefix length in LocalPingScanner constructor"); + throw std::runtime_error("Bad prefix length"); + } + if (timeoutMs_ <= 0) { + timeoutMs_ = 2000; + } +} + +const std::vector &LocalPingScanner::alive() const +{ + return alive_; +} + +uint32_t LocalPingScanner::ipToU32(const std::string &ip) +{ + using namespace boost::asio::ip; + return static_cast(make_address_v4(ip).to_uint()); +} + +std::string LocalPingScanner::u32ToIp(uint32_t v) +{ + using namespace boost::asio::ip; + return address_v4(v).to_string(); +} + +bool LocalPingScanner::pingHost(const std::string &ip) const +{ + try { + return Poco::Net::ICMPClient::pingIPv4( + ip, + /*repeat=*/1, + /*dataSize=*/48, + /*ttl=*/64, + /*timeout=*/timeoutMs_ + ) + > 0; + } catch (const std::exception &ex) { + Logger::error( + std::string("Ping failed for host ") + ip + ": " + ex.what() + ); + return false; + } catch (...) { + Logger::fatal(std::string("Unknown error pinging host ") + ip); + return false; + } +} + +void LocalPingScanner::runImpl() +{ + alive_.clear(); + + const uint32_t base = ipToU32(baseIp_); + const uint32_t mask = + (prefixLen_ == 0) ? 0u : (0xFFFFFFFFu << (32 - prefixLen_)); + const uint32_t network = base & mask; + const uint32_t broadcast = network | (~mask); + + // No hosts when prefix is too tight (e.g., /32). + if (broadcast - network <= 2) { + return; + } + + const std::string selfIp = u32ToIp(base); + + for (uint32_t v = network + 1; v < broadcast; ++v) { + const std::string ipStr = u32ToIp(v); + + if (ipStr == selfIp) { + continue; + } + + if (pingHost(ipStr)) { + alive_.push_back(ipStr); + } + } +} + +void LocalPingScanner::run() +{ + try { + runImpl(); + } catch (const std::exception &ex) { + Logger::error( + std::string("Exception in LocalPingScanner::run: ") + ex.what() + ); + } catch (...) { + Logger::fatal("Unknown exception in LocalPingScanner::run"); + } +} diff --git a/src/core/local_ping_scanner.hpp b/src/core/local_ping_scanner.hpp new file mode 100644 index 0000000..e41ffb6 --- /dev/null +++ b/src/core/local_ping_scanner.hpp @@ -0,0 +1,26 @@ +#pragma once +#include +#include +#include + +class LocalPingScanner +{ + public: + LocalPingScanner( + const std::string &baseIp, int prefixLen, int timeoutMs = 2000 + ); + + void run(); // blocking scan over subnet + const std::vector &alive() const; + + private: + static std::string u32ToIp(uint32_t v); + static uint32_t ipToU32(const std::string &ip); + bool pingHost(const std::string &ip) const; + void runImpl(); + + std::string baseIp_; + int prefixLen_; + int timeoutMs_; + std::vector alive_; +};