·一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
今天的目的是实现一个简单的数据抓包工具,为此我们将借助原始套接字的强大功能。 出于简化问题的考虑,这里不考虑因为特殊目的增长协议头部数据的分组。仅仅考虑每一层头部都是准确的长度的数据包。 对于不属于这类的数据包,直接放弃处理。
首先声明一个原始套接字,并且我们要在数据链路层的级别使用它,以获得尽可能多的数据信息。
sock = socket(PF_PACKET,SOCK_RAW,htons(ETH_P_IP))
然后开始不断从中读取数据
size = recvfrom(sock, buffer, 1024, 0, 0, 0);
在我们限定的这种条件下 size 的值必须得是大于 42的 。
为什么呢? 因为他有14字节的 mac 帧。 20字节的 IP 头部 8 字节的头,可能是TCP可能是UDP,也有可能是ICMP等等。 所以我们先去掉不符合这些条件的包。
if(size < 42){
continue;
}
然后我们可以开始解析数据了,首先是两个mac地址,各自占了6个字节。
auto p = buf;
char dst_mac[6];
char src_mac[6];
memcpy(dst_mac, p, 6);
memcpy(src_mac, p + 6, 6);
之后是我们的IP帧,我们要拿到其中的源IP和目标IP。首先要从数据包的最开始跳到IP包的开始,然后在IP帧的后半部分才是两个IP地址。另外我们要从IP头中得到后续数据使用的协议。
p += 14;
char src_ip[4];
char dst_ip[4];
memcpy(src_ip, p + 12, 4);
memcpy(dst_ip, p + 16, 4);
char proto = p[9];
然后是我们的其他数据解析。这里需要首先对协议进行识别。协议定义都有对应的常量,因此我们简单写个switch语句或者ifelse语句就行了。 不同情况不同处理。
p += 20;
switch (proto)
{
case IPPROTO_TCP:
//可以获取端口,syn, ack, fin, rst之类的信息
break;
case IPPROTO_UDP:
//可以获取端口,
break;
default:
break;
}
注意p偏移到了这部分数据。
对于具体需要的数据可以用类似的方式获取。
先前得到的信息我们也可以按自己的需要处理。注意对于IP地址可以直接使用系统自带的格式转换函数得到字符串形式。
这里还有一个在某些时候有效的优化方法,由于这里数据不会被修改,如果在本次循环之间数据就能被处理完成,那么我们可以不同定义这么多中间变量,直接让指针指在某个位置,只要处理时注意固定长度即可。