Бесшовный роуминг (миграция между ТД) в wi-fi для Android клиентов.

android wifi roamingКак и обещал ранее, рассказываю, как заставить Android устройства с радио на atheros/qualcomm нормально мигрировать (в большинстве случаев).

Сначала хорошая новость. Достаточно давно atheros/qualcomm добавили в свои драйвера вполне внятную логику handover (миграции внутри плоской L2 сети, с точки зрения клиента,  с множественными AP, на которых установлен один SSID). Собственно, тот самый роуминг, да ещё и бесшовный (ну если используемые AP не совсем плохи и умеют моментально пропихивать в опорную сеть критичные данные, например, ARP-ы для обновления arp таблиц на устройствах в сети + ещё ряд условий на тему кривости).

Теперь по проблеме. Handover есть, сеть с нормальными AP есть, но чтобы клиент мигрировал, по-прежнему требуется пинок, иначе висит до последнего… Что делать и кто виноват? И вот тут, по ходу рассмотрения крутилок, я это проясню.

Для продолжения нужно само устройство, обязательно рутованное, обязательно использующее радио на чипе qualcomm (например yota phone 2).

Перемонтируем разделы в RW, идём в /system/etc/wifi, видим там файлик WCNSS_qcom_cfg.ini – собственно, это основной конфигурационный файл, читаемый драйвером wifi.

Драйверы QCA сами реализуют слои SME/MLME, не экспортируя эти функции на плечи wpa_supplicant. И вся логика управления подключениями и миграцией возложена на них. Wpa_supplicant в Android собран с NO_ROAMING, а значит можно ожидать, что это сделано так же у всех абсолютно чипмэйкеров для Android (конфиги, естественно, разные, или же, как у Broadcom, интересующие нас параметры к исправлению задаются при компиляции, и изменить их на лету невозможно).

Первая крутилка, которая нас будет интересовать в конфиге, это:

# default value of this parameter is zero to enable dynamic threshold allocation
# to set static roming threshold uncomment below parameter and set vaule
gNeighborLookupThreshold=78

Эта крутилка напрямую отвечает за то, когда клиент начнёт пытаться искать кандидата (форсирует сканирование, или запросит список ближайших AP по RRM и проведёт короткую процедуру скана для подтверждения).  И значение у нас тут -78дБ… Как работает автоматика (когда в 0 выставлено значение), надо будет разобраться, как будет время.

И вроде бы всё хорошо, но… Мы как обычно забыли, что то, что слышит клиент и то, что слышит AP, может отличаться по уровню на десятки дБ… Обычно в android-клиентах передатчик в 20МГц полосе может выдать 16дБм, в 40МГц в лучшем случае 14дБм. Тогда как со стороны AP обычно имеем как минимум 20дБм дури даже в 40МГц полосе (обычно определяется законодательными ограничениями конкретной страны, для РФ 20дБм в 2.4ГГц и 23дБм в 5ГГц независимо от ширины, хоть в 80МГц эти 23дБм, хоть в 20МГц). Из опыта, при таком раскладе в среднем перекос по уровням в прямой видимости уже в 10 метрах будет составлять около 10-15Дб, а если есть преграды, то запросто перевалит и за 30.

Но возьмём 10дБ для простоты. Т.е. когда клиент видит AP с уровнем в -78дБ, AP видит клиента уже с уровнем около -88дБ. Стоит говорить, что нормальной работы при таком раскладе уже не будет (особенно в 2.4ГГц в зашумленном эфире офиса)? TCP, требующий подтверждения доставки, просто встанет колом, голос начнёт квакать и т.д.

Плюс, что бы этого избежать (плюс приземлить побольше клиентов, ведь для этого надо заставить всех работать на самой ближней AP, желательно с максимальными рэйтами и без повторов передачи), админ в сети наверняка на AP настроил handoff с уровнем эдак в -75дБ. Т.е. при -75дБ RSSI handoff со стороны AP застрелит такого клиента (при этом на клиенте уровень от AP будет аж -65дБ, т.е. далеко до заветных -78дБ, когда он решит подумать мигрировать). Т.е. будет link beat, сброс стэйтов и обрыв соединений на пользовательской стороне.

