Skip to content

Latest commit

 

History

History
164 lines (134 loc) · 14.7 KB

File metadata and controls

164 lines (134 loc) · 14.7 KB

dcvp-server

Авторизованное исследование собственного автомобиля. Владелец изучает штатную телематику своей машины на изолированном лабораторном стенде. Сервер принимает кадры только от синтетического клиента (client/fake_tbox.py) и/или собственного T-BOX владельца в бортовой сети; он не подключается к серверам производителя, ничего не пишет в живую машину и не перехватывает чужой трафик. Реальные хосты платформы используются исключительно как имена SAN в локальном лабораторном CA.

Приёмный сервер телематического протокола DCVP (Dongfeng Cloud Vehicle Platform) для блока T-BOX автомобиля VOYAH Courage / 岚图知音 四驱全球版.

Цель — принимать выгрузку штатного T-BOX на свой сервер (локальный, в бортовой сети), без китайского облака: переиспользовать штатные датчики/GPS/антенны для собственной телеметрии и заодно снять паразитный разряд 12 В (радио в flight, аплоад по Ethernet). Протокол восстановлен статическим реверсом прошивки tsp_manager (T_BOX_H37A3630849BA), без правок на живой машине.

⚠️ Исследовательский инструмент для собственного автомобиля. Секреты конкретной машины (ключи, сертификаты, ICCID/IMEI/MAC/VIN, GPS) в репозитории отсутствуют и не должны здесь появляться.

Протокол (что известно из прошивки)

  • Схема данныхproto/vcp_custom_message.proto (пакет tboxev.vcp, proto2, 68 сообщений / 534 поля), извлечена из встроенного FileDescriptorProto в .rodata бинаря tsp_manager. Ключевые: in_binding_info (идентификационный хендшейк), in_veh_data (97 полей состояния), in_location_data, in_driving_report/in_alarm_info, in_general_config (адрес платформы по протоколу).

  • Обёртка кадра — GB/T 32960 (китайский нацстандарт телематики ЭМ), поверх mTLS-сокета:

    off size поле
    0 2 23 23 (##, 起始符)
    2 1 cmd — командный флаг
    3 1 resp — флаг ответа (0xFE=запрос, 0x01=успешный ACK)
    4 17 VIN (ASCII)
    21 1 enc — шифрование data-unit (0x01=нет, 0x02=AES)
    22 2 len — длина data-unit, big-endian
    24 N data-unit
    24+N 1 BCC — XOR байтов [2 .. 24+N)

    Полный кадр = 25 + N байт. protobuf tboxev.vcp.* едет в data-unit под cmd = 0xCB (заводской диапазон 0x80..0xFE), а не отдельным каналом.

  • Последовательность сессии: login(0x01) → ACK → heartbeat(0x07) каждые heartBeat_Time сек + realtime(0x02)/custom(0xCB) по событию → logout(0x04). До логина данные буферизуются. При 3 неудачных логинах — ретрай-цикл 30 мин.

  • Порты/транспорт: 6504/6507 — DCVP-сессия (TCP mTLS, GB/T 32960); 6553 — HTTPS POST /log/logReceiveGateway (загрузка логов, отдельный канал); KPI/PKI — HTTPS-JSON.

  • mTLS: по разбору прошивки, T-BOX проверяет сервер по trust store в перезаписываемой области блока; владелец может (вручную, вне этого репо) указать своему блоку локальный CA из certs/, чтобы блок доверял лабораторному серверу. Клиентский сертификат сервер принимает любой (или VERIFYPEER=0) — упрощение стенда.

  • Bootstrap PKI: до провижининга клиентского сертификата T-BOX поднимает TLS на заводском tbox.pfx, затем CSR → client.crt.

Что реализует сервер

  • Терминирует mTLS нашим CA (порты 6504/6507).
  • deframe GB/T 32960: поиск старта ##, разбор заголовка, длина BE по смещению 22, проверка XOR-BCC, ресинхронизация сквозь мусор, придержка недочитанного кадра.
  • Диспетч по cmd; custom-кадры (0xCB) распаковывает как protobuf tboxev.vcp.*; enc=0x02 (AES) помечает, не пытаясь парсить.
  • Генерирует GB-ACK (resp=0x01): для login/logout — эхо time+serial, для heartbeat — header-only. Без ACK T-BOX ловит response_TimeOut и переоткрывает сессию.
  • Отдельный HTTPS-хендлер на 6553 для POST /log/logReceiveGateway.
  • Персистит всё принятое (см. «Хранение»): кадр сохраняется до отправки ACK, чтобы переоткрытие сессии клиентом не теряло уже принятое. Битые (BCC) кадры сохраняются, но не ACK'аются.

Хранение принятого

Каталог данных — DCVP_LOGDIR (в контейнере /app/logs, том ./logs, gitignored). Реализация — server/store.py, SQLite из stdlib (WAL), сторонних зависимостей нет.

logs/dcvp.sqlite3            база: sessions / frames / log_uploads / meta (версия схемы)
logs/uploads/<ts>_<name>     тела журналов T-BOX (POST /log/logReceiveGateway), как пришли
logs/raw/<ts>_<port>_<peer>.raw   сырой захват каждого соединения (см. ниже)
Таблица Что Ключевые поля
sessions одно TCP-соединение на порту сессии opened_ts/closed_ts, peer, port, vin, login_ts (момент login — до него кадры небиндингованные), frames, close_reason
frames каждый кадр GB/T 32960 ts (UTC, мс), session_id, vin, cmd/cmd_name, resp, enc, bcc_ok, logged_in, status, pb_type, pb_json, raw (кадр целиком: заголовок + data-unit + BCC), acked
log_uploads загрузка журнала query-параметры запроса (filename, sha256, …), content_type, size, заявленный/фактический sha256 и их совпадение, путь к файлу тела

status кадра: decoded — custom-payload распознан как tboxev.vcp.*pb_json; при неоднозначности типа рядом список _alternatives — разбор перебором типов, это эвристика), structural — data-unit стандарта (login/logout), encryptedenc=0x02, сырьё без разбора, undecoded — custom не распознан, bad_bcc — контроль не сошёлся, empty — без data-unit (heartbeat).

