重新排序在多个链接上发送的字节流段的想法
Ideas for reordering segments of a bytestream that were sent on multiple links
短版:
我正在尝试对可能代表任何内容的字节流(例如 TCP 流)进行分段,然后通过多个(不可靠的)link 将它们发送到我控制下的接收系统。在那里,我想将这些段重新排序为发送系统首先收到它们的顺序。有关示例,请参见第二个 ascii 图像。生成的隧道不需要像 TCP 一样可靠(UDP-ish 很好),但它应该处理使用多个内部 link 的事实(这不是微不足道的)。
使用包含 size
和 sequence number
字段的 header 可以解决所有问题,但仅限于完美世界。如果 link 中的一个行为不当并且对等点不同步(意思是,接收方将读取它认为应该构成 header 但没有读取的字节),则需要某种重新同步机制。关于 UDP,我想主要的网络堆栈会解析完整的 UDP header 并对它是否不同步做出有根据的猜测,但在此类失败后是否有重新回到正轨的好方法?
更长的版本,如果需要更多 details/context 则使用:
我正在尝试在几个不同的 links/tunnels 之上创建一个隧道。这些 link 可能不可靠,它们的设置方式无关紧要:套接字、tun、tap,你有什么。我正在创建的隧道不在乎。显然,它真正关心的是它的客户获得良好的服务,类似于您对 UDP 的期望。
当前版本:
我已经在一个可靠的 link 上实现了这样一个工具作为第一个版本(简单地说,我将 here 引用的源代码移植到 C++):
+------\ TCP /------+
tun socket <---> socket tun
+------/ \------+
client server
它的工作原理很简单:客户端从它的 tun 设备读取字节并将它们写入套接字。两者都简单地实现为对文件描述符执行 Linux 读写。服务器为接收做相反的事情。这有效 full-duplex。这里值得注意的是:
- 客户端和服务器不关心他们操作的字节代表什么,他们只是把它们移到另一边。我必须假设 Linux 网络堆栈正在做一些聪明的事情来理解它得到的字节的含义,即使在出现奇怪的碎片的情况下也是如此。
- 这两个端点实现了一个非常简单的协议,其中写入套接字的每一组数据前面都有要读取的字节数(同样,所有这些都来自 url多于)。这就是端点跟踪事物的方式(据我所知,这只是为了提高性能)
下一版本:
现在,这些端点将不得不使用多个 links 相互通信,而不是单流套接字,例如:
+----------\ /----------+
| .-- tun1A <---> tun1B --. |
tun0A ---- tapA <---> tapB ---- tun0B
| '-- socA <---> socB --' |
+----------/ \----------+
由于没有简单的方法来判断端点是否已读取其 tun0 接口上的完整 TCP 段,我打算继续将进入 tun 设备的所有内容简单地视为字节流。我想使用三个 link 中的任何一个将它发送到另一端,而不管原始字节流代表什么。这样一来,原本表示单个 TCP 段的一堆字节可能会使用 4 个单独的包发送到另一端。这些包需要在接收端重新排序,这本身没有问题:只需在我已有的字段中添加第二个字段:a sequence number
,对吗?
但是,如果基础 link 不可靠(很可能是)怎么办?如果这些包只是在它们的有效负载中进行了一些交换,我 假设 目前,接收端的网络堆栈能够处理它。但是如果 header 坏了怎么办?端点可能会了解到它应该读取的错误字节数,并且两个端点不同步且没有明显的修复。
我考虑过使用各种确认和超时来实现它,但我并没有放弃对更简单、更优雅的方法的希望(请记住,最终结果不需要可靠)。一些协议使用可靠的 out-of-band 通道(如 TCP)来管理有效负载隧道,但目标是纯粹使用 links。此外,即使 Linux 提供了各种很酷的网络工具(iproute2、netfilter 等),所有这些都应该在 C/C++ 中实现(如果稍微使用这些提供了一个很好的解决方案)。
现在,我缺乏想法,我希望任何人都可以提出一些其他的方法来解决这个问题!如果需要更多信息,请询问,我很乐意写更多 :)
*原来把这个问题放在networkengineering.stackexchange.
编辑 1
我正在考虑用自己的数据报套接字包装器包装每个 "inner link",如上图所示。这个套接字将(希望)至少保证我收到完整的数据报,这样我就不必担心 link 在 trunc 的情况下中断泰德消息。不过,它确实增加了一些开销。
很难理解您对 out-of-sync 的想法,因为网络堆栈处理这个问题。无论如何,有很多方法可以解决这个问题。
- 在 TCP 中,您有数据偏移量字段,它告诉您负载从哪里开始。
- 整个TCP段的大小是从IP数据报的“总长度”字段中得到的header。
- TCP 有一个校验和字段,可以在一定程度上确保检测到错误。 IP 有一个 header 校验和。在 UDP 中,您可以启用或禁用该校验和。
- 在第 1 层和第 2 层,还有其他技术和方法可确保接收到的帧正确无误:同步化、CRC 等。
您的应用程序需要实现自己的应用程序层协议,并且为了处理在不同通道中以任何顺序传入的消息,您可以使用不同的技术:
- 首先,有一种方法可以确定应用层收到消息的长度。可能在 TCP 套接字中,您遇到过在 recv 或 read returns 时可能无法读取完整的消息。为了解决这个问题,您可以实施 TLV 格式(类型、长度、值)。
- 要处理乱序和丢失的消息,您可以查看 IPv4 如何实现分段。这是完全相同的问题。 https://www.rfc-editor.org/rfc/rfc791
- 您还可以查看 TCP 在不丢弃后续接收到的段的同时处理丢失段的方式
- 序列号和确认对于拥有可靠的面向连接的虚拟隧道至关重要。
短版:
我正在尝试对可能代表任何内容的字节流(例如 TCP 流)进行分段,然后通过多个(不可靠的)link 将它们发送到我控制下的接收系统。在那里,我想将这些段重新排序为发送系统首先收到它们的顺序。有关示例,请参见第二个 ascii 图像。生成的隧道不需要像 TCP 一样可靠(UDP-ish 很好),但它应该处理使用多个内部 link 的事实(这不是微不足道的)。
使用包含 size
和 sequence number
字段的 header 可以解决所有问题,但仅限于完美世界。如果 link 中的一个行为不当并且对等点不同步(意思是,接收方将读取它认为应该构成 header 但没有读取的字节),则需要某种重新同步机制。关于 UDP,我想主要的网络堆栈会解析完整的 UDP header 并对它是否不同步做出有根据的猜测,但在此类失败后是否有重新回到正轨的好方法?
更长的版本,如果需要更多 details/context 则使用:
我正在尝试在几个不同的 links/tunnels 之上创建一个隧道。这些 link 可能不可靠,它们的设置方式无关紧要:套接字、tun、tap,你有什么。我正在创建的隧道不在乎。显然,它真正关心的是它的客户获得良好的服务,类似于您对 UDP 的期望。
当前版本:
我已经在一个可靠的 link 上实现了这样一个工具作为第一个版本(简单地说,我将 here 引用的源代码移植到 C++):
+------\ TCP /------+
tun socket <---> socket tun
+------/ \------+
client server
它的工作原理很简单:客户端从它的 tun 设备读取字节并将它们写入套接字。两者都简单地实现为对文件描述符执行 Linux 读写。服务器为接收做相反的事情。这有效 full-duplex。这里值得注意的是:
- 客户端和服务器不关心他们操作的字节代表什么,他们只是把它们移到另一边。我必须假设 Linux 网络堆栈正在做一些聪明的事情来理解它得到的字节的含义,即使在出现奇怪的碎片的情况下也是如此。
- 这两个端点实现了一个非常简单的协议,其中写入套接字的每一组数据前面都有要读取的字节数(同样,所有这些都来自 url多于)。这就是端点跟踪事物的方式(据我所知,这只是为了提高性能)
下一版本:
现在,这些端点将不得不使用多个 links 相互通信,而不是单流套接字,例如:
+----------\ /----------+
| .-- tun1A <---> tun1B --. |
tun0A ---- tapA <---> tapB ---- tun0B
| '-- socA <---> socB --' |
+----------/ \----------+
由于没有简单的方法来判断端点是否已读取其 tun0 接口上的完整 TCP 段,我打算继续将进入 tun 设备的所有内容简单地视为字节流。我想使用三个 link 中的任何一个将它发送到另一端,而不管原始字节流代表什么。这样一来,原本表示单个 TCP 段的一堆字节可能会使用 4 个单独的包发送到另一端。这些包需要在接收端重新排序,这本身没有问题:只需在我已有的字段中添加第二个字段:a sequence number
,对吗?
但是,如果基础 link 不可靠(很可能是)怎么办?如果这些包只是在它们的有效负载中进行了一些交换,我 假设 目前,接收端的网络堆栈能够处理它。但是如果 header 坏了怎么办?端点可能会了解到它应该读取的错误字节数,并且两个端点不同步且没有明显的修复。
我考虑过使用各种确认和超时来实现它,但我并没有放弃对更简单、更优雅的方法的希望(请记住,最终结果不需要可靠)。一些协议使用可靠的 out-of-band 通道(如 TCP)来管理有效负载隧道,但目标是纯粹使用 links。此外,即使 Linux 提供了各种很酷的网络工具(iproute2、netfilter 等),所有这些都应该在 C/C++ 中实现(如果稍微使用这些提供了一个很好的解决方案)。
现在,我缺乏想法,我希望任何人都可以提出一些其他的方法来解决这个问题!如果需要更多信息,请询问,我很乐意写更多 :)
*原来把这个问题放在networkengineering.stackexchange.
编辑 1
我正在考虑用自己的数据报套接字包装器包装每个 "inner link",如上图所示。这个套接字将(希望)至少保证我收到完整的数据报,这样我就不必担心 link 在 trunc 的情况下中断泰德消息。不过,它确实增加了一些开销。
很难理解您对 out-of-sync 的想法,因为网络堆栈处理这个问题。无论如何,有很多方法可以解决这个问题。
- 在 TCP 中,您有数据偏移量字段,它告诉您负载从哪里开始。
- 整个TCP段的大小是从IP数据报的“总长度”字段中得到的header。
- TCP 有一个校验和字段,可以在一定程度上确保检测到错误。 IP 有一个 header 校验和。在 UDP 中,您可以启用或禁用该校验和。
- 在第 1 层和第 2 层,还有其他技术和方法可确保接收到的帧正确无误:同步化、CRC 等。
您的应用程序需要实现自己的应用程序层协议,并且为了处理在不同通道中以任何顺序传入的消息,您可以使用不同的技术:
- 首先,有一种方法可以确定应用层收到消息的长度。可能在 TCP 套接字中,您遇到过在 recv 或 read returns 时可能无法读取完整的消息。为了解决这个问题,您可以实施 TLV 格式(类型、长度、值)。
- 要处理乱序和丢失的消息,您可以查看 IPv4 如何实现分段。这是完全相同的问题。 https://www.rfc-editor.org/rfc/rfc791
- 您还可以查看 TCP 在不丢弃后续接收到的段的同时处理丢失段的方式
- 序列号和确认对于拥有可靠的面向连接的虚拟隧道至关重要。