Пакет DMRD: побайтовый разбор протокола Homebrew

Категория: Сеть и доступСложность: ~11 мин

Хотспот подключается к серверу, в дашборде бегут вызовы — и всё это едет по протоколу, у которого нет официальной спецификации. Homebrew (он же MMDVM-протокол) описан только исходным кодом и обрывочными вики. Разберём его так, как он выглядит в реальном трафике: что за что отвечает в каждом байте, как устроен вход по паролю и почему один и тот же протокол у разных программ немного разный.

Откуда взялся протокол и почему нет спецификации

Homebrew придумали для того, чтобы самодельный репитер мог подключиться к сети как обычный узел. Первое описание сделал DL5DI, но в вики BrandMeister прямо сказано, что эта спецификация неполна, а версия, реализованная в MMDVMHost, имеет недокументированные отличия. Поэтому единственный надёжный источник правды — код: hblink.py в HBlink3 и DMRNetwork.cpp в MMDVMHost.

Практическое следствие: если вы пишете свой сервер, ориентируйтесь на поведение живых клиентов, а не на текстовые описания. Мы в DMRhub так и делали — свой мастер писался под то, что реально присылают Pi-Star, WPSD и приложения.

Порт Классический порт мастера — 62030 (так у BrandMeister), но он не зашит в протокол: DMRhub принимает на 62031, другие сети выбирают своё. Порт всегда UDP.

Кадр DMRD: 55 байт, в которых лежит весь голос

Голосовой и данный трафик едет одним типом пакета — он начинается с ASCII-сигнатуры DMRD. Общая длина 55 байт: 20 байт заголовка и 33 байта полезной нагрузки (264 бита — ровно один DMR-бёрст).

БайтыПолеЧто означает
0–3СигнатураВсегда DMRD (0x44 0x4D 0x52 0x44)
4SequenceСчётчик пакетов в потоке, растёт по кругу 0–255
5–7RF SourceDMR ID говорящего, 3 байта — отсюда предел 16 777 215
8–10DestinationНомер группы или ID абонента при приватном вызове
11–14Peer IDID узла, приславшего пакет: хотспота, репитера или приложения
15ФлагиСлот, тип вызова, тип кадра — разбор ниже
16–19Stream IDСлучайный идентификатор одной передачи от нажатия до отпускания PTT
20–52Payload33 байта = 264 бита DMR-бёрста (голос AMBE или данные)

Байт 15: четыре смысла в восьми битах

Самый плотный байт кадра. Разбирается так:

  • Бит 7 (маска 0x80) — таймслот: установлен → TS2, снят → TS1.
  • Бит 6 (маска 0x40) — тип вызова: установлен → приватный (unit), снят → групповой.
  • Биты 5–4 (маска 0x30, сдвиг вправо на 4) — тип кадра: голос, голос с синхронизацией или данные.
  • Биты 3–0 (маска 0x0F) — уточнение. Для данных: 1 — заголовок голосового вызова, 2 — терминатор. Для голоса: 05 — бёрсты A…F в шестикадровом суперкадре.
  • Отдельная проверка: если (флаги & 0x23) == 0x23, это CSBK внутри голосового потока.
Почему это важно на практике Терминатор — байт 15 с типом кадра «данные» и значением 2 в младших битах. Именно по нему сервер понимает, что передача кончилась. Если хотспот пропал из эфира, не прислав терминатор (сел аккумулятор, оборвался канал), сервер обязан закрыть поток по таймауту, иначе группа останется «занятой». Мы на этих граблях стояли: без страховки по таймауту одна оборванная передача блокировала мост.

Вход в сеть: соль, SHA-256 и четыре шага

Прежде чем слать голос, узел должен войти. Обмен текстовый — все команды, кроме DMRD, это ASCII-сигнатуры с приклеенными данными.

  1. Пир → мастер: RPTL + свой ID (4 байта). «Здравствуйте, я такой-то».
  2. Мастер → пир: RPTACK + соль (4 случайных байта). Соль на каждое подключение новая — поэтому перехваченный ответ нельзя переиграть.
  3. Пир → мастер: RPTK + свой ID + sha256(соль ‖ пароль) в виде hex-строки. Сам пароль по сети не идёт никогда.
  4. Мастер: считает то же самое у себя и сравнивает. Совпало — RPTACK, не совпало — MSTNAK и разрыв.

