diff --git a/examples/distribution_network_packet/README.md b/examples/distribution_network_packet/README.md new file mode 100644 index 00000000..2b414acf --- /dev/null +++ b/examples/distribution_network_packet/README.md @@ -0,0 +1,402 @@ +# Network Packet Monitoring System + +This system consists of three components: + +1. **Packet Agent** - Captures network packets and sends them to the monitor server +2. **Packet Monitor Server** - Receives, displays, and processes packet data from agents +3. **Packet Receiver Service** - Receives forwarded packet data from the monitor server + +## Environment Requirements + +### Linux/Unix +- C++17 compatible compiler (GCC 7+ or Clang 5+) +- libtins library (for packet_agent) +- libpcap development files +- pthread library +- openssl development files + +### macOS +- Xcode Command Line Tools +- Homebrew (recommended for installing dependencies) +- libtins library + +### Windows +- Visual Studio 2019+ with C++ development tools +- WinPcap or Npcap +- CMake 3.13+ + +## Installation + +### Linux Installation + +```bash +# Debian/Ubuntu +sudo apt-get install build-essential cmake libpcap-dev libssl-dev +sudo apt-get install libtins-dev + +# Fedora/RHEL/CentOS +sudo dnf install gcc-c++ cmake libpcap-devel openssl-devel +sudo dnf install libtins-devel +``` + +### macOS Installation + +```bash +# Install Homebrew if not already installed +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" + +# Install dependencies +brew install cmake libtins libpcap openssl +``` + +### Windows Installation + +1. Install [Visual Studio](https://visualstudio.microsoft.com/downloads/) with C++ development tools +2. Install [CMake](https://cmake.org/download/) +3. Install [WinPcap](https://www.winpcap.org/install/) or [Npcap](https://nmap.org/npcap/) +4. Build using provided `build_windows.bat` script + +## Building the System + +### Using Make (Linux/Unix/macOS) + +```bash +make +``` + +This will generate three executables: +- `packet_agent` +- `packet_monitor_server` +- `packet_receiver_service` + +### Using CMake (Cross-platform) + +```bash +mkdir build +cd build +cmake .. +cmake --build . --config Release +``` + +### macOS Specific Compilation + +```bash +# Use the provided macOS compilation script +chmod +x mac_compile.sh +./mac_compile.sh +``` + +### Windows Compilation + +```batch +# Run the Windows build script +build_windows.bat +``` + +## Basic Usage + +### Step 1: Start the Packet Receiver Service + +```bash +./packet_receiver_service [options] +``` + +Options: +- `-p, --port PORT` - Listen port (default: 5600) +- `-o, --output FILE` - Log file (default: received_packets.log) +- `-v, --verbose` - Show all packets on console +- `-a, --address IP` - Bind to specific IP address (default: 0.0.0.0) +- `-d, --debug` - Enable debug mode with extra connection information + +### Step 2: Start the Packet Monitor Server + +```bash +./packet_monitor_server [options] +``` + +Options: +- `-p, --port PORT` - Listen port (default: 5500) +- `-o, --output FILE` - Save packet data to file +- `-n, --no-color` - Disable colored output +- `-s, --no-stats` - Disable statistics +- `-i, --interval SEC` - Statistics display interval (default: 10) +- `-f, --forward IP:PORT` - Forward packets to external service (default: 127.0.0.1:5600) +- `--no-forward` - Disable packet forwarding +- `--connect-retry SEC` - Connection retry interval (default: 5 seconds) +- `--connect-timeout SEC` - Connection timeout (default: 3 seconds) +- `--local` - Force use of localhost (127.0.0.1) for forwarding + +### Step 3: Start the Packet Agent + +```bash +sudo ./packet_agent [options] +``` + +Arguments: +- `interface` - Network interface to monitor (e.g., eth0, wlan0) +- `monitor_ip` - IP address of the monitor server + +Options: +- `-p, --port PORT` - Monitor port (default: 5500) +- `-f, --filter FILTER` - BPF packet filter +- `-l, --list` - List available network interfaces and exit + +OS-specific interface examples: +- Linux: `eth0`, `wlan0` +- macOS: `en0` (WiFi), `en1` (Ethernet) +- Windows: Use the `-l` option to list available interfaces + +## Packet Data Format + +### Internal Protocol Formats + +The system uses the following data formats for communication between components: + +#### 1. Agent to Monitor Server Format + +``` +PACKET||||||| +``` + +Fields: +- `timestamp`: ISO format timestamp (YYYY-MM-DD HH:MM:SS) +- `src_ip`: Source IP address +- `src_port`: Source port number (0 if not applicable) +- `dst_ip`: Destination IP address +- `dst_port`: Destination port number (0 if not applicable) +- `protocol`: Protocol name (HTTP, HTTPS, DNS, TCP, UDP, ICMP, etc.) +- `size`: Packet size in bytes + +Example: +``` +PACKET|2023-06-15 14:22:33|192.168.1.105|58234|93.184.216.34|443|HTTPS|1420 +``` + +#### 2. Monitor Server to Receiver Service Format + +``` +FORWARD|||||||| +``` + +Fields: +- Same as above plus `agent_hostname` which identifies the originating agent + +Example: +``` +FORWARD|2023-06-15 14:22:33|192.168.1.105|58234|93.184.216.34|443|HTTPS|1420|laptop-sensor1 +``` + +### Integrating with External Services + +To consume packet data from the receiver service: + +1. **Direct Log File Consumption**: + The receiver service writes packets to a log file (default: `received_packets.log`) in the following format: + ``` + [YYYY-MM-DD HH:MM:SS] src_ip:src_port -> dst_ip:dst_port (protocol, size bytes) from hostname + ``` + +2. **Custom Integration**: + You can create your own service that listens on a port and receives forwarded data from the monitor server: + + - Configure the monitor server to forward to your service: + ``` + ./packet_monitor_server -f your_service_ip:port + ``` + + - In your service, parse the incoming data in the format: + ``` + FORWARD|timestamp|src_ip|src_port|dst_ip|dst_port|protocol|size|agent_hostname + ``` + +3. **CSV Export**: + The monitor server provides a feature to export packet history to CSV: + - Start the monitor server and enter `export filename.csv` command + - CSV format contains: Timestamp, Source IP, Source Port, Destination IP, Destination Port, Protocol, Size, Agent Hostname + +## Public IP Connection Setup + +We provide easy-to-use setup scripts that automatically configure and test connections using public IP addresses: + +### On Linux/Unix: +```bash +# Make script executable +chmod +x public_ip_setup.sh +# Run setup tool +./public_ip_setup.sh +``` + +### On Windows: +``` +public_ip_setup.bat +``` + +**What these scripts do:** +1. Detect your public IP address +2. Check if ports are available +3. Start the packet receiver service on an available port +4. Test local and public IP connections +5. Provide instructions for router port forwarding +6. Show the exact commands to run the packet monitor server and agent + +This is the easiest way to set up public IP connections. + +## Connection Setup Scenarios + +### 1. Local Setup (Everything on the Same Machine) + +This is the simplest setup and works out of the box: + +```bash +# Terminal 1 - Start the receiver service +./packet_receiver_service -v + +# Terminal 2 - Start the packet monitor with default settings +./packet_monitor_server + +# Terminal 3 - Start the packet agent +sudo ./packet_agent eth0 127.0.0.1 +``` + +The packet monitor will automatically connect to the receiver service on localhost. + +### 2. Local Network Setup + +If the packet monitor and receiver service are on different machines in the same local network: + +```bash +# On Machine 1 (IP: 192.168.1.100) - Start the receiver service +./packet_receiver_service -v + +# On Machine 2 - Start the packet monitor pointing to Machine 1 +./packet_monitor_server -f 192.168.1.100:5600 + +# On Machine 2 or other machines - Start agents pointing to the monitor +sudo ./packet_agent eth0 192.168.1.101 +``` + +### 3. Public/Remote Network Setup + +For connecting across the internet using public IP addresses: + +#### On the receiver side: +1. Set up port forwarding on your router: forward port 5600 to the internal IP of the machine running the receiver service +2. Allow incoming connections on port 5600 in your firewall +3. Get your public IP address: `curl ifconfig.me` or `curl checkip.amazonaws.com` +4. Start the receiver service: + ```bash + ./packet_receiver_service -v -d + ``` + +#### On the monitor side: +```bash +./packet_monitor_server -f YOUR_PUBLIC_IP:5600 --connect-timeout 10 +``` + +The `--connect-timeout 10` option gives more time to establish connections over the internet. + +## Troubleshooting Connection Issues + +If you're having trouble connecting the packet monitor server to the receiver service: + +### Connection Test Tool + +We provide test scripts to diagnose common connection issues: + +#### On Linux/Unix: +```bash +# Make the script executable +chmod +x connection_test.sh +# Run the test +./connection_test.sh +``` + +#### On Windows: +``` +connection_test.bat +``` + +The test script will: +1. Check if required components are available +2. Check if port 5600 is already in use +3. Show network interface information +4. Detect your public IP address +5. Test local connectivity +6. Check firewall status +7. Provide recommendations based on the results + +### Manual Troubleshooting + +1. Check if the receiver service is running +Make sure the packet_receiver_service is running and listening on the correct port. + +2. Try with localhost first +Use the `--local` option to test the connection locally: +```bash +./packet_monitor_server --local +``` + +3. Check firewall settings +Make sure your firewall allows: +- Outgoing connections on port 5600 from the packet_monitor_server +- Incoming connections on port 5600 to the packet_receiver_service + +4. Test port forwarding +If using a public IP, test if your port forwarding is working using an online port checker or: +```bash +nc -vz YOUR_PUBLIC_IP 5600 +``` + +5. Increase connection timeout +For slow or unstable networks, increase the connection timeout: +```bash +./packet_monitor_server -f TARGET_IP:5600 --connect-timeout 10 --connect-retry 10 +``` + +## Firewall Configuration + +### Linux (UFW) +```bash +sudo ufw allow 5600/tcp +sudo ufw allow 5500/tcp +``` + +### macOS +```bash +# Add to pf firewall config or use System Preferences +sudo /usr/libexec/ApplicationFirewall/socketfilterfw --add $(pwd)/packet_receiver_service +sudo /usr/libexec/ApplicationFirewall/socketfilterfw --unblock $(pwd)/packet_receiver_service +``` + +### Windows +```batch +netsh advfirewall firewall add rule name="Packet Receiver" dir=in action=allow protocol=TCP localport=5600 +netsh advfirewall firewall add rule name="Packet Monitor" dir=in action=allow protocol=TCP localport=5500 +``` + +## Data Flow + +``` +[Network] → [Packet Agent] → [Packet Monitor Server] → [Packet Receiver Service] +``` + +1. Packet Agent captures network packets +2. Agent sends packet data to Monitor Server +3. Monitor Server processes and displays packet information +4. Monitor Server automatically forwards packet data to Receiver Service +5. Receiver Service logs and processes the forwarded data + +## Commands + +### Monitor Server Commands + +- `export [filename]` - Export packet history to CSV file +- `help` - Show help message +- `quit` - Exit the program + +## Notes + +- All components require appropriate permissions to create and use sockets +- The packet agent requires root/sudo privileges to capture packets +- Make sure the firewalls allow connections on the specified ports +- By default, packet forwarding is enabled to localhost (127.0.0.1:5600) \ No newline at end of file diff --git a/examples/distribution_network_packet/packet_agent.cpp b/examples/distribution_network_packet/packet_agent.cpp new file mode 100644 index 00000000..fcf93b3e --- /dev/null +++ b/examples/distribution_network_packet/packet_agent.cpp @@ -0,0 +1,507 @@ +/* + * Copyright (c) 2023, Muhammad Gilang Ramadhan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Linux socket headers +#include +#include +#include +#include +#include +#include + +using namespace Tins; +using namespace std; + +// Configuration +struct AgentConfig { + string interface; // Network interface to monitor + string monitor_ip; // IP address of the monitor server + int monitor_port = 5500;// Port to connect to monitor + string bpf_filter; // BPF filter + int reconnect_delay = 5;// Seconds to wait before reconnection attempt + bool promiscuous = true;// Enable promiscuous mode +}; + +// Globals +atomic running(true); +int client_socket = -1; +mutex socket_mutex; +PacketSender packet_sender; + +// Signal handler +void signal_handler(int signal) { + cout << "Received signal " << signal << ". Stopping agent..." << endl; + running = false; +} + +// Connect to monitor server +bool connect_to_monitor(const AgentConfig& config) { + lock_guard lock(socket_mutex); + + // Close existing socket if open + if (client_socket >= 0) { + close(client_socket); + client_socket = -1; + } + + // Create socket + client_socket = socket(AF_INET, SOCK_STREAM, 0); + if (client_socket < 0) { + cerr << "Error creating socket: " << strerror(errno) << endl; + return false; + } + + // Set socket options for better reliability + int opt = 1; + if (setsockopt(client_socket, SOL_SOCKET, SO_KEEPALIVE, &opt, sizeof(opt)) < 0) { + cerr << "Warning: Could not set SO_KEEPALIVE: " << strerror(errno) << endl; + } + + // Set up server address + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_port = htons(config.monitor_port); + + // Convert IP address + if (inet_pton(AF_INET, config.monitor_ip.c_str(), &server_addr.sin_addr) <= 0) { + cerr << "Invalid monitor IP address: " << config.monitor_ip << endl; + close(client_socket); + client_socket = -1; + return false; + } + + // Connect to server with timeout + struct timeval timeout; + timeout.tv_sec = 5; // 5 second timeout + timeout.tv_usec = 0; + + if (setsockopt(client_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0) { + cerr << "Warning: Could not set receive timeout: " << strerror(errno) << endl; + } + + if (setsockopt(client_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + cerr << "Warning: Could not set send timeout: " << strerror(errno) << endl; + } + + // Connect to server + if (connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + cerr << "Connection to monitor failed: " << strerror(errno) << endl; + close(client_socket); + client_socket = -1; + return false; + } + + cout << "Connected to monitor at " << config.monitor_ip << ":" << config.monitor_port << endl; + + // Send device information + string hostname = "unknown"; + char buffer[256]; + if (gethostname(buffer, sizeof(buffer)) == 0) { + hostname = buffer; + } + + ostringstream device_info; + device_info << "AGENT_INFO|" << hostname << "|" << config.interface; + + string info = device_info.str(); + if (send(client_socket, info.c_str(), info.size(), 0) < 0) { + cerr << "Error sending device info: " << strerror(errno) << endl; + close(client_socket); + client_socket = -1; + return false; + } + + return true; +} + +// Reconnection thread +void reconnect_thread(const AgentConfig& config) { + while (running) { + if (client_socket < 0) { + cout << "Attempting to connect to monitor..." << endl; + if (connect_to_monitor(config)) { + cout << "Connection established" << endl; + } else { + cout << "Connection failed. Retrying in " << config.reconnect_delay << " seconds..." << endl; + } + } + + // Sleep before next attempt + for (int i = 0; i < config.reconnect_delay && running; ++i) { + this_thread::sleep_for(chrono::seconds(1)); + } + } +} + +// Send packet data to monitor +void send_packet_data(const string& packet_data) { + lock_guard lock(socket_mutex); + + if (client_socket >= 0) { + string data = packet_data + "\n"; + ssize_t sent = send(client_socket, data.c_str(), data.size(), 0); + + if (sent < 0) { + cerr << "Error sending data to monitor: " << strerror(errno) << endl; + close(client_socket); + client_socket = -1; + } + } +} + +// Packet handler callback +bool packet_handler(const PDU& pdu) { + if (!running) return false; + + // Skip processing if not connected to monitor + if (client_socket < 0) { + return running; + } + + // Initialize packet details + string src_ip = "-"; + string dst_ip = "-"; + string protocol = "Unknown"; + int src_port = 0; + int dst_port = 0; + size_t packet_size = pdu.size(); + + // Get timestamp + auto now = chrono::system_clock::now(); + time_t now_c = chrono::system_clock::to_time_t(now); + tm *now_tm = localtime(&now_c); + + char time_buffer[64]; + strftime(time_buffer, sizeof(time_buffer), "%Y-%m-%d %H:%M:%S", now_tm); + string timestamp(time_buffer); + + // Extract IP layer (IPv4) + if (const IP* ip = pdu.find_pdu()) { + src_ip = ip->src_addr().to_string(); + dst_ip = ip->dst_addr().to_string(); + + // Determine protocol + switch (ip->protocol()) { + case 6: // TCP + protocol = "TCP"; + if (const TCP* tcp = pdu.find_pdu()) { + src_port = tcp->sport(); + dst_port = tcp->dport(); + + // Identify common application protocols + if (dst_port == 80 || src_port == 80) { + protocol = "HTTP"; + } else if (dst_port == 443 || src_port == 443) { + protocol = "HTTPS"; + } else if (dst_port == 22 || src_port == 22) { + protocol = "SSH"; + } + } + break; + + case 17: // UDP + protocol = "UDP"; + if (const UDP* udp = pdu.find_pdu()) { + src_port = udp->sport(); + dst_port = udp->dport(); + + // Identify common UDP protocols + if (dst_port == 53 || src_port == 53) { + protocol = "DNS"; + } else if (dst_port == 67 || dst_port == 68) { + protocol = "DHCP"; + } + } + break; + + case 1: // ICMP + protocol = "ICMP"; + break; + + default: + protocol = "IP:" + to_string(ip->protocol()); + } + } + // Extract IP layer (IPv6) + else if (const IPv6* ipv6 = pdu.find_pdu()) { + src_ip = ipv6->src_addr().to_string(); + dst_ip = ipv6->dst_addr().to_string(); + + // Similar protocol determination for IPv6 + switch (ipv6->next_header()) { + case 6: // TCP + protocol = "TCP"; + if (const TCP* tcp = pdu.find_pdu()) { + src_port = tcp->sport(); + dst_port = tcp->dport(); + + if (dst_port == 80 || src_port == 80) { + protocol = "HTTP"; + } else if (dst_port == 443 || src_port == 443) { + protocol = "HTTPS"; + } + } + break; + + case 17: // UDP + protocol = "UDP"; + if (const UDP* udp = pdu.find_pdu()) { + src_port = udp->sport(); + dst_port = udp->dport(); + + if (dst_port == 53 || src_port == 53) { + protocol = "DNS"; + } + } + break; + + case 58: // ICMPv6 + protocol = "ICMPv6"; + break; + + default: + protocol = "IPv6:" + to_string(ipv6->next_header()); + } + } + // Check for ARP layer + else if (const ARP* arp = pdu.find_pdu()) { + protocol = "ARP"; + src_ip = arp->sender_ip_addr().to_string(); + dst_ip = arp->target_ip_addr().to_string(); + } + + // Prepare packet data to send + ostringstream packet_data; + packet_data << "PACKET|" + << timestamp << "|" + << src_ip << "|" + << src_port << "|" + << dst_ip << "|" + << dst_port << "|" + << protocol << "|" + << packet_size; + + // Send packet data to monitor + send_packet_data(packet_data.str()); + + return running; +} + +// Show available interfaces +void show_interfaces() { + cout << "Available Network Interfaces:" << endl; + cout << "-----------------------------" << endl; + + vector interfaces = NetworkInterface::all(); + for (const auto& iface : interfaces) { + cout << "- " << iface.name(); + + try { + auto info = iface.info(); + cout << " ("; + + // Display IP address information + bool has_ip = false; + try { + // Try to get IPv4 address + IPv4Address ip = iface.addresses().ip_addr; + cout << "IPv4: " << ip.to_string(); + has_ip = true; + } catch (exception&) { + // No IPv4 address or exception getting it + } + + // Try to get subnet mask if we have an IP + try { + if (has_ip) { + IPv4Address mask = iface.addresses().netmask; + cout << "/" << mask.to_string(); + } + } catch (exception&) { + // No mask or exception getting it + } + + // Try to get hardware (MAC) address + try { + if (has_ip) cout << ", "; + HWAddress<6> hw = iface.hw_address(); + cout << "MAC: " << hw.to_string(); + } catch (exception&) { + // No MAC address or exception getting it + if (!has_ip) cout << "No address info"; + } + + // Show interface status + cout << ", Status: " << (iface.is_up() ? "Up" : "Down"); + + cout << ")"; + } catch (exception& ex) { + cout << " (Error getting info: " << ex.what() << ")"; + } + + cout << endl; + } + cout << endl; +} + +// Parse command line arguments +void parse_arguments(int argc, char* argv[], AgentConfig& config) { + if (argc < 3) { + cout << "Packet Agent - Send network packets to a monitoring server" << endl; + cout << "Usage: " << argv[0] << " [options]" << endl; + cout << "Options:" << endl; + cout << " -p, --port PORT Specify monitor port (default: 5500)" << endl; + cout << " -f, --filter FILTER Set packet filter (BPF syntax)" << endl; + cout << " -l, --list List available interfaces and exit" << endl; + exit(1); + } + + config.interface = argv[1]; + config.monitor_ip = argv[2]; + + // Process additional options + for (int i = 3; i < argc; ++i) { + string arg = argv[i]; + + if (arg == "-l" || arg == "--list") { + show_interfaces(); + exit(0); + } else if ((arg == "-p" || arg == "--port") && i + 1 < argc) { + config.monitor_port = atoi(argv[++i]); + } else if ((arg == "-f" || arg == "--filter") && i + 1 < argc) { + config.bpf_filter = argv[++i]; + } + } +} + +int main(int argc, char* argv[]) { + // Just list interfaces if -l is the first argument + if (argc > 1 && (string(argv[1]) == "-l" || string(argv[1]) == "--list")) { + show_interfaces(); + return 0; + } + + // Register signal handler + signal(SIGINT, signal_handler); + + // Default configuration + AgentConfig config; + + // Check if we have enough arguments + if (argc < 3) { + cout << "Packet Agent - Send network packets to a monitoring server" << endl; + cout << "Usage: " << argv[0] << " [options]" << endl; + cout << "Options:" << endl; + cout << " -p, --port PORT Specify monitor port (default: 5500)" << endl; + cout << " -f, --filter FILTER Set packet filter (BPF syntax)" << endl; + cout << " -l, --list List available interfaces and exit" << endl; + return 1; + } + + parse_arguments(argc, argv, config); + + // Initialize the packet sender with the specified interface + try { + NetworkInterface iface(config.interface); + packet_sender.default_interface(iface); + } catch (exception& ex) { + cerr << "Error setting default interface: " << ex.what() << endl; + cerr << "Make sure the interface name is correct. Use -l to list interfaces." << endl; + return 1; + } + + // Start reconnection thread + thread reconnect(reconnect_thread, config); + + try { + // Configure sniffer + SnifferConfiguration sniffer_config; + sniffer_config.set_promisc_mode(config.promiscuous); + + // Set packet filter if specified + if (!config.bpf_filter.empty()) { + sniffer_config.set_filter(config.bpf_filter); + cout << "Using filter: " << config.bpf_filter << endl; + } + + // Verify interface exists and is up + NetworkInterface iface(config.interface); + if (!iface.is_up()) { + cerr << "Warning: Interface " << config.interface << " is not up" << endl; + cout << "Attempting to continue anyway..." << endl; + } + + // Create sniffer + cout << "Starting packet agent on interface " << config.interface << endl; + cout << "Sending packet data to " << config.monitor_ip << ":" << config.monitor_port << endl; + cout << "Press Ctrl+C to stop" << endl; + + Sniffer sniffer(config.interface, sniffer_config); + + // Start packet capture + sniffer.sniff_loop(packet_handler); + + // Wait for reconnection thread to finish + reconnect.join(); + + // Close socket + if (client_socket >= 0) { + close(client_socket); + } + + } catch (exception& ex) { + cerr << "Error: " << ex.what() << endl; + + cerr << "\nTroubleshooting:" << endl; + cerr << "1. Make sure you're running with sudo privileges" << endl; + cerr << "2. Verify the interface name is correct (use -l to list interfaces)" << endl; + cerr << "3. Check that libpcap and libtins are properly installed" << endl; + cerr << "4. Ensure the monitor server is running and accessible" << endl; + cerr << "5. Try running 'sudo ip link set " << config.interface << " up' if interface is down" << endl; + + running = false; + reconnect.join(); + + return 1; + } + + return 0; +} \ No newline at end of file diff --git a/examples/distribution_network_packet/packet_monitor_server.cpp b/examples/distribution_network_packet/packet_monitor_server.cpp new file mode 100644 index 00000000..7f564583 --- /dev/null +++ b/examples/distribution_network_packet/packet_monitor_server.cpp @@ -0,0 +1,1046 @@ +/* + * Copyright (c) 2023, Muhammad Gilang Ramadhan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Linux socket headers +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Monitor configuration +struct MonitorConfig { + int listen_port = 5500; + string output_file; + bool color_output = true; + bool show_stats = true; + int stats_interval = 10; + int max_connections = 10; + bool forward_enabled = true; // Enable forwarding by default + string forward_ip = "127.0.0.1"; // Default to localhost - more reliable for initial setup + int forward_port = 5600; // Default port for the receiver service + int connection_retry_interval = 5; // Seconds between connection attempts + int connection_timeout = 3; // Connection timeout in seconds +}; + +// Connected agent information +struct AgentInfo { + int socket; + string hostname; + string interface; + string address; + time_t connected_time; + uint64_t packet_count = 0; + uint64_t byte_count = 0; +}; + +// Packet data structure +struct PacketData { + string timestamp; + string src_ip; + int src_port; + string dst_ip; + int dst_port; + string protocol; + size_t size; + string agent_hostname; +}; + +// Statistics +struct Stats { + map protocol_count; + map ip_packet_count; + map ip_byte_count; + map port_count; + uint64_t total_packets = 0; + uint64_t total_bytes = 0; + + mutex stats_mutex; +}; + +// Packet history +struct PacketHistory { + deque packets; + mutex history_mutex; + size_t max_history = 10000; // Maximum number of packets to keep in history +}; + +// Globals +atomic running(true); +vector agents; +mutex agents_mutex; +Stats stats; +ofstream output_file; +PacketHistory packet_history; +string csv_export_path = "packet_history.csv"; + +// External service connection +int forward_socket = -1; +mutex forward_socket_mutex; +bool forward_connected = false; + +// ANSI color codes +namespace Color { + const string RESET = "\033[0m"; + const string RED = "\033[31m"; + const string GREEN = "\033[32m"; + const string YELLOW = "\033[33m"; + const string BLUE = "\033[34m"; + const string MAGENTA = "\033[35m"; + const string CYAN = "\033[36m"; + const string WHITE = "\033[37m"; + const string BOLD = "\033[1m"; +} + +// Signal handler +void signal_handler(int signal) { + cout << "\nReceived signal " << signal << ". Stopping monitor..." << endl; + running = false; +} + +// Get timestamp +string get_timestamp() { + auto now = chrono::system_clock::now(); + time_t now_c = chrono::system_clock::to_time_t(now); + tm *now_tm = localtime(&now_c); + + char buffer[64]; + strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", now_tm); + return string(buffer); +} + +// Pretty format for bytes +string format_size(size_t bytes) { + const char* suffixes[] = {"B", "KB", "MB", "GB"}; + int suffix_index = 0; + double size = bytes; + + while (size >= 1024 && suffix_index < 3) { + size /= 1024; + suffix_index++; + } + + ostringstream oss; + oss << fixed << setprecision(suffix_index > 0 ? 1 : 0) << size << " " << suffixes[suffix_index]; + return oss.str(); +} + +// Update the connect_to_external_service function to improve connection handling +bool connect_to_external_service(const MonitorConfig& config) { + lock_guard lock(forward_socket_mutex); + + // Close existing connection if open + if (forward_socket >= 0) { + close(forward_socket); + forward_socket = -1; + forward_connected = false; + } + + if (!config.forward_enabled) { + return false; + } + + cout << "Connecting to external service at " << config.forward_ip << ":" << config.forward_port << "..." << endl; + + // Try to resolve the hostname/IP first for better diagnostics + struct addrinfo hints, *res; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + char port_str[10]; + snprintf(port_str, sizeof(port_str), "%d", config.forward_port); + + int status = getaddrinfo(config.forward_ip.c_str(), port_str, &hints, &res); + if (status != 0) { + cerr << "Error resolving address " << config.forward_ip << ": " + << gai_strerror(status) << endl; + return false; + } + + // Extract resolved IP address for diagnostics + char ip_str[INET_ADDRSTRLEN]; + void *addr; + struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr; + addr = &(ipv4->sin_addr); + inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str)); + + cout << "Resolved " << config.forward_ip << " to " << ip_str << endl; + + // Create socket + forward_socket = socket(res->ai_family, res->ai_socktype, res->ai_protocol); + if (forward_socket < 0) { + cerr << "Error creating forward socket: " << strerror(errno) << endl; + freeaddrinfo(res); + return false; + } + + // Set socket options for better reliability + int opt = 1; + if (setsockopt(forward_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + cerr << "Warning: Could not set SO_REUSEADDR for external service" << endl; + } + + // Set socket options + struct timeval timeout; + timeout.tv_sec = config.connection_timeout; + timeout.tv_usec = 0; + + if (setsockopt(forward_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) < 0 || + setsockopt(forward_socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)) < 0) { + cerr << "Warning: Could not set socket timeout for external service" << endl; + } + + // Set non-blocking mode for connection + int flags = fcntl(forward_socket, F_GETFL, 0); + fcntl(forward_socket, F_SETFL, flags | O_NONBLOCK); + + // Try to connect with timeout + int connect_result = connect(forward_socket, res->ai_addr, res->ai_addrlen); + + // Free address info as we don't need it anymore + freeaddrinfo(res); + + if (connect_result < 0 && errno != EINPROGRESS) { + cerr << "Connection to external service failed immediately: " << strerror(errno) << endl; + cerr << "Target: " << config.forward_ip << ":" << config.forward_port << endl; + cerr << "Error code: " << errno << endl; + + // Print detailed diagnostic information + cout << "\nDiagnostic Information:" << endl; + cout << "- Ensure the receiver service is running on " << config.forward_ip << ":" << config.forward_port << endl; + cout << "- Check that any firewalls allow outbound connections to port " << config.forward_port << endl; + cout << "- For public IPs, ensure port forwarding is configured on the router" << endl; + cout << "- Try testing the connection with:" << endl; + cout << " $ nc -zv " << config.forward_ip << " " << config.forward_port << endl; + cout << " $ telnet " << config.forward_ip << " " << config.forward_port << endl; + + // Check if this is a local connection attempt + if (config.forward_ip == "127.0.0.1" || config.forward_ip == "localhost") { + cout << "\nLocal connection troubleshooting:" << endl; + cout << "- Verify the packet_receiver_service is running on this machine" << endl; + cout << "- Check if the port is already in use by another application:" << endl; + cout << " $ lsof -i :" << config.forward_port << " || netstat -tuln | grep " << config.forward_port << endl; + } + // Check if this is a public IP connection attempt + else if (config.forward_ip != "0.0.0.0" && config.forward_ip != "::1") { + cout << "\nPublic/Remote IP troubleshooting:" << endl; + cout << "- Run packet_receiver_service with -d flag for debug mode" << endl; + cout << "- Ensure receiver service is binding to all interfaces (0.0.0.0)" << endl; + cout << "- Check router port forwarding for port " << config.forward_port << endl; + cout << "- Try our connection test script: ./connection_test.sh" << endl; + } + + close(forward_socket); + forward_socket = -1; + return false; + } + + if (connect_result < 0) { // EINPROGRESS + // Wait for connection completion with select + fd_set write_fds; + FD_ZERO(&write_fds); + FD_SET(forward_socket, &write_fds); + + // Set timeout for connection attempt + struct timeval connect_timeout; + connect_timeout.tv_sec = config.connection_timeout; + connect_timeout.tv_usec = 0; + + int select_result = select(forward_socket + 1, NULL, &write_fds, NULL, &connect_timeout); + + if (select_result <= 0) { + if (select_result == 0) { + cerr << "Connection to external service timed out" << endl; + cerr << "Try increasing the timeout with --connect-timeout" << endl; + } else { + cerr << "Error during connection select: " << strerror(errno) << endl; + } + close(forward_socket); + forward_socket = -1; + return false; + } + + // Check if connection was successful + int error = 0; + socklen_t error_len = sizeof(error); + if (getsockopt(forward_socket, SOL_SOCKET, SO_ERROR, &error, &error_len) < 0 || error != 0) { + if (error != 0) { + cerr << "Connection to external service failed: " << strerror(error) << endl; + cerr << "Target: " << config.forward_ip << ":" << config.forward_port << endl; + } else { + cerr << "Error checking connection status: " << strerror(errno) << endl; + } + close(forward_socket); + forward_socket = -1; + return false; + } + } + + // Set back to blocking mode for normal operation + fcntl(forward_socket, F_SETFL, flags); + + // Try sending a test message + string test_msg = "CONNECT_TEST|" + get_timestamp() + "\n"; + if (send(forward_socket, test_msg.c_str(), test_msg.size(), 0) < 0) { + cerr << "Error sending test message to external service: " << strerror(errno) << endl; + close(forward_socket); + forward_socket = -1; + return false; + } + + // Try to receive acknowledgment + char ack_buf[64] = {0}; + struct timeval read_timeout; + read_timeout.tv_sec = 2; // Short timeout for ack + read_timeout.tv_usec = 0; + + // Use select to wait for data with timeout + fd_set read_fds; + FD_ZERO(&read_fds); + FD_SET(forward_socket, &read_fds); + + if (select(forward_socket + 1, &read_fds, NULL, NULL, &read_timeout) > 0) { + if (recv(forward_socket, ack_buf, sizeof(ack_buf) - 1, 0) > 0) { + cout << "Received acknowledgment from receiver service" << endl; + } + } + + cout << "Successfully connected to external service at " << config.forward_ip << ":" << config.forward_port << endl; + forward_connected = true; + return true; +} + +// Forward packet data to external service +void forward_packet_data(const PacketData& packet) { + lock_guard lock(forward_socket_mutex); + + if (!forward_connected || forward_socket < 0) { + return; + } + + // Format data for external service + ostringstream data; + data << "FORWARD|" + << packet.timestamp << "|" + << packet.src_ip << "|" + << packet.src_port << "|" + << packet.dst_ip << "|" + << packet.dst_port << "|" + << packet.protocol << "|" + << packet.size << "|" + << packet.agent_hostname << "\n"; + + string data_str = data.str(); + ssize_t sent = send(forward_socket, data_str.c_str(), data_str.size(), 0); + + if (sent < 0) { + cerr << "Error sending data to external service: " << strerror(errno) << endl; + close(forward_socket); + forward_socket = -1; + forward_connected = false; + } +} + +// Reconnection thread for external service +void external_service_reconnect_thread(const MonitorConfig& config) { + // Reconnect delay based on config + int reconnect_delay = config.connection_retry_interval; + int attempt_count = 0; + const int max_attempts = 10; // Limit number of initial connection attempts + + while (running && config.forward_enabled) { + if (!forward_connected) { + // Increase wait time as we make more attempts + if (attempt_count > 3) { + reconnect_delay = config.connection_retry_interval * 2; + } + + cout << "Attempting to connect to external service... (attempt " << ++attempt_count << ")" << endl; + if (connect_to_external_service(config)) { + cout << "Connection to external service established" << endl; + attempt_count = 0; // Reset attempts counter on success + } else { + cout << "Connection to external service failed. Retrying in " + << reconnect_delay << " seconds..." << endl; + + // Try alternative IP if public IP fails (automatic fallback) + if (config.forward_ip != "127.0.0.1" && !forward_connected) { + cout << "Trying fallback to localhost (127.0.0.1)..." << endl; + MonitorConfig local_config = config; + local_config.forward_ip = "127.0.0.1"; + if (connect_to_external_service(local_config)) { + cout << "Connected to local service instead" << endl; + attempt_count = 0; // Reset attempts counter + } + } + } + } + + // Sleep before next attempt + for (int i = 0; i < reconnect_delay && running; ++i) { + this_thread::sleep_for(chrono::seconds(1)); + } + } +} + +// Parse a line of packet data from agent +bool parse_packet_data(const string& line, PacketData& packet, string agent_hostname) { + vector parts; + stringstream ss(line); + string part; + + while (getline(ss, part, '|')) { + parts.push_back(part); + } + + if (parts.size() < 8 || parts[0] != "PACKET") { + return false; + } + + packet.timestamp = parts[1]; + packet.src_ip = parts[2]; + packet.src_port = atoi(parts[3].c_str()); + packet.dst_ip = parts[4]; + packet.dst_port = atoi(parts[5].c_str()); + packet.protocol = parts[6]; + packet.size = stoul(parts[7]); + packet.agent_hostname = agent_hostname; + + return true; +} + +// Process data from an agent +void process_agent_data(int agent_index, const string& data) { + AgentInfo& agent = agents[agent_index]; + + // Check if it's agent info + if (data.substr(0, 10) == "AGENT_INFO") { + vector parts; + stringstream ss(data); + string part; + + while (getline(ss, part, '|')) { + parts.push_back(part); + } + + if (parts.size() >= 3) { + agent.hostname = parts[1]; + agent.interface = parts[2]; + + cout << "Agent connected: " << agent.hostname + << " (" << agent.address << ") monitoring interface " + << agent.interface << endl; + } + return; + } + + // Process packet data + PacketData packet; + if (parse_packet_data(data, packet, agent.hostname)) { + // Store in history + { + lock_guard lock(packet_history.history_mutex); + packet_history.packets.push_back(packet); + + // Remove old packets if we exceed max_history + while (packet_history.packets.size() > packet_history.max_history) { + packet_history.packets.pop_front(); + } + } + + // Update agent statistics + agent.packet_count++; + agent.byte_count += packet.size; + + // Update global statistics + { + lock_guard lock(stats.stats_mutex); + stats.total_packets++; + stats.total_bytes += packet.size; + stats.protocol_count[packet.protocol]++; + stats.ip_packet_count[packet.src_ip]++; + stats.ip_byte_count[packet.src_ip] += packet.size; + + if (packet.src_port > 0) stats.port_count[packet.src_port]++; + if (packet.dst_port > 0) stats.port_count[packet.dst_port]++; + } + + // Forward packet to external service if enabled + if (forward_connected) { + forward_packet_data(packet); + } + + // Format the output + ostringstream output; + output << left << setw(22) << packet.timestamp; + output << setw(18) << packet.src_ip; + if (packet.src_port > 0) { + output << ":" << setw(5) << left << packet.src_port; + } else { + output << " "; + } + + output << " → " << setw(18) << packet.dst_ip; + if (packet.dst_port > 0) { + output << ":" << setw(5) << left << packet.dst_port; + } else { + output << " "; + } + + output << " | " << setw(10) << left << packet.protocol; + output << " | " << setw(10) << right << format_size(packet.size); + output << " | " << packet.agent_hostname; + + string output_str = output.str(); + + // Print to console with colors + if (MonitorConfig().color_output) { + string color; + if (packet.protocol == "HTTP" || packet.protocol == "HTTPS") { + color = Color::GREEN; + } else if (packet.protocol == "DNS") { + color = Color::CYAN; + } else if (packet.protocol == "ICMP" || packet.protocol == "ICMPv6") { + color = Color::YELLOW; + } else if (packet.protocol == "ARP") { + color = Color::MAGENTA; + } else if (packet.protocol.find("TCP") != string::npos) { + color = Color::BLUE; + } else if (packet.protocol.find("UDP") != string::npos) { + color = Color::RED; + } else { + color = Color::WHITE; + } + + cout << color << output_str << Color::RESET << endl; + } else { + cout << output_str << endl; + } + + // Save to file if enabled + if (output_file.is_open()) { + output_file << output_str << endl; + } + } +} + +// Print statistics +void print_statistics() { + // Take a snapshot of stats to avoid race conditions + map protocol_count; + map ip_packet_count; + map ip_byte_count; + map port_count; + uint64_t total_packets; + uint64_t total_bytes; + + { + lock_guard lock(stats.stats_mutex); + protocol_count = stats.protocol_count; + ip_packet_count = stats.ip_packet_count; + ip_byte_count = stats.ip_byte_count; + port_count = stats.port_count; + total_packets = stats.total_packets; + total_bytes = stats.total_bytes; + } + + cout << "\n===== Network Monitor Statistics =====" << endl; + cout << "Total Packets: " << total_packets << endl; + cout << "Total Data: " << format_size(total_bytes) << endl; + + // Connected agents + { + lock_guard lock(agents_mutex); + cout << "\nConnected Agents (" << agents.size() << "):" << endl; + for (const auto& agent : agents) { + cout << " " << agent.hostname << " (" << agent.address << ") - " + << agent.packet_count << " packets, " + << format_size(agent.byte_count) << endl; + } + } + + // Protocol statistics + cout << "\nTop Protocols:" << endl; + vector> protocol_vec(protocol_count.begin(), protocol_count.end()); + sort(protocol_vec.begin(), protocol_vec.end(), + [](const pair& a, const pair& b) { + return a.second > b.second; + }); + + for (size_t i = 0; i < min(size_t(5), protocol_vec.size()); ++i) { + cout << " " << setw(10) << left << protocol_vec[i].first << ": " + << protocol_vec[i].second << " packets"; + if (total_packets > 0) { + cout << " (" << fixed << setprecision(1) + << (protocol_vec[i].second * 100.0 / total_packets) << "%)"; + } + cout << endl; + } + + // IP address statistics + cout << "\nTop Source IPs:" << endl; + vector> ip_vec(ip_packet_count.begin(), ip_packet_count.end()); + sort(ip_vec.begin(), ip_vec.end(), + [](const pair& a, const pair& b) { + return a.second > b.second; + }); + + for (size_t i = 0; i < min(size_t(5), ip_vec.size()); ++i) { + cout << " " << setw(18) << left << ip_vec[i].first << ": " + << ip_vec[i].second << " packets, " + << format_size(ip_byte_count[ip_vec[i].first]) << endl; + } + + // Port statistics + cout << "\nTop Ports:" << endl; + vector> port_vec(port_count.begin(), port_count.end()); + sort(port_vec.begin(), port_vec.end(), + [](const pair& a, const pair& b) { + return a.second > b.second; + }); + + for (size_t i = 0; i < min(size_t(5), port_vec.size()); ++i) { + string port_service = to_string(port_vec[i].first); + // Add known service names for common ports + if (port_vec[i].first == 80) port_service += " (HTTP)"; + else if (port_vec[i].first == 443) port_service += " (HTTPS)"; + else if (port_vec[i].first == 53) port_service += " (DNS)"; + else if (port_vec[i].first == 22) port_service += " (SSH)"; + + cout << " Port " << setw(15) << left << port_service << ": " + << port_vec[i].second << " uses" << endl; + } + + cout << endl; +} + +// Handle agent connections and data +void handle_agents(int server_socket, const MonitorConfig& config) { + fd_set read_fds, master_fds; + FD_ZERO(&master_fds); + FD_SET(server_socket, &master_fds); + + int max_fd = server_socket; + + // For reading data from clients + char buffer[4096]; + vector incomplete_data(config.max_connections + 1); + + while (running) { + // Copy the master set to read_fds + read_fds = master_fds; + + // Set up timeout + struct timeval timeout; + timeout.tv_sec = 1; // 1 second timeout for clean exit + timeout.tv_usec = 0; + + // Wait for activity on any socket + int activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); + + if (activity < 0 && errno != EINTR) { + cerr << "Select error: " << strerror(errno) << endl; + break; + } + + // Check for new connections + if (FD_ISSET(server_socket, &read_fds)) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + + int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_len); + if (client_socket >= 0) { + // Add to agents list + AgentInfo agent; + agent.socket = client_socket; + agent.address = inet_ntoa(client_addr.sin_addr); + agent.connected_time = time(NULL); + + { + lock_guard lock(agents_mutex); + agents.push_back(agent); + } + + // Add to master set + FD_SET(client_socket, &master_fds); + if (client_socket > max_fd) { + max_fd = client_socket; + } + + cout << "New connection from " << agent.address << endl; + } + } + + // Check data from agents + { + lock_guard lock(agents_mutex); + + for (size_t i = 0; i < agents.size(); ++i) { + int client_socket = agents[i].socket; + + if (FD_ISSET(client_socket, &read_fds)) { + // Receive data + memset(buffer, 0, sizeof(buffer)); + int bytes_read = recv(client_socket, buffer, sizeof(buffer) - 1, 0); + + if (bytes_read <= 0) { + // Connection closed or error + cout << "Agent disconnected: " << agents[i].hostname + << " (" << agents[i].address << ")" << endl; + + close(client_socket); + FD_CLR(client_socket, &master_fds); + + // Remove from agents list + agents.erase(agents.begin() + i); + i--; // Adjust index + } else { + // Process received data + buffer[bytes_read] = '\0'; + string data = incomplete_data[i] + buffer; + + // Process complete lines + size_t pos = 0; + while ((pos = data.find('\n')) != string::npos) { + string line = data.substr(0, pos); + data = data.substr(pos + 1); + + // Process the complete line + process_agent_data(i, line); + } + + // Save any incomplete data + incomplete_data[i] = data; + } + } + } + } + } +} + +// Parse command line arguments +void parse_arguments(int argc, char* argv[], MonitorConfig& config) { + for (int i = 1; i < argc; ++i) { + string arg = argv[i]; + + if (arg == "-h" || arg == "--help") { + cout << "Packet Monitor Server - Receive and display network packets from agents" << endl; + cout << "Usage: " << argv[0] << " [options]" << endl; + cout << "Options:" << endl; + cout << " -p, --port PORT Listen on port (default: 5500)" << endl; + cout << " -o, --output FILE Save packet data to file" << endl; + cout << " -n, --no-color Disable colored output" << endl; + cout << " -s, --no-stats Disable statistics" << endl; + cout << " -i, --interval SEC Statistics display interval (default: 10)" << endl; + cout << " -f, --forward IP:PORT Forward packets to external service (default: 127.0.0.1:5600)" << endl; + cout << " --no-forward Disable packet forwarding" << endl; + cout << " --connect-retry SEC Connection retry interval (default: 5 seconds)" << endl; + cout << " --connect-timeout SEC Connection timeout (default: 3 seconds)" << endl; + cout << " --local Force use of localhost (127.0.0.1) for forwarding" << endl; + exit(0); + } else if ((arg == "-p" || arg == "--port") && i + 1 < argc) { + config.listen_port = atoi(argv[++i]); + } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) { + config.output_file = argv[++i]; + } else if (arg == "-n" || arg == "--no-color") { + config.color_output = false; + } else if (arg == "-s" || arg == "--no-stats") { + config.show_stats = false; + } else if ((arg == "-i" || arg == "--interval") && i + 1 < argc) { + config.stats_interval = atoi(argv[++i]); + } else if ((arg == "-f" || arg == "--forward") && i + 1 < argc) { + config.forward_enabled = true; + string forward_address = argv[++i]; + size_t colon_pos = forward_address.find(':'); + if (colon_pos != string::npos) { + config.forward_ip = forward_address.substr(0, colon_pos); + config.forward_port = atoi(forward_address.substr(colon_pos + 1).c_str()); + } else { + cerr << "Invalid forward address format. Use IP:PORT format." << endl; + exit(1); + } + } else if (arg == "--no-forward") { + config.forward_enabled = false; + } else if (arg == "--local") { + config.forward_ip = "127.0.0.1"; + } else if (arg == "--connect-retry" && i + 1 < argc) { + config.connection_retry_interval = atoi(argv[++i]); + } else if (arg == "--connect-timeout" && i + 1 < argc) { + config.connection_timeout = atoi(argv[++i]); + } + } +} + +// Add new function for CSV export +void export_to_csv(const string& filename) { + lock_guard lock(packet_history.history_mutex); + + ofstream csv_file(filename); + if (!csv_file.is_open()) { + cerr << "Error: Could not open CSV file for writing: " << filename << endl; + return; + } + + // Write CSV header + csv_file << "Timestamp,Source IP,Source Port,Destination IP,Destination Port,Protocol,Size (bytes),Agent Hostname" << endl; + + // Write packet data + for (const auto& packet : packet_history.packets) { + csv_file << packet.timestamp << "," + << packet.src_ip << "," + << packet.src_port << "," + << packet.dst_ip << "," + << packet.dst_port << "," + << packet.protocol << "," + << packet.size << "," + << packet.agent_hostname << endl; + } + + csv_file.close(); + cout << "Packet history exported to " << filename << endl; +} + +// Add new function to handle export command +void handle_export_command(const string& command) { + vector parts; + stringstream ss(command); + string part; + + while (getline(ss, part, ' ')) { + parts.push_back(part); + } + + if (parts.size() >= 2 && parts[0] == "export") { + string filename = parts[1]; + if (filename.empty()) { + filename = csv_export_path; + } + export_to_csv(filename); + } +} + +// Add command handling to the main loop +void handle_commands() { + string command; + while (running) { + if (getline(cin, command)) { + if (command == "export" || command.substr(0, 7) == "export ") { + handle_export_command(command); + } else if (command == "help") { + cout << "\nAvailable commands:" << endl; + cout << " export [filename] - Export packet history to CSV file" << endl; + cout << " help - Show this help message" << endl; + cout << " quit - Exit the program" << endl; + } else if (command == "quit") { + running = false; + } + } + } +} + +int main(int argc, char* argv[]) { + // Register signal handler + signal(SIGINT, signal_handler); + + // Default configuration + MonitorConfig config; + parse_arguments(argc, argv, config); + + // Open output file if specified + if (!config.output_file.empty()) { + output_file.open(config.output_file); + if (!output_file.is_open()) { + cerr << "Error: Could not open output file '" << config.output_file << "'" << endl; + return 1; + } + } + + // Create server socket + int server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { + cerr << "Error creating server socket" << endl; + return 1; + } + + // Set socket options for reuse + int opt = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + cerr << "Error setting socket options" << endl; + close(server_socket); + return 1; + } + + // Bind socket to port + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + server_addr.sin_addr.s_addr = INADDR_ANY; + server_addr.sin_port = htons(config.listen_port); + + if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + cerr << "Error binding server socket to port " << config.listen_port << ": " << strerror(errno) << endl; + close(server_socket); + return 1; + } + + // Listen for connections + if (listen(server_socket, config.max_connections) < 0) { + cerr << "Error listening on server socket: " << strerror(errno) << endl; + close(server_socket); + return 1; + } + + cout << "Packet Monitor Server started" << endl; + cout << "Listening for agent connections on port " << config.listen_port << endl; + + if (!config.output_file.empty()) { + cout << "Saving packet data to '" << config.output_file << "'" << endl; + } + + // Initialize external service connection if enabled + thread external_service_thread; + if (config.forward_enabled) { + cout << "Forwarding packets to external service at " + << config.forward_ip << ":" << config.forward_port << endl; + + // Initial connection attempt + if (connect_to_external_service(config)) { + cout << "Successfully connected to external service" << endl; + } else { + cout << "Initial connection to external service failed. Will retry in background..." << endl; + } + + // Start reconnection thread + external_service_thread = thread(external_service_reconnect_thread, config); + } else { + cout << "Packet forwarding is disabled" << endl; + } + + cout << "\nAvailable commands:" << endl; + cout << " export [filename] - Export packet history to CSV file" << endl; + cout << " help - Show this help message" << endl; + cout << " quit - Exit the program" << endl; + cout << "\nPress Ctrl+C to stop" << endl << endl; + + // Print column headers + cout << left << setw(22) << "TIMESTAMP"; + cout << setw(24) << "SOURCE"; + cout << setw(24) << "DESTINATION"; + cout << setw(11) << "PROTOCOL"; + cout << setw(11) << "SIZE"; + cout << "AGENT" << endl; + + cout << string(100, '-') << endl; + + // Create command handling thread + thread command_thread(handle_commands); + + // Create statistics thread + thread stats_thread; + if (config.show_stats) { + stats_thread = thread([&config]() { + while (running) { + for (int i = 0; i < config.stats_interval && running; ++i) { + this_thread::sleep_for(chrono::seconds(1)); + } + + if (running && stats.total_packets > 0) { + print_statistics(); + } + } + }); + } + + // Handle agent connections + handle_agents(server_socket, config); + + // Wait for threads to finish + if (command_thread.joinable()) { + command_thread.join(); + } + + if (config.show_stats && stats_thread.joinable()) { + stats_thread.join(); + } + + if (config.forward_enabled && external_service_thread.joinable()) { + external_service_thread.join(); + } + + // Export final packet history before exit + export_to_csv(csv_export_path); + + // Clean up + close(server_socket); + + if (forward_socket >= 0) { + close(forward_socket); + } + + if (output_file.is_open()) { + output_file.close(); + } + + // Close any open agent connections + { + lock_guard lock(agents_mutex); + for (const auto& agent : agents) { + close(agent.socket); + } + } + + cout << "\nPacket Monitor Server stopped" << endl; + + return 0; +} \ No newline at end of file diff --git a/examples/distribution_network_packet/packet_receiver_service.cpp b/examples/distribution_network_packet/packet_receiver_service.cpp new file mode 100644 index 00000000..bed5f514 --- /dev/null +++ b/examples/distribution_network_packet/packet_receiver_service.cpp @@ -0,0 +1,458 @@ +/* + * Copyright (c) 2023, Muhammad Gilang Ramadhan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Socket headers +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Configuration +struct Config { + int listen_port = 5600; + string output_file = "received_packets.log"; + bool verbose = false; + bool bind_all_interfaces = true; // By default, bind to all interfaces + string bind_address = "0.0.0.0"; // Default address to bind to (all interfaces) + bool debug_mode = false; // Add debug mode for detailed connection info +}; + +// Packet data structure +struct PacketData { + string timestamp; + string src_ip; + int src_port; + string dst_ip; + int dst_port; + string protocol; + size_t size; + string agent_hostname; + + // Parse from incoming data string + static bool parse(const string& data, PacketData& packet) { + if (data.substr(0, 8) != "FORWARD|") { + return false; + } + + vector parts; + stringstream ss(data); + string part; + + while (getline(ss, part, '|')) { + parts.push_back(part); + } + + // Ensure we have all parts + if (parts.size() < 9) { + return false; + } + + packet.timestamp = parts[1]; + packet.src_ip = parts[2]; + packet.src_port = stoi(parts[3]); + packet.dst_ip = parts[4]; + packet.dst_port = stoi(parts[5]); + packet.protocol = parts[6]; + packet.size = stoull(parts[7]); + packet.agent_hostname = parts[8]; + + return true; + } + + // Convert to string representation + string to_string() const { + ostringstream ss; + ss << "[" << timestamp << "] " + << src_ip << ":" << src_port << " -> " + << dst_ip << ":" << dst_port + << " (" << protocol << ", " << size << " bytes) " + << "from " << agent_hostname; + return ss.str(); + } +}; + +// Function type for packet processing +typedef function PacketProcessor; + +// Global variables +atomic running(true); +ofstream log_file; + +// Signal handler +void signal_handler(int signal) { + cout << "Received signal " << signal << ". Stopping receiver..." << endl; + running = false; +} + +// Process received packets +void process_packet(const PacketData& packet, const Config& config) { + string packet_str = packet.to_string(); + + // Print to console if verbose + if (config.verbose) { + cout << "Received: " << packet_str << endl; + } + + // Write to log file + if (log_file.is_open()) { + log_file << packet_str << endl; + log_file.flush(); // Ensure it's written immediately + } + + // Additional processing could be added here: + // - Send to a database + // - Process for alerts + // - Forward to another service + // - etc. +} + +// Handle client connections +void handle_client(int client_socket, const Config& config) { + char buffer[4096]; + string incomplete_data; + + // Get client address info + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + getpeername(client_socket, (struct sockaddr*)&client_addr, &addr_len); + string client_ip = inet_ntoa(client_addr.sin_addr); + + // Log the successful connection + cout << "Client connected from " << client_ip << " - waiting for data..." << endl; + + while (running) { + // Receive data + memset(buffer, 0, sizeof(buffer)); + int bytes_read = recv(client_socket, buffer, sizeof(buffer) - 1, 0); + + if (bytes_read <= 0) { + // Connection closed or error + if (bytes_read < 0) { + cerr << "Error receiving data from " << client_ip << ": " << strerror(errno) << endl; + } else { + cout << "Client " << client_ip << " disconnected" << endl; + } + break; + } + + // Process received data + buffer[bytes_read] = '\0'; + string data = incomplete_data + buffer; + + // Process complete lines + size_t pos = 0; + while ((pos = data.find('\n')) != string::npos) { + string line = data.substr(0, pos); + data = data.substr(pos + 1); + + // Check if it's a connection test + if (line.substr(0, 13) == "CONNECT_TEST|") { + cout << "Received connection test from " << client_ip << endl; + + // Send acknowledgment for connection test + string ack = "ACK_CONNECT_TEST\n"; + send(client_socket, ack.c_str(), ack.size(), 0); + continue; // Skip further processing + } + + // Parse and process packet data + PacketData packet; + if (PacketData::parse(line, packet)) { + process_packet(packet, config); + } else { + cerr << "Failed to parse packet data: " << line << endl; + } + } + + // Save any incomplete data for next time + incomplete_data = data; + } + + close(client_socket); +} + +// Parse command line arguments +void parse_arguments(int argc, char* argv[], Config& config) { + for (int i = 1; i < argc; ++i) { + string arg = argv[i]; + + if (arg == "-h" || arg == "--help") { + cout << "Packet Receiver Service - Receive forwarded packets from monitor server" << endl; + cout << "Usage: " << argv[0] << " [options]" << endl; + cout << "Options:" << endl; + cout << " -p, --port PORT Listen on port (default: 5600)" << endl; + cout << " -o, --output FILE Save packet data to file (default: received_packets.log)" << endl; + cout << " -v, --verbose Show all packets on console" << endl; + cout << " -a, --address IP Bind to specific IP address (default: 0.0.0.0)" << endl; + cout << " -d, --debug Enable debug mode with extra connection information" << endl; + exit(0); + } else if ((arg == "-p" || arg == "--port") && i + 1 < argc) { + config.listen_port = atoi(argv[++i]); + } else if ((arg == "-o" || arg == "--output") && i + 1 < argc) { + config.output_file = argv[++i]; + } else if (arg == "-v" || arg == "--verbose") { + config.verbose = true; + } else if ((arg == "-a" || arg == "--address") && i + 1 < argc) { + config.bind_all_interfaces = false; + config.bind_address = argv[++i]; + } else if (arg == "-d" || arg == "--debug") { + config.debug_mode = true; + } + } +} + +// Function to print network interfaces +void print_network_interfaces() { + cout << "\nAvailable network interfaces:" << endl; + cout << "----------------------------" << endl; + + // Try different commands depending on availability + if (system("ip -br addr") != 0) { + if (system("ifconfig") != 0) { + system("ipconfig"); // Try Windows command + } + } + + cout << endl; +} + +// Function to check port availability +bool check_port_availability(int port) { + int test_socket = socket(AF_INET, SOCK_STREAM, 0); + if (test_socket < 0) { + cerr << "Error creating test socket: " << strerror(errno) << endl; + return false; + } + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + // Try to bind to the port + int bind_result = ::bind(test_socket, (struct sockaddr*)&addr, sizeof(addr)); + close(test_socket); + + if (bind_result < 0) { + return false; // Port is in use + } + + return true; // Port is available +} + +int main(int argc, char* argv[]) { + // Register signal handler + signal(SIGINT, signal_handler); + + // Default configuration + Config config; + parse_arguments(argc, argv, config); + + // Open log file + log_file.open(config.output_file, ios::app); // Append to existing file + if (!log_file.is_open()) { + cerr << "Error: Could not open log file '" << config.output_file << "'" << endl; + return 1; + } + + // Print network configuration if debug mode enabled + if (config.debug_mode) { + cout << "\n=== Network Configuration ===" << endl; + print_network_interfaces(); + + // Check for port availability + if (!check_port_availability(config.listen_port)) { + cerr << "Warning: Port " << config.listen_port << " may already be in use!" << endl; + cout << "Processes using this port:" << endl; + system(("lsof -i :" + to_string(config.listen_port) + " || netstat -tuln | grep " + to_string(config.listen_port)).c_str()); + cout << endl; + } + } + + // Create server socket + int server_socket = socket(AF_INET, SOCK_STREAM, 0); + if (server_socket < 0) { + cerr << "Error creating server socket: " << strerror(errno) << endl; + return 1; + } + + // Set socket options for reuse + int opt = 1; + if (setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) { + cerr << "Error setting socket options: " << strerror(errno) << endl; + close(server_socket); + return 1; + } + + // Bind socket to port + struct sockaddr_in server_addr; + memset(&server_addr, 0, sizeof(server_addr)); + server_addr.sin_family = AF_INET; + + // Bind to specific address or all interfaces + if (config.bind_all_interfaces) { + server_addr.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces + } else { + if (inet_pton(AF_INET, config.bind_address.c_str(), &server_addr.sin_addr) <= 0) { + cerr << "Error: Invalid address to bind: " << config.bind_address << endl; + close(server_socket); + return 1; + } + } + + server_addr.sin_port = htons(config.listen_port); + + if (::bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + cerr << "Error binding server socket to port " << config.listen_port << ": " << strerror(errno) << endl; + cerr << "You may need to wait a minute for the port to be released if it was recently used." << endl; + cerr << "Alternatively, try a different port with the -p option." << endl; + close(server_socket); + return 1; + } + + // Listen for connections + if (listen(server_socket, 5) < 0) { + cerr << "Error listening on server socket: " << strerror(errno) << endl; + close(server_socket); + return 1; + } + + cout << "Packet Receiver Service started" << endl; + cout << "Listening for forwarded packets on " << (config.bind_all_interfaces ? "all interfaces (0.0.0.0)" : config.bind_address) + << ":" << config.listen_port << endl; + + // Print network interfaces for diagnostic purposes + print_network_interfaces(); + + cout << "\nSaving packet data to '" << config.output_file << "'" << endl; + + // Check for port availability and conflicts + if (!check_port_availability(config.listen_port)) { + cout << "Note: Another process might be using the same port. The service may not work correctly." << endl; + } + + cout << "Press Ctrl+C to stop" << endl << endl; + + // Connection guide + cout << "Connection guide:" << endl; + cout << "- For local connection: ./packet_monitor_server --local" << endl; + cout << "- For remote connection: ./packet_monitor_server -f YOUR_IP:" << config.listen_port << endl; + cout << endl; + + // Print firewall warning + cout << "=== Important ===" << endl; + cout << "If connecting from a different machine:" << endl; + cout << "1. Ensure this port is open in your firewall" << endl; + cout << "2. If using a public IP, set up port forwarding on your router" << endl; + cout << "3. Test connectivity with: nc -zv YOUR_IP " << config.listen_port << endl; + cout << endl; + + // Server loop + fd_set read_fds, master_fds; + FD_ZERO(&master_fds); + FD_SET(server_socket, &master_fds); + int max_fd = server_socket; + + vector client_threads; + + while (running) { + // Copy the master set to read_fds + read_fds = master_fds; + + // Set up timeout for select + struct timeval timeout; + timeout.tv_sec = 1; // 1 second timeout for clean exit + timeout.tv_usec = 0; + + // Wait for activity + int activity = select(max_fd + 1, &read_fds, NULL, NULL, &timeout); + + if (activity < 0 && errno != EINTR) { + cerr << "Select error: " << strerror(errno) << endl; + break; + } + + // Check for new connections + if (FD_ISSET(server_socket, &read_fds)) { + struct sockaddr_in client_addr; + socklen_t addr_len = sizeof(client_addr); + + int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_len); + if (client_socket >= 0) { + // Log the connection + cout << "New connection from " << inet_ntoa(client_addr.sin_addr) + << ":" << ntohs(client_addr.sin_port) << endl; + + // Start a thread to handle this client + client_threads.push_back(thread(handle_client, client_socket, config)); + } else { + cerr << "Error accepting connection: " << strerror(errno) << endl; + } + } + } + + // Clean up + cout << "Waiting for client threads to finish..." << endl; + for (auto& t : client_threads) { + if (t.joinable()) { + t.join(); + } + } + + close(server_socket); + + if (log_file.is_open()) { + log_file.close(); + } + + cout << "Packet Receiver Service stopped" << endl; + + return 0; +} \ No newline at end of file