Ещё пример – когда AP видит клиента с уровнем в -75дБ, клиент видит AP с уровнем -65дБ!! Или, когда на клиенте видим -78дБ, то со стороны AP клиент будет слышен лишь с -88дБ уровнем.

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

Или же нам придётся снижать мощность на AP что бы “уравнять” шансы, что в условиях грязного эфира может привести к катастрофическому падению SNR и как следствие булькам, хрипам и прочему.

А вот чтобы этого не происходило, достаточно выше обозначенную переменную поправить, выставив значение в диапазоне -65 ~ -70дБ.

Побочный эффект – чуть упадёт скорость у клиента, т.к. чаще будет выполняться фоновые сканирования, плюс незначительно вырастет энергопотребление. Зато у него “будет повод” начать смотреть, кто есть вокруг, и попытаться мигрировать, не дожидаясь, пока ему прилетит по голове от AP.

Следующий блок:

# CCX Support and fast transition
CcxEnabled=0
FastTransitionEnabled=1


CcxEnabled – это поддержка rrm ccx location-measurement, т.е. определения местоположения. Зачастую бесполезна и лишь будет расходовать батарею почём зря.

FastTransitionEnabled – собственно поддержка 802.11R, однако в старых драйверах не полная, но хотя бы умеет учитывать MDIE при миграции (работает всегда, если переменная в значении 1). Заметим, что FT, а именно ускорение фазы аутентификации просто при взводе значения переменной в 1 работать не начнёт, т.к. требуется ещё и Supplicant пропатчить + обвязку андроидную (как это делает, например, Samsung в своих топовых моделях). Однако, учёт MDIE – уже польза. Включаем.

Не забываем также отключить 802.11d,  иначе встретим много чудес в 5ГГц с DFS каналами. Пусть использование или не использование оных лежит на совести админа сети.

# 802.11d support
g11dSupportEnabled=0

Далее блок:

# Legacy (non-CCX, non-802.11r) Fast Roaming Support
# To enable, set FastRoamEnabled=1
# To disable, set FastRoamEnabled=0
FastRoamEnabled=1

Вот оно самое заветное. Включает собственно возможность миграции в любых сетях. Иначе, логика  handover будет активироваться, только если клиент видит, что AP вещает поддержку 802.11R в IECAP. И/или используется WPA*-Enterprise (сюрпрайз)… Ну а как вы хотели? =)

Надо же как-то продавать Enterprise AP, вот и вырубим хэндовер между обычными, не анонсирующими поддержку 802.11R ни в каком виде. Муркетинг животворящий, блин.

Такие клиенты зачастую даже в open сетях висят до последнего. =) Но благо, всё больше вендоров отключают такое поведение установкой вот этой переменной в 1.

В новых драйверах, поставляемых в SDK для Android 6.0.1, добавлена ещё одна прекрасная опция:

# Handoff Enable(1) Disable(0)

gEnableHandoff=0

Эта опция (если выставить в 1) позволяет обрабатывать handoff с пинком со стороны AP как сигнал к миграции, т.е. банально не генерируется LinkBeat при kickout со стороны AP, а сразу выполняется попытка перескочить на следующую подходящую выбранную сканом AP, и только если не получилось сгенерировать Link Beat системе (т.е. о том, что его выпнули, знает только драйвер, и сразу пытается мигрировать, если не получается, то тогда уже сообщает системе, что всё, связи с этой сетью больше нет, надо выбрать другой SSID из тех, которые знает android).

Т.е. пользовательские соединения при этом останутся целыми (ну разве что iperf пострадает, но мессенджеры и голос останутся работать, правда в случае голоса будет квак).

Это слегка нарушает “стандарт” 802.11, но в случае миграции ооочень полезная штука. После её включения, устройство прозрачно (с точки зрения запущенных приложений) сможет мигрировать даже в сетях, где весь роуминг построен исключительно на отстрелах клиента по уровню.

А вот ещё одна крайне полезная крутилка:

