Skip to content

notes_ru

Vasily Evseenko edited this page Aug 16, 2024 · 2 revisions

Записки об архитектуре WFB-ng

Введение

Почему обычный WiFi не подходит для связи на большие расстояния?

  1. Классический WiFi после передачи каждого пакета ждет подтверждения от приемника (пакет ACK). И ждет он не больше некоторого фиксированного времени (ACK timeout). Если это время вышло, то пакет будет признан потерянным и будет отправлен снова. Соответственно на дальних расстояниях из-за конечной скорости света время передачи пакета будет больше ACK timeout'а и соединения не будет. Да, некоторые карточки (в основном операторского класса) могут увеличивать ACK timeout, но обычные так не умеют. Но это по прежнему требует чтобы связь работала в обе стороны.

  2. Ассоциация между точкой доступа и устройством в обычном WiFi дело очень медленное и так же требует обязательного наличия двусторонней связи.

Как работает WFB-ng.

WFB-ng не использует классический стек протоколов 802.11. Вместо этого карточка просто используется как приемопередатчик, который может отправлять и принимать пакеты без запроса ACK пакетов с другой стороны. Для борьбы с неизбежными потерями используется избыточное кодирование - FEC. В данном случае Reed-Solomon on Vandermonde matrix. Это менее эффективно, чем использование ACK'ов (все-таки WiFi делали не совсем дураки), но позволяет передавать данные на большие расстояния с минимальными задержками (latency).

Самая большая проблема сделать FEC что бы он с одной стороны был максимально надежный, а с другой не вносил задержек и имел минимальные накладные расходы. Фундаментально это проблема в общем случае не решается, но можно решить ее в каких-то частных случаях.

Введение в FEC:

Кольцевой буфер

В WFB-ng реализован кольцевой буфер, который позволяет собирать одновременно несколько FEC блоков, так как выяснилось, что некоторые карточки (особенно если их несколько или в система разнесена на несколько машин в сети) могут вполне себе принимать пакеты с довольно большими плавающими задержками относительно друг-друга (собственно usb протокол им в этом способствует) особенно на большой скорости передачи. Если какой-то блок собирается целиком (то есть получено К пакетов - с дырами или без), то все не закрытые блоки перед ним там же выдаются в выход (если там есть дыры, то они игнорируются - там как лучше выдать хоть что-то, чем выкинуть блок с дырой). Таким образом выполняется инвариант, что на выходе wfb_rx никогда не бывает пакетов в неправильном порядке (могут быть только дыры - то есть потерянные пакеты).

Как уже говорилось ранее если перед пакетом нет дыры (с начала блока) и его блок самый ранний в буфере (то есть перед ним нет не закрытых блоков), то он выдается наружу без задержки. Соответственно если приходит пакет, который закрывает дыру, то все, что до него было без дыр так же выдается сразу наружу

FEC timeout

Если передача только односторонняя, то кроме увеличения latency потеря пакетов в канале ни на что не влияет. Но если коммуникация двусторонняя (например mavlink), то тут начинается веселье. Например мы послали запрос из K mavlink пакетов (например скачать файл с полетного контроллера) и первый пакет потерялся по дороге. Соответственно wfb_rx будет терпеливо ждать следующего пакета, что бы закрыть блок и запустить FEC. Но передатчик его не пришлет, так как он запрос послал и теперь ждет ответ. Получается классический deadlock Именно по этой причине по умолчанию у mavlink'а и тоннеля я рекомендую ставить FEC 1/2 или 1/3. То есть чтобы K=1 и не было задержек на приеме. Но если хочется сделать K > 1, то надо выставить fec_timeout. Тогда передатчик (wfb_tx) если видит не закрытый FEC блок и ему не приходит ничего за время T, то он выпускает специальный пакет размера ноль и снова сбрасывает таймер ожидания. Соответственно в худшем случае timeout будет (K-1) * T (если послали только один пакет и он потерялся).

FEC delay

Если у нас есть импульсная помеха, то выдача сразу всех FEC пакетов (N - K) без задержек после передачи последнего пакета с данными с одной стороны хороша (мы минимизируем latency), а с другой мы можем их все разом и потерять (если в этот момент времени в эфире будет помеха). Поэтому добавлена опция fec_delay. Если ее поставить ненулевой, то перед передачей каждого N - K FEC пакета wfb_tx будет ожидать некоторый timeout. В случае медленных потоков вроде mavlink'а это допустимо, но в случае с видео (400 и более пакетов в секунду) у нас будет всего 2мс на пакет и доп задержки тут просадят скорость, поэтому переходим к следующему пункту.

Traffic shaper

