9 Commits

Author SHA1 Message Date
Ваше Имя 526b27deaf chore(aliases): remove SteadyTimer alias 2025-10-07 18:37:31 +04:00
Ваше Имя 7c19123da9 chore(.gitignore): add ignore file 2025-10-07 18:36:54 +04:00
Ваше Имя c9e1b0a149 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.
2025-10-06 00:05:11 +04:00
Ваше Имя 0d96c21199 feat(common): add Boost Asio aliases
Introduce aliases for Asio IP, TCP, UDP, and timer types to enhance code
readability and structure.
2025-10-06 00:04:34 +04:00
Ваше Имя 679d752177 refactor(common): remove session and tcp server base classes 2025-10-06 00:03:10 +04:00
Ваше Имя 9f88390b1a refactor(option_utils): enhance option handling with default values and names
Removed server.hpp as part of refactoring the option utilities to use Boost's
program_options more effectively, allowing for named default values.
2025-10-05 14:20:21 +04:00
Ваше Имя a4c84379ff chore: remove UDP server implementation and main example 2025-10-04 17:37:58 +04:00
Ваше Имя 827cc07175 feat(option_utils): add withDefault utility for Boost program options 2025-10-04 17:09:41 +04:00
Ваше Имя ba91359db3 feat(common): introduce common networking and CLI infrastructure 2025-09-30 18:33:06 +04:00
23 changed files with 391 additions and 78 deletions
+1
View File
@@ -2,3 +2,4 @@
.xmake .xmake
build build
compile_commands.json compile_commands.json
ignore
+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_;
};
+29
View File
@@ -0,0 +1,29 @@
#pragma once
#include <boost/asio.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/program_options.hpp>
#include <boost/system/error_code.hpp>
// --- Error handling ---
using ErrorCode = boost::system::error_code;
// --- Asio types ---
using IoContext = boost::asio::io_context;
using SteadyTimer = boost::asio::steady_timer;
// --- Ip types ---
using IpAddress = boost::asio::ip::address;
// --- TCP types ---
using TcpSocket = boost::asio::ip::tcp::socket;
using TcpAcceptor = boost::asio::ip::tcp::acceptor;
using TcpEndpoint = boost::asio::ip::tcp::endpoint;
// --- UDP types ---
using UdpSocket = boost::asio::ip::udp::socket;
using UdpEndpoint = boost::asio::ip::udp::endpoint;
// --- Program options ---
using OptionsDescription = boost::program_options::options_description;
using VariablesMap = boost::program_options::variables_map;
+34
View File
@@ -0,0 +1,34 @@
#include "cli_base.hpp"
#include <boost/program_options/parsers.hpp>
#include <iostream>
CliBase::CliBase(const std::string &title)
: desc_(title)
, help_(false)
{
}
void CliBase::parse(int argc, char *argv[])
{
using namespace boost::program_options;
setupOptions(); // must be first
parsed_options parsed = parse_command_line(argc, argv, desc_);
store(parsed, vm_);
notify(vm_);
if (vm_.count("help")) {
help_ = true;
}
}
bool CliBase::isHelp() const
{
return help_;
}
void CliBase::printHelp() const
{
std::cout << desc_ << "\n";
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include <boost/program_options/options_description.hpp>
#include <boost/program_options/variables_map.hpp>
#include <string>
class CliBase
{
public:
CliBase(const std::string &title);
virtual ~CliBase() = default;
// parse argc/argv
void parse(int argc, char *argv[]);
bool isHelp() const;
void printHelp() const;
protected:
// children add their specific options here
virtual void setupOptions() = 0;
boost::program_options::options_description desc_;
boost::program_options::variables_map vm_;
bool help_;
};
@@ -1,5 +1,4 @@
#include "logger.hpp" #include "logger.hpp"
#include <boost/date_time/posix_time/posix_time_io.hpp> #include <boost/date_time/posix_time/posix_time_io.hpp>
void Logger::init(bool toConsole, const std::string_view filePath) void Logger::init(bool toConsole, const std::string_view filePath)
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#include <boost/program_options.hpp>
class OptionUtils
{
public:
template <typename T>
using OptionValue = boost::program_options::typed_value<T> *;
template <typename T>
static OptionValue<T>
withDefault(const T &value, const char *name = nullptr);
protected:
OptionUtils() = default;
~OptionUtils() = default;
};
#include "option_utils.tpp"
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "option_utils.hpp"
template <typename T>
OptionUtils::OptionValue<T>
OptionUtils::withDefault(const T &value, const char *name)
{
auto v = boost::program_options::value<T>()->default_value(value);
if (name) {
v->value_name(name); // Optional
}
return v;
}
-7
View File
@@ -1,7 +0,0 @@
#pragma once
#include <cstdint>
#include <vector>
// Alias for raw byte buffer
using ByteBuffer = std::vector<std::uint8_t>;
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <cstdint>
// Sequential file chunk index
using ChunkIndex = std::uint32_t;
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <cstdint>
// File total size in bytes
using FileSize = std::uint64_t;
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <cstdint>
// Wire length prefix type for a single framed message (big-endian on the wire)
using FrameLength = std::uint32_t;
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <cstdint>
// Client, group, or message ID
using Id = std::uint64_t;
-6
View File
@@ -1,6 +0,0 @@
#pragma once
#include <boost/asio/io_context.hpp>
// IO Context
using IoContext = boost::asio::io_context;
@@ -1,9 +0,0 @@
#pragma once
#include <boost/asio/ip/tcp.hpp>
// TCP
using TcpSocket = boost::asio::ip::tcp::socket;
using TcpAcceptor = boost::asio::ip::tcp::acceptor;
using TcpEndpoint = boost::asio::ip::tcp::endpoint;
using TcpResolver = boost::asio::ip::tcp::resolver;
@@ -1,6 +0,0 @@
#pragma once
#include <boost/asio/steady_timer.hpp>
// Timers
using SteadyTimer = boost::asio::steady_timer;
@@ -1,6 +0,0 @@
#pragma once
#include <boost/system/error_code.hpp>
// Error handling
using ErrorCode = boost::system::error_code;
+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};
};
+32 -10
View File
@@ -1,16 +1,38 @@
#include <boost/asio.hpp> #include "cli_base.hpp"
#include <iostream> #include "logger.hpp"
#include "port_scanner.hpp"
#include "portscan_cli.hpp"
int main() #include <string>
#include <vector>
static std::vector<uint16_t> allPorts()
{ {
std::cout << "Hello, Boost + C++23 + xmake!\n"; 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;
}
boost::asio::io_context io; int main(int argc, char **argv)
boost::asio::steady_timer timer(io, std::chrono::seconds(1)); {
timer.async_wait([](const boost::system::error_code &) { Logger::init(true);
std::cout << "Timer fired after 1 second!\n";
});
io.run(); 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; 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