【问题标题】:Why do I see strange UDP fragmentation on my C++ server?为什么在我的 C++ 服务器上看到奇怪的 UDP 碎片?
【发布时间】:2022-01-22 17:01:58
【问题描述】:

我已经用 C++ 构建了一个 UDP 服务器,对此我有几个问题。

目标:

我有传入的 TCP 流量,我需要将其作为 UDP 流量进一步发送。然后我自己的 UDP 服务器处理这个 UDP 数据。 TCP 数据包的大小可以变化。

详情:

在我的示例中,我有一个总共包含 2000 个字节 (4 random bytes, 1995 'a' (0x61) bytes and the last byte being 'b' (0x62)) 的 TCP 数据包。

我的 UDP 服务器有一个大于 2000 字节的缓冲区 (recvfrom buffer)。 我的 MTU 大小到处都是 1500。

我的服务器正在正确接收此数据包。在我的 UDP 服务器中,我可以看到接收到的数据包的长度为 2000,如果我检查最后一个字节 buffer[1999],它会打印 'b' (0x62),这是正确的。但是如果我打开tcpdump -i eth0,我只会看到一个UDP 数据包:09:06:01.143207 IP 192.168.1.1.5472 > 192.168.1.2.9000: UDP, bad length 2004 > 1472。 使用tcpdump -i eth0 -X 命令,我看到了数据包的数据,但只有~1472 字节,其中不包括'b'(0x62)字节。

ethtool -k eth0 命令打印udp-fragmentation-offload: off。

所以我的问题是:

  1. 为什么我只看到一个数据包而不是两个数据包(片段 1 和 2)?
  2. 为什么我在 tcpdump 中看不到 'b' (0x62) 字节?
  3. 在我的 C++ 服务器中,最好使用什么缓冲区大小?我现在在 65535 上拥有它,因为传入的 TCP 数据包可以是任意大小。
  4. 如果大小超过 65535 字节会发生什么,我是否必须在将 TCP 数据包作为 UDP 发送之前制定自己的分片方案?

【问题讨论】:

  • TCP 是基于流的,像 'TCP 数据包' 这样的东西不存在。实际上,底层传输 (IP) 是基于数据包的,但这些数据包会尽可能地被填满,然后发送(如果有足够的数据可用)——很容易在一个数据包中获得多个自定义协议数据包从流或部分包中读取。如果你想要一个基于 TCP 的基于数据包的协议,你需要自己实现一个合适的分离算法!
  • 我已经在多个场合使用COBS 来实现此目的——并结合每条消息包含的 CRC。您通过零字节分隔消息,CRC 确保 - 除了捕获传输错误 - 如果您不小心从填充的原始零字节开始接收,您可以检测到部分消息......
  • 您知道 MTU 还计算数据包标头...对吗? MTU 为 1500 时,UDP 数据包,包括标头和所有内容,不能大于 1500 字节...尝试发送不大于 1460 的 UDP 有效负载...甚至更好,限制有效负载到 1350 字节,就像 QUIC 一样。
  • 为什么需要切换协议?仅将 TCP 数据作为 TCP 转发可以使整个内容更不容易出错(尽管您仍然需要在第二个服务器上分离单个消息)。有两台服务器的原因是什么?将两者结合在一个服务器中可能会导致设计不那么复杂。
  • 有史以来最好的防火墙:禁止通信:D

标签: c++ sockets networking udp


【解决方案1】:

TCP 数据包的大小可以变化。

虽然上面的句子没有显示代码,并且您的描述表明您对 TCP 工作原理的假设是错误的。

与 UDP 不同,TCP 不是基于消息的协议,而是字节流。这尤其意味着它不保证发件人的单个send 将与收件人的单个recv 匹配。因此,即使send 完成了 2000 个字节,它可能仍然是第一个 recv 仅获得 1400 个字节,而另一个 recv 将获得其余部分 - 无论所有内容是否都可以立即放入套接字缓冲区。