Дальше пир присылает RPTC с конфигурацией (позывной, частоты RX/TX, мощность, координаты, высота, описание, URL) и, если нужно, RPTO с опциями. Живость поддерживается RPTPINGMSTPONG. Отключение: RPTCL от пира или MSTCL от мастера.

Самая частая ошибка входа «Неверный пароль» при верном пароле почти всегда означает одно из двух: вы подставили ID не того устройства (пароль проверяется в паре с ID) или клиент шлёт хеш от соли в другом представлении. Проверять надо не пароль, а то, какой именно ID уходит в RPTL. Разбор типичных случаев — в статье хотспот не выходит в сеть.

Почему у BlueDV «другой» Homebrew

Исторически разошлись две ветки: классический Homebrew 2015 года и вариант MMDVM 2020 года. Отличия в наборе полей конфигурации и в мелочах поведения, но их достаточно, чтобы инструменты анализа не понимали чужой диалект. Показательный пример: единственный публичный диссектор для Wireshark честно предупреждает, что работает только с протоколом MMDVMHost и не работает с «generic homebrew», которым пользуется BlueDV.

Если пишете сервер — принимайте оба варианта: отличать их можно по длине и составу пакета RPTC.

Как посмотреть это своими глазами

Ничего экзотического не нужно. На хотспоте с Pi-Star или WPSD:

  • снять дамп: sudo tcpdump -i any -w dmr.pcap udp port 62031;
  • забрать файл на компьютер и открыть в Wireshark;
  • смотреть на первые 20 байт полезной нагрузки UDP — по таблице выше они читаются вручную за минуту.

Для автоматического разбора есть открытые описания формата в виде .ksy-схем Kaitai Struct (проект OK-DMR) — они покрывают и Homebrew 2015, и MMDVM 2020, включая нестандартные расширения. Для Python-разбора полей внутри 33-байтной нагрузки пригодится ok-dmrlib: там реализованы и помехоустойчивые коды, и разбор PDU.

Про лицензии Если берёте чужой код как основу для своего сервиса, посмотрите лицензию заранее: ok-dmrlib и Kaitai-схемы OK-DMR распространяются под AGPL-3.0, а это накладывает обязательства даже при использовании по сети. Диссектор MMDVM для Wireshark — под CC BY-NC-SA, то есть без коммерческого применения.

Что внутри 33 байт

Полезная нагрузка — это сырой DMR-бёрст: 264 бита, из которых 216 занимает голос (три кадра AMBE+2 по 72 бита) либо данные, а середину занимает синхронизация или встроенная сигнализация. Как эти биты защищены от ошибок и почему их так странно перемешивают — отдельная тема, разобранная в статье про помехоустойчивое кодирование DMR. О том, как голос превращается в эти 72 бита, — в статье про вокодер AMBE.

Свой мастер вместо чужой сети

DMRhub — это полностью свой сервер на этом самом протоколе: хотспоты подключаются напрямую, эфир и журнал вызовов видны в панели, а маршруты вы задаёте сами. Подключить существующий хотспот — дело нескольких минут.

Источники

  1. HBlink3, разбор кадра DMRD и обмен RPTL/RPTK — github.com/HBLink-org/hblink3
  2. Homebrew repeater protocol, вики BrandMeister (порт 62030, SHA-256 с солью, оговорка о неполноте спецификации) — wiki.brandmeister.network
  3. MMDVMHost, реализация клиентской стороны — github.com/g4klx/MMDVMHost
  4. MMDVM-Dissector для Wireshark (ограничение по generic homebrew, лицензия CC BY-NC-SA) — github.com/marrold/MMDVM-Dissector
  5. dmr-kaitai, схемы Homebrew 2015 и MMDVM 2020 — github.com/OK-DMR/dmr-kaitai
  6. Homebrew/MMDVM коннектор в openSPOT2 — manuals.sharkrf.com
В сети DMRhub

Настроили рацию — заходите в эфир: свой мастер, хотспоты и приложение с DMR прямо в телефоне.

Войти в портал