refactor(port_scanner): rework to use Boost.Asio for asynchronous port scanning
Rewritten to use Boost.Asio for asynchronous scanning, introducing timeout management via timers and concurrent port probing up to 256. Added openPorts() method to retrieve results.
This commit is contained in:
+86
-102
@@ -1,142 +1,126 @@
|
|||||||
#include "port_scanner.hpp"
|
#include "port_scanner.hpp"
|
||||||
#include "logger.hpp"
|
#include "logger.hpp"
|
||||||
#include <boost/asio.hpp>
|
#include <algorithm>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
PortScanner::PortScanner(
|
PortScanner::PortScanner(std::string ip, int timeoutMs)
|
||||||
const std::string &ip, const std::vector<uint16_t> &ports
|
: ip_(std::move(ip))
|
||||||
)
|
, timeoutMs_(timeoutMs)
|
||||||
: ports_(ports)
|
|
||||||
{
|
{
|
||||||
ErrorCode ec;
|
if (timeoutMs_ <= 0) {
|
||||||
addr_ = boost::asio::ip::make_address(ip, ec);
|
timeoutMs_ = 250;
|
||||||
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)
|
PortScanner::Probe::Probe(IoContext &io, uint16_t p)
|
||||||
|
: port(p)
|
||||||
|
, socket(io)
|
||||||
|
, timer(io)
|
||||||
{
|
{
|
||||||
if (ms > 0) {
|
|
||||||
timeout_ms_ = ms;
|
|
||||||
Logger::debug(
|
|
||||||
std::string("PortScanner: timeout set to ")
|
|
||||||
+ std::to_string(timeout_ms_) + " ms"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::vector<uint16_t> &PortScanner::openPorts() const
|
||||||
|
{
|
||||||
|
return openPorts_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortScanner::run()
|
void PortScanner::run()
|
||||||
{
|
{
|
||||||
if (!ip_ok_) {
|
openPorts_.clear();
|
||||||
Logger::error("PortScanner::run - aborting: ip not ok");
|
nextPort_ = kFirstPort;
|
||||||
|
outstanding_ = 0;
|
||||||
|
active_.clear();
|
||||||
|
|
||||||
|
while (outstanding_ < kMaxConcurrency && nextPort_ <= kLastPort) {
|
||||||
|
startNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
io_.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PortScanner::startNext()
|
||||||
|
{
|
||||||
|
if (nextPort_ > kLastPort) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (uint16_t p : ports_) {
|
|
||||||
if (probe(p)) {
|
const uint16_t port = static_cast<uint16_t>(nextPort_);
|
||||||
Logger::info(std::to_string(p));
|
nextPort_ += 1;
|
||||||
}
|
|
||||||
}
|
startProbe(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PortScanner::probe(uint16_t port)
|
void PortScanner::startProbe(uint16_t port)
|
||||||
{
|
{
|
||||||
TcpSocket s(io_);
|
// Lifetime of Probe is managed in 'active_' until probe finishes.
|
||||||
SteadyTimer t(io_);
|
auto prPtr = std::make_unique<Probe>(io_, port);
|
||||||
auto ep = endpoint(port);
|
Probe *pr = prPtr.get();
|
||||||
|
active_.push_back(std::move(prPtr));
|
||||||
|
|
||||||
Logger::debug(
|
outstanding_ += 1;
|
||||||
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);
|
using namespace boost::asio::ip;
|
||||||
}
|
|
||||||
|
|
||||||
void PortScanner::armTimer(SteadyTimer &t, TcpSocket &s)
|
TcpEndpoint ep(make_address_v4(ip_), port);
|
||||||
{
|
|
||||||
t.expires_after(std::chrono::milliseconds(timeout_ms_));
|
|
||||||
|
|
||||||
auto onTimeout = [&s](const ErrorCode &ec) {
|
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) {
|
if (!ec) {
|
||||||
Logger::debug("Timer expired -> cancelling socket");
|
pr->socket.cancel();
|
||||||
s.cancel();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
t.async_wait(onTimeout);
|
auto onAsyncConnect = [this, pr](const ErrorCode &ec) {
|
||||||
}
|
onConnect(pr, ec);
|
||||||
|
|
||||||
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);
|
pr->timer.async_wait(onTimer);
|
||||||
}
|
pr->socket.async_connect(ep, onAsyncConnect);
|
||||||
|
|
||||||
void PortScanner::runUntilDone()
|
|
||||||
{
|
|
||||||
io_.restart();
|
|
||||||
while (!done_ && io_.run_one()) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortScanner::cancelTimer(SteadyTimer &t)
|
void PortScanner::onConnect(Probe *pr, const ErrorCode &ec)
|
||||||
{
|
{
|
||||||
ErrorCode ec;
|
pr->timer.cancel();
|
||||||
t.cancel();
|
|
||||||
if (ec) {
|
// Open if connect succeeded; REFUSED means closed but host is alive.
|
||||||
Logger::warn(std::string("cancelTimer: ") + ec.message());
|
if (!ec) {
|
||||||
}
|
openPorts_.push_back(pr->port);
|
||||||
|
Logger::info(std::string("Open port: ") + std::to_string(pr->port));
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortScanner::drainLeftovers()
|
ErrorCode ignored;
|
||||||
{
|
pr->socket.close(ignored);
|
||||||
io_.restart();
|
|
||||||
while (io_.run_one()) {
|
finishProbe(pr);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PortScanner::close(TcpSocket &s)
|
void PortScanner::finishProbe(Probe *pr)
|
||||||
{
|
{
|
||||||
ErrorCode ig;
|
if (outstanding_ > 0) {
|
||||||
if (s.close(ig)) {
|
outstanding_ -= 1;
|
||||||
Logger::warn(std::string("close socket failed: ") + ig.message());
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+29
-23
@@ -1,37 +1,43 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "aliases.hpp"
|
#include "aliases.hpp"
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class PortScanner
|
class PortScanner
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PortScanner(const std::string &ip, const std::vector<uint16_t> &ports);
|
explicit PortScanner(std::string ip, int timeoutMs = 250);
|
||||||
|
|
||||||
void setTimeoutMs(int ms);
|
void run(); // blocking scan
|
||||||
|
const std::vector<uint16_t> &openPorts() const;
|
||||||
void run();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool probe(uint16_t port);
|
struct Probe {
|
||||||
TcpEndpoint endpoint(uint16_t port) const;
|
uint16_t port{0};
|
||||||
|
TcpSocket socket;
|
||||||
|
SteadyTimer timer;
|
||||||
|
|
||||||
void armTimer(SteadyTimer &t, TcpSocket &s);
|
Probe(IoContext &io, uint16_t p);
|
||||||
void armConnect(TcpSocket &s, const TcpEndpoint &ep);
|
};
|
||||||
void runUntilDone();
|
|
||||||
void cancelTimer(SteadyTimer &t);
|
void startNext();
|
||||||
void drainLeftovers();
|
void startProbe(uint16_t port);
|
||||||
void close(TcpSocket &s);
|
void onConnect(Probe *pr, const ErrorCode &ec);
|
||||||
|
void finishProbe(Probe *pr);
|
||||||
private:
|
|
||||||
IoContext io_;
|
// Fixed settings
|
||||||
IpAddress addr_;
|
static constexpr uint16_t kFirstPort = 1;
|
||||||
std::vector<uint16_t> ports_;
|
static constexpr uint16_t kLastPort = 65535;
|
||||||
|
static constexpr std::size_t kMaxConcurrency = 256;
|
||||||
int timeout_ms_{300};
|
|
||||||
bool ip_ok_{false};
|
// State
|
||||||
bool ok_{false};
|
std::string ip_;
|
||||||
bool done_{false};
|
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_;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user