From c9e1b0a149180db7cd8aee139350a4d05651c51b 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: Mon, 6 Oct 2025 00:05:11 +0400 Subject: [PATCH] feat(portscanner): implement port scanning CLI and core logic Implement command-line interface for port scanning with IP validation and option parsing. Add core scanning logic using Boost.Asio for asynchronous socket checks, including timeout handling and port probing. Update xmake.lua to configure build target and include necessary files. --- src/cli/portscan_cli.cpp | 26 +++++++ src/cli/portscan_cli.hpp | 19 +++++ src/core/port_scanner.cpp | 142 ++++++++++++++++++++++++++++++++++++++ src/core/port_scanner.hpp | 37 ++++++++++ src/main.cpp | 38 ++++++++++ xmake.lua | 20 +++--- 6 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 src/cli/portscan_cli.cpp create mode 100644 src/cli/portscan_cli.hpp create mode 100644 src/core/port_scanner.cpp create mode 100644 src/core/port_scanner.hpp create mode 100644 src/main.cpp diff --git a/src/cli/portscan_cli.cpp b/src/cli/portscan_cli.cpp new file mode 100644 index 0000000..0e637b2 --- /dev/null +++ b/src/cli/portscan_cli.cpp @@ -0,0 +1,26 @@ +#include "portscan_cli.hpp" +#include +#include + +PortScanCli::PortScanCli() + : CliBase("Port Scanner Options") + , ip_("") +{ +} + +void PortScanCli::setupOptions() +{ + // clang-format off + desc_.add_options() + ("help,h", "Show help") + ("ip,H", withDefault(ip_, ""), "Local IPv4 to scan"); + // clang-format on +} + +std::string PortScanCli::getIp() const +{ + if (vm_["ip"].empty() == 0) { + return "127.0.0.1"; + } + return vm_.at("ip").as(); +} diff --git a/src/cli/portscan_cli.hpp b/src/cli/portscan_cli.hpp new file mode 100644 index 0000000..bf12444 --- /dev/null +++ b/src/cli/portscan_cli.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "cli_base.hpp" +#include "option_utils.hpp" +#include + +class PortScanCli : public CliBase, protected OptionUtils +{ + public: + PortScanCli(); + + std::string getIp() const; + + protected: + void setupOptions() override; + + private: + std::string ip_; +}; diff --git a/src/core/port_scanner.cpp b/src/core/port_scanner.cpp new file mode 100644 index 0000000..5b93976 --- /dev/null +++ b/src/core/port_scanner.cpp @@ -0,0 +1,142 @@ +#include "port_scanner.hpp" +#include "logger.hpp" +#include +#include + +PortScanner::PortScanner( + const std::string &ip, const std::vector &ports +) + : ports_(ports) +{ + 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); + } +} + +void PortScanner::setTimeoutMs(int ms) +{ + if (ms > 0) { + timeout_ms_ = ms; + Logger::debug( + std::string("PortScanner: timeout set to ") + + std::to_string(timeout_ms_) + " ms" + ); + } +} + +void PortScanner::run() +{ + if (!ip_ok_) { + Logger::error("PortScanner::run - aborting: ip not ok"); + return; + } + for (uint16_t p : ports_) { + if (probe(p)) { + Logger::info(std::to_string(p)); + } + } +} + +bool PortScanner::probe(uint16_t port) +{ + TcpSocket s(io_); + SteadyTimer t(io_); + auto ep = endpoint(port); + + Logger::debug( + std::string("PortScanner::probe - probing port ") + std::to_string(port) + ); + + 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" + ); + } + + return ok_; +} + +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()); + } +} diff --git a/src/core/port_scanner.hpp b/src/core/port_scanner.hpp new file mode 100644 index 0000000..217b87a --- /dev/null +++ b/src/core/port_scanner.hpp @@ -0,0 +1,37 @@ +#pragma once +#include "aliases.hpp" +#include +#include +#include +#include + +class PortScanner +{ + public: + PortScanner(const std::string &ip, const std::vector &ports); + + void setTimeoutMs(int ms); + + void run(); + + private: + bool probe(uint16_t port); + TcpEndpoint endpoint(uint16_t port) const; + + 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); + + private: + IoContext io_; + IpAddress addr_; + std::vector ports_; + + int timeout_ms_{300}; + bool ip_ok_{false}; + bool ok_{false}; + bool done_{false}; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..5189aec --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,38 @@ +#include "cli_base.hpp" +#include "logger.hpp" +#include "port_scanner.hpp" +#include "portscan_cli.hpp" + +#include +#include + +static std::vector allPorts() +{ + std::vector v; + v.reserve(65535); + for (uint32_t p = 1; p <= 65535; ++p) { + v.push_back(static_cast(p)); + } + return v; +} + +int main(int argc, char **argv) +{ + Logger::init(true); + + PortScanCli cli; + cli.parse(argc, argv); + if (cli.isHelp()) { + cli.printHelp(); + return 0; + } + + const std::string ip = cli.getIp(); + auto ports = allPorts(); + + Logger::info("Starting full port scan (1..65535)..."); + PortScanner scanner(ip, ports); + scanner.run(); + Logger::info("Scan completed."); + return 0; +} diff --git a/xmake.lua b/xmake.lua index fc0928b..2a474e2 100644 --- a/xmake.lua +++ b/xmake.lua @@ -2,16 +2,18 @@ set_languages "c++23" add_rules("plugin.compile_commands.autoupdate", { outputdir = "." }) -add_requires("boost", { - system = true, -- sudo pacman -S boost - +add_requires("cmake::Boost", { + alias = "boost", + system = true, configs = { - all = false, - system = true, -- asio + components = { "program_options", "log" }, }, }) -target "app" -set_kind "binary" -add_files "src/*.cpp" -add_links "boost_system" +target "port_scanner" +do + set_kind "binary" + add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp") + add_includedirs("src", "src/cli", "src/common", "src/core", { public = true }) + add_packages "boost" +end