Esta é uma implementação de prova de conceito do RTSP sobre o protocolo Dahua P2P. Funciona com Dahua e câmeras/NVRs derivados.
O protocolo Dahua P2P é utilizado para acesso remoto a dispositivos Dahua. É comumente usado por aplicativos Dahua, como gDMSS Lite no Android ou SmartPSS, KBiVMS no Windows.
No meu cenário específico, tenho um sistema KBVision CCTV. Embora eu possa acessar as câmeras usando o cliente KBiVMS, uso principalmente plataformas não Windows. Portanto, eu queria explorar opções alternativas para streaming de vídeo usando um cliente RTSP, que é mais amplamente suportado. Como resultado, decidi experimentar reimplementar o protocolo Dahua P2P.
src/*.rs
- Arquivos de origem do RustCargo.toml
- Dependências de ferrugemmain.py
- Script principalhelpers.py
- Funções auxiliaresrequirements.txt
- Dependências do Pythondh-p2p.lua
- Dissecador Wireshark para protocolo Dahua P2P Implementação de Rust utilizando programação assíncrona e padrão de passagem de mensagens, tornando-o mais eficiente e flexível.
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
A implementação Python do DH-P2P é uma abordagem simples e direta. É usado para fins de desenho e teste devido à sua natureza rápida e fácil de escrever. Além disso, a implementação é mais linear e segue um fluxo de execução de cima para baixo, facilitando o entendimento. Python, sendo uma linguagem de programação popular, contribui ainda mais para sua acessibilidade e familiaridade entre os desenvolvedores.
# 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 "
Para usar o script com um dispositivo que requer autenticação ao criar um canal, use a opção -t 1
.
Ao executar no modo --debug
ou quando --type
> 0, os argumentos USERNAME
e PASSWORD
são obrigatórios. Além disso, certifique-se de que ffplay
esteja no caminho do sistema quando o modo de depuração estiver ativado.
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
e -rtsp_transport tcp
Para fazer engenharia reversa do protocolo, usei Wireshark e KBiVMS V2.02.0 como cliente no Windows. Usando o dissector dh-p2p.lua
, você pode ver o protocolo no Wireshark mais facilmente.
Para clientes RTSP, VLC ou ffplay podem ser usados para facilitar o controle dos sinais.
gráfico LR
Aplicativo[[Este script]]
Serviço[Easy4IPCloud]
Dispositivo[Câmera/NVR]
Aplicativo -- 1 --> Serviço
Serviço -- 2 --> Dispositivo
Aplicativo <-. 3.-> Dispositivo
O protocolo Dahua P2P inicia com um handshake P2P. Este processo envolve localizar o dispositivo usando seu número de série (SN) através de um serviço de terceiros, Easy4IPCloud:
gráfico LR
Dispositivo[Câmera/NVR]
Aplicativo[[Este script]]
Cliente1[Cliente RTSP 1]
Cliente2[Cliente RTSP 2]
Clienten[Cliente RTSP n]
Cliente1 -- TCP --> Aplicativo
Cliente2 -- TCP --> Aplicativo
Clienten -- TCP --> Aplicativo
Aplicativo <-. Protocolo UDPnPTCP .-> Dispositivo
Após o handshake P2P, o script começa a escutar conexões RTSP na porta 554. Após a conexão de um cliente, o script inicia um novo domínio dentro do protocolo PTCP. Essencialmente, este script serve como um túnel entre o cliente e o dispositivo, facilitando a comunicação através do encapsulamento PTCP.
diagrama de sequência
participante A como este script
participante B como Easy4IPCloud
participante C1 como servidor P2P
participante C2 como servidor de retransmissão
participante C3 como Agente Servidor
participante D como câmera/NVR
A->>B: /probe/p2psrv
B-->>A: ;
A->>B: /online/p2psrv/{SN}
B ->> A: informações p2psrv
A->>C1: /sonda/dispositivo/{SN}
C1-->>A: ;
A->>B: /online/relé
B -->>A: informações do relé
A->>B: /dispositivo/{SN}/canal p2p (*)
par
A->>C2: /relé/agente
C2 -->>A: informações do agente + token
A->>C3: /relay/start/{token}
C3-->>A: ;
fim
B -->>A: informações do dispositivo
A->>B: /device/{SN}/relay-channel + informações do agente
C3 -->>A: Informações Nat do Servidor!
A->>C3: PTCP SYN
A->>C3: Sinal de solicitação PTCP
C3 -->>A: sinal PTCP
A->>D: aperto de mão PTCP (*)
Nota : Ambas as conexões marcadas com (*)
e todas as conexões subsequentes ao dispositivo devem usar a mesma porta local UDP.
PTCP (PhonyTCP) é um protocolo proprietário desenvolvido pela Dahua. Serve para encapsular pacotes TCP dentro de pacotes UDP, permitindo a criação de um túnel entre um cliente e um dispositivo atrás de um NAT.
Observe que a documentação oficial do PTCP não está disponível. As informações fornecidas aqui são baseadas em engenharia reversa.
O cabeçalho do pacote PTCP é uma estrutura fixa de 24 bytes, conforme descrito abaixo:
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
: Um valor constante, PTCP
.sent
e recv
: rastreia o número de bytes enviados e recebidos, respectivamente.pid
: o ID do pacote.lmid
: o ID local.rmid
: O ID local do pacote recebido anteriormente.O corpo do pacote varia em tamanho (0, 4, 12 bytes ou mais) com base no tipo de pacote. Sua estrutura é a seguinte:
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
: especifica o tipo de pacote.len
: O comprimento do campo data
.realm
: o ID do território da conexão.padding
: Bytes de preenchimento, sempre definidos como 0.data
: os dados do pacote.Tipos de pacotes:
0x00
: SYN, o corpo tem sempre 4 bytes 0x00030100
.0x10
: Dados TCP, onde len
é o comprimento dos dados TCP.0x11
: Solicitação de porta de ligação.0x12
: Status da conexão, onde os dados são CONN
ou DISC
.realm
definido como 0):0x13
: Heartbeat, onde len
é sempre 0.0x17
0x18
0x19
: Autenticação.0x1a
: Resposta do servidor após 0x19
.0x1b
: Resposta do cliente após 0x1a
. Este projeto foi inspirado e influenciado pelos seguintes projetos e pessoas: