4 Commits

Author SHA1 Message Date
Ваше Имя 7af9a49eef feat(packet_sniffer): replace port scanner with packet sniffer 2025-10-08 18:30:12 +04:00
Ваше Имя 4f325fead2 feat(sniffer): implement packet sniffer with IP masking and protocol logging
Captures network packets, extracts source/destination IPs, protocol type, and
ports. Masks IPs when enabled and logs details with protocol information.
2025-10-08 18:28:47 +04:00
Ваше Имя 5ac2995b2d feat(packet-sniffer): add CLI options for IP filtering and masking 2025-10-08 18:27:02 +04:00
Ваше Имя 8016d570b8 refactor: remove port scanner functionality 2025-10-08 18:22:54 +04:00
9 changed files with 223 additions and 241 deletions
+30
View File
@@ -0,0 +1,30 @@
#include "packet_sniffer_cli.hpp"
#include <boost/program_options/value_semantic.hpp>
PacketSnifferCli::PacketSnifferCli()
: CliBase("Packet Sniffer Options")
, ip_("")
{
}
void PacketSnifferCli::setupOptions()
{
// clang-format off
desc_.add_options()
("help,h", "Show help")
("ip,H", withDefault(ip_, "<IP>"), "Local IPv4 to sniff (e.g. 192.168.1.42)")
("mask,m", boost::program_options::value<bool>()->default_value(false)->implicit_value(true),
"Mask IP addresses in output (demo)");
// clang-format on
}
std::string PacketSnifferCli::getIp() const
{
return vm_.at("ip").as<std::string>();
}
bool PacketSnifferCli::isMaskEnabled() const
{
const auto it = vm_.find("mask");
return (it != vm_.end()) ? it->second.as<bool>() : false;
}
@@ -1,15 +1,15 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
class PortScanCli : public CliBase, protected OptionUtils
class PacketSnifferCli : public CliBase, protected OptionUtils
{
public:
PortScanCli();
PacketSnifferCli();
std::string getIp() const;
bool isMaskEnabled() const;
protected:
void setupOptions() override;
-26
View File
@@ -1,26 +0,0 @@
#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>();
}
-142
View File
@@ -1,142 +0,0 @@
#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
@@ -1,37 +0,0 @@
#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};
};
+135
View File
@@ -0,0 +1,135 @@
#include "sniffer.hpp"
#include "logger.hpp"
#include <tins/tins.h>
Sniffer::Sniffer(std::string localIp, bool maskIp)
: ip_(std::move(localIp))
, mask_(maskIp)
{
}
int Sniffer::run()
{
const auto ifaceName = resolveIfaceNameByIp(ip_);
Tins::SnifferConfiguration cfg;
cfg.set_promisc_mode(true);
cfg.set_immediate_mode(true);
Tins::Sniffer sniffer(ifaceName, cfg);
auto running = [this] { return !stop_.load(std::memory_order_relaxed); };
auto handler = [&](Tins::PDU &pdu) -> bool {
if (!running())
return false;
std::string src, dst;
const char *type = "OTHER";
uint16_t sport = 0, dport = 0;
if (auto *arp = pdu.find_pdu<Tins::ARP>()) {
src = arp->sender_ip_addr().to_string();
dst = arp->target_ip_addr().to_string();
type = "ARP";
} else if (auto *ip4 = pdu.find_pdu<Tins::IP>()) {
src = ip4->src_addr().to_string();
dst = ip4->dst_addr().to_string();
if (auto *tcp = pdu.find_pdu<Tins::TCP>()) {
type = "TCP";
sport = tcp->sport();
dport = tcp->dport();
} else if (auto *udp = pdu.find_pdu<Tins::UDP>()) {
type = "UDP";
sport = udp->sport();
dport = udp->dport();
} else if (pdu.find_pdu<Tins::ICMP>()) {
type = "ICMP";
} else {
type = "IP";
}
} else if (auto *ip6 = pdu.find_pdu<Tins::IPv6>()) {
src = ip6->src_addr().to_string();
dst = ip6->dst_addr().to_string();
if (auto *tcp = pdu.find_pdu<Tins::TCP>()) {
type = "TCP";
sport = tcp->sport();
dport = tcp->dport();
} else if (auto *udp = pdu.find_pdu<Tins::UDP>()) {
type = "UDP";
sport = udp->sport();
dport = udp->dport();
} else if (pdu.find_pdu<Tins::ICMPv6>()) {
type = "ICMPv6";
} else {
type = "IPv6";
}
} else {
Logger::info("(unknown src) -> (unknown dst) [OTHER]");
return running();
}
if (mask_) {
src = hideHalf(src);
dst = hideHalf(dst);
}
printLine(src, dst, type, sport, dport);
return running();
};
sniffer.sniff_loop(handler);
return 0;
}
void Sniffer::stop()
{
stop_.store(true, std::memory_order_relaxed);
}
std::string Sniffer::hideHalf(const std::string &s) const
{
if (s.empty()) {
return s;
}
const size_t n = s.size();
const size_t hide = n / 2;
const size_t keep = n - hide;
std::string out = s.substr(0, keep);
out.append(hide, '*');
return out;
}
std::string Sniffer::resolveIfaceNameByIp(const std::string &ip)
{
const Tins::IPv4Address want(ip);
for (const auto &iface : Tins::NetworkInterface::all()) {
const auto info = iface.addresses(); // .ip_addr/.netmask/...
if (info.ip_addr == want) {
return iface.name();
}
}
throw std::runtime_error("Interface with IP " + ip + " not found");
}
void Sniffer::printLine(
const std::string &src,
const std::string &dst,
const char *type,
uint16_t sport,
uint16_t dport
)
{
if (sport || dport) {
Logger::info(
std::string(src) + ":" + std::to_string(sport) + " -> " + dst + ":"
+ std::to_string(dport) + " [" + type + "]"
);
} else {
Logger::info(std::string(src) + " -> " + dst + " [" + type + "]");
}
}
+32
View File
@@ -0,0 +1,32 @@
#pragma once
#include <atomic>
#include <cstdint>
#include <string>
class Sniffer
{
public:
explicit Sniffer(std::string localIp, bool maskIp = false);
Sniffer(const Sniffer &) = delete;
Sniffer &operator=(const Sniffer &) = delete;
std::string hideHalf(const std::string &s) const;
int run();
void stop();
private:
static std::string resolveIfaceNameByIp(const std::string &ip);
static void printLine(
const std::string &src,
const std::string &dst,
const char *type,
uint16_t sport = 0,
uint16_t dport = 0
);
std::string ip_;
std::atomic<bool> stop_{false};
bool mask_{false};
};
+19 -31
View File
@@ -1,38 +1,26 @@
#include "cli_base.hpp"
#include "logger.hpp"
#include "port_scanner.hpp"
#include "portscan_cli.hpp"
#include "packet_sniffer_cli.hpp"
#include "sniffer.hpp"
#include <exception>
#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)
int main(int argc, char *argv[])
{
Logger::init(true);
try {
PacketSnifferCli cli;
cli.parse(argc, argv);
if (cli.isHelp()) {
cli.printHelp();
return 0;
}
PortScanCli cli;
cli.parse(argc, argv);
if (cli.isHelp()) {
cli.printHelp();
return 0;
Sniffer sniffer(cli.getIp(), cli.isMaskEnabled());
return sniffer.run();
} catch (const std::exception &ex) {
Logger::fatal(ex.what());
return 1;
} catch (...) {
Logger::fatal("unknown error");
return 1;
}
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;
}
+4 -2
View File
@@ -10,10 +10,12 @@ add_requires("cmake::Boost", {
},
})
target "port_scanner"
add_requires("cmake::libtins", { alias = "libtins", system = true })
target "packet_sniffer"
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"
add_packages("boost", "libtins")
end