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_; };