Это доказательство реализации концепции RTSP поверх протокола Dahua P2P. Он работает с Dahua и производными камерами/сетевыми видеорегистраторами.
Протокол Dahua P2P используется для удаленного доступа к устройствам Dahua. Он обычно используется приложениями Dahua, такими как gDMSS Lite на Android или SmartPSS, KBIVMS на Windows.
В моем конкретном сценарии у меня есть система видеонаблюдения KBVision. Хотя я могу получить доступ к камерам с помощью клиента KBIVMS, я в основном использую платформы, отличные от Windows. Поэтому я хотел изучить альтернативные варианты потоковой передачи видео с помощью клиента RTSP, который поддерживается более широко. В результате я решил поэкспериментировать с перереализацией протокола Dahua P2P.
src/*.rs
— исходные файлы RustCargo.toml
— зависимости Rustmain.py
— Основной скриптhelpers.py
— Вспомогательные функцииrequirements.txt
— зависимости Pythondh-p2p.lua
— диссектор Wireshark для протокола Dahua P2P Реализация Rust с использованием асинхронного программирования и шаблона передачи сообщений, что делает ее более эффективной и гибкой.
A PoC implementation of TCP tunneling over Dahua P2P protocol.
Usage: dh-p2p [OPTIONS] <SERIAL>
Arguments:
<SERIAL> Serial number of the camera
Options:
-p, --port <[bind_address:]port:remote_port>
Bind address, port and remote port. Default: 127.0.0.1:1554:554
-h, --help
Print help
Реализация DH-P2P на Python представляет собой простой и понятный подход. Он используется для целей разработки и тестирования из-за его быстрого и простого написания. Кроме того, реализация более линейна и следует нисходящему потоку выполнения, что упрощает понимание. Python, будучи популярным языком программирования, еще больше способствует его доступности и узнаваемости среди разработчиков.
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Run
python main.py [CAMERA_SERIAL]
# Stream (e.g. with ffplay) rtsp://[username]:[password]@127.0.0.1/cam/realmonitor?channel=1&subtype=0
ffplay -rtsp_transport tcp -i " rtsp://[username]:[password]@127.0.0.1/cam/realmonitor?channel=1&subtype=0 "
Чтобы использовать скрипт с устройством, которое требует аутентификации при создании канала, используйте опцию -t 1
.
При работе в режиме --debug
или когда --type
> 0 аргументы USERNAME
и PASSWORD
являются обязательными. Кроме того, убедитесь, что ffplay
находится в системном пути, когда включен режим отладки.
usage: main.py [-h] [-u USERNAME] [-p PASSWORD] [-d] serial
positional arguments:
serial Serial number of the camera
options:
-h, --help show this help message and exit
-d, --debug Enable debug mode
-t TYPE, --type TYPE Type of the camera
-u USERNAME, --username USERNAME
Username of the camera
-p PASSWORD, --password PASSWORD
Password of the camer
ffplay
и -rtsp_transport tcp
Для реверс-инжиниринга протокола я использовал Wireshark и KBIVMS V2.02.0 в качестве клиента в Windows. Используя диссектор dh-p2p.lua
, вы сможете легче просмотреть протокол в Wireshark.
Для клиента RTSP можно использовать VLC или ffplay для упрощения управления сигналами.
график LR
Приложение[[Этот скрипт]]
Сервис[Easy4IPCloud]
Устройство[Камера/NVR]
Приложение -- 1 --> Сервис
Сервис -- 2 --> Устройство
Приложение <-. 3 .-> Устройство
Протокол Dahua P2P запускается с помощью P2P-рукопожатия. Этот процесс включает в себя поиск устройства по его серийному номеру (SN) через стороннюю службу Easy4IPCloud:
график LR
Устройство[Камера/NVR]
Приложение[[Этот скрипт]]
Клиент1[RTSP-клиент 1]
Клиент2[RTSP-клиент 2]
Clientn[Клиент RTSP n]
Клиент1 -- TCP --> Приложение
Клиент2 -- TCP --> Приложение
Клиентн -- TCP --> Приложение
Приложение <-. Протокол UDPnPTCP .-> Устройство
После установления связи P2P сценарий начинает прослушивать соединения RTSP на порту 554. При подключении клиента сценарий инициирует новую область в протоколе PTCP. По сути, этот сценарий служит туннелем между клиентом и устройством, облегчая связь посредством инкапсуляции PTCP.
последовательностьдиаграмма
участник А как этот сценарий
участник Б как Easy4IPCloud
участник C1 в качестве P2P-сервера
участник C2 в качестве сервера ретрансляции
участник C3 в качестве сервера агента
участник D в качестве камеры/NVR
А->>Б: /probe/p2psrv
Б-->>А: ;
А->>Б: /online/p2psrv/{SN}
B -->> A: информация p2psrv
A->>C1: /probe/device/{SN}
C1 -->>A: ;
A->>B: /онлайн/реле
B -->> A: информация о реле
A->>B: /device/{SN}/p2p-канал (*)
номинал
A->>C2: /реле/агент
C2 -->>A: информация об агенте + токен
A->>C3: /relay/start/{токен}
C3-->>A: ;
конец
B -->> A: информация об устройстве
A->>B: /device/{SN}/relay-channel + информация об агенте
C3 -->>A: Информация о сервере Nat!
A->>C3: PTCP SYN
A->>C3: знак запроса PTCP
C3 -->>A: знак PTCP
A->>D: подтверждение связи PTCP (*)
Примечание . Оба подключения, отмеченные (*)
, и все последующие подключения к устройству должны использовать один и тот же локальный порт UDP.
PTCP (PhonyTCP) — это собственный протокол, разработанный Dahua. Он служит для инкапсуляции TCP-пакетов в UDP-пакеты, обеспечивая создание туннеля между клиентом и устройством за NAT.
Обратите внимание, что официальная документация по PTCP недоступна. Представленная здесь информация основана на реверс-инжиниринге.
Заголовок пакета PTCP представляет собой фиксированную 24-байтовую структуру, как показано ниже:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| magic |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| sent |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| recv |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| pid |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| lmid |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| rmid |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
magic
: постоянное значение, PTCP
.sent
и recv
: отслеживать количество отправленных и полученных байтов соответственно.pid
: идентификатор пакета.lmid
: локальный идентификатор.rmid
: локальный идентификатор ранее полученного пакета.Размер тела пакета варьируется (0, 4, 12 байт или более) в зависимости от типа пакета. Его структура следующая:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| type | len |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| realm |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
type
: указывает тип пакета.len
: длина поля data
.realm
: идентификатор области соединения.padding
: байты заполнения, всегда равные 0.data
: пакетные данные.Типы пакетов:
0x00
: SYN, тело всегда состоит из 4 байтов 0x00030100
.0x10
: данные TCP, где len
— длина данных TCP.0x11
: запрос порта привязки.0x12
: состояние соединения, где данные — CONN
или DISC
.realm
установленной на 0):0x13
: Heartbeat, где len
всегда равен 0.0x17
0x18
0x19
: Аутентификация.0x1a
: ответ сервера после 0x19
.0x1b
: ответ клиента после 0x1a
. Этот проект был вдохновлен и вдохновлен следующими проектами и людьми: