216 lines
6.0 KiB
C++
216 lines
6.0 KiB
C++
#include "../../include/capture/pcapCapture.hpp"
|
|
#include "../../include/stats/protocolStats.hpp"
|
|
|
|
/* get a list of all available network interfaces */
|
|
void PcapCapture::initialize() {
|
|
/* find all devs available in network, save them to pcap_if_t struct (interfaces) */
|
|
if (pcap_findalldevs(&interfaces, errbuf) == -1) {
|
|
throw std::runtime_error("Error: pcap_findalldevs has been failed");
|
|
/*fprintf(stderr, "Error: pcap_findalldevs has been failed - %s\n", errbuf);*/
|
|
}
|
|
}
|
|
|
|
void PcapCapture::datalink_type(int type) {
|
|
switch (type) {
|
|
|
|
case DLT_EN10MB: {
|
|
offset = 14;
|
|
get_ether_type = [](const u_char *p) {
|
|
const auto *eth = reinterpret_cast<const ether_header *>(p);
|
|
return ntohs(eth->ether_type);
|
|
};
|
|
break;
|
|
}
|
|
|
|
case DLT_LINUX_SLL: {
|
|
offset = 16;
|
|
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 14)); };
|
|
break;
|
|
}
|
|
|
|
case DLT_LINUX_SLL2: {
|
|
offset = 20;
|
|
get_ether_type = [](const u_char *p) { return ntohs(*reinterpret_cast<const uint16_t *>(p + 18)); };
|
|
break;
|
|
}
|
|
default:
|
|
throw std::runtime_error("Unsupported datalink type");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Start live packet capture.
|
|
*
|
|
* Steps:
|
|
* 1. Resolve network mask
|
|
* 2. Open device in promiscuous mode
|
|
* 3. Compile and apply BPF filter (if provided)
|
|
* 4. Start pcap_loop in a separate thread
|
|
*/
|
|
void PcapCapture::start() {
|
|
// getting the netmask of the interface
|
|
if (pcap_lookupnet(interface.c_str(), &net, &mask, errbuf) == -1) {
|
|
fprintf(stderr, "Couldn't get netmask for device %s: %s\n", interface.c_str(), errbuf);
|
|
net = 0;
|
|
mask = 0;
|
|
}
|
|
|
|
/* open capture device */
|
|
handle.reset(pcap_open_live(interface.c_str(), SNAP_LEN, 1, 1000, errbuf));
|
|
if (handle == nullptr) {
|
|
throw std::runtime_error("Couldn't open device " + interface + ": " + errbuf);
|
|
}
|
|
|
|
datalink_type(pcap_datalink(handle.get()));
|
|
|
|
if (!filter_exp.empty()) {
|
|
/* compile the filter expression */
|
|
if (pcap_compile(handle.get(), &fp, filter_exp.c_str(), 0, net) == -1) {
|
|
throw std::runtime_error("Couldn't parse filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
|
}
|
|
|
|
/* apply the compiled filter */
|
|
if (pcap_setfilter(handle.get(), &fp) == -1) {
|
|
throw std::runtime_error("Couldn't install filter " + filter_exp + ": " + pcap_geterr(handle.get()));
|
|
}
|
|
}
|
|
|
|
/* start a separate thread */
|
|
running = true;
|
|
thread = std::thread([this]() {
|
|
if (pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this)) < 0) {
|
|
// fprintf(stderr, "Error in pcap_loop: %s\n", pcap_geterr(handle));
|
|
// pcap_close(handle);
|
|
// throw std::runtime_error("Couldn't start capture");
|
|
}
|
|
running = false;
|
|
});
|
|
}
|
|
PcapCapture::~PcapCapture() { stop(); }
|
|
void PcapCapture::stop() {
|
|
pcap_freecode(&fp);
|
|
if (!handle) {
|
|
return;
|
|
}
|
|
|
|
running = false;
|
|
|
|
pcap_breakloop(handle.get());
|
|
|
|
if (thread.joinable()) {
|
|
thread.join();
|
|
}
|
|
|
|
handle.reset();
|
|
|
|
if (interfaces != nullptr) {
|
|
pcap_freealldevs(interfaces);
|
|
interfaces = nullptr;
|
|
}
|
|
}
|
|
|
|
/* print all available interfaces */
|
|
void PcapCapture::print_interfaces() {
|
|
int i = 0;
|
|
for (const pcap_if_t *dev = interfaces; dev != nullptr; dev = dev->next) {
|
|
printf("%d. %s ", ++i, dev->name);
|
|
if (dev->description != nullptr) {
|
|
printf("(%s)\n", dev->description);
|
|
} else {
|
|
printf("\n");
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* @brief Static wrapper required by libpcap C API.
|
|
*
|
|
* Since libpcap expects a C-style function pointer,
|
|
* we forward the call to the current class instance.
|
|
*
|
|
* @param user Pointer to PcapCapture instance
|
|
* @param header Packet metadata
|
|
* @param packet Raw packet bytes
|
|
*/
|
|
void PcapCapture::callback(u_char *user, const struct pcap_pkthdr *header, const u_char *packet) {
|
|
auto *const self = reinterpret_cast<PcapCapture *>(user);
|
|
if (!self->isRunning()) {
|
|
return;
|
|
}
|
|
self->got_packet(header, packet);
|
|
}
|
|
|
|
/**
|
|
* @brief Parses a single captured packet.
|
|
*
|
|
* Responsibilities:
|
|
* - Parse Ethernet header
|
|
* - Detect IP version (IPv4 / IPv6)
|
|
* - Extract transport & application protocols
|
|
* - Construct Packet abstraction
|
|
* - Forward packet to Stats engine
|
|
*
|
|
* Only IPv4 and IPv6 are currently processed.
|
|
* Other Ethernet types are ignored.
|
|
*/
|
|
void PcapCapture::got_packet(const struct pcap_pkthdr *header, const u_char *packet) {
|
|
if (!running) {
|
|
return;
|
|
}
|
|
|
|
// --- Ethernet header ---
|
|
const uint16_t ether_type = get_ether_type(packet);
|
|
|
|
/* if we have a ipv4 type */
|
|
if (ether_type == ETHERTYPE_IP) {
|
|
IPv4 ip(packet + offset);
|
|
const TransportProtocol prot = ip.get_protocol();
|
|
|
|
Packet packetView(v4, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
|
|
ip.get_payload_len(), ip.get_payload_ptr());
|
|
stats->add_packet(packetView);
|
|
stats->push(packetView);
|
|
}
|
|
/* ipv6 type */
|
|
else if (ether_type == ETHERTYPE_IPV6) {
|
|
IPv6 ip(packet + offset);
|
|
const TransportProtocol prot = ip.get_protocol();
|
|
Packet packetView(v6, prot, ip.get_source(), ip.get_dest(), ip.get_src_port(), ip.get_dest_port(), header->len,
|
|
ip.get_payload_len(), ip.get_payload_ptr());
|
|
stats->add_packet(packetView);
|
|
stats->push(packetView);
|
|
}
|
|
}
|
|
|
|
void PcapCapture::set_capabilities(const std::string &interface, int num_packets, const std::string &filter_exp,
|
|
const int packets_limit, Stats *stats) {
|
|
this->interface = interface;
|
|
this->num_packets = num_packets;
|
|
this->filter_exp = filter_exp;
|
|
this->stats = stats;
|
|
this->stats->set_packets_limit(packets_limit);
|
|
}
|
|
/**
|
|
* @brief Processes packets from an offline .pcap file.
|
|
*
|
|
* Differences from live mode:
|
|
* - Runs synchronously
|
|
* - No additional thread is created
|
|
* - Blocks until entire file is processed
|
|
*
|
|
* Used for post-capture analysis and exporting results.
|
|
*/
|
|
void PcapCapture::start_offline(const std::string &fpath) {
|
|
handle.reset(pcap_open_offline(fpath.c_str(), errbuf));
|
|
if (handle == nullptr) {
|
|
fprintf(stderr, "Error opening offline file: %s\n", errbuf);
|
|
return;
|
|
}
|
|
datalink_type(pcap_datalink(handle.get()));
|
|
|
|
running = true;
|
|
|
|
pcap_loop(handle.get(), num_packets, &PcapCapture::callback, reinterpret_cast<u_char *>(this));
|
|
|
|
running = false;
|
|
}
|