python发送以太网报文_python之分解以太帧
掌握该技术的前提是熟悉socket(int domain, int type, int protocol)接口,并清楚地认识到其在数据传输中的应用。
此外,在结合PF_PACKET域的同时配合使用数据类型SOCK_RAW以及相应的协议配置后就能实现对所需通信协议以太帧的捕获与解析
1.获得各个协议的头部
以太协议类型有很多,仅贴上一部分,如下图:
图一
本文仅介绍0x0800(IPV4)的监听与拆分。现在我们就可以利用:
s=socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(0x0800))
遵循ipv4协议生成的以太帧。随后接收,并按照协议格式分解。接着查看以太帧格式部分,如下:
图二
我们获取的数据中没有Preamble部分,此部分被以太网硬件过滤掉。这里说明一下,上图中的Type/Length表示数据的类型和长度信息
该特性仅存在于Ethernet-II中,在原始IEEE 802.3帧中,这部分表示负载数据的长度。如何区分?其数值较小。
1500代表帧负载数据长度的同时, >=1536(十六进制数值为Ox 6 2 4)则用于指示Ethernet-II类型的字段(如图一所示)。
此外需要注意的是,在VLAN(虚拟局域网)中类大小设定为0x8100时遵循这一规范的具体情况。
图三
vlan标签中有12位用于表示vLAN id。此处与本文关联不大,主要介绍其广泛应用。继续正文
其中,我们使用 pkt 标记为 str 类型,并根据图二所示的信息, pkt 的前 14 个字符包含了目的 mac 地址(共 6 字节)。
A MAC地址和一个类型标识(各占6字节与2字节)之间存在对应关系。A IPv4数据包会被封装进一个以太网帧里,在后续步骤需要考察其IPv4头字段的具体格式是什么样的。
图四
从图四可以看出IPv4最少占用20个字节(每个字段占据四个字节共计五个字段),因此可以通过pkt[14:34]的方式获得其头部结构
由于在IPv4层上存在TCP和UDP等多种通信协议,在这里我们主要介绍如何获取TCP数据。其中当IPv4头字段中的protocol字段值为6时,则对应的是TCP报文。
TCP header如下:
图五
Tcp头部最小也是20bytes,所以tcp_header=pkt[34:54]。
2.获得每个协议的每个字段
在此之前,则提一个问题。例如要把4存进int型变量的低8位,在Python中这个还是相对简单的做法;随后则需再次取出即可。
不算太难。但如果将4存储于2bytes的数据类型中或者从保存4的字符串类型(str)中获取,则没有像C语言这样强大的处理能力了。
简单。这里介绍struct模块,有了它,一切就简单了!我们通过如下:
tcp_h=struct.unpack("
此方法就得到了tcp头部的个部分信息。
首先"'、'!'或者'='。H表示无符号16位,
I 代表 unsignedint, b 代表 signed char. 因此 HHII 分别是 source port(16 bits)和 destination port(16 bits)。
Sequence number(32bits)、Ack number(32bits)(看图五),bbHHH同理。
当你设置相应的unpack()函数返回一个与之匹配的字符序列数组时
就会返回长度为9的数组,并且每个元素都是int型。
同理,对于以太帧头部和ipv4头部处理与tcp header类似。
更详细的格式化字符对照如下:
3.代码实现
通过上面讲述实现主体代码如下:
1 ipv4=0x0800
2 udptype=0x11
3 tcptype=0x06
4 bufsize=1500
5 s=socket.socket(socket.PF_PACKET,socket.SOCK_RAW,socket.htons(ipv4))6
7 whileTrue:8 pkt=s.recv(bufsize)9 if len(pkt)<=0:10 break
elif packet length exceeds 54 bytes:
assign ethernet header as the first 7 bytes of the packet
assign ip header as the next 20 bytes of the packet
unpack ethernet header into three parts consisting of two byte source IP, two byte destination IP, and two byte protocol fields
unpack ip header to retrieve target IP address from the specified field
16、17行用了两个类对ip和tcp头部解析,如下:
1 import os, socket
2 import struct
3 class IPv4Parser:
4 def init(self, name):
5 self.dict.update({self.class.name: name})
6 @property
7 @staticmethod
8 def version():
9 return int.from_bytes(socket.inet_aton(self.ip), byteorder='big') >> 4
9 returnv10 @property11 defheaderLen(self):12 l=(self.ip[0]&0x000f)*4
13 returnl
@property
def protocol(self):
return self.ip[6]14 defsourceip(self):15 sip=self.__getstrip(self.ip[8])16 returnsip17 defdestinationip(self):18 dip=self.__getstrip(self.ip[9])19 returndip20 deftotalLen(self):21 return ((self.ip[2]&0xff)<<8)|((self.ip[2]>>8)&0xff)
22 defchecksum(self):23 pass
该方法用于获取IP地址的各部分.
该方法通过按位与运算获取IP地址的低字节,中字节,高字节并拼接成字符串.
类TCPParser定义了一个属性属性属性属性属性属性属性属性属性属性.
属性sport返回TCP连接的第一个端口.
属性dport返回TCP连接的第二个端口.
38 @property39 defheaderLen(self):40 return ((self.tcp[4]>>4)&0x0f)*4
该段未对IP和TCP协议的头信息进行完全解析。如需进一步处理,请特别关注字节顺序。
将两个代码块放置在一个文件中,在通常情况下会顺利运行。请确保您拥有执行此操作所需的root权限。
信息如下,当然也可以输出更详细的信息。
我们完全可以更进一步,对于tcp上层的协议比如http继续解包。