Сырьё хранится всегда: структура ACK/login и точные теги полей статически не выведены, и именно по raw их можно будет сверить с живым перехватом, не теряя уже накопленное.

  • Схема версионируется (meta.schema_version, миграции в store.MIGRATIONS — только добавлять новые номера, старые не править). База новее кода → сервер не стартует.
  • Retention: DCVP_RETENTION_DAYS (или --retention-days), 0 = хранить всё; чистка на старте и раз в час. Вручную: dcvp_export.py purge --days N.
  • Экспорт: make stats, make export [SINCE=… TABLE=frames|sessions|log_uploads VIN=…]logs/export.jsonl (raw — hex). Утилита server/dcvp_export.py работает параллельно с сервером.
  • В консоль координаты (lng/lat), ICCID и серийники блока/BT не печатаются — только в базу; data-unit login в консоли обрезан до time+serial.

Сырой захват соединений (logs/raw/)

Страховка на первое время живого трафика: раскладка кадра выведена статически, и если она окажется не такой, gb_deframe начнёт «терять» байты, а база получит bad_bcc/undecoded. Поэтому server/rawcap.py пишет всё, что пришло из сокета и что мы ответили, до кадрирования — один файл на соединение (и для сессии, и для канала логов). Записи: 'DR' | ts float64 | dir (0=rx, 1=tx, 2=meta) | len u32 | payload; файл открыт без буферизации, обрыв процесса ничего не теряет; пустые соединения файла не оставляют. Хвост, не разобранный на кадры к моменту закрытия, дополнительно печатается в консоль как TAIL.

  • Включён по умолчанию: DCVP_RAW_CAPTURE=1; выключить 0, когда формат подтверждён и базы достаточно.
  • Смотреть: make raw (весь каталог) / make raw FILE=<имя>.raw LIMIT=0 — hexdump по записям с метками времени и направлением. Программно — rawcap.iter_records(path).
  • Файлы подпадают под retention вместе с базой.
  • Это plaintext после TLS: неудачный handshake (например, блок не доверяет нашему CA) сюда не попадает — в консоли будет TLS … handshake failed, а сам TLS-обмен можно снять только на хосте: tcpdump -i <iface> -w tbox.pcap 'host <ip блока> and (port 6504 or port 6507 or port 6553)'.

Ещё не выведено статически (нужен живой перехват / qemu-стенд)

  • Точная структура data-unit ACK сервера (какие поля кроме serial_number валидирует T-BOX). Текущий ACK минимальный, но по коду достаточный для удержания сессии.
  • Реальные heartBeat_Time/таймауты и включён ли AES data-unit (enc=0x02) — берутся из конфига блока.
  • Точная 54-байтная раскладка login data-unit (время/流水号/ICCID видны, порядок подсистем — по GB/T 32960.2).
  • Приём remote-config in_general_config от произвольного пира (второй вектор задания адреса платформы).

Структура

proto/     vcp_custom_message.proto     схема выгрузки (tboxev.vcp)
server/    dcvp_server.py + Dockerfile  приёмник GB/T 32960 + GB-ACK + HTTPS-логи
           store.py                     хранилище: SQLite (кадры/сессии/загрузки) + uploads/
           rawcap.py                    сырой захват соединений до кадрирования (logs/raw/)
           dcvp_export.py               stats / JSONL-экспорт / purge / hexdump raw-захвата
postman/   коллекция + environment      проба HTTPS-канала логов (6553) из Postman, см. postman/README.md
client/    fake_tbox.py                 синтетический клиент: GB/T 32960-сессия без машины
harness/   README.md                    план qemu-стенда: живой tsp_manager → наш сервер
certs/     gen-ca.sh                    генератор своего CA + server/client cert (сами cert'ы gitignored)
docker-compose.yml · Makefile

Быстрый старт

make certs          # свой CA + server/client cert (генерятся локально, в git не идут)
make build          # образ; protobuf-байндинги генерятся внутри из .proto
make up             # сервер на :6504/6507 (сессия) + :6553 (HTTPS-логи), лог в консоль
# в другом окне — синтетический клиент (GB/T 32960-сессия end-to-end):
make client
make stats          # что накопилось в logs/dcvp.sqlite3
make export         # logs/export.jsonl
make raw            # hexdump сырого захвата соединений (logs/raw/)

Канал логов (6553) можно пробовать из Postman — postman/README.md. Сессию GB/T 32960 Postman не умеет (сырой TCP) — только make client.

Отладка без TLS/клиентского cert: добавить в command сервера --plaintext или --no-client-cert. Синтетический клиент прогоняет login → binding → location → N×(heartbeat + veh_data) → logout и печатает ACK.

Приватность

certs/наш лабораторный CA (не секреты машины), генерится локально и в git не коммитится (.gitignore). ⛔ Реальные device-секреты T-BOX (rsa_aes_tbox.key, боевой client.crt, tbox.pfx, настоящий ca.pem), а также VIN/ICCID/IMEI/MAC/GPS конкретной машины сюда класть нельзя. Значения в client/fake_tbox.py — синтетические.