Libpcap是一个开源C库,它提供了一个API,用于直接从Unix衍生操作系统的数据链路层捕获数据包。它被流行的数据包捕获应用程序(如tcpdump和snort)使用,使它们能够在几乎任何风格的Unix上运行。
下面是一个基于libpcap的简单数据包嗅探器应用程序的示例, 该示例通过libpcap库接收数据链路层frame数据包,重组为TCP数据流,或UDP数据包,带有TCP重传、乱序缓存等功能
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <pcap.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <netinet/udp.h>
#include <array>
#include <map>
#include <utility>
#pragma GCC diagnostic ignored "-Wunused-variable"
typedef std::array<u_char, 2048> Buf;
uint32_t nextExpectedSeq;
std::map<uint32_t, std::pair<uint32_t, Buf> > seq2UnprocessedMap;
const size_t buflen = 1024*1000;
u_char buf[buflen];
u_char* stream = buf;
u_char* recvp = buf;
uint32_t streamlen = 0;
void my_packet_handler(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
void printHex(const u_char *payload, int payload_length);
size_t processStream(u_char *stream, size_t streamlen);
int main(int argc, char **argv)
{
const char *filename = "p3p1.20220127.2.pcap";
char error_buffer[PCAP_ERRBUF_SIZE];
pcap_t *handle;
/* End the loop after this many packets are captured */
int total_packet_count = 20;
u_char *my_arguments = NULL;
struct bpf_program filter;
char filter_exp[] = "dst port 60001";
// handle = pcap_open_live(device, snapshot_length, 0, 10000, error_buffer);
// Open the device for live capture, as opposed to reading a packet
// capture file.
if ((handle = pcap_open_offline(filename, error_buffer)) == NULL)
{
printf("pcap_open_offline(): %s\n", error_buffer);
return 1;
}
if (pcap_compile(handle, &filter, filter_exp, 0, 0) == -1) {
printf("Bad filter - %s\n", pcap_geterr(handle));
return 2;
}
if (pcap_setfilter(handle, &filter) == -1) {
printf("Error setting filter - %s\n", pcap_geterr(handle));
return 2;
}
pcap_loop(handle, total_packet_count, my_packet_handler, my_arguments);
return 0;
}
/* Finds the payload of a TCP/IP packet */
void my_packet_handler(
u_char *args,
const struct pcap_pkthdr *header,
const u_char *packet)
{
printf("------------------------------------------------------------\n");
/* First, lets make sure we have an IP packet */
struct ether_header *eth_header;
eth_header = (struct ether_header *)packet;
if (ntohs(eth_header->ether_type) != ETHERTYPE_IP)
{
printf("Not an IP packet. Skipping...\n\n");
return;
}
/* The total packet length, including all headers
and the data payload is stored in
header->len and header->caplen. Caplen is
the amount actually available, and len is the
total packet length even if it is larger
than what we currently have captured. If the snapshot
length set with pcap_open_live() is too small, you may
not have the whole packet. */
printf("Total packet size: %d\n", header->caplen);
// printf("Expected packet size: %d bytes\n", header->len);
/* Pointers to start point of various headers */
// const u_char *ip_header;
// const u_char *tcp_header;
// const u_char *udp_header;
const u_char *payload;
/* Header lengths in bytes */
int ethernet_header_length = 14; /* Doesn't change */
int ip_header_length;
int tcp_header_length;
int udp_header_length;
int payload_length;
/* Find start of IP header */
struct iphdr* ip = (struct iphdr*)(packet + ethernet_header_length);
/* The second-half of the first byte in ip_header
contains the IP header length (IHL). */
ip_header_length = ip->ihl;
/* The IHL is number of 32-bit segments. Multiply
by four to get a byte count for pointer arithmetic */
ip_header_length = ip_header_length * 4;
// printf("IP header length (IHL) in bytes: %d\n", ip_header_length);
struct sockaddr_in source;
struct sockaddr_in dest;
memset(&source, 0, sizeof(source));
source.sin_addr.s_addr = ip->saddr;
memset(&dest, 0, sizeof(dest));
dest.sin_addr.s_addr = ip->daddr;
printf("%s -> ", inet_ntoa(source.sin_addr));
printf("%s\n", inet_ntoa(dest.sin_addr));
/* Now that we know where the IP header is, we can
inspect the IP header for a protocol number to
make sure it is TCP before going any further.
Protocol is always the 10th byte of the IP header */
u_char protocol = ip->protocol;
if (protocol == IPPROTO_TCP)
{
/* Add the ethernet and ip header length to the start of the packet
to find the beginning of the TCP header */
// tcp_header = packet + ethernet_header_length + ip_header_length;
struct tcphdr* tcp = (struct tcphdr*)(packet + ethernet_header_length + ip_header_length);
printf("%d -> %d\n", ntohs(tcp->source), ntohs(tcp->dest));
// tcp_header_length = tcp_header_length * 4;
tcp_header_length = tcp->doff * 4;
printf("TCP header length in bytes: %d\n", tcp_header_length);
printf("TCP seq: %u\n", ntohl(tcp->seq));
/* Add up all the header sizes to find the payload offset */
int total_headers_size = ethernet_header_length + ip_header_length + tcp_header_length;
// printf("Size of all headers combined: %d bytes\n", total_headers_size);
payload_length = header->caplen -
(ethernet_header_length + ip_header_length + tcp_header_length);
printf("Payload size: %d\n", payload_length);
if( payload_length < 0)
{
printf("Payload size is less than zero!!!\n");
return;
}
payload = packet + total_headers_size;
// printf("Memory address where payload begins: %p\n\n", payload);
/* Print payload in ASCII */
// printHex(payload, payload_length);
uint32_t tcpseq = ntohl(tcp->seq);
uint32_t tcplen = tcp->doff * 4;
uint32_t headerlen = total_headers_size;
const u_char* data = packet + headerlen;
uint32_t datalen = payload_length;
bool syn = (tcp->syn != 0);
bool fin = (tcp->fin != 0);
bool rst = (tcp->rst != 0);
bool psh = (tcp->psh != 0);
if(syn)
{
printf("SYN, will reset the next expected seq:%u\n", nextExpectedSeq);
seq2UnprocessedMap.clear();
nextExpectedSeq = 0;
}
else
{
nextExpectedSeq = tcpseq;
}
// if(datalen == 0)
// {
// return;
// }
if(nextExpectedSeq !=0 && tcpseq != nextExpectedSeq)
{
printf("Arrived seq %u is not expected %u\n", tcpseq, nextExpectedSeq);
if(seq2UnprocessedMap.find(tcpseq) != seq2UnprocessedMap.end())
{
printf("Duplicated packet %u\n", tcpseq);
}
else
{
Buf buf;
memcpy(buf.data(), data, datalen);
seq2UnprocessedMap.emplace(tcpseq, std::make_pair(datalen, buf));
}
return;
}
if(buflen - streamlen < datalen)
{
printf("Not enough buf, exit!!!\n");
exit(1);
}
// move unprocessed stream from buf tail to head
if(buf + buflen - recvp < datalen)
{
memcpy(buf, stream, streamlen);
stream = buf;
recvp = buf + streamlen;
}
//
memcpy(recvp, data, datalen);
recvp += datalen;
streamlen += datalen;
nextExpectedSeq = tcpseq + datalen;
// try to assemble data in cache
while(seq2UnprocessedMap.size() > 0 && seq2UnprocessedMap.find(nextExpectedSeq) != seq2UnprocessedMap.end())
{
const auto& datapair = seq2UnprocessedMap[nextExpectedSeq];
size_t datasize = datapair.first;
if(buflen - streamlen < datalen)
{
printf("Not enough buf, break!!!\n");
break;
}
// move unprocessed stream from buf tail to head
if(buf + buflen - recvp < datalen)
{
memcpy(buf, stream, streamlen);
stream = buf;
recvp = buf + streamlen;
}
memcpy(recvp, datapair.second.data(), datasize);
recvp += datasize;
streamlen += datasize;
seq2UnprocessedMap.erase(nextExpectedSeq);
nextExpectedSeq += datasize;
}
// process assembled stream
size_t res = processStream(stream, streamlen);
stream += res;
streamlen -= res;
}
else if (protocol == IPPROTO_UDP)
{
// udp_header = packet + ethernet_header_length + ip_header_length;
struct udphdr* udp = (struct udphdr*)(packet + ethernet_header_length + ip_header_length);
printf("%d -> %d\n", ntohs(udp->source), ntohs(udp->dest));
udp_header_length = 8;
int total_headers_size = ethernet_header_length + ip_header_length + udp_header_length;
payload_length = header->caplen -
(ethernet_header_length + ip_header_length + udp_header_length);
payload = packet + total_headers_size;
/* Print payload in ASCII */
printHex(payload, payload_length);
}
else
{
printf("Not a TCP/UDP packet. Skipping...\n\n");
}
return;
}
void printHex(const u_char *payload, int payload_length)
{
if (payload_length > 0)
{
const u_char *temp_pointer = payload;
int byte_count = 0;
while (byte_count++ < payload_length)
{
printf("%02x", *temp_pointer);
temp_pointer++;
if ((byte_count & 0x0F) == 0)
{
printf("\n");
}
else
{
printf(" ");
}
}
printf("\n");
}
}
size_t processStream(u_char *stream, size_t streamlen)
{
if(streamlen < sizeof(MDQPHdr))
{
return 0;
}
MDQPHdr* hdr = reinterpret_cast<MDQPHdr*>(stream);
size_t bodylen = hdr->Length;
printf("[MDQPHdr] Flag:%02x, TypeID:%02x, Length:%u, RequestID:%d\n", hdr->Flag, hdr->TypeID, bodylen, hdr->RequestID);
size_t packetlen = bodylen + sizeof(MDQPHdr);
if(packetlen > streamlen)
{
return 0;
}
else
{
return packetlen;
}
}
参考:
https://www.tcpdump.org/index.html
https://vichargrave.github.io/programming/develop-a-packet-sniffer-with-libpcap/#tcp-and-udp-header-parsing
https://www.devdungeon.com/content/using-libpcap-c