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:
@@ -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>();
|
||||
}
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user