Стек 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 намного проще и более типичен в потоковых ситуациях.