Как устроены узлы: Dell DW5821e-eSIM и DW5829e-eSIM на Linux
Каждый узел Freedom Checker — обычная Linux-машина с проводным подключением и LTE-модемом Dell, у которого встроенная eUICC (eSIM) держит профили нескольких операторов. Переключение между операторами программное: узел включает другой eSIM-профиль, поднимает сессию данных, уводит её в отдельный network namespace и прогоняет проверки оттуда.
Всё, что описано ниже, работает в продакшене с мая 2026 года и опубликовано в открытом репозитории dell-dw5821e-dw5829e-esim-linux (страница проекта): скрипты, правила udev, юниты systemd и документация по каждому отказу. Прошивок Dell и проприетарных бинарников там нет — драйверы уже есть в ядре, репозиторий закрывает то, чего не хватало в пространстве пользователя. Это страница-конспект; детали и актуальные версии скриптов — в репозитории.
Два модема
Оба — на Qualcomm Snapdragon X20, оба со встроенной eUICC и без слота для физической SIM. Различаются они тем, как Linux разговаривает с eUICC и как поднимается сессия данных.
| DW5829e-eSIM · DW5821e-eSIM | |
| USB ID | 413c:81e4 · 413c:81e0 (81d7 — DW5821e без eSIM) |
| Модуль | Snapdragon X20 · Snapdragon X20, Foxconn T77W968, прошивка T77W968.F1.0.0.5.2.GC.013 |
| Драйверы ядра | qmi_wwan, cdc_mbim, option — у обоих |
| Конфигурация USB | DW5829e обязан быть в конфигурации 1 (QMI), по умолчанию 2 (MBIM) — правило udev · DW5821e работает в конфигурации по умолчанию |
| Доступ к eUICC | QMI через /dev/cdc-wdm0 и qmi-proxy, слот UIM 2 · AT+CSIM через последовательный AT-порт |
| Сессия данных | QMI WDS, wwan0 в режиме raw-IP · pppd и ATD*99# через AT-порт |
| Смена профиля | lpac enable → DMS reset (qmicli --dms-set-operating-mode=reset) · lpac enable → AT+CFUN=0 / AT+CFUN=1 |
| Проверено на | Ubuntu 24.04, ядро 6.8, libqmi 1.36, libmbim 1.32, pppd 2.4.9 |
eSIM-профили: lpac
Профилями управляет lpac — открытый LPA-клиент (AGPL-3.0, в репозиторий не входит). Он разговаривает с eUICC через APDU-бэкенд, а с сервером оператора (SM-DP+) — через HTTP-бэкенд (curl). Под два модема нужны две сборки: для DW5829e — бэкенд qmi поверх qmi-proxy, чтобы управляющее устройство делилось с qmicli; для DW5821e — бэкенд at_csim на AT-порту, который в этот момент обязан быть свободен от pppd.
export LPAC_APDU=at_csim LPAC_APDU_AT_DEVICE=/dev/dw5821e-at LPAC_HTTP=curl lpac profile list lpac profile download -s <SM-DP+ host> -m <matching id> lpac profile enable <AID> lpac notification process -a
Профиль опознаётся по ISD-P AID (a0000005591010ffffffff89000011xx), а не по ICCID; объявление аплинков узла — это пары «AID → APN». Коды активации и matching ID в репозиторий не попадают: это одноразовый ключ на скачивание профиля. Первое, что надо сделать на любой из двух машин, — выключить и замаскировать ModemManager: он захватывает QMI-устройство и AT-порты в момент появления модема, и lpac после этого падает с ошибками, которые выглядят как неисправность eUICC.
Полный цикл переключения оператора
Включить другой профиль недостаточно — модем должен заново прочитать eUICC. У каждого модема для этого свой ритуал, и обе последовательности описаны целиком, потому что любой пропущенный шаг даёт отказ, похожий на проблему у оператора.
DW5829e (QMI), 40–60 секунд
- остановить WDS-сессию;
- USB unbind/rebind, чтобы никто не держал управляющее устройство;
lpac profile disableтекущего,enableцелевого;- DMS reset — модем заново появляется на USB-шине, ждать ≈30 с, а не опрашивать устройство, которое вот-вот исчезнет; lpac во время reset не трогать;
- ещё один rebind, затем старт сессии данных;
- маршрутизация и network namespace.
DW5821e (PPP), ≈80 секунд
- остановить pppd, освободить AT-порт, закрыть зависшие логические каналы на eUICC;
- проверить
AT+CFUN?до вызова lpac. Если прошлый цикл умер междуCFUN=0иCFUN=1, радио выключено, eUICC обесточен и lpac не возвращает ничего; скрипт отправляетAT+CFUN=1и ждёт+CPIN: READY; lpac profile list→ disable текущего → enable целевого;AT+CFUN=0, две секунды,AT+CFUN=1; ждать регистрации в сети поAT+CREG?;- сверить APN (
esim-ensure-apn.sh) — подробности в граблях ниже; - сгенерировать PPP-peer и chatscript под APN,
pppd call, дождаться адреса наppp0, таблица маршрутизации 100 и правило, namespace.
Сессия данных
QMI WDS. Интерфейс wwan0 переводится в raw-IP, qmicli --wds-start-network="apn=…,ip-type=4" поднимает сессию, --wds-get-current-settings отдаёт адрес, шлюз, DNS и MTU. Маршрут по умолчанию через модем ставится в отдельной таблице (у нас 100) с правилом ip rule from <адрес wwan> lookup 100 и никогда — в main: проводной маршрут машины остаётся на месте. Состояние сессии пишется в файл в /run, откуда его читают остальные скрипты и помощник namespace.
PPP. Файл peer и chatscript генерируются заново при каждом подъёме: однажды peer, правленный руками, с неверным путём к устройству прогнал лестницу восстановления через семь ступеней впустую. В логе pppd строки Could not determine remote IP address: defaulting to 10.64.64.64 и IPV6CP: timeout sending Config-Requests безвредны; PAP проходит с пустыми учётными данными у российских операторов, которыми мы пользуемся. Порты модема получают постоянные имена от udev по номеру USB-интерфейса — /dev/dw5821e-at (AT-команды и PPP), /dev/dw5821e-at2 (второй AT-порт для диагностики, пока первый занят PPP), -gps, -dm, — чтобы скрипты никогда не угадывали ttyUSB.
Изоляция: один namespace на аплинк
Помощник создаёт namespace esim с парой veth, маршрутом по умолчанию через сторону хоста, SNAT на адрес модема и своим resolv.conf. Всё, что запущено через ip netns exec esim …, ходит через мобильный аплинк и больше никуда — так можно измерять «что пропускает этот оператор», не трогая маршрутизацию хоста.
Правило, которое мы вывели дорого: мерить из namespace или не мерить вовсе. Если переключение не удалось, а проверка всё равно запустилась, она молча идёт через провод и ложится в базу под именем оператора. Поэтому неудачное переключение обязано бросать исключение, а перед выдачей мобильной задачи проверяется, что namespace существует. До этого правила мы нашли недели «мобильных» успехов, которые были проводом.
Восстановление после отказов
Сотовые операторы рвут простаивающие сессии (NAT, PDP), и узел обязан это замечать сам. Пробник здоровья делает HTTPS-запрос изнутри namespace каждые 60 секунд с таймаутом 8 секунд; три подряд неудачи запускают лестницу: обновление bearer → повторное переключение профиля → reset модема, с паузами между ступенями 60 с / 5 мин / 15 мин, не больше трёх reset в час и затем час тишины. Пока аплинк восстанавливается, задач на него не выдаётся. «Модем пропал с USB» засчитывается только после четырёх проверок за ~30 секунд — одна проверка попадает в окно собственного rebind.
При блокировке на стороне оператора лестница помочь не может и будет перезагружать модем три раза в час бесконечно; такой аплинк надо ограничить или выключить.
Грабли, которые стоили недель
- ModemManager. Захватывает
/dev/cdc-wdm0и AT-порты при появлении модема. Выключить и замаскировать; оба модема прекрасно живут без него. - DW5829e появляется как MBIM. Конфигурация 2 по умолчанию, QMI-драйверам нужна 1. Одна строка udev.
- Радио остаётся выключенным после прерванного
CFUN=0/1(DW5821e).+CFUN: 0,lpac profile listпуст,AT+CPIN?→+CME ERROR: 13, chatscript падает наATH → ERROR. Поскольку документированный CFUN-цикл стоит после lpac, до него не доходит, и узел крутится на «профиль не найден» вечно — наш крутился 30 часов. Лечится проверкойAT+CFUN?до lpac. CFUN-циклы надо ограничивать: 170 штук за два часа однажды довели прошивку до состояния, в котором модем висел на шине и не отвечал ничем. - Attach с APN предыдущего оператора (DW5821e). При
CFUN=0модем отвечаетERRORнаAT+CGDCONT, а приCFUN=1цепляется к сети с тем APN, что лежал в контексте 1 раньше. Классический chatscript ставитCGDCONT=1только передATD*99#, уже после attach, и bearer остаётся с чужим APN. Снаружи это выглядит как проблема оператора: зарегистрирован, PDP активен, адрес есть, полезной нагрузки ноль — один оператор молчит, другой отвечаетRST, третий уводит любой HTTP на портал «неверный APN / баланс». Диагноз —AT+CGCONTRDP=1на втором AT-порту. Лечение —esim-ensure-apn.sh:CGDCONT=1при включённом радио, сверка с активным контекстом, при расхожденииAT+CGATT=0/AT+CGATT=1(≈14 с). Дефект прятался месяцами, потому что переключения на других операторов падали ещё на lpac и в контексте оставался APN работающего; первое удачное чужое переключение сломало всех разом. - «Обновление bearer», которое на деле USB unbind/rebind (DW5829e). Модем исчезает из
/devна 10–20 секунд. Нервная лестница «одна неудачная 4-секундная проба раз в 20 с → refresh» получала ровно это: задачи в полёте гибли в окне rebind, следующая проба попадала в следующее окно, переключение оператора не укладывалось в таймаут, а разовая проверка «модем на шине?» ловила rebind и открывала часовой предохранитель на живом модеме. Устоявшиеся значения — 60 с / 8 с / три неудачи подряд. - Состояние абонента у оператора выглядит ровно как поломка модема.
+CEREG: 0,1,+CGACT: 1,1, адрес выдан, PPP поднят, байтов ноль. Прежде чем трогать модем: открыть из namespace портал самого оператора — еслиlogin.<оператор>илиbalance.<оператор>отвечает, а внешний мир нет, абонент в «огороженном» состоянии (период охлаждения, капча, приостановка), и модем ни при чём; проверить APN по+CGCONTRDP=1; прочитать SMS (AT+CMGL="ALL") — операторы часто пишут причину; USSD-запрос баланса (+CUSD: 5в ответ — сам по себе подсказка); те же операторы на другом узле с другими SIM исключают общий сбой сети. - Мелочи ценой в деплой каждая. Остаточный
/var/lock/LCK..ttyUSB0после мёртвого pppd срывает следующий дозвон; lpac, не отпустивший AT-порт, морит pppd голодом; peer с устаревшим путём к устройству после переименования udev даёт «unrecognized option».
Что лежит в репозитории
| docs/ | по одному документу на модем (QMI и PPP), заметка про сборку lpac под оба бэкенда и раскладку RPATH, и pitfalls.md — каждый отказ: как выглядит снаружи и что помогло |
|---|---|
| scripts/dw5829e-qmi/ | переключение профиля, bearer up/down/refresh, bootstrap для QMI-модема |
| scripts/dw5821e-ppp/ | то же для PPP-модема плюс esim-ensure-apn.sh и шаблон peer |
| scripts/common/ | network namespace с SNAT на аплинк, подготовка симлинков lpac, помощник скачивания профиля |
| udev/, systemd/ | постоянные имена портов DW5821e, пин USB-конфигурации DW5829e, одноразовые юниты, восстанавливающие раскладку lpac после перезагрузки |
| examples/ | как объявляются аплинки (профили × APN) для bootstrap-скрипта |
Лицензия скриптов и документации — MIT. Приветствуются дополнения по другим модулям на Snapdragon X20 (Foxconn T77W968 встречается под несколькими OEM-именами) и по DW5821e в режиме MBIM, который нам не понадобился и не проверялся.
Данные, которые эти узлы собирают, открыты: открытый пакет данных с архивом по дням, схемой полей и лицензией CC BY 4.0.