【问题标题】:Regarding ICMP "Fragmentation needed, DF bit set" or ICMP packet too big message关于 ICMP “需要分片,设置 DF 位”或 ICMP 数据包太大消息
【发布时间】:2015-08-04 17:12:37
【问题描述】:

我正在向服务器注入 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()

这是客户端代码:

#!/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()

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)

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

【问题讨论】:

    标签: networking tcp scapy icmp


    【解决方案1】:

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

    ICMP 直接在 IP 上运行,如 RFC 792 中所述:

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

    这可能会像ICMP is classified as a network layer protocol rather than a transport layer protocol 一样令人困惑,但考虑到它只是对 IP 的补充,用于携带错误、路由和控制消息和数据,这是有道理的。因此,它不能依赖 TCP 层来传输自己,因为 TCP 层依赖于 ICMP 帮助管理和排除故障的 IP 层。


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

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

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

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

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

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

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

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

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

    RFC 5927, section 2.2 指的是 RFC 1122 的第 4.2.3.9 节,其中指出当ICMP Fragmentation needed and DF set 错误消息从 IP 层向上传递时,TCP 应该中止连接,因为它表示硬错误条件。 RFC 规定主机应实现此行为,但这不是必须的(第 4.2.5 节)。该 RFC 还在第 3.2.2.1 节中声明,接收到的 Destination Unreachable 消息必须报告给 TCP 层。当在 TCP 连接上接收到 ICMP Fragmentation needed and DF set 错误消息时,实现这两者将导致破坏,这没有任何意义,而且显然不是预期的行为。

    另一方面,RFC 1191 声明 this 关于所需行为:

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

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

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


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

    Here 是如何构建 ICMP 消息的示例实现。如果将 ICMP 消息作为对其中一个 TCP 数据包的响应发送失败,我建议您甚至在首先接收与其相关的 TCP 数据包之前尝试发送 ICMP 消息,以确保在 ACK 之前收到它。仅当这也失败时,请尝试完全取消 ACK。

    【讨论】:

    • 约尔非常感谢。这个细节是非常需要的。如果 IP 处理分段,那么所有连接将具有由 IP 层决定的相同路径 MTU,这将造成混乱。据我所知,路径 MTU 是面向连接的,并且对于不同的连接是不同的。如果是这种情况,TCP如何提取ICMP数据包太大消息报告的下一跳MTU。
    • 由于每个目的地(而不是每个连接)的 MTU 可能不同,因此由 IP 层为每个目的地正确处理它。我不知道具体的实现细节,但我怀疑它存储了在最后 X 秒内发送数据包的每个目的地的最后一个已知 MTU(仅当它与默认 MTU 不同时)。
    • 感谢 Yoel 提供这个急需的答案。我同意路径 MTU 发现基于目的地而不是基于连接是有意义的。但是根据 RFC 5297,这是由 TCP 处理的,TCP 验证收到的 ICMP 以测试它是真正的 ICMP 还是伪造的 ICMP,如果是真的,它会根据 ICMP 报告的下一跳 MTU 更新路径 MTU。
    • 你确定是RFC 5297(使用高级加密标准(AES)的合成初始化向量(SIV)认证加密)?
    • 我已经实现了一切,但仍然无法正常工作。我在这里stackoverflow.com/questions/27027206/… 发布了一个包含所有实现细节的新问题。
    【解决方案2】:

    据我理解,主机收到“需要ICMP碎片和DF设置”,但消息可能来自路径中的中间设备(路由器),因此主机无法直接匹配当前会话的 icmp 响应,icmp 仅包含目标 ip 和 mtu 限制。

    然后主机向路由表中添加一个条目,用于记录路由和 mtu 的目标 ip,有效期为 10 分钟。

    这可以通过在执行触发 icmp 响应的跟踪路径或 ping 之后使用 ip route get x.x.x.x 询问特定路由在 linux 上观察到。

    $ ip route get 10.x.y.z
    10.z.y.z via 10.a.b.1 dev eth0  src 10.a.b.100 
    cache  expires 598sec mtu 1300
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-19
      • 2020-05-16
      • 1970-01-01
      • 2012-08-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多