Я не буду здесь рассказывать всю теорию (она длинная и можно ее почитать в куче мест), то в Linux'е traffic shaper это грубо говоря набор очередей (конечного размера, которые умеют переполняться) и планировщик который выдергивает из них следующий пакет согласно алгоритму (их есть куча разных от простых до очень сложных). wfb_tx умеет помечать метками (fwmark) пакеты в разных потоках (video, mavlink, tunnel и т.д.) а так же ставить отдельные метки для data и fec пакетов. То есть можно сказать, что mavlink (например) имеет наивысший приоритет, потом видео и потом туннель. Ну и задать жестко (или не очень) ширину полосы под каждый вид трафика. Например сейчас есть scripts/tc.sh он это все делает. Если еще немного по колдовать с traffic shaper'ом, то можно сказать, что fec пакеты имеют приоритет ниже, чем у данных и передавать их можно с задержкой. То есть traffic shaper будет смешивать при отправке пакеты данных блока M + 1 и FEC'и от блока M, без задержки начала передачи данных блока M + 1, как было бы при использовании fec_delay.

Архитектура WFB-ng

Q: Какие карточки поддерживает WFB-ng кроме rtl8812au?

A: Любые с поддержкой injection, но тут есть один нюанс (как в известном анекдоте).

Кроме стандартного режима работы (wifi-client) карточка может поддерживать еще и не стандартные: monitoring и injection. Первое означает что можно принимать любые пакеты, а второе - что их так же можно и передавать. Есть два типа карточек: FullMAC и SoftMAC. В первых firmware (то есть процессор внутри карточки) делает все (весь 802.11 стек), а во втором - только часть связанную с непосредственной передачей пакета в эфир. Карточки первого типа обычно невозможно заставить делать ни monitoring ни injection (только если у вас не исходников firmware). Пример таких карточек: Intel и ath10k (новый Atheros). Примером карточек второго типа является ath9k и rtl8812au. Тут уже большая часть делается процессоров основной системы в драйвере ядра, от которого есть исходники.

Как узнать есть ли у карточки поддержка injection? В общем случае никак, так как это недокументированный режим (собственно как и monitoring). Но если monitoring поддерживает большая часть карточек, то injection - единицы. Самый простой способ это посмотреть что используют другие проекты которым нужен данный функционал: aircrack-ng, openhd и т.д. Либо посмотреть исходники драйвера на предмет наличия слова "injection".

Теперь о нюансах:

  1. injection бывает разного качества. У некоторых карточек он например ограничен по скорости и/или по типам передаваемых пакетов (например для ath9k можно передать любой пакет, а для rtl8812au нельзя передать rts/cts)

  2. Кроме injection мы используем еще STBC и LDPC. Первое это возможность одновременно передачи (и приема) через несколько антенн. Позволяет подключить к одной карточке например все-направленную и узконаправленную антенны. Второе - это внутри пакетный FEC, который позволяет исправить некоторое количество ошибок ВНУТРИ пакета (то есть улучшить немного SNR программными методами). 8812au поддерживает оба метода (а вот например ath9k, 8811*, 8812[bc]u и ralink не поддерживает некоторые из них). Если у карточки нет поддержки одного их этих протоколов, то такой пакет будет просто отброшен при приеме. Что бы это починить надо выключить LDPC и/или STBC в /etc/wifibroadcast.cfg на стороне передатчика.

  3. Ограничение по частотам и мощности. Стандартный wifi подчиняется регуляторам (FCC, Госсвязьнадзор и т.д.) и имеет сильные ограничения по мощности и частоте в разных странах. Для этого был придуман механизм CRDA. Если ему следовать, то летать получится сильно не далеко и на загаженной частоте (где висят точки доступа, сигнализации, микроволновые печи и т.д.). Но при наличии Softmac драйвера и исходников можно данные ограничения частично выключить. К сожалению иногда они бывают зашиты в firmware (или в недокументированные регистры карточки), так что выключить их не получается. Именно по этому wfb-ng использует отдельную версию драйвера rtl8812au v5.2.20. Это последняя версия в которой можно выкрутить мощность на полную. В более новых realtek похоже поменял firmware и так сделать уже не получается.

Q: Зачем в WFB-ng часть кода написана на Python? Почему не переписать это все на C/C++/Rust/etc ?

A: Потому что это повышает надежность и уменьшает сложность разработки.

WFB-ng состоит из двух частей:

  1. data plane
  2. control plane.

Первое отвечает за передачу данных, довольно простое внутри и для максимизации производительности написано на минимальном подмножестве C++.

Второе отвечает за настройку и управление первой частью, написано на асинхронном питоне (Twisted) и имеет сильно большую сложность.

В отличии от других проектов (например OpenHD) data plane отделим от control plane и может быть использован самостоятельно (например так делает OpenIPC), но требует написание собственного control plane или значительной деградации функционала (отсутствие двусторонней телеметрии, поддержки несколько карточек, IP-тоннеля и т.д.).

Почему именно python/twisted, а не C/C++ ? Потому, что это позволяет сильно сократить количество кода и ошибок в нем (при любой ошибке у вас будет exception с полным trace до места ошибки, а не переполнение буфера или segmentation fault). Если бы я это все писал на C/C++ и libevent то количество кода можно было бы умножать на 10.

Clone this wiki locally