在上一期中,我们讨论了基本的网络连接。现在,将某些内容发送到服务器时,不仅是发送的数据,而且是有关数据的元数据。它被使用,因此路由器知道可以在哪里引导网络流量(或在某些情况下阻止它)。在本文中,我们将研究涉及IP信息的网络传输的一部分。
但是首先是二进制的
二进制是1s和0s的序列,代表了两个功率的不同级别。例如,如果我们采用二进制值1111,它将变为:
2^0 + 2^1 + 2^2 + 2^3
或15. 2^0为1允许奇数值。重要的是要注意,只有在序列中有1时才能计算功率。所以:
0011 = 3(2^0 + 2^1)
0101 = 5(2^0 + 2^2)
关于二进制数据如何在较低级别上工作,通常将其置于固定值中。以8位(1个字节)值为例。这将使最大值为255。这确实提出了如何处理值1的问题1。在二进制1中,仅由1(2^0)表示。那么如何处理其他7位?固定长度的二进制值在左侧通过0填充处理。这意味着1现在变为:
00000001
在Python中,数据类型旨在占据一定数量的字节。此类数据类型占用的最低空间是1个字节或8位。由于网络信息可以在少于空间保护的字节中包装,因此位运算符用于与字节值的基础位数据一起使用。以8位分解为两个4位零件的8位字段。一个持有值6,另一个持有值3:
6 | 3 |
---|---|
0110 | 0011 |
现在的问题变成了我们如何获得各自的价值观?为了解决这个问题,我将打开python并设置一个临时值,并使用4位组合值:
my_value = 0b01100011
我们首先要考虑获得值6,这是8位值的前4位。正确的移位操作员可用于此。几个示例的工作方式:
>>> format(my_value, '08b')
'01100011'
>>> format(my_value >> 1, '08b')
'00110001'
>>> format(my_value >> 2, '08b')
'00011000'
>>> format(my_value >> 3, '08b')
'00001100'
>>> format(my_value >> 4, '08b')
'00000110'
format
带有08b
,让我们可以将变量的二进制值打印为特定长度(8bits),并在需要时用0击倒左侧。在每次迭代中,从右侧删除了一个值,并将0添加到左侧。通过在4个空间上移动6个值的值并用0填充左侧,它基本上取出了第二个4位截面,并且由于左侧用0填充,我们获得了值6:
>>> 0b00000110
6
因此,一个合乎逻辑的想法是尝试右移的右移的反面。问题是因为方向相反的替换0不再是填充,而是实际值的一部分。例如:
>>> format(my_value, '08b')
'01100011'
>>> format(my_value << 1, '08b')
'11000110'
>>> format(my_value << 2, '08b')
'110001100'
>>> format(my_value << 3, '08b')
'1100011000'
>>> format(my_value << 4, '08b')
'11000110000'
>>> my_value << 4
1584
这是因为左移会导致1击中并应用于两个最初的功率值。为了解决这个问题,我们可以使用位运算符进行最后4位。位运算符“&”将占两个二进制值,如有必要,请用零填充,并基于此逻辑比较每个值:
:- 0和0是0
- 0和1是0
- 1和1是1
由于这种逻辑,我们可以将值00001111
用作快捷方式来掩盖前4位,因为零填充物,最后4个位将如下。速记0xF
也起作用,因为十六进制的F是1111,它将被4 0填充以做同样的事情:
>>> my_value & 0b00001111
3
>>> my_value & 0xF
3
请注意,尝试第一个X位的尝试无法正常工作。因此规则是:
- 如果您想要一个值的第一个x位:value >>(length_of_value_in_bits -x)
- 如果您想要一个值的最后一个x位:value&0b [1重复x times]
这两个都认为该值的位长度大于或等于您要获得的位。
开始小包嗅探
使用python,您可以利用socket
库进行简单内置的数据包sniffer:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
s.bind(("192.168.1.81", 0))
s.ioctl(socket.SIO_RCVALL,socket.RCVALL_ON)
请注意,此操作将需要管理权限,例如 *nix中的root/sudo或Windows中的高架命令提示。因此,首先创建了原始插座。这种类型的套接字让我们以原始形式查看数据包,而在对我们进行解析和抽象后。需要bind
,因此我们的数据包sniffer可以接收数据。提供端口0基本上告诉OS选择一个随机的免费端口,因为我们不在乎我们要使用哪种端口。最后,SIO_RCVALL
设置为RCVALL_ON
,该RCVALL_ON
告诉我们的网络接口控制器(NIC)输入一个称为promiscuous mode的特殊状态。这有效地使我们的数据包嗅探器能够接收数据包。
现在要接收一个数据包,我们需要知道要使用多少数据。事实证明,Internet协议规范规定了max a packet can be is 65,535 octets。八位位只是在1个字节不一定等于8位时指定8位的一种方法。因此收到一个数据包:
packet = s.recvfrom(65535)
packet = packet[0]
packet = packet[0]
分配是因为s.recvfrom
返回了(数据,地址)的元组,地址部分对我们不关心,只有第一个数据值是。现在,数据包看起来像这样(截断了很大):
>>> packet
(b'E\x00\x05\x1dw\'@\x00\x80\x06\x00\x00\xc0\xa8\x01Qh\xf4*D\xccS\x01\xbb\xe5\x80\xad\x0b\xdeU\xce\xb3P\x18\x04\x01ZA\x00\x00\x17\x03\x03\x04\xf0\xa5\xd2ns\xa4\x14\xe1\xda\x03[\x06\x11[hC\\x8\x8f\xd1\x02\x06\xb2\xa1%\x91|D\xbclt\x0f GB4\xf1h\xf2Y\xfa\xee\x05i~\xfb\x88\xe9\xbeN\x17t\x1f\xb0K#\x8b\xa9\xa1P\x107l{\x9e]\xb2\x9c\x13\x10%\x02`?\xdd\x0b\xc20\x95\xbf\x07>\xa1\xd1\xb8\xc5\xe8d\x8e\xbf{\xb5\x84ip\x9aJ\x1c\x8e{\x1f\xae1y\xc0\x9f\x89\xda\xaaZ\xac[\x80\xb6\xa3\xd1YX\xed\xde\x8c\xcea\x84\x13w5\xd2\x8aD9Ur\xe1\xdf\x1c-\x1aWB\tl_\x921Ek\xf7\xd7\xca\xb7\x148\x18\x91\x15\\!M\xaf\xab\xc2\xbf\\F\x06\xba\x8d\xfe\t%\xc4b\xa1\xf6\xb2\x0eS\x9b@F\xb1&\x8e\x19Q\xa7\x80\x10\xe3|L~\xbbr\x8f\xb2\x06\x844\xab\xfe\x0e\xd1\x08[<\xc1;~i\xfc\x92\x89\xf9\x8f\xe6\xf7[\xee\xf9\xa4;o\xaf\xde\xcd<snip>
IP标头
数据包的第一部分是IP标头。这是一系列特殊的字节,可提供有关数据包的源,目的地和其他有用的元数据的信息。通过网络硬件(例如路由器)来知道将流量引导到哪里(或在某些情况下丢失流量)来使用此信息。请求评论列出了section 3.1中的IP标头格式。可以在IPv4 Wikipedia页面上找到清洁的视觉格式:
标题本身由至少5 32位字段组成,每个字段跨越4个字节。这使得20个字节的最小尺寸是IPv4标头的最小尺寸。其余的最多可以具有10个选项字段,使IP标头的总最大大小为60字节。该布局已经显示了一些是子字节级别的值,例如标志的3位和版本的4位。这意味着我们需要使用位运算符来获取一些值。
理解拆开
是字节流很难使用。您可以使用拼接之类的东西来针对单个字节组。相反,尽管我们可以使用方便的struct.unpack方法。这是按二进制数据读取的,该数据按顺序填充,这实际上是我们的IP数据包。根据RFC 791标准,最小尺寸20字节IP标头的强制性字段为:
1st 32位字段
- 版本:4位
- IHL:4位
- 服务类型:8位
- 总长度:16位
2nd 32位字段
- 标识:16位
- 标志:3位
- 片段偏移:13位
3rd 32位字段
- 生活的时间:8位
- 协议8位
- 标题校验和16位
第4个32位字段
- 源地址:32bits
5th 32位字段
- 目标地址:32bits
struct.unpack
的工作方式是二进制流并将它们映射到各种字节大小的特定数据类型。论点的第一部分将与byte ordering有关。在过去,大恩迪安和小恩迪安之间存在分歧。 Kohei Otsuka有一篇关于差异的不错文章。大多数现代PC是小的endian编码,您可以通过sys.byteorder
找到它:
>>> import sys
>>> sys.byteorder
'little'
- unsigned char(1个字节/8位值)b
- 未签名的短(2个字节/16位值)h
- 未签名长(4个字节/32位值)l
- xs = x字节数
忽略子字节值,我们将把所有内容都提取为:
- B:版本 + IHL(8位)
- b:服务类型(8位)
- H:总长度(16位)
- H:识别(16位)
- H:标志 +片段偏移量(16位)
- b:生活时间(8位)
- b:协议(8位)
- H:标题校验和(16位)
- 4S:源(IP)地址(32位AS)
- 4S:dest(ip)地址(32位as-is)
因此,我们将采用数据包的前20个字节(由于数据包的每个索引都是字节,我们可以使用列表接头进行操作),然后使用我们的最终表达式对其进行解压缩:
>>> ip_header_bytes = packet[0:20]
>>> import struct
>>> ip_header = struct.unpack('!BBHHHBBH4s4s', ip_header_bytes)
>>> ip_header
(69, 0, 1309, 30503, 16384, 128, 6, 0, b'\xc0\xa8\x01Q', b'h\xf4*D')
现在我们可以分析实际字段。
解析IP标题字段
我们将采用包含版本和Internet标题长度(IHL)的前8个字节。以二进制形式,目前看起来像:
>>> format(ip_header[0], '08b')
'01000101'
因此,它是二进制中的0100
,二进制中的0101
总共填充了8位。使用正确的班次,我们可以获得前4位。如二进制部分所述,要获取第一个X位,您要拿出位的总数8,然后减去想要多少,4:
>>> ip_version = ip_header[0] >> 4
>>> format(ip_version, '08b')
'00000100'
>>> ip_version
4
现在从技术上讲,因为我们只是在插入IPv4数据包,因此无需真正关心这一点,因为我们始终知道版本是4。以类似的方式,互联网标题字段是最后4位一个位操作员:
>>> ihl = ip_header[0] & 0xf
>>> ihl
5
>>> format(ihl, '08b')
'00000101'
查看完整的值,显示了两个4位值,因此我们知道代码有效。
整个包裹
现在,基础知识已经下降,我们可以努力拉出其余的IP标头。服务类型是一种可能用于交通优先级的值,如果网络设备支持它。从野外看的突破,有3个位,用于优先旗,另外3个涉及称重包装的位,还有另外两个保留的位。如果我们查看整个字节值:
>>> format(ip_header[1], '08b')
'00000000'
根据规范有正常的优先级,其余的交通属性也设置为正常。有效优先级的另一个示例:
10111000
这具有较高级别的评论家/ECP优先级,并为低延迟和高吞吐量设置了礼物。接下来是数据包的总长度:
>>> ip_header[2]
1309
这意味着总数据大小为1309个字节,与字节列表的长度匹配:
>>> len(packet)
1309
标识是下一个值:
>>> format(ip_header[3], '08b')
'111011100100111'
>>> ip_header[3]
30503
这仅用作数据包组件中的标识符。该值的标志占据了16位值的前3位:
>>> format(ip_header[4] >> 13, '03b')
'010'
查看规范始终为0。下一个值表示该数据包不应将其分解为片段。因此,片段偏移也没有设置:
>>> format(ip_header[4] & 0b0001111111111111, '08b')
'00000000'
talks about fragmentation in more detail有一篇有趣的文章。接下来是该数据包在互联网上跨越几秒钟的时间的时候:
>>> ip_header[5]
128
此值也可以在添加额外时间的路线时进行修改。接下来是协议:
>>> ip_header[6]
6
RFC 790将各自的小数和八十个值列为其各自的协议。在这种情况下,我们正在处理协议6或TCP。接下来是标题校验和
>>> format(ip_header[7], '016b')
'0000000000000000'
这通常是在客户端看不到的。取而代之的是对其进行修改,因为它在变化时会通过它传递,从而改变了校验和。来源和目标地址在解析方面是同一件事:
>>> socket.inet_ntoa(ip_header[8])
'192.168.1.81'
>>> socket.inet_ntoa(ip_header[9])
'104.244.42.68'
socket.inet_ntoa将采用包装在字节中的IPv4地址的二进制形式,并返回您可能使用的标准点表示法。在目的地上做一个Arin Whois,我们发现IP属于Twitter,因此该数据包是与Twitter服务器通信。
结论
这将结束我们对IP标头结构的审查,并使用Python在二进制级别上使用它。在标准互联网流量发生的事情的窗帘后面达到顶峰是很有趣的。在该系列的下一部分中,我们将研究两个流行的协议:TCP和UDP。