XBox 360的TCP堆栈不能用0字节负载响应TCP零窗口探测。

16
我正在尝试使用Android应用程序通过UPnP流式传输音乐到XBox。流媒体大部分时间都可以正常工作,但是当网络上有其他活动时,流媒体经常在一两分钟后停滞不前,特别是在流式传输到其他非XBox设备时从未发生这种情况。我已经确认了许多不同的UPnP服务器应用程序中的此行为。
经过分析大量的Wireshark跟踪,我找到了根本原因。似乎在XBox上TCP接收窗口已满之后,它仅在包含1字节有效负载数据的零窗口探测器的响应下明确重新通告窗口更新。
虽然基于Windows的机器会发送包含1字节有效负载的零窗口探测器,但基于Linux的机器会发送包含0字节有效负载(纯ACKs)的探测器。
在理想的网络条件下,这不是问题,因为接收方将始终在其窗口中释放足够的空间以避免愚笨的窗口综合症,并发送一个单个的窗口更新ACK消息。但是,如果该单个窗口更新数据包被丢失,则linux Android设备永远不会再次响应,因为这些设备上的TCP堆栈使用零窗口探测器并带有0字节有效负载(在Wirehsark中看起来像保持活动数据包)。
XBox和WMP之间的TCP停滞看起来像这样:注意Xbox积极响应零窗口探测数据包。
XBox和Android客户端之间的正常TCP停顿如下所示:

7099 174.844077 10.0.2.214 10.0.2.183 TCP [TCP ZeroWindow] [TCP ACKed lost segment] 20067 > ssdp [ACK] Seq=143 Ack=2962598 Win=0 Len=0
7100 175.067981 10.0.2.183 10.0.2.214 TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=2962597 Ack=143 Win=6912 Len=0
7107 175.518024 10.0.2.183 10.0.2.214 TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=2962597 Ack=143 Win=6912 Len=0
7108 175.894079 10.0.2.214 10.0.2.183 TCP [TCP Window Update] 20067 > ssdp [ACK] Seq=143 Ack=2962598 Win=16384 Len=0
注意Xbox不响应KeepAlive数据包。如果初始窗口更新的通告被忽略,XBox和我的Android设备之间的TCP停滞会像这样:

7146 175.925019  10.0.2.214            10.0.2.183            TCP [TCP ZeroWindow] 20067 > ssdp [ACK] Seq=143 Ack=3000558 Win=0 Len=0
7147 176.147901  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7155 176.597820  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7165 177.498087  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7218 179.297763  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7297 182.897804  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7449 190.097780  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
7759 204.498070  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
8412 233.298081  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
9617 290.898134  10.0.2.183            10.0.2.214            TCP [TCP Keep-Alive|TCP Keep-Alive] ssdp > 20067 [ACK] Seq=3000557 Ack=143 Win=6912 Len=0
11326 358.047838  10.0.2.214            10.0.2.183            TCP      20067 > ssdp [FIN, ACK] Seq=143 Ack=3000558 Win=16384 Len=0

请注意,XBox从不重新宣布其开放窗口,并最终终止连接。
我通过编写一个小的数据包注入程序来确认我的理论。当我遇到阻塞时,我可以发送一个手工制作的TCP零窗口探测数据包。这样做后,XBox会立即恢复正常工作并继续运行。不幸的是,我无法从我的应用程序中执行此操作,因为制作这样的数据包需要CAP_NET_RAW功能,而我不能将其授予我的应用程序。
以下是上述情况,使用手动注入的零窗口探测(数据包7258)。正确的Seq / Ack号码甚至不需要。唯一需要的是一个字节的数据。

  7253 373.274394  10.0.2.214            10.0.2.186            TCP      [TCP ZeroWindow] 39378 > ssdp [ACK] Seq=3775184695 Ack=1775679761 Win=0 Len=0
  7254 375.367317  10.0.2.186            10.0.2.214            TCP      [TCP Keep-Alive] ssdp > 39378 [ACK] Seq=1775679760 Ack=3775184695 Win=3456 Len=0
  7255 379.562480  10.0.2.186            10.0.2.214            TCP      [TCP Keep-Alive] ssdp > 39378 [ACK] Seq=1775679760 Ack=3775184695 Win=3456 Len=0
  7256 387.953095  10.0.2.186            10.0.2.214            TCP      [TCP Keep-Alive] ssdp > 39378 [ACK] Seq=1775679760 Ack=3775184695 Win=3456 Len=0
  7257 404.703312  10.0.2.186            10.0.2.214            TCP      [TCP Keep-Alive] ssdp > 39378 [ACK] Seq=1775679760 Ack=3775184695 Win=3456 Len=0
  7258 406.571301  10.0.2.186            10.0.2.214            TCP      [TCP ACKed lost segment] [TCP Retransmission] ssdp > 39378 [ACK] Seq=1 Ack=1 Win=1 Len=1
  7259 406.603512  10.0.2.214            10.0.2.186            TCP      39378 > ssdp [ACK] Seq=3775184695 Ack=1775679761 Win=16384 Len=0
