15

我正在尝试使用通过 UPnP 将音乐流式传输到 XBox 的 Android 应用程序。流媒体在大多数情况下都能正常工作,但很常见的是,在一两分钟后,流媒体就会停止,尤其是当网络上有其他活动时。流式传输到其他非 Xbox 设备时,这永远不会发生。我已经用许多不同的 UPnP 服务器应用程序确认了这种行为。

在分析了很多 Wireshark 痕迹后,我找到了根本原因。似乎在 TCP 接收器窗口填满 Xbox 后,它只是明确地重新宣布窗口更新以响应包含 1 字节有效负载数据的零窗口探测。

基于 Windows 的机器发送包含 1 字节有效负载的零窗口探测,而基于 Linux 的机器发送包含 0 字节有效负载(纯 ACK)的探测。

在理想的网络条件下,这不是问题,因为一旦在其窗口中释放了足够的空间以避免愚蠢的窗口综合症,接收者将始终发送单个窗口更新 ACK 消息。但是,如果丢失了单个 Window Update 数据包,它将永远不会再次响应基于 linux 的 Android 设备,因为这些设备上的 TCP 堆栈使用带有 0 字节有效负载的零窗口探测器(它们看起来像 Wirehsark 的 Keep Alive 数据包)。

Xbox 和 WMP 之间的 TCP 停顿如下所示:


   4966 92.330358   10.0.2.214            10.0.2.133            TCP      [TCP ZeroWindow] 27883 > 10243 [ACK] Seq=183 Ack=1723007 Win=0 Len=0
   4971 92.648068   10.0.2.133            10.0.2.214            TCP      [TCP ZeroWindowProbe] 10243 > 27883 [ACK] Seq=1723007 Ack=183 Win=64240 Len=1
   4972 92.649009   10.0.2.214            10.0.2.133            TCP      [TCP ZeroWindowProbeAck] [TCP ZeroWindow] 27883 > 10243 [ACK] Seq=183 Ack=1723007 Win=0 Len=0
   4977 93.256579   10.0.2.133            10.0.2.214            TCP      [TCP ZeroWindowProbe] 10243 > 27883 [ACK] Seq=1723007 Ack=183 Win=64240 Len=1
   4978 93.263118   10.0.2.214            10.0.2.133            TCP      [TCP ZeroWindowProbeAck] [TCP ZeroWindow] 27883 > 10243 [ACK] Seq=183 Ack=1723007 Win=0 Len=0
   4999 94.310534   10.0.2.214            10.0.2.133            TCP      [TCP Window Update] 27883 > 10243 [ACK] Seq=183 Ack=1723007 Win=16384 Len=0

请注意,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 功能而无需设备植根?
  • 我可以使用任何其他技巧来强制 Linux TCP 层发送带有 1 字节有效负载数据的零窗口探测吗?
  • 是否有任何其他模糊的 TCP 选项我可以尝试让我唤醒 Xbox 的 TCP 堆栈?
  • 是否有其他一些带外方法来说服 Xbox 发送另一个 Window 更新?
  • 我可能会考虑其他一些完全不相关的方法吗?

编辑:这是对所提供建议不起作用的原因的描述。

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

  2. TCP_QUICKACK影响主机确认它接收的数据包的方式。我面临的问题是我需要更改发送方确认接收到的数据包的方式

  3. MSG_OOB只设置 TCP 紧急标志。就窗口化而言,紧急数据的处理方式没有任何不同,并且当接收器的窗口关闭时仍然不会发送。

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

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

4

3 回答 3

3

我发现了一些可能有帮助的东西:

  1. TCPioctl(2) TCP_NODELAY将导致内核立即发送一个 PSH 数据包。它可能会断开连接。

  2. TCPioctl(2) TCP_QUICKACK会对 ACK 数据包做一些有趣的事情。它可能会断开连接。

  3. 如果你使用send(2),你可以设置MSG_OOB标志,这可能会戳到 Xbox 的眼睛,引起它的注意,也许事情可以重新开始。CISCO 写了一篇关于不同平台如何响应 TCP URG 的很好的总结,他们的建议是避免使用 URG,但这很疯狂,它可能会起作用。

  4. TCP 套接字选项TCP_CONGESTION允许您选择不同的拥塞避免算法。也许你可以找到一个有助于避免首先被填满的窗户?(至少 TCP Vegas 是作为一个模块实现的,在 android 平台上可能无法改变默认的拥塞避免算法。)

于 2011-01-30T09:16:10.440 回答
2

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

于 2013-01-14T17:24:46.703 回答
0

您可能需要考虑使用 UDP 而不是 TCP。我假设您希望 Xbox 播放音频,而不是在本地创建它的副本?在这种情况下,您真的不在乎是否可靠地获取每个数据包。数据包传输的可靠性是您使用 TCP 获得的开销,但也许您并不真正需要它。UDP 更简单,在流式传输情况下更典型。

于 2011-01-29T21:06:29.817 回答