Стек TCP XBox 360 не отвечает на TCP Zero Window Probes с 0-байтовой полезной нагрузкой
Я экспериментирую с Android-приложением, которое передает музыку через UPnP на XBox. Потоковая передача работает по большей части, но довольно часто, через минуту или две, потоковые киоски, особенно когда есть другая активность в сети. Это никогда не происходит при потоковой передаче на другие устройства, отличные от XBox. Я подтвердил это поведение несколькими различными серверными приложениями UPnP.
После анализа множества следов Wireshark я нашел причину. Похоже, что после того, как окно приемника TCP заполнило XBox, оно только явно повторно объявляет обновление окна в ответ на Zero Window Probes, которые содержат 1 байт данных полезной нагрузки.
В то время как машины на базе Windows отправляют пробники Zero Window, содержащие 1-байтовую полезную нагрузку, машины на базе Linux отправляют пробники, содержащие 0-байтовые полезные нагрузки (чистые ACK).
В идеальных условиях сети это не проблема, так как приемник всегда отправляет одно сообщение ACK Window Update, когда он освобождает достаточно места в своем окне, чтобы избежать синдрома глупых окон. Однако, если этот единственный пакет обновления Windows пропущен, он больше не будет отвечать на Linux-устройство Android, потому что стек TCP на этих устройствах использует Zero Window Probes с 0-байтовой полезной нагрузкой (они выглядят как Keep Alive packets для Wirehsark).
Задержка TCP между XBox и WMP выглядит так:
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 активно реагирует на пакеты Zero Window Probe.
Обычная остановка TCP между XBox и клиентом Android выглядит следующим образом:
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.
Столбец TCP между XBox и моим Android-устройством выглядит так, если не объявлено первоначальное объявление обновления Windows:
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 Zero Window Probe с ручной обработкой. Когда это происходит, XBox мгновенно возвращается к жизни и продолжает работать как обычно. К сожалению, я не могу сделать это из своего приложения, потому что для создания такого пакета требуется возможность CAP_NET_RAW, и я не могу предоставить это для своего приложения.
Здесь приведенный выше случай, с помощью Zero Window Probe с ручным вводом (пакет 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, тем не менее, снова возвращается к жизни и снова начинает поток.
- Есть ли способ получить возможности CAP_NET_RAW в приложении для Android, не требуя, чтобы устройство было внедрено?
- Есть ли какой-либо другой трюк, который я могу использовать, чтобы заставить слой Linux TCP отправлять свои Zero Window Probes с 1 байтом данных полезной нагрузки?
- Есть ли какая-нибудь другая непонятная опция TCP, которую я мог бы попробовать, которая позволила бы мне разбудить стек TCP XBox?
- Есть ли какой-нибудь другой внеполосный подход к убеждению XBox отправить другое обновление Windows?
- Есть ли еще какой-то совершенно несвязанный подход, который я мог бы рассмотреть?
Изменить: Это описание того, почему предоставленные предложения не будут работать.
-
TCP_NODELAY
влияет только на то, как пакеты отправляются, когда окно открыто. В частности, установка этого параметра запрещает стеку TCP ждать несколько мс для получения большего количества данных при попытке создать пакет TCP, который заполняет MSS. Он не позволяет отправлять данные при закрытии окна приемника.
-
TCP_QUICKACK
влияет на способ приема пакетов ACK хоста. Проблема, с которой я сталкиваюсь, заключается в том, что мне нужно изменить способ отправки ACK пакетами, которые он получает.
-
MSG_OOB
устанавливает только флаг срочности TCP. Срочные данные не обрабатываются по-разному, пока окно не идет, и все равно не будет отправлено при закрытии окна приемника.
-
Изменение алгоритма управления перегрузкой TCP также не поможет. Поскольку XBox принудительно ограничивает скорость передачи данных на скорость воспроизведения MP3, практически невозможно избежать заполнения окна перегрузки. Возможно, можно уменьшить окно перегрузки, указав пропускную способность, но это уменьшит вероятность только что заполненного окна перегрузок, а не полностью его предотвратит.
-
Использование UDP не является опцией, так как использование стека UPnP является требованием, а UPnP передает данные через HTTP и, следовательно, TCP.
Ответы
Ответ 1
Я нашел несколько вещей, которые могут помочь:
-
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.)
Ответ 2
На самом деле, вы столкнулись с ошибкой Linux. Linux не совместим с RFC793 при работе с ситуациями с нулевым окном. Windows на самом деле делает правильные вещи. Обратите внимание, что RFC 793 НЕ требует, чтобы получатель отправил незапрашиваемое сообщение об обновлении окна. Вместо этого требование заключается в том, что отправитель отправляет окно-зонд с по меньшей мере одним октетом данных.
Ответ 3
Возможно, вам захочется использовать UDP вместо TCP. Я предполагаю, что вы хотите, чтобы Xbox воспроизводил звук, а не создавал его копию локально? В этом случае вам действительно все равно, если вы получите каждый отдельный пакет надежно. Надежность передачи пакетов - это накладные расходы, которые вы получаете с помощью TCP, но, возможно, вам это действительно не нужно. UDP намного проще и более типичен в потоковых ситуациях.