网络抓包数据详解
- 最近在使用qt重写一个抓包软件,原因是用c#感觉获取速度有延迟(用的第三方库抓包,然后拼包)
- C#使用sharppcap和xx第三方库,其实是对npcap库的二次封装
- 想用c++直接调用npcap库,然后自己从网络包中提取出自己想要的tcp数据包,最后按照自定义协议拼接处业务数据包
- 那么如何从网络数据包中提取出tcp数据包呢?
- 按照规则解析网络数据,这里我们要分成两种情况
- 本地回环的数据
- 真实的网络数据
- 按照规则解析网络数据,这里我们要分成两种情况
拆解网络数据包
以太网层
- 一般14个字节
| dest mac | src mac | flags |
|---|---|---|
| 6个字节 | 6个字节 | 2个字节 |
| 我到哪里去 | 我从哪里来 | 我是干什么的 |
- 用48位也就是6个字节来表示一个mac地址
- 最后的两个字节是这个数据包的标志,我们这里只关注一个值:
0800,表示ipv4
ip层
-
一般ip包头是20个字节,以4个字节为一组,分成5组。
-
-
第一组:
- 第一个字节的高4位:版本号,因为是ipv4,所以这里一般是4
- 第一个字节的低4位:长度,一般这里是5,也就是 5 * 4 = 20个字节
- 第二个字节:8位服务类型,tos,这个我也不是很清楚,有需要再深入了解
- 第三四个字节:ip包的总长度:ip头+tcp头+tcp数据 = 总长度
-
第二组:
- 第一二个字节:标志,不清楚干嘛的
- 第三四个字节:
- 高三位:标志
- 低13位:偏移量,拆包情况下,这个包在原来总包中的顺序
-
第三组:
- 第一个字节:生存时间 ttl,一般是32或者64,表示最多传播 n 个路由器
- 第二个字节:8位协议,表示这个ip数据包的类型,tcp or udp,其中如果是06的话就是tcp
- 第三四个字节:校验和
-
第四组:
- src ip
-
第五组:
- dest ip
tcp层
- 一般tcp包头是20个字节,我们还是将它分成5组
- 第一组:
- 第一二个字节: src port
- 第三四个字节:dest port
- 第二组:
- 序号
- 第三组:
- 确认序号
- 第四组:
- 第一个字节:
- 高4位:长度,一般为5(5 * 4 = 20)
- 低四位的前三个位:保留
- 低四位的最后一个位: Accurate ECN
- 第二个字节:
Congestion Window Reduced、ECN-Echo、Urgent、Acknowledgment、PUsh、Rest、Syn、Fin
- 第三四个字节:窗口
- 第一个字节:
- 第五组
- 第一二个字节:校验和
- 第三四个字节:紧急指针
提取Tcp数据(payload)
- 了解了以上网络分层后,可以轻松的将tcp数据提取出来,最主要的是找到ip的len和tcp的len,随后就知道payload的偏移量啦,这里我给出一个demo
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
uint len = header->len;
if (len < 55) return; // tcp packet must be larger than 54
int offset = 0;
// 以太网
if (pkt_data[12] == 8 && pkt_data[13] == 0)
{
// ipv4数据包
offset = 14;
}
else {
return;
}
// ip
u_char ip_len = (pkt_data[offset] & 15) * 4;
offset += ip_len;
// tcp
u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4;
offset += tcp_len;
int payload_len = len - offset;
qDebug() << QString("data len: %1").arg(payload_len);
}
-
但是经过测试时,发现了两个问题
- tcp包有数据填充,导致我写的代码误判长度
- 本地回环没有以太网层
-
先说第一个问题
-
-
修改后的代码为
int ip_pack_total_len = pkt_data[offset+2] * 256 + pkt_data[offset+3]; u_char ip_len = (pkt_data[offset] & 15) * 4; offset += ip_len; // tcp u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4; offset += tcp_len; int payload_len = ip_pack_total_len - ip_len - tcp_len;
-
本地回环的特殊性
-
在测试过程中发现,本地回环的数据包,没有以太网层,而是以
02 00 00 00开头,以后紧接着ip数据包,如下图所示 -
那么只需要在判断非以太网分支那里增加一个分支,具体代码如下
if (pkt_data[0] == 2 && pkt_data[1] == 0 && pkt_data[2] == 0 && pkt_data[3] == 0) { // loop back offset = 4; } else return;
附录
完整代码
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
Q_UNUSED(param)
Q_UNUSED(pkt_data)
uint len = header->len;
if (len < 55) return; // tcp packet must be larger than 54
int offset = 0;
// 以太网
if (pkt_data[12] == 8 && pkt_data[13] == 0)
{
// ipv4数据包
offset = 14;
}
else {
if (pkt_data[0] == 2 && pkt_data[1] == 0 && pkt_data[2] == 0 && pkt_data[3] == 0)
{
// loop back
offset = 4;
}
else
return;
}
// ip
int ip_pack_total_len = pkt_data[offset+2] * 256 + pkt_data[offset+3];
u_char ip_len = (pkt_data[offset] & 15) * 4;
offset += ip_len;
// tcp
u_char tcp_len = (((pkt_data[offset + 12]) >> 4) & 15) * 4;
offset += tcp_len;
int payload_len = ip_pack_total_len - ip_len - tcp_len;
qDebug() << QString("data len: %1").arg(payload_len);
}