实验任务
-
使用
libpcap
编写捕包程序- 利用
libpcap
获取本机数据包,可自定义过滤条件 - 逐层解析数据包,获取
IPv4
数据包的src_ip
,dst_ip
,src_port
,dst_port
- 将上述四元组写入文件(每次运行生成一个新文件)
- 利用
-
效果展示 (源码)
规则文件格式:行内
or
关系,行间and
关系,该文件对应的过滤规则见上图记录文件首行运行方式:
./capture [rule_file]
实验步骤一、熟悉libpcap
使用
- 官方文档
- 捕包实例
- 过滤规则
实验步骤二、编写整体框架
-
libpcap
使用流程int main(int argc, char *argv[]) { char errBuf[PCAP_ERRBUF_SIZE]; // 存放设备 pcap_if_t* device; // 存放网络信息 bpf_u_int32 netAddr = 0, netMask = 0; char filter[BUFSIZE] = {0}; // 存放过滤器 struct bpf_program fp; // 网卡句柄 pcap_t *pcap_handle; // 读取过滤规则 if(argc == 2) { readFilter(filter); } // 查找网卡设备 if(pcap_findalldevs(&device, errBuf) != 0) { printf("error: %s\n", errBuf); exit(0); } if(device == NULL) { printf("no availabel device\n"); exit(0); } printf("use device: %s\n", device->name); // 网络地址信息 if(pcap_lookupnet(device->name, &netAddr, &netMask, errBuf) != 0) { printf("get net error: %s\n", errBuf); exit(0); } printf("net: %x\tmask: %x\n", netAddr, netMask); // 打开设备,运行时需要root权限 if((pcap_handle = pcap_open_live(device->name, 65535, 0, 1000, errBuf)) == NULL) { printf("pcap open live error: %s\n", errBuf); exit(0); } // 编译过滤规则 if(pcap_compile(pcap_handle, &fp, filter, 1, netAddr) != 0) { printf("compile filter errro: %s\n", errBuf); exit(0); } // 设置过滤器 if(pcap_setfilter(pcap_handle, &fp) != 0) { printf("set filter error: %s\n", errBuf); exit(0); } // 循环捕包,每次捕4个即返回 if(pcap_loop(pcap_handle, 4, pktHandler, NULL) != 0) { printf("loop error: %s\n", errBuf); exit(0); } // 释放资源 pcap_close(pcap_handle); pcap_freealldevs(device); return 0; }
-
读取过滤规则文件
void readFilter(char *filter) { FILE *f; // 规则文件 if((f = fopen(argv[1], "r")) == NULL) { printf("file error\n"); exit(0); } int tlen = 1, rlen; // 多条规则使用与连接 filter[0] = '('; while(fgets(filter + tlen, RULELEN, f) != NULL) { rlen = strlen(filter); filter[rlen - 1] = ')'; strncpy(filter + rlen, concator, clen); tlen = rlen + clen; filter[tlen++] = '('; } memset(filter + tlen - clen, 0, clen); printf("%s\n", filter); }
实验步骤二、解析MAC帧
-
以太网帧头部格式
typedef struct ether_header { u_char ether_dst[6]; u_char ether_src[6]; u_short ether_type; // 网络层协议类型 } EtherHeader;
-
解析流程
// 处理mac帧 void pktHandler(u_char *args, const struct pcap_pkthdr *pktHeader, const u_char *pktContent) { printf("----------------------------------------------\n"); // 输出捕获时间 printf("%sdata: %d(%d)\n", ctime(&pktHeader->ts.tv_sec), pktHeader->caplen, pktHeader->len); // 提取以太网头 EtherHeader *ehead = (EtherHeader *)pktContent; printf("src mac: "); getMac(ehead->ether_src); printf("dst mac: "); getMac(ehead->ether_dst); // 查看上层写协议类型,需要进行字节序转换 switch (ntohs(ehead->ether_type)) { case 0x0800: printf("ip protocol\n"); deliverPkt(pktContent + 14); break; case 0x0806: printf("arp protocol\n"); break; default: printf("no hit\n"); break; } }
实验步骤三、解析IP数据报
-
IP
报头格式typedef struct ip_header { u_char ip_version_headlen; // 版本和头部长度 u_char ip_serv_type; u_short ip_totlen; u_short ip_id; u_short ip_flag_fragoffset; u_char ip_ttl; u_char ip_protocol; // 传输层协议 u_short ip_checksum; u_int ip_src; // 源IP u_int ip_dst; // 目的IP } IpHeader;
-
传输层仅提取端口信息
typedef struct trans_header { u_short src_port; u_short dst_port; } TransHeader;
-
提取流程
// 处理IP数据报 void deliverPkt(const u_char *ipContent) { // 提取IP头 IpHeader *ihead = (IpHeader *)ipContent; // 提取传输层头,仅获取端口号 TransHeader *thead = (TransHeader *)(ipContent + ((ihead->ip_version_headlen & 0x0f) << 2)); printf("src ip: %s:%d\n", getIP(ihead->ip_src), ntohs(thead->src_port)); printf("dst ip: %s:%d\n", getIP(ihead->ip_dst), ntohs(thead->dst_port)); // 提取传输层协议 switch (ihead->ip_protocol) { case 1: printf("icmp\n"); break; case 6: printf("tcp\n"); break; case 17: printf("udp\n"); break; default: printf("no hit\n"); break; } }
实验步骤四、将捕获结果写入文件
- 打开记录文件,并将
printf
修改为fprintf
写入文件即可