4 Commits

Author SHA1 Message Date
Ваше Имя 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
19 changed files with 52 additions and 375 deletions
-1
View File
@@ -2,4 +2,3 @@
.xmake .xmake
build build
compile_commands.json compile_commands.json
ignore
-30
View File
@@ -1,30 +0,0 @@
#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
@@ -1,19 +0,0 @@
#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_;
};
-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;
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include <boost/asio/io_context.hpp>
// IO Contextct
using IoContext = boost::asio::io_context;
+8
View File
@@ -0,0 +1,8 @@
#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,5 @@
#pragma once
#include <boost/asio/steady_timer.hpp>
// Timers
using SteadyTimer = boost::asio::steady_timer;
@@ -0,0 +1,5 @@
#pragma once
#include <boost/system/error_code.hpp>
// Error handling
using ErrorCode = boost::system::error_code;
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <cstdint>
// Fixed-width unsigned integers
using u8 = std::uint8_t;
using u32 = std::uint32_t;
using u64 = std::uint64_t;
-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;
}
-135
View File
@@ -1,135 +0,0 @@
#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
@@ -1,32 +0,0 @@
#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};
};
+12 -22
View File
@@ -1,26 +1,16 @@
#include "logger.hpp" #include <boost/asio.hpp>
#include "packet_sniffer_cli.hpp" #include <iostream>
#include "sniffer.hpp"
#include <exception>
int main(int argc, char *argv[]) int main()
{ {
Logger::init(true); std::cout << "Hello, Boost + C++23 + xmake!\n";
try {
PacketSnifferCli cli; boost::asio::io_context io;
cli.parse(argc, argv); boost::asio::steady_timer timer(io, std::chrono::seconds(1));
if (cli.isHelp()) { timer.async_wait([](const boost::system::error_code &) {
cli.printHelp(); std::cout << "Timer fired after 1 second!\n";
});
io.run();
return 0; 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;
}
}
@@ -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 -12
View File
@@ -2,20 +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::libtins", { alias = "libtins", system = true }) target "app"
target "packet_sniffer"
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", "libtins")
end