6 Commits

Author SHA1 Message Date
Ваше Имя 9ccabcd1eb refactor(common): reorganize type aliases and add application-specific types
Reorganize type aliases into dedicated directories and add application-specific
type definitions for chunk index, file size, frame length, and IDs. Improves
code clarity and type safety.
2025-10-09 23:12:25 +04:00
Ваше Имя aeeb22d351 refactor(aliases): replace std_cstdint with ByteBuffer alias 2025-10-09 15:23:38 +04:00
Ваше Имя a4f103d0c8 feat(boost_asio): add TcpResolver alias for Boost.Asio TCP resolver 2025-10-09 14:38:41 +04:00
Ваше Имя bb99d8d812 feat(aliases): add type aliases for fixed-width integers 2025-10-09 14:35:09 +04:00
Ваше Имя 1e6fecb7bd refactor(aliases): add Boost type aliases 2025-10-09 14:25:54 +04:00
Ваше Имя 88520915dc feat(utils/logger): add logging utility with console and file output 2025-10-08 20:24:17 +04:00
25 changed files with 78 additions and 545 deletions
-1
View File
@@ -2,4 +2,3 @@
.xmake .xmake
build build
compile_commands.json compile_commands.json
ignore
-28
View File
@@ -1,28 +0,0 @@
#include "portscan_cli.hpp"
#include <boost/program_options/value_semantic.hpp>
#include <cassert>
PortScanCli::PortScanCli()
: CliBase("Port Scanner Options")
{
}
void PortScanCli::setupOptions()
{
// clang-format off
desc_.add_options()
("help,h", "Show help")
("ip,H", withDefault(ip_, "<IPv4>"), "Base IPv4 inside your LAN (e.g. 10.0.1.42)")
("prefix,p", withDefault(prefix_, "<0..32>"), "CIDR prefix length (e.g. 24)");
// clang-format on
}
int PortScanCli::getPrefix() const
{
return vm_.at("prefix").as<int>();
}
std::string PortScanCli::getBaseIp() const
{
return vm_.at("ip").as<std::string>();
}
-21
View File
@@ -1,21 +0,0 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
class PortScanCli : public CliBase, protected OptionUtils
{
public:
PortScanCli();
int getPrefix() const;
std::string getBaseIp() const;
protected:
void setupOptions() override;
private:
int prefix_{24};
std::string ip_{"10.0.1.0"};
};
-29
View File
@@ -1,29 +0,0 @@
#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
@@ -1,34 +0,0 @@
#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
@@ -1,27 +0,0 @@
#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_;
};
-20
View File
@@ -1,20 +0,0 @@
#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
@@ -1,13 +0,0 @@
#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
@@ -0,0 +1,7 @@
#pragma once
#include <cstdint>
#include <vector>
// Alias for raw byte buffer
using ByteBuffer = std::vector<std::uint8_t>;
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
// Sequential file chunk index
using ChunkIndex = std::uint32_t;
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
// File total size in bytes
using FileSize = std::uint64_t;
+6
View File
@@ -0,0 +1,6 @@
#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
@@ -0,0 +1,6 @@
#pragma once
#include <cstdint>
// Client, group, or message ID
using Id = std::uint64_t;
+6
View File
@@ -0,0 +1,6 @@
#pragma once
#include <boost/asio/io_context.hpp>
// IO Context
using IoContext = boost::asio::io_context;
@@ -0,0 +1,9 @@
#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;
@@ -0,0 +1,6 @@
#pragma once
#include <boost/asio/steady_timer.hpp>
// Timers
using SteadyTimer = boost::asio::steady_timer;
@@ -0,0 +1,6 @@
#pragma once
#include <boost/system/error_code.hpp>
// Error handling
using ErrorCode = boost::system::error_code;
-103
View File
@@ -1,103 +0,0 @@
#include "local_ping_scanner.hpp"
#include "logger.hpp"
#include <Poco/Net/ICMPClient.h>
#include <boost/asio/ip/address_v4.hpp>
#include <stdexcept>
LocalPingScanner::LocalPingScanner(
const std::string &baseIp, int prefixLen, int timeoutMs
)
: baseIp_(baseIp)
, prefixLen_(prefixLen)
, timeoutMs_(timeoutMs)
{
if (prefixLen_ < 0 || prefixLen_ > 32) {
Logger::error("Invalid prefix length in LocalPingScanner constructor");
throw std::runtime_error("Bad prefix length");
}
if (timeoutMs_ <= 0) {
timeoutMs_ = 2000;
}
}
const std::vector<std::string> &LocalPingScanner::alive() const
{
return alive_;
}
uint32_t LocalPingScanner::ipToU32(const std::string &ip)
{
using namespace boost::asio::ip;
return static_cast<uint32_t>(make_address_v4(ip).to_uint());
}
std::string LocalPingScanner::u32ToIp(uint32_t v)
{
using namespace boost::asio::ip;
return address_v4(v).to_string();
}
bool LocalPingScanner::pingHost(const std::string &ip) const
{
try {
return Poco::Net::ICMPClient::pingIPv4(
ip,
/*repeat=*/1,
/*dataSize=*/48,
/*ttl=*/64,
/*timeout=*/timeoutMs_
)
> 0;
} catch (const std::exception &ex) {
Logger::error(
std::string("Ping failed for host ") + ip + ": " + ex.what()
);
return false;
} catch (...) {
Logger::fatal(std::string("Unknown error pinging host ") + ip);
return false;
}
}
void LocalPingScanner::runImpl()
{
alive_.clear();
const uint32_t base = ipToU32(baseIp_);
const uint32_t mask =
(prefixLen_ == 0) ? 0u : (0xFFFFFFFFu << (32 - prefixLen_));
const uint32_t network = base & mask;
const uint32_t broadcast = network | (~mask);
// No hosts when prefix is too tight (e.g., /32).
if (broadcast - network <= 2) {
return;
}
const std::string selfIp = u32ToIp(base);
for (uint32_t v = network + 1; v < broadcast; ++v) {
const std::string ipStr = u32ToIp(v);
if (ipStr == selfIp) {
continue;
}
if (pingHost(ipStr)) {
alive_.push_back(ipStr);
}
}
}
void LocalPingScanner::run()
{
try {
runImpl();
} catch (const std::exception &ex) {
Logger::error(
std::string("Exception in LocalPingScanner::run: ") + ex.what()
);
} catch (...) {
Logger::fatal("Unknown exception in LocalPingScanner::run");
}
}
-26
View File
@@ -1,26 +0,0 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
class LocalPingScanner
{
public:
LocalPingScanner(
const std::string &baseIp, int prefixLen, int timeoutMs = 2000
);
void run(); // blocking scan over subnet
const std::vector<std::string> &alive() const;
private:
static std::string u32ToIp(uint32_t v);
static uint32_t ipToU32(const std::string &ip);
bool pingHost(const std::string &ip) const;
void runImpl();
std::string baseIp_;
int prefixLen_;
int timeoutMs_;
std::vector<std::string> alive_;
};
-126
View File
@@ -1,126 +0,0 @@
#include "port_scanner.hpp"
#include "logger.hpp"
#include <algorithm>
PortScanner::PortScanner(std::string ip, int timeoutMs)
: ip_(std::move(ip))
, timeoutMs_(timeoutMs)
{
if (timeoutMs_ <= 0) {
timeoutMs_ = 250;
}
}
PortScanner::Probe::Probe(IoContext &io, uint16_t p)
: port(p)
, socket(io)
, timer(io)
{
}
const std::vector<uint16_t> &PortScanner::openPorts() const
{
return openPorts_;
}
void PortScanner::run()
{
openPorts_.clear();
nextPort_ = kFirstPort;
outstanding_ = 0;
active_.clear();
while (outstanding_ < kMaxConcurrency && nextPort_ <= kLastPort) {
startNext();
}
io_.run();
}
void PortScanner::startNext()
{
if (nextPort_ > kLastPort) {
return;
}
const uint16_t port = static_cast<uint16_t>(nextPort_);
nextPort_ += 1;
startProbe(port);
}
void PortScanner::startProbe(uint16_t port)
{
// Lifetime of Probe is managed in 'active_' until probe finishes.
auto prPtr = std::make_unique<Probe>(io_, port);
Probe *pr = prPtr.get();
active_.push_back(std::move(prPtr));
outstanding_ += 1;
{
using namespace boost::asio::ip;
TcpEndpoint ep(make_address_v4(ip_), port);
pr->timer.expires_after(std::chrono::milliseconds(timeoutMs_));
auto onTimer = [pr](const ErrorCode &ec) {
// Cancel socket on timeout; completion will run onConnect with
// ec=operation_aborted.
if (!ec) {
pr->socket.cancel();
}
};
auto onAsyncConnect = [this, pr](const ErrorCode &ec) {
onConnect(pr, ec);
};
pr->timer.async_wait(onTimer);
pr->socket.async_connect(ep, onAsyncConnect);
}
}
void PortScanner::onConnect(Probe *pr, const ErrorCode &ec)
{
pr->timer.cancel();
// Open if connect succeeded; REFUSED means closed but host is alive.
if (!ec) {
openPorts_.push_back(pr->port);
Logger::info(std::string("Open port: ") + std::to_string(pr->port));
}
ErrorCode ignored;
pr->socket.close(ignored);
finishProbe(pr);
}
void PortScanner::finishProbe(Probe *pr)
{
if (outstanding_ > 0) {
outstanding_ -= 1;
}
// Remove 'pr' from active_ (active_ size <= kMaxConcurrency, linear
// erase is fine).
auto it = std::find_if(
active_.begin(), active_.end(), [pr](const std::unique_ptr<Probe> &u) {
return u.get() == pr;
}
);
if (it != active_.end()) {
active_.erase(it);
}
if (nextPort_ <= kLastPort) {
startNext();
}
if (outstanding_ == 0 && nextPort_ > kLastPort) {
io_.stop();
return;
}
}
-43
View File
@@ -1,43 +0,0 @@
#pragma once
#include "aliases.hpp"
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
class PortScanner
{
public:
explicit PortScanner(std::string ip, int timeoutMs = 250);
void run(); // blocking scan
const std::vector<uint16_t> &openPorts() const;
private:
struct Probe {
uint16_t port{0};
TcpSocket socket;
SteadyTimer timer;
Probe(IoContext &io, uint16_t p);
};
void startNext();
void startProbe(uint16_t port);
void onConnect(Probe *pr, const ErrorCode &ec);
void finishProbe(Probe *pr);
// Fixed settings
static constexpr uint16_t kFirstPort = 1;
static constexpr uint16_t kLastPort = 65535;
static constexpr std::size_t kMaxConcurrency = 256;
// State
std::string ip_;
IoContext io_;
std::vector<uint16_t> openPorts_;
std::vector<std::unique_ptr<Probe>> active_; // <= kMaxConcurrency
uint32_t nextPort_{kFirstPort};
std::size_t outstanding_{0};
int timeoutMs_;
};
+11 -56
View File
@@ -1,61 +1,16 @@
#include "local_ping_scanner.hpp" #include <boost/asio.hpp>
#include "logger.hpp" #include <iostream>
#include "port_scanner.hpp"
#include "portscan_cli.hpp"
#include <string>
int main(int argc, char **argv) int main()
{ {
Logger::init(true); std::cout << "Hello, Boost + C++23 + xmake!\n";
PortScanCli cli;
cli.parse(argc, argv);
if (cli.isHelp()) {
cli.printHelp();
return 0;
}
const std::string baseIp = cli.getBaseIp();
const int prefix = cli.getPrefix();
if (prefix < 0 || prefix > 32) {
Logger::fatal("Prefix must be in range 0..32\n");
return 2;
}
LocalPingScanner scanner(baseIp, prefix);
Logger::info(std::format("Scanning subnet: {}/{}", baseIp, prefix));
scanner.run();
const auto &alive = scanner.alive();
if (alive.empty()) {
Logger::info("No alive hosts found.\n");
return 0;
}
std::cout << "Alive hosts:\n";
for (const auto &ip : alive) {
std::cout << " " << ip << "\n";
}
// For each alive host, run full TCP port scan
for (const auto &ip : alive) {
std::cout << "Scanning ports on " << ip << " ...\n";
PortScanner ps(ip);
ps.run();
const auto &open = ps.openPorts();
if (open.empty()) {
std::cout << " No open TCP ports found on " << ip << "\n";
} else {
std::cout << " Open ports on " << ip << ":\n";
for (uint16_t port : open) {
std::cout << " " << port << "\n";
}
}
}
boost::asio::io_context io;
boost::asio::steady_timer timer(io, std::chrono::seconds(1));
timer.async_wait([](const boost::system::error_code &) {
std::cout << "Timer fired after 1 second!\n";
});
io.run();
return 0; return 0;
} }
@@ -1,4 +1,5 @@
#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)
+8 -18
View File
@@ -2,26 +2,16 @@ set_languages "c++23"
add_rules("plugin.compile_commands.autoupdate", { outputdir = "." }) add_rules("plugin.compile_commands.autoupdate", { outputdir = "." })
add_requires("cmake::Boost", { add_requires("boost", {
alias = "boost", system = true, -- sudo pacman -S boost
system = true,
configs = { configs = {
components = { "program_options", "log" }, all = false,
system = true, -- asio
}, },
}) })
add_requires("cmake::Poco", { target "app"
alias = "poco",
system = true,
configs = {
components = { "Net", "Foundation" },
},
})
target "port_scanner"
do
set_kind "binary" set_kind "binary"
add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp") add_files "src/*.cpp"
add_includedirs("src", "src/cli", "src/common", "src/core", { public = true }) add_links "boost_system"
add_packages("boost", "poco")
end