miniyuan
计算机网络 Lab4 路由协议
libpcap 库
libpcap 的整体执行流程如下:
核心数据结构
-
会话句柄
typedef struct pcap pcap_t; // 捕获会话句柄(不透明结构) typedef struct pcap_dumper pcap_dumper_t; // 转储文件句柄注:句柄可以类比函数指针,但是不完全相同。函数指针是内存地址,可以直接调用;句柄只是一个对象的标识,只能传入接口函数由其内部调用,实际上它可能是函数指针也可能仅仅是一个整数标识符。
-
数据包头部
struct pcap_pkthdr { struct timeval ts; /* 时间戳(捕获时间) */ bpf_u_int32 caplen; /* 实际捕获的数据长度(可能截断) */ bpf_u_int32 len; /* 数据包原始长度 */ };其中:
#include <sys/time.h> struct timeval { time_t tv_sec; // 秒数(从 1970-01-01 00:00:00 UTC 开始) suseconds_t tv_usec; // 微秒数(0-999999) };注:
caplenlen,当设置 snaplen 截断捕获时,两者可能不等。 -
回调函数
在抓到包后调用,按用户自定义的方式处理数据包。
typedef void (*pcap_handler)(u_char *user, const struct pcap_pkthdr *h, const u_char *bytes);user:用户自定义数据h:libpcap 提供的数据包的头部bytes:libpcap 提供的数据包的实际内容(以太网帧开始)
-
BPF 过滤器程序
伯克利数据包过滤器(Berkeley Packet Filter)。
struct bpf_program { u_int bf_len; /* 指令数量 */ struct bpf_insn *bf_insns;/* 指向过滤指令数组的指针 */ }; -
网络设备信息
typedef struct pcap_if { struct pcap_if *next; /* 下一个设备 */ char *name; /* 设备名(如 "eth0") */ char *description; /* 设备描述 */ struct pcap_addr *addresses; /* 地址列表 */ u_int flags; /* 标志(PCAP_IF_LOOPBACK等) */ } pcap_if_t; -
统计信息
struct pcap_stat { u_int ps_recv; /* 收到的包数 */ u_int ps_drop; /* 丢弃的包数(缓冲区满) */ u_int ps_ifdrop; /* 接口层丢弃的包数 */ };
核心函数接口
-
设备查找与打开
由于 PC 上有多个网卡(设备),比如有线网卡
wth0、无线网卡wlan0、回环网卡lo,所以我们必须指定捕获设备。获取设备信息:
函数原型 功能说明 char *pcap_lookupdev(char *errbuf)返回第一个可用的非回环设备名(已废弃,建议用 pcap_findalldevs)int pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf)获取所有可用网络设备列表, alldevsp是一个在堆上的链表void pcap_freealldevs(pcap_if_t *alldevs)释放设备列表,也即释放堆上分配的内存 int pcap_lookupnet(const char *device, bpf_u_int32 *netp, bpf_u_int32 *maskp, char *errbuf)获取设备的网络地址和掩码 打开实时捕获设备:
pcap_t *pcap_open_live(const char *device, int snaplen, int promisc, int to_ms, char *errbuf);snaplen: 最大捕获字节数(通常设 65535 或 BUFSIZ)。promisc: 1 开启混杂模式,0 非混杂。混杂模式下会接收所有经过设备的数据包。to_ms: 读取超时(毫秒),0 表示无超时阻塞等待。
离线操作:
// 打开一个之前保存的抓包文件 pcap_t *pcap_open_offline(const char *fname, char *errbuf); // 创建虚拟句柄(用于编译 BPF 过滤器) pcap_t *pcap_open_dead(int linktype, int snaplen); -
数据包捕获
函数原型 功能说明 int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)循环捕获 cnt个包(-1 表示无限),每次捕获调用callbackint pcap_dispatch(pcap_t *p, int cnt, pcap_handler callback, u_char *user)类似 loop,但超时或处理完缓冲区内数据即返回 const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)捕获单个数据包,阻塞等待,返回指向原始数据包的指针 int pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header, const u_char **pkt_data)捕获单个包(返回 1:成功,0:超时,-1:错误,-2:离线文件EOF)void pcap_breakloop(pcap_t *p)中断正在运行的 pcap_loop/dispatch -
数据包过滤
// 编译过滤表达式 int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask); // 应用过滤器 int pcap_setfilter(pcap_t *p, struct bpf_program *fp); // 释放编译后的 BPF 程序内存 void pcap_freecode(struct bpf_program *fp);p:提供编译所需的链路层类型、快照长度。fp:是输出参数,编译生成的 BPF 指令会存储在这里。str:人类可读的过滤表达式字符串,例如"tcp port 80"或"host 192.168.1.1"。optimize:是否优化生成的 BPF 指令,1表示优化,0表示不优化。netmask:网络掩码,用于解析net/mask这类过滤器;不需要时可传PCAP_NETMASK_UNKNOWN(通常就是0)。
示例流程:
pcap_t *handle = pcap_open_live("eth0", 65535, 1, 1000, errbuf); struct bpf_program fp; pcap_compile(handle, &fp, "icmp or tcp port 80", 1, net); pcap_setfilter(handle, &fp); pcap_freecode(&fp); -
数据包转储
// 打开转储文件 pcap_dumper_t *pcap_dump_open(pcap_t *p, const char *fname); pcap_dumper_t *pcap_dump_fopen(pcap_t *p, FILE *fp); // 写入数据包(可作为 pcap_loop 的回调函数) void pcap_dump(u_char *user, struct pcap_pkthdr *h, u_char *sp); // 刷新缓冲区到磁盘 int pcap_dump_flush(pcap_dumper_t *p); // 关闭转储文件 void pcap_dump_close(pcap_dumper_t *p);注:
-
辅助与清理函数
函数 说明 int pcap_datalink(pcap_t *p)获取链路层类型(如 Ethernet 为 1) int pcap_snapshot(pcap_t *p)获取 snaplen int pcap_stats(pcap_t *p, struct pcap_stat *ps)获取捕获统计 char *pcap_geterr(pcap_t *p)获取错误信息 void pcap_perror(pcap_t *p, char *prefix)打印错误信息 void pcap_close(pcap_t *p)关闭会话并释放资源
典型使用流程
char errbuf[PCAP_ERRBUF_SIZE];
pcap_if_t *alldevs;
pcap_t *handle;
struct bpf_program fp;
// 1. 查找设备
pcap_findalldevs(&alldevs, errbuf);
// 2. 打开设备
handle = pcap_open_live(alldevs->name, 65535, 1, 1000, errbuf);
// 3. 编译并设置过滤器
pcap_compile(handle, &fp, "port 80", 0, PCAP_NETMASK_UNKNOWN);
pcap_setfilter(handle, &fp);
// 4. 循环捕获
pcap_loop(handle, 0, packet_handler, NULL);
// 5. 清理
pcap_freecode(&fp);
pcap_close(handle);
pcap_freealldevs(alldevs);
参考文档:
common.h
网络设备有关数据结构
/* Network device information */
typedef struct {
char name[32]; // Device name
pcap_t *handle; // pcap handle
pthread_t thread_id; // Capture thread ID
int index; // Device index
} net_device_t;
/* Packet buffer entry */
typedef struct {
net_device_t *device; // Ingress device
uint8_t data[PACKET_BUF_SIZE]; // Packet data
uint32_t len; // Packet length
uint64_t timestamp; // Capture timestamp
} packet_entry_t;
/* Global packet buffer */
typedef struct {
packet_entry_t packets[MAX_PACKETS];
int head;
int tail; // circular buffer
pthread_mutex_t lock; // mutex lock
} packet_buffer_t;
lab
第一次执行会写路由表,所以 Hub 中会出现 IPv6 包。
Makefile 的 test_hub 增加 sleep 1,保证避免过快 killall 导致 ping reply 没有记录