由于TCP Seq / Ack号码不正确,Wireshark将该包解释为带有无效ACK的错误数据传输,但XBox仍然会立即恢复正常工作并开始流式传输。
  • 有没有办法在Android应用程序中获取CAP_NET_RAW权限而不需要root设备?
  • 我是否有其他办法来强制Linux TCP层发送带有1字节有效负载数据的零窗口探测包?
  • 我能不能尝试其他TCP选项来唤醒XBox的TCP堆栈呢?
  • 有没有其他的带外方法可以说服XBox发送另一个窗口更新?
  • 有没有其他完全不相关的方法可以考虑?

编辑: 这是提供的建议无法解决问题的描述。

  1. TCP_NODELAY选项仅影响窗口打开时如何发送数据包。具体来说,设置此选项会防止TCP堆栈等待几毫秒以获取更多数据,以尝试创建填满MSS的TCP数据包。 它不允许在接收方窗口关闭时发送数据。

  2. TCP_QUICKACK选项影响主机回复它正在接收的数据包的方式。 我面临的问题是我需要更改发送方确认所接收的数据包的方式。

  3. MSG_OOB选项仅设置TCP紧急标志。 紧急数据在窗口管理方面并没有特别处理,因此在接收方窗口关闭时仍不会被发送。

  4. 更改TCP拥塞控制算法也无济于事。因为XBox强制将数据发送速率限制为MP3的播放速率,所以几乎不可能避免填充拥塞窗口。可能通过推断吞吐量来减少拥塞窗口,但这只会降低填满拥塞窗口的可能性,而不能完全防止它。

  5. 使用UDP不是一个选项,因为使用UPnP栈是一个要求,并且UPnP通过HTTP传递数据,因此需要使用TCP。


即使看起来不是 Linux 的 bug,而更像是 Windows/Xbox360 TCP 的 bug,您也应该在 https://bugzilla.kernel.org/ 报告 Linux 内核的 bug。为了实现互操作性,可以在 Linux TCP 栈中添加解决方法。 - Yann Droneaud
3个回答

3

我找到了一些可能有用的东西:

  1. TCP ioctl(2) TCP_NODELAY 会导致内核发送一个立即 PSH 数据包。它可能会解决连接问题。

  2. TCP ioctl(2) TCP_QUICKACK 会对 ACK 数据包进行一些有趣的处理。它可能会解决连接问题。

  3. 如果您使用 send(2),您可以设置 MSG_OOB 标志,这可能会直接通知 XBox 并引起其注意,或许事情能够重新开始。CISCO 写了一个关于不同平台如何响应 TCP URG 的很好的总结,他们的建议是避免使用 URG,但这个方法可能会奏效。

  4. TCP 套接字选项 TCP_CONGESTION 可以让您选择不同的拥塞避免算法。也许您可以找到一个可帮助避免第一次出现填满窗口的算法?(至少 TCP Vegas 是作为一个模块实现的,可能无法从 Android 平台默认的拥塞避免算法中更改。)


1
谢谢,这些是很好的建议。我已经尝试了大部分,可能应该提到我已经尝试过什么。我会在原帖中添加关于它们无法使用的原因的注释。 - Jason LeBrun

2

实际上,您遇到了Linux的一个bug。在处理零窗口情况时,Linux不符合RFC793标准。Windows实际上是在做正确的事情。请注意,RFC 793并不要求接收方发送未经请求的窗口更新消息。相反,要求发送方发送至少一个八位字节的窗口探测。


0

您可能考虑使用UDP而不是TCP。我假设您希望Xbox播放音频,而不是在本地创建副本?如果是这样,那么您真的不介意可靠地获取每个数据包。数据包传输的可靠性是通过TCP获得的开销,但您可能并不需要它。UDP更简单,并且在流媒体情况下更常见。


1
是的,在流媒体时,UDP通常是更好的选择。不幸的是,这是一个UPnP流应用程序。UPnP内容服务通过HTTP运行,因此使用TCP。我并没有在XBox本身上开发任何代码,而是使用XBox的内置UPnP功能,因此我无法控制在此处使用的协议栈。 - Jason LeBrun
1
糟糕。我担心这个问题超出了我的能力范围。我想不到其他不需要 root 访问权限的解决方法了。我不指望微软会很快修复 Xbox 的问题;看起来那是正确的解决方案。但你的故障排除真的很棒。分析得非常好。 - Dave MacLean
1
谢谢,听到你觉得它有道理我很高兴。在写这个的时候,它开始感觉漫长而令人痛苦。我原本希望可能有一些明显的答案我没注意到,但鉴于这个问题上的活动缺乏,我猜我只能接受这个行为了!虽然从符合标准的TCP角度来说这是一个bug,但它保证在Windows机器上工作,因为Windows机器始终使用1字节的零宽度空格。我努力不去感觉这是有意为非微软设备降低性能的调整。 - Jason LeBrun

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接