经过分析大量的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发送另一个窗口更新?
- 有没有其他完全不相关的方法可以考虑?
编辑: 这是提供的建议无法解决问题的描述。
TCP_NODELAY
选项仅影响窗口打开时如何发送数据包。具体来说,设置此选项会防止TCP堆栈等待几毫秒以获取更多数据,以尝试创建填满MSS的TCP数据包。 它不允许在接收方窗口关闭时发送数据。TCP_QUICKACK
选项影响主机回复它正在接收的数据包的方式。 我面临的问题是我需要更改发送方确认所接收的数据包的方式。MSG_OOB
选项仅设置TCP紧急标志。 紧急数据在窗口管理方面并没有特别处理,因此在接收方窗口关闭时仍不会被发送。更改TCP拥塞控制算法也无济于事。因为XBox强制将数据发送速率限制为MP3的播放速率,所以几乎不可能避免填充拥塞窗口。可能通过推断吞吐量来减少拥塞窗口,但这只会降低填满拥塞窗口的可能性,而不能完全防止它。
使用UDP不是一个选项,因为使用UPnP栈是一个要求,并且UPnP通过HTTP传递数据,因此需要使用TCP。