10 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
Ваше Имя 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
15 changed files with 280 additions and 164 deletions
+1
View File
@@ -2,3 +2,4 @@
.xmake .xmake
build build
compile_commands.json compile_commands.json
ignore
+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;
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "cli_base.hpp"
#include "option_utils.hpp"
#include <string>
class PacketSnifferCli : public CliBase, protected OptionUtils
{
public:
PacketSnifferCli();
std::string getIp() const;
bool isMaskEnabled() const;
protected:
void setupOptions() override;
private:
std::string ip_;
};
+8 -6
View File
@@ -1,27 +1,29 @@
#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;
// --- IO Context --- // --- Asio types ---
using IoContext = boost::asio::io_context; using IoContext = boost::asio::io_context;
using SteadyTimer = boost::asio::steady_timer;
// --- TCP --- // --- Ip types ---
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 (для DNS) --- // --- UDP types ---
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;
+8 -2
View File
@@ -4,11 +4,17 @@
class OptionUtils 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: protected:
OptionUtils() = default; OptionUtils() = default;
~OptionUtils() = default; ~OptionUtils() = default;
template <typename T> static auto withDefault(T value);
}; };
#include "option_utils.tpp" #include "option_utils.tpp"
+8 -3
View File
@@ -1,8 +1,13 @@
#pragma once #pragma once
#include "option_utils.hpp" #include "option_utils.hpp"
template <typename T> auto OptionUtils::withDefault(T value) template <typename T>
OptionUtils::OptionValue<T>
OptionUtils::withDefault(const T &value, const char *name)
{ {
return boost::program_options::value<T>()->default_value(value); auto v = boost::program_options::value<T>()->default_value(value);
if (name) {
v->value_name(name); // Optional
}
return v;
} }
-9
View File
@@ -1,9 +0,0 @@
#pragma once
class Server
{
public:
virtual ~Server() = default;
virtual void start() = 0;
virtual void stop() = 0;
};
-70
View File
@@ -1,70 +0,0 @@
#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);
}
-25
View File
@@ -1,25 +0,0 @@
#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_;
};
-23
View File
@@ -1,23 +0,0 @@
#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();
}
-17
View File
@@ -1,17 +0,0 @@
#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_;
};
+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};
};
+26
View File
@@ -0,0 +1,26 @@
#include "logger.hpp"
#include "packet_sniffer_cli.hpp"
#include "sniffer.hpp"
#include <exception>
int main(int argc, char *argv[])
{
Logger::init(true);
try {
PacketSnifferCli 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;
}
}
+12 -8
View File
@@ -2,16 +2,20 @@ 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" add_requires("cmake::libtins", { alias = "libtins", system = true })
target "packet_sniffer"
do
set_kind "binary" set_kind "binary"
add_files "src/*.cpp" add_files("src/main.cpp", "src/cli/*.cpp", "src/common/*.cpp", "src/core/*.cpp")
add_links "boost_system" add_includedirs("src", "src/cli", "src/common", "src/core", { public = true })
add_packages("boost", "libtins")
end