Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20640e766e | |||
| 75563091c1 |
@@ -2,4 +2,3 @@
|
|||||||
.xmake
|
.xmake
|
||||||
build
|
build
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
ignore
|
|
||||||
|
|||||||
@@ -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>();
|
|
||||||
}
|
|
||||||
@@ -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"};
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#include "echo_client.hpp"
|
||||||
|
#include <boost/asio/connect.hpp>
|
||||||
|
|
||||||
|
EchoClient::EchoClient(IoContext &io, std::string host, unsigned short port)
|
||||||
|
: io_(io)
|
||||||
|
, host_(std::move(host))
|
||||||
|
, port_(port)
|
||||||
|
, socket_(io_)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EchoClient::run(const std::string &message)
|
||||||
|
{
|
||||||
|
if (!connect()) {
|
||||||
|
Logger::error("EchoClient: connect failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const bool ok = send(message);
|
||||||
|
close();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EchoClient::connect()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
boost::asio::ip::tcp::resolver resolver(io_);
|
||||||
|
auto results = resolver.resolve(host_, std::to_string(port_));
|
||||||
|
boost::asio::connect(socket_, results);
|
||||||
|
Logger::info(
|
||||||
|
"EchoClient: connected to " + host_ + ":" + std::to_string(port_)
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
Logger::error(
|
||||||
|
std::string("EchoClient: connect exception: ") + ex.what()
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EchoClient::send(const std::string &message)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
auto bytes = boost::asio::write(socket_, boost::asio::buffer(message));
|
||||||
|
Logger::info("EchoClient: sent " + std::to_string(bytes) + " bytes");
|
||||||
|
return true;
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
Logger::error(std::string("EchoClient: send exception: ") + ex.what());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EchoClient::close()
|
||||||
|
{
|
||||||
|
ErrorCode ec;
|
||||||
|
socket_.shutdown(TcpSocket::shutdown_both, ec);
|
||||||
|
socket_.close(ec);
|
||||||
|
Logger::info("EchoClient: connection closed");
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "aliases.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class EchoClient final
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EchoClient(IoContext &io, std::string host, unsigned short port);
|
||||||
|
|
||||||
|
bool run(const std::string &message);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool connect();
|
||||||
|
bool send(const std::string &message);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
private:
|
||||||
|
IoContext &io_;
|
||||||
|
std::string host_;
|
||||||
|
unsigned short port_;
|
||||||
|
TcpSocket socket_{io_};
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
#include "echo_client_cli.hpp"
|
||||||
|
#include <boost/program_options/value_semantic.hpp>
|
||||||
|
|
||||||
|
EchoClientCli::EchoClientCli()
|
||||||
|
: CliBase("Echo Client Options")
|
||||||
|
, host_("127.0.0.1")
|
||||||
|
, port_(9000)
|
||||||
|
, message_("Hello from client")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EchoClientCli::setupOptions()
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
desc_.add_options()
|
||||||
|
("help,h", "Show help")
|
||||||
|
("host,H", withDefault(host_, "<HOST>"), "Server host (IP or DNS)")
|
||||||
|
("port,p", withDefault(port_, "<PORT>"), "Server port")
|
||||||
|
("message,m", withDefault(message_, "<TEXT>"), "Message to send");
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string EchoClientCli::getHost() const
|
||||||
|
{
|
||||||
|
return vm_.at("host").as<std::string>();
|
||||||
|
}
|
||||||
|
int EchoClientCli::getPort() const
|
||||||
|
{
|
||||||
|
return vm_.at("port").as<int>();
|
||||||
|
}
|
||||||
|
std::string EchoClientCli::getMessage() const
|
||||||
|
{
|
||||||
|
return vm_.at("message").as<std::string>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cli_base.hpp"
|
||||||
|
#include "option_utils.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class EchoClientCli : public CliBase, protected OptionUtils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EchoClientCli();
|
||||||
|
|
||||||
|
std::string getHost() const;
|
||||||
|
int getPort() const;
|
||||||
|
std::string getMessage() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setupOptions() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// defaults (used only to show defaults in help via withDefault)
|
||||||
|
std::string host_;
|
||||||
|
int port_;
|
||||||
|
std::string message_;
|
||||||
|
};
|
||||||
@@ -1,29 +1,27 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <boost/asio/ip/address.hpp>
|
|
||||||
#include <boost/program_options.hpp>
|
#include <boost/program_options.hpp>
|
||||||
#include <boost/system/error_code.hpp>
|
#include <boost/system/error_code.hpp>
|
||||||
|
|
||||||
// --- Error handling ---
|
// --- Error handling ---
|
||||||
using ErrorCode = boost::system::error_code;
|
using ErrorCode = boost::system::error_code;
|
||||||
|
|
||||||
// --- Asio types ---
|
// --- IO Context ---
|
||||||
using IoContext = boost::asio::io_context;
|
using IoContext = boost::asio::io_context;
|
||||||
using SteadyTimer = boost::asio::steady_timer;
|
|
||||||
|
|
||||||
// --- Ip types ---
|
// --- TCP ---
|
||||||
using IpAddress = boost::asio::ip::address;
|
|
||||||
|
|
||||||
// --- TCP types ---
|
|
||||||
using TcpSocket = boost::asio::ip::tcp::socket;
|
using TcpSocket = boost::asio::ip::tcp::socket;
|
||||||
using TcpAcceptor = boost::asio::ip::tcp::acceptor;
|
using TcpAcceptor = boost::asio::ip::tcp::acceptor;
|
||||||
using TcpEndpoint = boost::asio::ip::tcp::endpoint;
|
using TcpEndpoint = boost::asio::ip::tcp::endpoint;
|
||||||
|
|
||||||
// --- UDP types ---
|
// --- UDP (для DNS) ---
|
||||||
using UdpSocket = boost::asio::ip::udp::socket;
|
using UdpSocket = boost::asio::ip::udp::socket;
|
||||||
using UdpEndpoint = boost::asio::ip::udp::endpoint;
|
using UdpEndpoint = boost::asio::ip::udp::endpoint;
|
||||||
|
|
||||||
|
// --- Таймеры ---
|
||||||
|
using SteadyTimer = boost::asio::steady_timer;
|
||||||
|
|
||||||
// --- Program options ---
|
// --- Program options ---
|
||||||
using OptionsDescription = boost::program_options::options_description;
|
using OptionsDescription = boost::program_options::options_description;
|
||||||
using VariablesMap = boost::program_options::variables_map;
|
using VariablesMap = boost::program_options::variables_map;
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
#include "session_base.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include <boost/asio/write.hpp>
|
||||||
|
|
||||||
|
SessionBase::SessionBase(TcpSocket socket)
|
||||||
|
: socket_(std::move(socket))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionBase::start()
|
||||||
|
{
|
||||||
|
doRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionBase::doRead()
|
||||||
|
{
|
||||||
|
using namespace boost::asio;
|
||||||
|
|
||||||
|
auto self = shared_from_this();
|
||||||
|
|
||||||
|
auto handler = [this, self](ErrorCode ec, std::size_t bytesTransferred) {
|
||||||
|
onRead(ec, bytesTransferred);
|
||||||
|
};
|
||||||
|
|
||||||
|
socket_.async_read_some(buffer(buffer_), handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionBase::onRead(ErrorCode ec, std::size_t bytesTransferred)
|
||||||
|
{
|
||||||
|
if (ec) {
|
||||||
|
Logger::error("Read error: " + ec.message());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string rawRequest(buffer_.data(), bytesTransferred);
|
||||||
|
Logger::info("Received request:\n" + rawRequest);
|
||||||
|
|
||||||
|
try {
|
||||||
|
handleRequest(rawRequest);
|
||||||
|
} catch (const std::exception &ex) {
|
||||||
|
Logger::error(std::string("Request handling failed: ") + ex.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SessionBase::doWrite(const std::string &response)
|
||||||
|
{
|
||||||
|
using namespace boost::asio;
|
||||||
|
|
||||||
|
auto self = shared_from_this();
|
||||||
|
auto onWriteHandler =
|
||||||
|
[this, self](ErrorCode ec, std::size_t /*bytesTransferred*/) {
|
||||||
|
if (ec) {
|
||||||
|
Logger::error("Write error: " + ec.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode shutdownEc;
|
||||||
|
if (socket_.shutdown(TcpSocket::shutdown_both, shutdownEc)) {
|
||||||
|
Logger::warn("Shutdown error: " + shutdownEc.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode closeEc;
|
||||||
|
if (socket_.close(closeEc)) {
|
||||||
|
Logger::warn("Close error: " + closeEc.message());
|
||||||
|
} else {
|
||||||
|
Logger::info("Connection closed");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async_write(socket_, buffer(response), onWriteHandler);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "aliases.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class SessionBase : public std::enable_shared_from_this<SessionBase>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SessionBase(TcpSocket socket);
|
||||||
|
virtual ~SessionBase() = default;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void handleRequest(const std::string_view rawRequest) = 0;
|
||||||
|
|
||||||
|
void doRead();
|
||||||
|
void onRead(ErrorCode ec, std::size_t bytesTransferred);
|
||||||
|
void doWrite(const std::string &response);
|
||||||
|
|
||||||
|
TcpSocket socket_;
|
||||||
|
std::array<char, 8192> buffer_;
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#include "tcp_server_base.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
TcpServerBase::TcpServerBase(IoContext &io, unsigned short port)
|
||||||
|
: acceptor_(io, TcpEndpoint(boost::asio::ip::tcp::v4(), port))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void TcpServerBase::start()
|
||||||
|
{
|
||||||
|
auto doAccept = [this]() {
|
||||||
|
acceptor_.async_accept([this](ErrorCode ec, TcpSocket socket) {
|
||||||
|
if (!ec) {
|
||||||
|
Logger::info("New client connected");
|
||||||
|
createSession(std::move(socket));
|
||||||
|
} else {
|
||||||
|
Logger::error("Accept error: " + ec.message());
|
||||||
|
}
|
||||||
|
start(); // continue accepting
|
||||||
|
});
|
||||||
|
};
|
||||||
|
doAccept();
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "aliases.hpp"
|
||||||
|
|
||||||
|
class TcpServerBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
TcpServerBase(IoContext &io, unsigned short port);
|
||||||
|
virtual ~TcpServerBase() = default;
|
||||||
|
|
||||||
|
void start();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual void createSession(TcpSocket socket) = 0;
|
||||||
|
|
||||||
|
TcpAcceptor acceptor_;
|
||||||
|
};
|
||||||
@@ -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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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_;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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_;
|
|
||||||
};
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#include "local_ping_scanner.hpp"
|
|
||||||
#include "logger.hpp"
|
|
||||||
#include "port_scanner.hpp"
|
|
||||||
#include "portscan_cli.hpp"
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
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 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";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#include "aliases.hpp"
|
||||||
|
#include "echo_client.hpp"
|
||||||
|
#include "echo_client_cli.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
EchoClientCli cli;
|
||||||
|
cli.parse(argc, argv);
|
||||||
|
if (cli.isHelp()) {
|
||||||
|
cli.printHelp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::init(true);
|
||||||
|
|
||||||
|
IoContext io;
|
||||||
|
const std::string host = cli.getHost();
|
||||||
|
const auto port = static_cast<unsigned short>(cli.getPort());
|
||||||
|
const std::string message = cli.getMessage();
|
||||||
|
|
||||||
|
EchoClient client(io, host, port);
|
||||||
|
const bool ok = client.run(message);
|
||||||
|
|
||||||
|
return ok ? 0 : 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "aliases.hpp"
|
||||||
|
#include "echo_server.hpp"
|
||||||
|
#include "echo_server_cli.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
EchoServerCli cli;
|
||||||
|
cli.parse(argc, argv);
|
||||||
|
if (cli.isHelp()) {
|
||||||
|
cli.printHelp();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger::init(true);
|
||||||
|
|
||||||
|
IoContext io;
|
||||||
|
const auto port = static_cast<unsigned short>(cli.getPort());
|
||||||
|
EchoServer server(io, port);
|
||||||
|
|
||||||
|
Logger::info("EchoServer starting on port " + std::to_string(port));
|
||||||
|
server.start();
|
||||||
|
|
||||||
|
io.run();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#include "echo_server.hpp"
|
||||||
|
#include "echo_session.hpp"
|
||||||
|
|
||||||
|
void EchoServer::createSession(TcpSocket socket)
|
||||||
|
{
|
||||||
|
auto session = std::make_shared<EchoSession>(std::move(socket));
|
||||||
|
session->start();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "tcp_server_base.hpp"
|
||||||
|
|
||||||
|
class EchoServer final : public TcpServerBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EchoServer(IoContext &io, unsigned short port)
|
||||||
|
: TcpServerBase(io, port)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void createSession(TcpSocket socket) override;
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
#include "echo_server_cli.hpp"
|
||||||
|
|
||||||
|
EchoServerCli::EchoServerCli()
|
||||||
|
: CliBase("Echo Server Options")
|
||||||
|
, port_(9000)
|
||||||
|
, address_("0.0.0.0")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EchoServerCli::setupOptions()
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
desc_.add_options()
|
||||||
|
("help,h", "Show help")
|
||||||
|
("port,p", withDefault(port_, "<PORT>"), "Port to listen on")
|
||||||
|
("address,a", withDefault(address_, "<ADDRESS>"), "Bind address (e.g. 0.0.0.0)");
|
||||||
|
// clang-format on
|
||||||
|
}
|
||||||
|
|
||||||
|
int EchoServerCli::getPort() const
|
||||||
|
{
|
||||||
|
return vm_.at("port").as<int>();
|
||||||
|
}
|
||||||
|
std::string EchoServerCli::getAddress() const
|
||||||
|
{
|
||||||
|
return vm_.at("address").as<std::string>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "cli_base.hpp"
|
||||||
|
#include "option_utils.hpp"
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class EchoServerCli : public CliBase, protected OptionUtils
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EchoServerCli();
|
||||||
|
|
||||||
|
int getPort() const;
|
||||||
|
std::string getAddress() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void setupOptions() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
int port_;
|
||||||
|
std::string address_;
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "echo_session.hpp"
|
||||||
|
#include "logger.hpp"
|
||||||
|
#include <utility> // std::move
|
||||||
|
|
||||||
|
EchoSession::EchoSession(TcpSocket socket)
|
||||||
|
: SessionBase(std::move(socket))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void EchoSession::handleRequest(const std::string_view rawRequest)
|
||||||
|
{
|
||||||
|
Logger::info(
|
||||||
|
std::string("EchoSession received: ") + std::string(rawRequest)
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "session_base.hpp"
|
||||||
|
|
||||||
|
class EchoSession final : public SessionBase
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit EchoSession(TcpSocket socket);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void handleRequest(const std::string_view rawRequest) override;
|
||||||
|
};
|
||||||
@@ -10,18 +10,20 @@ add_requires("cmake::Boost", {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
add_requires("cmake::Poco", {
|
-- Echo server target
|
||||||
alias = "poco",
|
target "echo_server"
|
||||||
system = true,
|
|
||||||
configs = {
|
|
||||||
components = { "Net", "Foundation" },
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
target "port_scanner"
|
|
||||||
do
|
do
|
||||||
set_kind "binary"
|
set_kind "binary"
|
||||||
add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp")
|
add_files("src/main/echo_server_main.cpp", "src/server/*.cpp", "src/session/*.cpp", "src/common/*.cpp")
|
||||||
add_includedirs("src", "src/cli", "src/common", "src/core", { public = true })
|
add_includedirs("src", "src/server", "src/session", "src/common", { public = true })
|
||||||
add_packages("boost", "poco")
|
add_packages "boost"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Echo client target
|
||||||
|
target "echo_client"
|
||||||
|
do
|
||||||
|
set_kind "binary"
|
||||||
|
add_files("src/main/echo_client_main.cpp", "src/client/*.cpp", "src/session/*.cpp", "src/common/*.cpp")
|
||||||
|
add_includedirs("src", "src/client", "src/session", "src/common", { public = true })
|
||||||
|
add_packages "boost"
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user