如何用C语言基于libpcap复现IP/TCP层功能并提取应用层PDU?
Great question—reconstructing application-layer PDUs exactly as the server sees them from raw packet captures is a common challenge when working with protocol analyzers or troubleshooting industrial protocols like IEC 60870-5-104. Let's break down how to implement this with libpcap in C, plus some tools that can save you from reinventing the wheel:
To match what the server's application layer receives, you need to replicate the OS's TCP/IP stack behavior step by step:
1.1 IP Fragment Reassembly
First, you'll need to handle fragmented IP packets:
- Check the IP header's
MF(More Fragments) flag andfragment offsetfield to identify partial packets. - Use a hash map (keyed by source IP, destination IP, and IP ID) to track in-progress fragment sets.
- When all fragments for an IP datagram are collected, concatenate them into a full IP packet before passing it to the TCP processing stage.
- Implement a timeout mechanism to clean up stale fragment sets (in case some fragments are lost in capture).
Here's a quick code snippet to detect fragments:
#include <netinet/ip.h> // Assume packet_data points to the start of the Ethernet frame struct iphdr *ip_header = (struct iphdr *)(packet_data + ETH_HLEN); bool is_fragment = (ip_header->frag_off & IP_MF) || (ip_header->frag_off & IP_OFFSET); if (is_fragment) { // Add fragment to reassembly cache } else { // Pass full IP packet directly to TCP processing }
1.2 TCP Stream Processing (Out-of-Order, Retransmissions, Duplicates)
This is the most complex part—you need to mimic the server's TCP receive buffer behavior:
- Identify unique TCP streams using the 4-tuple: source IP, destination IP, source port, destination port.
- For each stream, track:
- The next expected sequence number
- A sorted cache for out-of-order segments (stored by sequence number)
- A buffer for contiguous, ready-to-process payload data
- Duplicate segments: Check if the segment's sequence range already falls within received data—if so, discard it.
- Out-of-order segments: Store them in the sorted cache. When a subsequent segment fills the gap, concatenate all contiguous segments into the ready buffer.
- Retransmissions: Treat them like duplicates unless they carry updated TCP options (e.g., SACK) that require state adjustments.
Example stream structure to track state:
typedef struct TcpStream { uint32_t src_ip; uint32_t dst_ip; uint16_t src_port; uint16_t dst_port; uint32_t next_expected_seq; struct OutOfOrderSegment *ooo_cache; // Sorted linked list of out-of-order segments uint8_t *ready_payload; size_t ready_payload_len; } TcpStream;
1.3 IEC 60870-5-104 PDU Splitting
Since IEC 60870-5-104 uses a byte-stream over TCP, you need to handle sticky packets and partial PDUs:
- The protocol's APCI header includes a length field—use this to determine the full size of each PDU.
- Maintain an application-layer buffer: append new TCP payload data to it, then repeatedly check if you have enough bytes to extract a full PDU.
- If the buffer has insufficient bytes for a full PDU, wait for more data from subsequent TCP segments.
Simplified code for splitting PDUs:
#define IEC104_MIN_APCI_LEN 4 // Start char (1) + Length (2) + Type ID (1) void process_iec104_data(uint8_t *tcp_payload, size_t payload_len, uint8_t **app_buffer, size_t *app_buf_len) { // Append new data to application buffer *app_buffer = realloc(*app_buffer, *app_buf_len + payload_len); memcpy(*app_buffer + *app_buf_len, tcp_payload, payload_len); *app_buf_len += payload_len; // Extract complete PDUs while (*app_buf_len >= IEC104_MIN_APCI_LEN) { uint16_t pdu_total_len = ntohs(*(uint16_t *)(*app_buffer + 1)) + 2; // Add start char + length field size if (*app_buf_len < pdu_total_len) { break; // Not enough data for a full PDU } // Process the complete PDU (matches what the server's app layer gets) handle_complete_iec104_pdu(*app_buffer, pdu_total_len); // Shift remaining data to the start of the buffer memmove(*app_buffer, *app_buffer + pdu_total_len, *app_buf_len - pdu_total_len); *app_buf_len -= pdu_total_len; } }
Implementing full TCP/IP stack logic from scratch is error-prone. These mature libraries handle the heavy lifting:
- libnids: A dedicated network intrusion detection library with built-in IP reassembly and TCP stream reconstruction. It delivers contiguous TCP payloads to your callback function, so you only need to handle the IEC 60870-5-104 PDU splitting. It natively handles out-of-order segments, retransmissions, and duplicates.
- libtcpflow: A lightweight library focused on TCP stream reconstruction. It can write streams to files or pass them directly to your application, eliminating the need to code TCP state management.
- PF_RING + ntop Libraries: For high-performance capture scenarios, PF_RING provides ultra-fast packet capture, and ntop's accompanying libraries include robust stream reassembly logic.
- Follow TCP specifications strictly, including 32-bit sequence number wrap-around.
- Handle TCP options like SACK (Selective Acknowledgment), which can change how out-of-order segments are processed.
- Mimic the server's receive window behavior—if capture data includes window updates, adjust your buffer logic to avoid incorrectly discarding segments.
- Validate your implementation using known good IEC 60870-5-104 traffic: compare your reconstructed PDUs with a packet capture taken directly at the server's application layer.
内容的提问来源于stack exchange,提问作者reddi hari

