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.
This commit is contained in:
@@ -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 + "]");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user