【讨论】:

  • 当我将 recvfrom 缓冲区设置得较低时,比如说 1000 个字节,其他 1000 个字节将永远不会被读取,它们将被丢弃。如何区分 TCP 流数据大小和 UDP 数据包?
  • @Bart 您需要一种机制将传入流拆分为单独的消息。然后在一个单独的 UDP 数据包中发送每条消息。注意对于单个数据包来说太大的消息。然后您还需要拆分单个消息,然后事情变得复杂。您如何确保拆分消息中的任何数据包都不会丢失?您如何确保检测到丢失的包裹?您如何确保以正确的顺序接收数据包(如果数据包采用不同的路由路径,它们可能会相互超越)?
  • 只有一个路由路径,丢包不成问题。
  • @Bart 您仍然需要能够拆分和重新组合对于单个 UDP 包来说太大的消息(如果它们存在的话)——并且您需要能够检测单个消息的限制传入 TCP 端!
  • 我认为可以拆分那些 TCP 消息(小流?),并在接收端重建它们,某种自己的碎片。 “检测单个消息的限制”是什么意思? @阿空加瓜
【解决方案2】:

好的,从问题中出现的情况更加复杂,从您的 cmets 中提取,有以下可用信息:

  1. 某些客户端通过 TCP 向服务器发送数据(两者均不可修改)。
  2. 不过,两者之间存在防火墙,只允许通过 UDP 与服务器进行单向通信。
  3. 您现在打算实现一个代理,该代理由位于其间的两台服务器组成,并通过 UDP 隧道传输 TCP 数据。
  4. 服务器无法向后回复也不会造成问题。

我个人的做法如下:

  1. 让代理服务器完全不知道数据!让出站接收方接受(recv 或 recvfrom,具体取决于可用的单个或多个客户端)尚未适合 UDP 数据包的数据块,并按原样转发它们。
  2. 应用一些方法来确保至少能检测到丢失的数据,更好的是可以重建丢失的数据。由于防火墙限制,确认或重新请求消息是不可能的,但提高可靠性的唯一机会是通过冗余。
  3. 将最终目标服务器配置为仅侦听环回。
  4. 让入站代理通过 TCP 连接到目标服务器,只要不发生(不可恢复的)错误,就按原样转发任何传入数据。

为了能够检测到丢失的消息,我至少要为发送的任何 UDP 消息添加一个数据包计数器。如果两个后续消息没有提供连续的计数器值,则说明中间有一条消息丢失。

由于不可能进行向后通信,因此提高可靠性的唯一方法是无条件冗余,将您的一些传输速率换取,例如通过多次发送每条消息并忽略接收方的多余重复消息。

一种更精细的方法可能会将冗余数据分布在多个数据包上,以便可以从剩余的数据包中重建丢失的数据 - 可能类似于 RAID level 5 所做的。承认,你需要非常坚定地尝试……

最后一个问题是路由的样子。 UDP 无法保证数据包的接收顺序与发送顺序相同。如果真的只有一条固定路由从出站代理到通过防火墙的入站路由可用,那么数据包不应超过另一个 - 您可能仍希望至少适当地记录到文件以监控入站 UDP 数据包,并在发生错误的情况下应用适当的表示(缓冲数据包并在需要时重新排序)。

【讨论】:

  • 听起来差不多,路由很简单:TCP server -> my UDP client -> firewall -> my UDP server -> other TCP server my UDP client 到 my UDP server 之间的距离是几厘米
  • @Bart 防火墙还有多远???如果可能有不同的路线进出那里,那么数据包可能会切换路线,从而可能会互相超越!
  • 在这几厘米之间:p,从my UDP client到my UDP server只有一条可能的路线
  • “其他 tcp 服务器”上的应用程序是否知道如何处理丢失的数据?
  • @Effie 这是一个关键点。如果它可以忽略丢失/不完整的包,我们仍然可以。否则——在这个设置中,“其他服务器”不能重新请求丢失的数据——入站代理也不能。由于出站代理的“即发即弃”特性,模拟到客户端的连接丢失也是不可能的。所以整个设置仍然很脆弱——我仍然没有看到比尝试通过冗余来提高可靠性更好的选择......
猜你喜欢
  • 1970-01-01
  • 2020-12-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多