RU

Как устроены узлы: 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 ID413c: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 — у обоих
Конфигурация USBDW5829e обязан быть в конфигурации 1 (QMI), по умолчанию 2 (MBIM) — правило udev · DW5821e работает в конфигурации по умолчанию
Доступ к eUICCQMI через /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 секунд

  1. остановить WDS-сессию;
  2. USB unbind/rebind, чтобы никто не держал управляющее устройство;
  3. lpac profile disable текущего, enable целевого;
  4. DMS reset — модем заново появляется на USB-шине, ждать ≈30 с, а не опрашивать устройство, которое вот-вот исчезнет; lpac во время reset не трогать;
  5. ещё один rebind, затем старт сессии данных;
  6. маршрутизация и network namespace.

DW5821e (PPP), ≈80 секунд

  1. остановить pppd, освободить AT-порт, закрыть зависшие логические каналы на eUICC;
  2. проверить AT+CFUN? до вызова lpac. Если прошлый цикл умер между CFUN=0 и CFUN=1, радио выключено, eUICC обесточен и lpac не возвращает ничего; скрипт отправляет AT+CFUN=1 и ждёт +CPIN: READY;
  3. lpac profile list → disable текущего → enable целевого;
  4. AT+CFUN=0, две секунды, AT+CFUN=1; ждать регистрации в сети по AT+CREG?;
  5. сверить APN (esim-ensure-apn.sh) — подробности в граблях ниже;
  6. сгенерировать 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.

← Методика измерений · К таблице доступности