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