libpcap的使用

发布时间 2023-09-12 17:07:59作者: 写bug的民工

由于需要对tcpdump抓的包做一些细致的处理,得到我想要的信息,所以需要使用libpcap对pcap文件进行一些处理。

主要用到以下函数:
pcap_open_offline(file_path, errbuf);
用于打开一个pcap文件,返回一个pcap_t类型的指针,它代表了一个设备句柄,此处表示的是一个伪设备句柄。

pcap_loop(handle, -1, pkt_handler, (u_char*)handler_param)
用于遍历上述的pcap文件的每一条数据包,其中pkt_handler是对数据包进行处理的回调函数,它包含u_char *user_data, const struct pcap_pkthdr pkthdr, const u_char packet这三个参数,其中user_data就是handler_param的指针值,pkthdr是包记录的pcap头部信息,这个头部信息中包含了包的长度、包的接收时间。

struct pcap_pkthdr {
    struct timeval ts;  // 时间戳,表示捕获数据包的时间
    bpf_u_int32 caplen; // 捕获数据包的实际长度
    bpf_u_int32 len;    // 数据包的原始长度(包括截断的部分)
};

packet是包的实际内容,从链路层的头部位置开始。
不过要特别注意,链路层协议有很多种:
https://www.tcpdump.org/linktypes.html
不同的链路层协议对应的链路层头部长度和结构都不一样。

如果是从一个以太网网卡抓的包,那链路层头部的类型就是ether_header, 头部长度就是14,对应的link_type类型码是DLT_EN10MB
如果pcap文件是通过tcpdump命令加-i any参数抓的包得到的文件,那链路层头部其实是一个linux下特定的伪头部类型(为了兼容各种类型的链路层设备),它的长度是16, 对应的link_type类型码是DLT_LINUX_SLL:
https://www.tcpdump.org/linktypes/LINKTYPE_LINUX_SLL.html

这个链路层的头部类型是通过pcap_datalink(handle)来获取的,实际对应的存储位置是pcap的文件头的network字段:
https://wiki.wireshark.org/Development/LibpcapFileFormat

链路层头部后面就是网络层头部,它的类型一般是存储在上一层的链路层头部中,比如DLT_LINUX_SLL类型的头部中有Protocol type字段就是描述下一层的协议类型,对于IP类型这里的值是0x4,头部长度是在iphdr的ver_len的ihl(得到完整的头部字节长度还需要乘以4):
https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/ip.h#L87

再下一层就是传输层头部(如果包的内容层次中有的话),TCP协议或者UDP协议,对于TCP协议的头部长度是tcphdr的doff(得到完整的头部字节长度还需要乘以4)
https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/tcp.h#L25

对于tcp的数据部分的长度,tcp头部信息中是没有的,是通过ip头部的包总长度字段(ip头部长度+ip数据包长度),减去ip头部长度和tcp头部长度来得到。

tcp数据部分的内容,就是上层应用自定义的格式了,这里以mysql协议举例:

在command phase,包的内容格式为MySQLPacket,它们都是以3 Bytes的payload部分长度 + 1 Byte的sequence + n Bytes的payload部分组成。
当我们想对mysql的请求响应时间进行统计时,就以sequence为0的MySQLPacket作为发送开始时间点,同一个tcp连接上的下一个sequence为1的MySQLPacket作为响应时间点。

特别需要注意的一点是,如果直接从包数据中的裸字节数据进行解析为多字节的类型比如int、short等,需要注意字节序,tcp/ip/udp中使用的网络字节序(和大端字节序一样),本地字节序一般为小端(linux就是小端),需要使用ntohs、nothl进行转换或者自己按照格式自定义转换。