这是通过大华 P2P 协议实现 RTSP 的概念验证。它可与大华及其衍生摄像机/NVR 配合使用。
大华P2P协议用于远程访问大华设备。它通常被大华应用程序使用,例如 Android 上的 gDMSS Lite 或 Windows 上的 SmartPSS、KBiVMS。
在我的具体场景中,我有一个 KBVision CCTV 系统。虽然我可以使用 KBiVMS 客户端访问摄像机,但我主要使用非 Windows 平台。因此,我想探索使用得到更广泛支持的 RTSP 客户端流式传输视频的替代选项。于是,我决定尝试重新实现大华P2P协议。
src/*.rs
- Rust 源文件Cargo.toml
- Rust 依赖项main.py
- 主脚本helpers.py
- 辅助函数requirements.txt
- Python 依赖项dh-p2p.lua
- 大华 P2P 协议的 Wireshark 解析器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 .-> 设备
大华P2P协议以P2P握手发起。此过程涉及通过第三方服务 Easy4IPCloud 使用其序列号 (SN) 来定位设备:
图LR
设备[摄像机/NVR]
应用程序[[此脚本]]
客户端1[RTSP客户端1]
客户端2[RTSP客户端2]
客户端 n[RTSP 客户端 n]
客户端1 -- TCP --> 应用程序
客户端2 -- TCP --> 应用程序
客户端 -- TCP --> 应用程序
应用<-. UDPnPTCP 协议 .-> 设备
P2P 握手之后,脚本开始侦听端口 554 上的 RTSP 连接。在客户端连接后,脚本在 PTCP 协议内启动一个新领域。本质上,该脚本充当客户端和设备之间的隧道,通过 PTCP 封装促进通信。
序列图
参与者 A 作为此脚本
参与者 B 作为 Easy4IPCloud
参与者C1作为P2P服务器
参与者C2作为中继服务器
参与者C3作为代理服务器
参与者 D 作为摄像头/NVR
A->>B:/probe/p2psrv
B-->>A: ;
A->>B: /online/p2psrv/{SN}
B-->>A: p2psrv 信息
A->>C1:/探针/设备/{SN}
C1-->>A: ;
A->>B: /在线/中继
B-->>A:中继信息
A->>B: /device/{SN}/p2p-channel (*)
标杆
A->>C2: /relay/agent
C2-->>A:代理信息+令牌
A->>C3: /relay/start/{token}
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)是大华开发的专有协议。它的目的是将 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
:数据包 ID。lmid
:本地 ID。rmid
:先前接收到的数据包的本地 ID。数据包主体的大小根据数据包类型而变化(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
:连接的领域 ID。padding
:填充字节,始终设置为 0。data
:数据包数据。数据包类型:
0x00
:SYN,主体始终为 4 字节0x00030100
。0x10
:TCP数据,其中len
是TCP数据的长度。0x11
:绑定端口请求。0x12
:连接状态,其中数据为CONN
或DISC
。realm
设置为 0):0x13
:心跳,其中len
始终为 0。0x17
0x18
0x19
:身份验证。0x1a
: 0x19
之后的服务器响应。0x1b
: 0x1a
之后的客户端响应。 该项目受到以下项目和人员的启发和影响: