|
| 1 | +/* |
| 2 | + * WhatPulse External PCap Service - PCap Capture Thread Implementation |
| 3 | + * |
| 4 | + * Copyright (c) 2025 WhatPulse. All rights reserved. |
| 5 | + * |
| 6 | + * Licensed under CC BY-NC 4.0 with additional terms. |
| 7 | + * See LICENSE file for complete terms and conditions. |
| 8 | + * |
| 9 | + * NOTICE: This software integrates with WhatPulse services. Reverse engineering |
| 10 | + * the communication protocol or tampering with data transmission is prohibited. |
| 11 | + * |
| 12 | + * For licensing questions: [email protected] |
| 13 | + */ |
| 14 | + |
| 15 | +#include "pcapcapturethread.h" |
| 16 | +#include "pcapservice.h" |
| 17 | +#include <iostream> |
| 18 | +#include <cstring> |
| 19 | +#include <chrono> |
| 20 | +#include <net/ethernet.h> |
| 21 | +#include <netinet/ip.h> |
| 22 | +#include <netinet/ip6.h> |
| 23 | +#include <unistd.h> |
| 24 | + |
| 25 | +// Protocol constants |
| 26 | +#define ETHERTYPE_IP 0x0800 |
| 27 | +#define ETHERTYPE_IPV6 0x86dd |
| 28 | + |
| 29 | +// Performance optimization constants (matching built-in PCap monitor) |
| 30 | +#define DEFAULT_SNAPLEN 9000 |
| 31 | + |
| 32 | +PcapCaptureThread::PcapCaptureThread(const std::string &interface, bool verbose, PcapService *service) |
| 33 | +{ |
| 34 | + m_interface = interface; |
| 35 | + m_verbose = verbose; |
| 36 | + m_capturing = false; |
| 37 | + m_shouldStop = false; |
| 38 | + m_pcapHandle = nullptr; |
| 39 | + m_service = service; |
| 40 | +} |
| 41 | + |
| 42 | +PcapCaptureThread::~PcapCaptureThread() |
| 43 | +{ |
| 44 | + stop(); |
| 45 | + join(); |
| 46 | +} |
| 47 | + |
| 48 | +void PcapCaptureThread::start() |
| 49 | +{ |
| 50 | + m_thread = std::make_unique<std::thread>(&PcapCaptureThread::run, this); |
| 51 | +} |
| 52 | + |
| 53 | +void PcapCaptureThread::stop() |
| 54 | +{ |
| 55 | + m_shouldStop.store(true); |
| 56 | + |
| 57 | + std::lock_guard<std::mutex> lock(m_mutex); |
| 58 | + if (m_pcapHandle) |
| 59 | + { |
| 60 | + pcap_breakloop(m_pcapHandle); |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +void PcapCaptureThread::join() |
| 65 | +{ |
| 66 | + if (m_thread && m_thread->joinable()) |
| 67 | + { |
| 68 | + m_thread->join(); |
| 69 | + } |
| 70 | +} |
| 71 | + |
| 72 | +void PcapCaptureThread::run() |
| 73 | +{ |
| 74 | + char errbuf[PCAP_ERRBUF_SIZE]; |
| 75 | + |
| 76 | + if (m_verbose) |
| 77 | + { |
| 78 | + std::cout << "Starting capture on interface: " << m_interface << std::endl; |
| 79 | + } |
| 80 | + |
| 81 | + // Open pcap handle |
| 82 | + m_pcapHandle = pcap_open_live(m_interface.c_str(), |
| 83 | + DEFAULT_SNAPLEN, // optimized snap length |
| 84 | + 1, // promiscuous mode |
| 85 | + 1, // timeout (ms) - reduced for better performance |
| 86 | + errbuf); |
| 87 | + |
| 88 | + if (!m_pcapHandle) |
| 89 | + { |
| 90 | + std::cerr << "Unable to open interface " << m_interface << ": " << errbuf << std::endl; |
| 91 | + return; |
| 92 | + } |
| 93 | + |
| 94 | + // Set filter to capture only TCP and UDP traffic |
| 95 | + struct bpf_program filter; |
| 96 | + if (pcap_compile(m_pcapHandle, &filter, "tcp or udp", 1, PCAP_NETMASK_UNKNOWN) == -1) |
| 97 | + { |
| 98 | + std::cerr << "Unable to compile filter for interface " << m_interface << ": " |
| 99 | + << pcap_geterr(m_pcapHandle) << std::endl; |
| 100 | + pcap_close(m_pcapHandle); |
| 101 | + m_pcapHandle = nullptr; |
| 102 | + return; |
| 103 | + } |
| 104 | + |
| 105 | + if (pcap_setfilter(m_pcapHandle, &filter) == -1) |
| 106 | + { |
| 107 | + std::cerr << "Unable to set filter for interface " << m_interface << ": " |
| 108 | + << pcap_geterr(m_pcapHandle) << std::endl; |
| 109 | + pcap_freecode(&filter); |
| 110 | + pcap_close(m_pcapHandle); |
| 111 | + m_pcapHandle = nullptr; |
| 112 | + return; |
| 113 | + } |
| 114 | + |
| 115 | + pcap_freecode(&filter); |
| 116 | + m_capturing.store(true); |
| 117 | + |
| 118 | + if (m_verbose) |
| 119 | + { |
| 120 | + std::cout << "Capture started successfully on interface: " << m_interface << std::endl; |
| 121 | + } |
| 122 | + |
| 123 | + // Start packet capture loop |
| 124 | + while (!m_shouldStop.load()) |
| 125 | + { |
| 126 | + int result = pcap_dispatch(m_pcapHandle, 1000, packetHandler, reinterpret_cast<u_char *>(this)); |
| 127 | + if (result == -1) |
| 128 | + { |
| 129 | + // Error occurred |
| 130 | + if (!m_shouldStop.load()) |
| 131 | + { |
| 132 | + std::cerr << "Error in pcap_dispatch for interface " << m_interface << ": " |
| 133 | + << pcap_geterr(m_pcapHandle) << std::endl; |
| 134 | + } |
| 135 | + break; |
| 136 | + } |
| 137 | + else if (result == -2) |
| 138 | + { |
| 139 | + // Loop was broken |
| 140 | + break; |
| 141 | + } |
| 142 | + |
| 143 | + // No sleep needed with larger batch size - let it run at full speed |
| 144 | + } |
| 145 | + |
| 146 | + m_capturing.store(false); |
| 147 | + |
| 148 | + { |
| 149 | + std::lock_guard<std::mutex> lock(m_mutex); |
| 150 | + if (m_pcapHandle) |
| 151 | + { |
| 152 | + pcap_close(m_pcapHandle); |
| 153 | + m_pcapHandle = nullptr; |
| 154 | + } |
| 155 | + } |
| 156 | + |
| 157 | + if (m_verbose) |
| 158 | + { |
| 159 | + std::cout << "Capture stopped on interface: " << m_interface << std::endl; |
| 160 | + } |
| 161 | +} |
| 162 | + |
| 163 | +void PcapCaptureThread::packetHandler(u_char *userData, const struct pcap_pkthdr *header, const u_char *packet) |
| 164 | +{ |
| 165 | + PcapCaptureThread *thread = reinterpret_cast<PcapCaptureThread *>(userData); |
| 166 | + thread->handlePacket(header, packet); |
| 167 | +} |
| 168 | + |
| 169 | +void PcapCaptureThread::handlePacket(const struct pcap_pkthdr *header, const u_char *packet) |
| 170 | +{ |
| 171 | + if (m_shouldStop.load()) |
| 172 | + { |
| 173 | + return; |
| 174 | + } |
| 175 | + |
| 176 | + // Parse Ethernet header |
| 177 | + const struct ether_header *ethHeader = reinterpret_cast<const struct ether_header *>(packet); |
| 178 | + |
| 179 | + uint16_t etherType = ntohs(ethHeader->ether_type); |
| 180 | + uint8_t ipVersion = 0; |
| 181 | + |
| 182 | + if (etherType == ETHERTYPE_IP) |
| 183 | + { |
| 184 | + ipVersion = 4; |
| 185 | + } |
| 186 | + else if (etherType == ETHERTYPE_IPV6) |
| 187 | + { |
| 188 | + ipVersion = 6; |
| 189 | + } |
| 190 | + else |
| 191 | + { |
| 192 | + // Not IP traffic, ignore |
| 193 | + return; |
| 194 | + } |
| 195 | + |
| 196 | + // Create packet data structure |
| 197 | + PacketData packetData; |
| 198 | + packetData.ipVersion = ipVersion; |
| 199 | + packetData.dataLength = header->caplen; |
| 200 | + packetData.timestamp = static_cast<uint32_t>(std::chrono::duration_cast<std::chrono::seconds>( |
| 201 | + std::chrono::system_clock::now().time_since_epoch()) |
| 202 | + .count()); |
| 203 | + packetData.interfaceName = m_interface; |
| 204 | + |
| 205 | + // Copy packet data starting from IP header (skip Ethernet header) |
| 206 | + const u_char *ipPacket = packet + sizeof(struct ether_header); |
| 207 | + uint32_t ipPacketLength = header->caplen - sizeof(struct ether_header); |
| 208 | + |
| 209 | + packetData.packetData.assign(ipPacket, ipPacket + ipPacketLength); |
| 210 | + |
| 211 | + // Send to service |
| 212 | + if (m_service) |
| 213 | + { |
| 214 | + m_service->onPacketCaptured(packetData); |
| 215 | + } |
| 216 | +} |
0 commit comments