#Check if the AP to which we are roaming is better than current AP in terms of RSSI.
#Checking is disabled if set to Zero.Otherwise it will use this value as to how better
#the RSSI of the new/roamable AP should be for roaming
RoamRssiDiff=5

Дельта уровней между AP для выбора кандидата при миграции. Больше значение – меньше вероятность прилететь назад на ту же AP, но более отложенный старт миграции. 5дБ дефолтовых ИМХО маловато. Нужно иметь дельту около 8-10дБ. Для начала выставим 8.

RoamRssiDiff=8

Ещё интересная штука:

#Beacon Early Termination (1 = enable the BET feature, 0 = disable)
enableBeaconEarlyTermination=1
beaconEarlyTerminationWakeInterval=11

Эта опция откидывает все маяки, если в этих фрэймах  не взведён TIM бит, а клиент спит. Т.е. во сне клиент не может вести пассивный сбор данных о соседних AP. Хорошо для батарейки – вероятно, плохо для миграции (надо глубже копать драйвер).

gActiveMaxChannelTime=60
gActiveMinChannelTime=30
gActiveMaxChannelTimeConc=60
gActiveMinChannelTimeConc=30

Настройки времени прослушивания эфира при активном сканировании поканально, в мс. Больше значения – больше времени уйдёт на сканирование всего диапазона. Меньше время – быстрее просканируем, но можем половину не услышать.

RRM включается так:

# 802.11K support
gRrmEnable=1
gRrmOperChanMax=8
gRrmNonOperChanMax=8
gRrmRandIntvl=100

Правда, я на доступных мне железках (в смысле, на тех, в которых было вырублено и включил, на SGS A5 2017 запросы есть, но там и так всё с миграцией более-менее) так и не дождался ни одного запроса с использованием RRM от Yota phone… Видимо, отключена поддержка при сборке драйвера.

Это основное, что касается миграции.

Да и ещё:

# 1: Enable standby, 2: Enable Deep sleep, 3: Enable Mcast/Bcast Filter
gEnableSuspend=3

По дефолту обычно 0. В таком режиме клиент при переходе в режим сна (часто просто при выключении экрана) полностью тушит RF часть, временно просыпаясь только для того, чтобы AP его по таймауту не выпнула. При этом, реализация такова, что и роумингу зачастую становится туго. Следует установить его в 3, т.е. фильтровать броадкасты и мультикасты во сне, и только.

В конфиге ещё много интересных параметов. Например, куча энергосберегаек, если поотключать которые, миграция становиться более весёлой и точной, но батарея…

Ну и на закуску:

#Channel Bonding
gChannelBondingMode24GHz=1
gChannelBondingMode5GHz=1
gShortGI20Mhz=1
gShortGI40Mhz=1

Поддержка широких каналов (>20МГц) + поддержка SGI. Зачастую для 2.4ГГц поддержка 40МГц отключена, т.е. gChannelBondingMode24GHz установлен в 0. Что, во-первых, не позволяет работать на максимальных рэйтах (150Мбит/с для 1T1R в 2.4ГГц при 40МГц, будет только 72). Увы, проблема в том, что видимо для первого соединения значение перекрывается откуда-то ещё, и даже выставив gChannelBondingMode24GHz=1, сразу мы 150Мбит/с не видит. Но после первой же миграции драйвер плюёт на всех и взлетает в 40МГц полосе. =))

Также установка gChannelBondingMode24GHz решает проблему совместимости с некоторыми 2.4ГГц AP на Xiaomi и One+ (как обычно, намутили с анонсами в зависимости от региона, в итоге AP и клиент не могут договориться, в какой полосе разговаривать).

Не забываем после правки сохранить, перемонтировать назад системный раздел в RO и перезагрузиться.

P.S. Поправки и дополнения приветствуются, возможно что-то упустил, что-то не так понял по коду, пока только бегло и с проверкой на одном девайсе. Пробуйте на других. Отписывайтесь.

PP.S. Естественно, всё это будет работать только если вендор не отключил поддержку этого счастья на стадии сборки драйвера. Так что тут как повезёт.

PP.SS. Кому не лень, может накидать приложение для правки этих параметров из GUI.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.