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.
This commit is contained in:
Ваше Имя
2025-10-06 00:05:11 +04:00
parent 0d96c21199
commit c9e1b0a149
6 changed files with 273 additions and 9 deletions
+26
View File
@@ -0,0 +1,26 @@
#include "portscan_cli.hpp"
#include <boost/program_options/value_semantic.hpp>
#include <cassert>
PortScanCli::PortScanCli()
: CliBase("Port Scanner Options")
, ip_("")
{
}
void PortScanCli::setupOptions()
{
// clang-format off
desc_.add_options()
("help,h", "Show help")
("ip,H", withDefault(ip_, "<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<std::string>();
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
class PortScanCli : public CliBase, protected OptionUtils
{
public:
PortScanCli();
std::string getIp() const;
protected:
void setupOptions() override;
private:
std::string ip_;
};
+142
View File
@@ -0,0 +1,142 @@
#include "port_scanner.hpp"
#include "logger.hpp"
#include <boost/asio.hpp>
#include <string>
PortScanner::PortScanner(
const std::string &ip, const std::vector<uint16_t> &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());
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "aliases.hpp"
#include <boost/asio.hpp>
#include <cstdint>
#include <string>
#include <vector>
class PortScanner
{
public:
PortScanner(const std::string &ip, const std::vector<uint16_t> &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<uint16_t> ports_;
int timeout_ms_{300};
bool ip_ok_{false};
bool ok_{false};
bool done_{false};
};
+38
View File
@@ -0,0 +1,38 @@
#include "cli_base.hpp"
#include "logger.hpp"
#include "port_scanner.hpp"
#include "portscan_cli.hpp"
#include <string>
#include <vector>
static std::vector<uint16_t> allPorts()
{
std::vector<uint16_t> v;
v.reserve(65535);
for (uint32_t p = 1; p <= 65535; ++p) {
v.push_back(static_cast<uint16_t>(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;
}
+11 -9
View File
@@ -2,16 +2,18 @@ set_languages "c++23"
add_rules("plugin.compile_commands.autoupdate", { outputdir = "." }) add_rules("plugin.compile_commands.autoupdate", { outputdir = "." })
add_requires("boost", { add_requires("cmake::Boost", {
system = true, -- sudo pacman -S boost alias = "boost",
system = true,
configs = { configs = {
all = false, components = { "program_options", "log" },
system = true, -- asio
}, },
}) })
target "app" target "port_scanner"
set_kind "binary" do
add_files "src/*.cpp" set_kind "binary"
add_links "boost_system" 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