关于 ICMP“需要分段,DF 位设置”或 ICMP 数据包太大消息

Aar*_*n88 3 networking tcp scapy icmp

我将 ICMP“需要分段,设置 DF 位”注入服务器,理想情况下服务器应开始发送 ICMP 中“下一跳 MTU”字段中提到的大小的数据包。但这是行不通的。

这是服务器代码:

#!/usr/bin/env python 
import socket               # Import socket module
import time
import os

range= [1,2,3,4,5,6,7,8,9]
s = socket.socket()         # Create a socket object
host = '192.168.0.17'                   # Get local machine name
port = 12349               # Reserve a port for your service.
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((host, port))        # Bind to the port
rand_string = os.urandom(1600)

s.listen(5)                 # Now wait for client connection.
while True:
   c, addr = s.accept()     # Establish connection with client.
   print 'Got connection from', addr
   for i in range:
    c.sendall(rand_string)
        time.sleep(5)
   c.close()
Run Code Online (Sandbox Code Playgroud)

这是客户端代码:

#!/usr/bin/python           # This is client.py file

import socket               # Import socket module

s = socket.socket()         # Create a socket object
host = '192.168.0.17' # Get local machine name
port = 12348              # Reserve a port for your service.
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.connect((host, port))
while 1:
    print s.recv(1024)
s.close()
Run Code Online (Sandbox Code Playgroud)

Scapy注入ICMP:

###[ IP ]###
  version= 4
  ihl= None
  tos= 0x0
  len= None
  id= 1
  flags= DF
  frag= 0
  ttl= 64
  proto= ip
  chksum= None
  src= 192.168.0.45
  dst= 192.168.0.17
  \options\
###[ ICMP ]###
  type= dest-unreach
  code= fragmentation-needed
  chksum= None
  unused= 1300

Send(ip/icmp)
Run Code Online (Sandbox Code Playgroud)

未使用的字段在wireshark 中显示为下一跳MTU。服务器是否足够聪明,可以检查在与客户端通信时是否未设置 DF 位,并且仍在接收 ICMP“需要分段,已设置 DF 位”消息?如果不是,那么为什么服务器不将其数据包大小从 1500 减少到 1300?

Yoe*_*oel 6

首先,让我们回答您的第一个问题(ICMP 是通过 TCP 发送的吗?)。

ICMP 直接通过 IP 运行,如RFC 792中指定:

ICMP 消息使用基本 IP 标头发送。

这可能有点令人困惑,因为ICMP 被归类为网络层协议而不是传输层协议,但考虑到它只是 IP 的补充,用于承载错误、路由和控制消息和数据,这是有道理的。因此,它不能依赖 TCP 层来传输自身,因为 TCP 层依赖于 ICMP 帮助管理和故障排除的 IP 层。


现在,让我们解决第二个问题(如果 ICMP 不是通过 TCP 发送的,TCP 如何知道 MTU?)。我试图根据官方规范,尽我所能来回答这个问题,但也许最好的方法是分析一些开源网络堆栈实现,以了解到底发生了什么......

即使 ICMP 消息没有分层在 TCP 上,TCP 层也可能知道路径的 MTU 值。由操作系统和网络堆栈的实现来通知 MTU 的 TCP 层,以便它可以使用该值来更新其 MSS 值。

RFC 1122要求 ICMP 消息包含 IP 标头以及触发该 ICMP 消息的有问题数据报的前 8 个字节:

每个 ICMP 错误消息都包含 Internet 标头和触发错误的数据报的至少前 8 个数据八位位组;可以发送超过 8 个八位字节;该标头和数据必须与接收到的数据报保持不变。

在需要互联网层将 ICMP 错误消息传递到传输层的情况下,必须从原始报头中提取 IP 协议号并用于选择适当的传输协议实体来处理错误。

这说明了操作系统如何查明应更新其 MSS 的 TCP 连接,因为这 8 个字节包括源端口和目标端口。

RFC 1122 还规定,必须有一种机制,传输层可以通过该机制了解可以为给定的{源、目的地、TOS} 三元组发送的最大传输层消息大小。因此,我假设一旦ICMP Fragmentation needed and DF set收到错误消息,MTU 值就会以某种方式提供给 TCP 层,TCP 层可以使用它来更新其 MSS 值。

此外,我认为实例化 TCP 连接并使用它的应用层也可以处理此类消息并在更高级别对数据包进行分段。应用程序可以打开一个等待 ICMP 消息的套接字,并在收到此类消息时采取相应的操作。然而,应用层的数据包分段对于 TCP 和 IP 层来说是完全透明的。请注意,大多数应用程序将允许 TCP 和 IP 层自行处理这种情况。

然而,一旦ICMP Fragmentation needed and DF set主机接收到错误消息,其由较低层指示的行为就不是决定性的。

RFC 5927 第 2.2 节ICMP Fragmentation needed and DF set引用了 RFC 1122 第 4.2.3.9 节,其中规定当错误消息从 IP 层向上传递时,TCP 应中止连接,因为它表示硬错误情况。RFC 规定主机应该实现此行为,但这不是必须的(第 4.2.5 节)。该 RFC 还在第 3.2.2.1 节中指出,收到的“目的地不可达”消息必须报告给 TCP 层。当在该连接上收到错误消息时,实现这两种方法都会导致 TCP 连接被破坏ICMP Fragmentation needed and DF set,这没有任何意义,并且显然不是所需的行为。

另一方面,RFC 1191对于所需行为进行了说明:

RFC 1191没有概述发送主机期望的特定行为,因为不同的应用程序可能有不同的要求,并且不同的实现架构可能有利于不同的策略[这为这种方法-OA留下了空间]。

唯一需要的行为是主机必须尝试避免在不久的将来发送更多具有相同 PMTU 值的消息。主机可以停止设置 IP 标头中的“不分段”位(并允许路由器进行分段)或减小数据报大小。更好的策略是减小消息大小,因为碎片会导致更多流量并消耗更多互联网资源。

总而言之,我认为该规范对于主机收到ICMP Fragmentation needed and DF set错误消息时所需的行为并不是明确的。我的猜测是,两层(IP 和 TCP)都会收到消息通知,以便分别更新其 MTU 和 MSS 值,并且其中一层负责以较小的块重新传输有问题的数据包。


最后,关于您的实现,我认为为了完全符合 RFC 1122,您应该更新 ICMP 消息以包含有问题数据包的 IP 标头及其接下来的 8 个字节(尽管您可能不仅仅包含前 8 个字节)字节)。此外,您应该验证 ICMP 消息是否在 ICMP 消息所引用的数据包的相应 ACK 之前收到。事实上,为了安全起见,我会完全废除那个ACK。

以下是如何构建 ICMP 消息的示例实现。如果发送 ICMP 消息作为对其中一个 TCP 数据包的响应失败,我建议您尝试在接收与其相关的 TCP 数据包之前发送 ICMP 消息,以确保在 ACK 之前收到它。只有当这也失败时,才尝试完全废除 ACK。