13 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
Ваше Имя a4c84379ff chore: remove UDP server implementation and main example 2025-10-04 17:37:58 +04:00
Ваше Имя 827cc07175 feat(option_utils): add withDefault utility for Boost program options 2025-10-04 17:09:41 +04:00
Ваше Имя ba91359db3 feat(common): introduce common networking and CLI infrastructure 2025-09-30 18:33:06 +04:00
14 changed files with 458 additions and 21 deletions
+1
View File
@@ -2,3 +2,4 @@
.xmake
build
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_;
};
+29
View File
@@ -0,0 +1,29 @@
#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;
+34
View File
@@ -0,0 +1,34 @@
#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
@@ -0,0 +1,27 @@
#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_;
};
+62
View File
@@ -0,0 +1,62 @@
#include "logger.hpp"
#include <boost/date_time/posix_time/posix_time_io.hpp>
void Logger::init(bool toConsole, const std::string_view filePath)
{
using namespace boost::log;
using namespace boost::posix_time;
add_common_attributes();
if (toConsole) {
add_console_log(
std::clog,
keywords::format =
(expressions::stream
<< "["
<< expressions::attr<boost::posix_time::ptime>("TimeStamp")
<< "] "
<< "<" << trivial::severity << "> " << expressions::smessage)
);
}
if (!filePath.empty()) {
add_file_log(
keywords::file_name = filePath,
keywords::auto_flush = true,
keywords::format =
(expressions::stream
<< "["
<< expressions::attr<boost::posix_time::ptime>("TimeStamp")
<< "] "
<< "<" << trivial::severity << "> " << expressions::smessage)
);
}
core::get()->set_filter(trivial::severity >= trivial::info);
}
void Logger::info(const std::string_view msg)
{
BOOST_LOG_TRIVIAL(info) << msg;
}
void Logger::warn(const std::string_view msg)
{
BOOST_LOG_TRIVIAL(warning) << msg;
}
void Logger::error(const std::string_view msg)
{
BOOST_LOG_TRIVIAL(error) << msg;
}
void Logger::debug(const std::string_view msg)
{
BOOST_LOG_TRIVIAL(debug) << msg;
}
void Logger::fatal(const std::string_view msg)
{
BOOST_LOG_TRIVIAL(fatal) << msg;
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <boost/log/core.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>
class Logger
{
public:
static void
init(bool toConsole = true, const std::string_view filePath = "");
static void info(const std::string_view msg);
static void warn(const std::string_view msg);
static void error(const std::string_view msg);
static void debug(const std::string_view msg);
static void fatal(const std::string_view msg);
};
+20
View File
@@ -0,0 +1,20 @@
#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
@@ -0,0 +1,13 @@
#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
@@ -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};
};
+22 -12
View File
@@ -1,16 +1,26 @@
#include <boost/asio.hpp>
#include <iostream>
#include "logger.hpp"
#include "packet_sniffer_cli.hpp"
#include "sniffer.hpp"
#include <exception>
int main()
int main(int argc, char *argv[])
{
std::cout << "Hello, Boost + C++23 + xmake!\n";
boost::asio::io_context io;
boost::asio::steady_timer timer(io, std::chrono::seconds(1));
timer.async_wait([](const boost::system::error_code &) {
std::cout << "Timer fired after 1 second!\n";
});
io.run();
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_requires("boost", {
system = true, -- sudo pacman -S boost
add_requires("cmake::Boost", {
alias = "boost",
system = true,
configs = {
all = false,
system = true, -- asio
components = { "program_options", "log" },
},
})
target "app"
add_requires("cmake::libtins", { alias = "libtins", system = true })
target "packet_sniffer"
do
set_kind "binary"
add_files "src/*.cpp"
add_links "boost_system"
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", "libtins")
end