From 0e8d77971d6fc5a952f59252324408860feee590 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:41:37 +0400 Subject: [PATCH] refactor(port_scanner): rework to use Boost.Asio for asynchronous port scanning Rewritten to use Boost.Asio for asynchronous scanning, introducing timeout management via timers and concurrent port probing up to 256. Added openPorts() method to retrieve results. --- src/core/port_scanner.cpp | 218 ++++++++++++++++++-------------------- src/core/port_scanner.hpp | 48 +++++---- 2 files changed, 128 insertions(+), 138 deletions(-) diff --git a/src/core/port_scanner.cpp b/src/core/port_scanner.cpp index 5b93976..4dafb38 100644 --- a/src/core/port_scanner.cpp +++ b/src/core/port_scanner.cpp @@ -1,142 +1,126 @@ #include "port_scanner.hpp" #include "logger.hpp" -#include -#include +#include -PortScanner::PortScanner( - const std::string &ip, const std::vector &ports -) - : ports_(ports) +PortScanner::PortScanner(std::string ip, int timeoutMs) + : ip_(std::move(ip)) + , timeoutMs_(timeoutMs) { - ErrorCode ec; - addr_ = boost::asio::ip::make_address(ip, ec); - ip_ok_ = !ec; - - if (!ip_ok_) { - Logger::error(std::string("PortScanner: invalid IP: ") + ip); - } else { - Logger::debug(std::string("PortScanner: using IP: ") + ip); + if (timeoutMs_ <= 0) { + timeoutMs_ = 250; } } -void PortScanner::setTimeoutMs(int ms) +PortScanner::Probe::Probe(IoContext &io, uint16_t p) + : port(p) + , socket(io) + , timer(io) { - if (ms > 0) { - timeout_ms_ = ms; - Logger::debug( - std::string("PortScanner: timeout set to ") - + std::to_string(timeout_ms_) + " ms" - ); - } +} + +const std::vector &PortScanner::openPorts() const +{ + return openPorts_; } void PortScanner::run() { - if (!ip_ok_) { - Logger::error("PortScanner::run - aborting: ip not ok"); + openPorts_.clear(); + nextPort_ = kFirstPort; + outstanding_ = 0; + active_.clear(); + + while (outstanding_ < kMaxConcurrency && nextPort_ <= kLastPort) { + startNext(); + } + + io_.run(); +} + +void PortScanner::startNext() +{ + if (nextPort_ > kLastPort) { return; } - for (uint16_t p : ports_) { - if (probe(p)) { - Logger::info(std::to_string(p)); - } + + const uint16_t port = static_cast(nextPort_); + nextPort_ += 1; + + startProbe(port); +} + +void PortScanner::startProbe(uint16_t port) +{ + // Lifetime of Probe is managed in 'active_' until probe finishes. + auto prPtr = std::make_unique(io_, port); + Probe *pr = prPtr.get(); + active_.push_back(std::move(prPtr)); + + outstanding_ += 1; + + { + using namespace boost::asio::ip; + + TcpEndpoint ep(make_address_v4(ip_), port); + + pr->timer.expires_after(std::chrono::milliseconds(timeoutMs_)); + + auto onTimer = [pr](const ErrorCode &ec) { + // Cancel socket on timeout; completion will run onConnect with + // ec=operation_aborted. + if (!ec) { + pr->socket.cancel(); + } + }; + + auto onAsyncConnect = [this, pr](const ErrorCode &ec) { + onConnect(pr, ec); + }; + + pr->timer.async_wait(onTimer); + pr->socket.async_connect(ep, onAsyncConnect); } } -bool PortScanner::probe(uint16_t port) +void PortScanner::onConnect(Probe *pr, const ErrorCode &ec) { - TcpSocket s(io_); - SteadyTimer t(io_); - auto ep = endpoint(port); + pr->timer.cancel(); - Logger::debug( - std::string("PortScanner::probe - probing port ") + std::to_string(port) + // Open if connect succeeded; REFUSED means closed but host is alive. + if (!ec) { + openPorts_.push_back(pr->port); + Logger::info(std::string("Open port: ") + std::to_string(pr->port)); + } + + ErrorCode ignored; + pr->socket.close(ignored); + + finishProbe(pr); +} + +void PortScanner::finishProbe(Probe *pr) +{ + if (outstanding_ > 0) { + outstanding_ -= 1; + } + + // Remove 'pr' from active_ (active_ size <= kMaxConcurrency, linear + // erase is fine). + auto it = std::find_if( + active_.begin(), active_.end(), [pr](const std::unique_ptr &u) { + return u.get() == pr; + } ); - - armTimer(t, s); - armConnect(s, ep); - runUntilDone(); - cancelTimer(t); - drainLeftovers(); - close(s); - - if (ok_) { - Logger::debug(std::string("Port ") + std::to_string(port) + " is OPEN"); - } else { - Logger::debug( - std::string("Port ") + std::to_string(port) + " is CLOSED/FILTERED" - ); + if (it != active_.end()) { + active_.erase(it); } - return ok_; -} + if (nextPort_ <= kLastPort) { + startNext(); + } -TcpEndpoint PortScanner::endpoint(uint16_t port) const -{ - return TcpEndpoint(addr_, port); -} - -void PortScanner::armTimer(SteadyTimer &t, TcpSocket &s) -{ - t.expires_after(std::chrono::milliseconds(timeout_ms_)); - - auto onTimeout = [&s](const ErrorCode &ec) { - if (!ec) { - Logger::debug("Timer expired -> cancelling socket"); - s.cancel(); - } - }; - - t.async_wait(onTimeout); -} - -void PortScanner::armConnect(TcpSocket &s, const TcpEndpoint &ep) -{ - ok_ = false; - done_ = false; - - auto onConnect = [this](const ErrorCode &ec) { - if (!ec) { - ok_ = true; - Logger::debug("async_connect succeeded"); - } else { - ok_ = false; - Logger::debug(std::string("async_connect failed: ") + ec.message()); - } - done_ = true; - }; - - s.async_connect(ep, onConnect); -} - -void PortScanner::runUntilDone() -{ - io_.restart(); - while (!done_ && io_.run_one()) { - } -} - -void PortScanner::cancelTimer(SteadyTimer &t) -{ - ErrorCode ec; - t.cancel(); - if (ec) { - Logger::warn(std::string("cancelTimer: ") + ec.message()); - } -} - -void PortScanner::drainLeftovers() -{ - io_.restart(); - while (io_.run_one()) { - continue; - } -} - -void PortScanner::close(TcpSocket &s) -{ - ErrorCode ig; - if (s.close(ig)) { - Logger::warn(std::string("close socket failed: ") + ig.message()); + if (outstanding_ == 0 && nextPort_ > kLastPort) { + io_.stop(); + return; } } diff --git a/src/core/port_scanner.hpp b/src/core/port_scanner.hpp index 217b87a..22bb185 100644 --- a/src/core/port_scanner.hpp +++ b/src/core/port_scanner.hpp @@ -1,37 +1,43 @@ #pragma once #include "aliases.hpp" -#include #include +#include #include #include class PortScanner { public: - PortScanner(const std::string &ip, const std::vector &ports); + explicit PortScanner(std::string ip, int timeoutMs = 250); - void setTimeoutMs(int ms); - - void run(); + void run(); // blocking scan + const std::vector &openPorts() const; private: - bool probe(uint16_t port); - TcpEndpoint endpoint(uint16_t port) const; + struct Probe { + uint16_t port{0}; + TcpSocket socket; + SteadyTimer timer; - void armTimer(SteadyTimer &t, TcpSocket &s); - void armConnect(TcpSocket &s, const TcpEndpoint &ep); - void runUntilDone(); - void cancelTimer(SteadyTimer &t); - void drainLeftovers(); - void close(TcpSocket &s); + Probe(IoContext &io, uint16_t p); + }; - private: + void startNext(); + void startProbe(uint16_t port); + void onConnect(Probe *pr, const ErrorCode &ec); + void finishProbe(Probe *pr); + + // Fixed settings + static constexpr uint16_t kFirstPort = 1; + static constexpr uint16_t kLastPort = 65535; + static constexpr std::size_t kMaxConcurrency = 256; + + // State + std::string ip_; IoContext io_; - IpAddress addr_; - std::vector ports_; - - int timeout_ms_{300}; - bool ip_ok_{false}; - bool ok_{false}; - bool done_{false}; + std::vector openPorts_; + std::vector> active_; // <= kMaxConcurrency + uint32_t nextPort_{kFirstPort}; + std::size_t outstanding_{0}; + int timeoutMs_; };