Xbox 360 TCP 堆栈不响应具有 0 字节有效负载的 TCP 零窗口探测
我正在尝试一个 Android 应用程序,通过 UPnP 将音乐传输到 Xbox。流式传输在大多数情况下都可以正常工作,但一两分钟后,流式传输经常会停止,尤其是当网络上有其他活动时。当流式传输到其他非 Xbox 设备时,这种情况永远不会发生。我已经通过许多不同的 UPnP 服务器应用程序确认了此行为。
在分析了大量 Wireshark 痕迹后,我找到了根本原因。看起来,在 XBox 上的 TCP 接收器窗口填满后,它仅显式地重新宣布窗口更新,以响应包含 1 字节有效负载数据的零窗口探测。
基于 Windows 的计算机发送包含 1 字节有效负载的零窗口探测,而基于 Linux 的计算机发送包含 0 字节有效负载(纯 ACK)的探测。
在理想的网络条件下,这不是问题,因为一旦接收方在其窗口中释放了足够的空间以避免愚蠢的窗口综合症,它总是会发送单个窗口更新ACK消息。但是,如果错过了单个窗口更新数据包,它将永远不会再次响应基于 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 数据包。
如果错过了最初的 Window 更新公告,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 发送另一个 Window 更新?
- 我可能会考虑其他一些完全不相关的方法吗?
编辑:这是对所提供的建议不起作用的原因的描述。
TCP_NODELAY
仅影响窗口打开时数据包的发送方式。具体来说,设置此选项可防止 TCP 堆栈等待几毫秒以获取更多数据以尝试创建填充 MSS 的 TCP 数据包。当接收器窗口关闭时,它不允许发送数据。TCP_QUICKACK
影响主机确认其接收的数据包的方式。我面临的问题是我需要更改发送方确认其正在接收的数据包的方式。MSG_OOB
仅设置 TCP 紧急标志。就窗口而言,紧急数据没有任何区别,并且当接收者的窗口关闭时仍然不会发送。更改 TCP 拥塞控制算法也无济于事。由于 Xbox 强制将数据发送速率限制为 MP3 的播放速率,因此几乎不可能避免填充拥塞窗口。或许可以通过推断吞吐量来减少拥塞窗口,但这只会减少拥塞窗口被填满的可能性,而不能完全阻止它。
使用 UDP 不是一个选项,因为使用 UPnP 堆栈是一项要求,而 UPnP 通过 HTTP 传送数据,因此是 TCP。
使用
I'm experimenting with an Android app that streams Music via UPnP to an XBox. The streaming works for the most part, but quite frequently, after a minute or two, the streaming stalls, especially when there is other activity on the network. This never happens when streaming to other non-XBox devices. I've confirmed this behavior with a number of different UPnP server apps.
After analyzing lots of Wireshark traces, I've found the root cause. It seems that after the TCP receiver window has filled on the XBox, it only explicitly re-announces a window update in response to Zero Window Probes that contain 1 byte of payload data.
While Windows-based machines send Zero Window probes that contain a 1-byte payload, Linux-based machines send probes that contain 0-byte payloads (pure ACKs).
Under ideal network conditions, this isn't a problem, since a receiver will always send ana single Window Update ACK message once it's freed up enough space in its window to avoid the silly window syndrome. However, if that single Window Update packet is missed, it will never respond again to a linux-based Android device, because the TCP stack on those devices uses Zero Window Probes with a 0-byte payload (they look like Keep Alive packets to Wirehsark).
A TCP stall between the XBox and WMP looks like this:
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
Note that the Xbox is actively responding to the Zero Window Probe packets.
A normal TCP stall between the XBox and the Android client looks like this:
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
Note that the XBox does not respond to the KeepAlive packets.
A TCP stall between the XBox and my Android device look like this if the initial Window Update announcement is missed:
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
Note that the XBox never re-announces its open window, and eventually terminates the connection.
I've confirmed my theory by writing a small packet-injection program. When I get a stall, I can fire off a hand-crafted TCP Zero Window Probe packet. When do this, the XBox instantly springs back to life and continues on as normal. Unfortunately, I can't do this from my application, because crafting such a packet requires the CAP_NET_RAW capability, and I'm not able to grant that to my application.
Here's the above case, with a manually-injected Zero Window Probe (packet 7258). The right seq/ack numbers aren't even required. The only thing that's required is one byte of data.
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
Since the TCP Seq/Ack numbers are incorrect, Wireshark interprets the pack as a wayward data transmission with an invalid ACK, but the XBox nonetheless snaps back to life, and starts streaming again.
- Is there any way to get CAP_NET_RAW capabilities in an Android app without requiring the device to be rooted?
- Is there any other trick I can use to force the Linux TCP layer to send its Zero Window Probes with 1 byte of payload data?
- Is there any other obscure TCP option I could try that would let me wake the XBox's TCP stack up?
- Is there some other out-of-band approach to convincing the XBox to send another Window update?
- Is there some other completely unrelated approach that I might consider?
Edit: This is a description of why the provided suggestions won't work.
TCP_NODELAY
only affects how packets are sent while the window is open. Specifically, setting this option prevents the TCP stack from waiting for a few ms for more data in an attempt to create a TCP packet that fills up the MSS. It doesn't allow data to be sent when the receiver window is closed.TCP_QUICKACK
affects the way the host ACKs packets it's receiving. The problem I'm facing is that I need to change the way the sender ACKs the packets it is receiving.MSG_OOB
only sets the TCP urgent flag. Urgent data isn't treated any differently as far as windowing goes, and still will not be sent when the receiver's window is closed.Changing the TCP congestion control algorithm won't help either. Because the XBox is forcibly limiting the data send rate to the play rate of the MP3, it's virtually impossible to avoid filling the congestion window. It might be possible to reduce the congestion window by inferring the throughput, but this would only reduce the likelyhood of a filled congestion window, not prevent it completely.
Using UDP is not an option, since using the UPnP stack is a requirement, and UPnP delivers data via HTTP, and thus, TCP.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我发现了一些可能有帮助的事情:
TCP
ioctl(2)
TCP_NODELAY
将导致内核立即发送 PSH 数据包。它可能会断开连接。TCP
ioctl(2)
TCP_QUICKACK
会对 ACK 数据包做一些有趣的事情。它可能会断开连接。如果你使用
send(2)
,你可以设置MSG_OOB
标志,这可能会戳到XBox的眼睛,引起它的注意,也许事情就可以开始了超过。 CISCO 写了一篇关于不同平台如何响应 TCP 的精彩总结URG,他们的建议是避免使用 URG,但它足够疯狂,它可能会起作用。TCP 套接字选项
TCP_CONGESTION
允许您选择不同的拥塞避免算法。也许您可以找到一个可以帮助避免一开始就被填满的窗户的东西? (至少TCP Vegas是作为模块实现的,可能无法改变android平台上默认的拥塞避免算法。)I found a few things that may help:
TCP
ioctl(2)
TCP_NODELAY
will cause the kernel to send an immediate PSH packet. It might unstick the connection.TCP
ioctl(2)
TCP_QUICKACK
will do something funny with ACK packets. It might unstick the connection.If you use
send(2)
you can set theMSG_OOB
flag, which might poke the XBox right in the eye, get its attention, and maybe things can start over. CISCO wrote a nice summary of how different platforms respond to TCP URG, and their advice is to avoid using URG, but it's crazy enough it just might work.TCP socket option
TCP_CONGESTION
lets you select different congestion-avoidance algorithms. Maybe you could find one that helps avoids the filled windows in the first place? (At least TCP Vegas is implemented as a module, it might not be possible to change away from the default congestion avoidance algorithm on the android platform.)实际上,您遇到了 Linux 错误。 Linux 在处理零窗口情况时不符合 RFC793。 Windows 实际上正在做正确的事情。请注意,RFC 793 不要求接收方发送未经请求的窗口更新消息。相反,要求发送方发送至少包含一个八位字节数据的窗口探测。
Actually, you are running into a Linux bug. Linux is not RFC793 compliant when dealing with zero window situations. Windows is actually doing the correct thing. Note that RFC 793 does NOT require that the receiver send an unsolicited window update message. Instead, the requirement is that the sender send a window probe with at least ONE octet of data.
您可能需要考虑使用 UDP 而不是 TCP。我假设您希望 Xbox 播放音频,而不是在本地创建音频副本?在这种情况下,您实际上并不关心是否可靠地获取每个数据包。数据包传输的可靠性是 TCP 带来的开销,但也许您并不真正需要它。 UDP 更简单,并且在流媒体情况下更为典型。
You may want to consider using UDP instead of TCP. I'm assuming you want the Xbox to play the audio, not create a copy of it locally? In which case you really don't care if you get every single packet reliably. The reliability of packet transmission is the overhead that you get with TCP, but maybe you don't really need it. UDP is much simpler and is more typical in streaming situations.