ZLmediakit的webrtc使用

本文档以win调试为例

准备:安装openssl1.1.1;cmake编译项目时候configure的时候看看有没有正确导入openssl的头文件库文件;也可以等项目编译完从IDE的配置界面修改、增加libssl,libcrypt;

         将WWW文件夹,放到编译好的MediaServer服务的同级目录下;配置文件中webrtc的端口注意设置;

1、简单测试:浏览器输入 127.0.0.1:80/webrtc  

显示:

2、在当前界面说明你已经加载了js客户端,然后在本机的MediaServer(流媒体“服务”)拉一条流(代理拉流,使本机流媒体有一条媒体源);

3、在客户端操作webrtc拉流,拉取流媒体服务中的数据流;

数据传输,涉及stun打洞,获取公网IP,可能涉及到中继转发(要看NAT的模式);信令服务器交换媒体信息(房间加入,离开,bey等状态命令的交互),和传输协议信息等;peerconnection候选者收集,连通性测试,socket数据传输;

4 抓包交互流程:

4.1客户端界面:发送http请求后解析到/webrtc的时候,去www里加载对应的html,就显示了上边的界面;HTML对应的数据实现是:ZLMRTCClient.js是一个webrtc-full(默认、没看到SDP里有ICE-Lite,看交互又像Lite模式 emmm....) client

4.2客户端(jsclient)与ZLM(服务端)交互抓包 whep/whip与一般webrtc的区别是SDP放置的位置会有一定的差异:后续遇到再说

   ZLMRTCClient.js里有peerconnection,SDP 相关的代码

4.3 服务端的回复(这种不需要信令服务器)

4.3 offer/answer 里边的重要信息

    4.3.1 在WebRTC的SDP(会话描述协议)中,a=ice-options:trickle 这一属性用于启用 ‌Trickle ICE‌ 机制,其核心作用是允许端点(如客户端或服务端)在建立连接时逐步交换ICE候选地址(ICE Candidates),而非等待所有候选地址收集完毕后再统一交换;

    4.3.2a=ice-ufrag‌:表示ICE会话的用户名片段(username fragment),用于STUN/TURN请求的身份验证

    4.3.3  a=ice-pwd‌:表示ICE会话的密码(password),长度通常为22或32位(如示例中的f2c4f0baba9438fca75c36626e5eb360),用于加密候选地址信息‌

    4.3.4 a=setup 属性用于指定 DTLS/TLS 连接建立时,本端(发送 SDP 的一端)希望扮演的角色。其可能取值包括:

  • active:本端主动发起 TLS 握手(作为客户端)。
  • passive:本端等待对端发起 TLS 握手(作为服务器)。
  • actpass(默认值):本端支持两种角色(主动或被动),由对端决定最终角色。
  • holdconn(已弃用):预留连接但不进行握手

4.4 抓包的其他信息

公网IP绑定;DTLS加密相关;数据传输使用UDP

4.4.1关于打洞中继服务

mediaserver中stun等相关信息传输与上边的Http请求和信令传输用的不是一个协议;

#rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据,
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
port=8000
#rtc tcp服务器监听端口号,在udp 不通的情况下,会使用tcp传输数据
#该端口是多线程的,同时支持客户端网络切换导致的连接迁移
#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致
tcpPort = 8000

4.4.2 涉及到NAT的集中模式,对称,端口限制,全锥,打洞,为什么需要打洞,中继等等含义可以看下边的博客讲的很好

  推荐文章:https://www.cnblogs.com/ishang/p/3810382.html

  在cs模型的网络编程中,服务器都架设在公网,服务器端不用打洞

       P2P网络编程中,通信的双方A和B既要当服务器,又要当客户端。因此打洞是双方的:A在自己的NAT上为B打一个洞,让B的数据能过来;B在自己的NAT上为A打一个洞,让A的数据能过来

   Full cone NAT(全锥形NAT)所有从同一个内网的(IP,端口)发送出来的请求都会被映射到同一个外网(IP,端口),且任何一个外网主机都可以通过访问映射后的公网地址,实现访问位于内网的主机设备功能

   Restricted Cone NAT(地址受限锥形NAT):类似完全圆锥形NAT,但外部主机必须先收到内网主机的数据包才能发送数据给它;限制比全锥形NAT多了:IP地址限制。

   Port Restricted Cone NAT(端口受限锥形NAT):类似受限圆锥形NAT,但还有端口限制。外部主机必须使用固定的源端口才能发送数据给内网主机。

  Symetric NAT(对称NAT):所有从同一个内网(IP,端口)发送到同一个目的IP和端口的请求都会被映射到同一个IP和端口。换句话说(Src IP,Src port, Dst IP, Dst port)只要有一个发生变化都会使用不同的映射条目,即此NAT映射与报文四元组绑定

4.4.3 为什么要用stun turn?

STUN协议没有穿越的能力,它只是为穿越提供反射地址(Server Reflexive Address)

如果使用stun获取公网IP后,无法达到数据传输的目的 --》TURN协议使用中转的方式实现位于两个不同NAT后的客户端通信

4.4.4 为什么需要打洞穿越?

一般NAT不允许外部公网主机访问局域网主机(出口路由器实施了网络地址转换(NAT)策略,这导致了内网IP地址对外不可见);当时P2P这种通信方式,有可能任意一端给另一端发送消息,公对私,私对私;那这样就需要NAT内部的局域网做一个打洞操作,拿到自己的公网IP,公网IP的交换(候选者收集),让对方能通过本端打的洞,穿越过来;

4.4.5 抓包中的stun解析;

  STUN,首先在RFC3489中定义,作为一个完整的NAT穿透解决方案,英文全称是Simple Traversal of UDP Through NATs,即简单的用UDP穿透NAT;所以stun的交互用的是UDP

 

  • 客户端向 STUN 服务器发送请求​。
  • STUN 服务器处理并响应​。
  • 客户端接收响应并解析公共 IP 和端口​。
  • 客户端根据收到的公共 IP 和端口进行 NAT 穿越和路径选择​。
  • 数据包分析

4.4.5.1 stun绑定请求(https://www.rfc-editor.org/rfc/rfc3489.txt)

当客户端向 STUN 服务器发送一个 STUN 请求时,这个请求被称为 Binding Request。这个请求的目标是让 STUN 服务器返回客户端的公共 IP 地址和端口;

(用户名,候选信息:希望使用当前地址为交互地址,优先级,ICE协商流程)

消息类型:

  

      0x0001  :  Binding Request
      0x0101  :  Binding Response
      0x0111  :  Binding Error Response
      0x0002  :  Shared Secret Request
      0x0102  :  Shared Secret Response
      0x0112  :  Shared Secret Error Response

 消息属性:

   0x0001: MAPPED-ADDRESS
   0x0002: RESPONSE-ADDRESS
   0x0003: CHANGE-REQUEST
   0x0004: SOURCE-ADDRESS
   0x0005: CHANGED-ADDRESS
   0x0006: USERNAME
   0x0007: PASSWORD
   0x0008: MESSAGE-INTEGRITY
   0x0009: ERROR-CODE
   0x000a: UNKNOWN-ATTRIBUTES
   0x000b: REFLECTED-FROM

常见错误码

   400 (Bad Request): The request was malformed.  The client should not
        retry the request without modification from the previous
        attempt.

   401 (Unauthorized): The Binding Request did not contain a MESSAGE-
        INTEGRITY attribute.

   420 (Unknown Attribute): The server did not understand a mandatory
        attribute in the request.

   430 (Stale Credentials): The Binding Request did contain a MESSAGE-
        INTEGRITY attribute, but it used a shared secret that has
        expired.  The client should obtain a new shared secret and try
        again.

   431 (Integrity Check Failure): The Binding Request contained a
        MESSAGE-INTEGRITY attribute, but the HMAC failed verification.
        This could be a sign of a potential attack, or client
        implementation error.

   432 (Missing Username): The Binding Request contained a MESSAGE-
        INTEGRITY attribute, but not a USERNAME attribute.  Both must be
        present for integrity checks.

   433 (Use TLS): The Shared Secret request has to be sent over TLS, but
        was not received over TLS.

   500 (Server Error): The server has suffered a temporary error. The
        client should try again.

   600 (Global Failure:) The server is refusing to fulfill the request.
        The client should not retry.

服务端的响应:

4.4.5 DTLS信息交互(DTLS(数据报传输层安全协议)

WebRTC 使用 SRTP 来进行数据的加解密,DTLS 的作用仅仅是用来做密钥交换,商议出一个本条链路的传输秘钥,RTP/RTCP 的数据为了与历史设备兼容性的考虑,完全通过 SRTP 来实现。

DTLS 的作用是给数据通道数据加密(保证数据安全性)、增加链路证书校验机制(防止网络攻击)。与 TLS over TCP 不同,UDP层没有对数据报文的乱序、丢包做处理,会导致链路证书校验协商无法保证。所以:

DTLS 在创建连接时的握手消息里面,需要增加可靠性传输机制;(零声Github分享官

下述协商秘钥过程,走UDP传输协议:

                              (图片来源于华为产品网站)

协商具体细节:

DTLS握手流程详细描述如下。来源于华为

    Hello协商。
        ClientHello:DTLS客户端发送ClientHello报文给DTLS服务器启动握手,携带它支持的DTLS版本和加密套件等信息。
        HelloVerifyRequest:DTLS服务器响应HelloVerifyRequest给DTLS客户端,携带选定的版本、加密套件以及Cookie信息。如果DTLS服务器允许DTLS客户端在以后的通信中重用本次会话,DTLS服务器还会为本次会话分配会话ID。
    二次Hello协商。
        ClientHello:DTLS客户端发送Client Hello报文中加入服务端发送过来的Cookie,添加了Random,客户端还携带了用于协商的加密套件。
        ServerHello:DTLS服务端响应ServerHello给DTLS客户端,验证Cookie是否合法,若是通过Cookie验证,则继续进行握手连接;若验证失败,则拒绝建立连接。
        Certificate:DTLS服务器将携带自己公钥信息的数字证书发送给DTLS客户端,以便客户端对服务器进行身份认证。
        ServerKeyExchange:DTLS服务器向DTLS客户端发送自己的临时公钥。
        CertificateRequest:DTLS服务器要求DTLS客户端提供证书,以便服务器对客户端进行身份认证。
        ServerHelloDone:DTLS服务器通知DTLS客户端版本和加密套件协商结束,开始进行密钥交换。
    密钥协商。
        Certificate:DTLS客户端发送自己的证书给DTLS服务器。
        ClientKeyExchange:DTLS客户端验证DTLS服务器的证书合法后,利用证书中的公钥加密DTLS客户端随机生成的密钥发给DTLS服务器。
        CertificateVerify:DTLS客户端发送验证消息给服务器,以便服务器对客户端进行身份认证。
        ChangeCipherSpec:DTLS客户端通知DTLS服务器后续报文将采用协商好的密钥和加密套件进行加密。
        Finished:DTLS客户端通知DTLS服务器,握手过程结束。
        ChangeCipherSpec:DTLS服务器通知DTLS客户端后续报文将采用协商好的密钥和加密套件进行加密。
        Finished:DTLS服务器通知DTLS客户端,握手过程结束。

握手成功后,DTLS客户端也就完成了对DTLS服务器的身份验证。因为只有拥有私钥的DTLS服务器才能从ClientKeyExchange消息中解密得到密钥,才有后续握手的成功。

 协商之后开始传输数据一般是加密的RTP即SRTP数据;使用的传输端口就是stun,DTLS 使用的端口;继续传输数据;

5、ZLM作为服务端响应webrtc拉流经过了那些步骤:

进入到webapi.cpp下的/index/api/webrtc 接口响应处;根据Http请求的type值,首先协商SDP;查找媒体源,创建webrtcplayer;(代码来自ZLM)

通过回调函数,进图监听函数,在进入回调到这个位置:

然后通过协商SDP函数回到webapi代码层:

生成answer完成SDP交互

WebRtcPlayer::WebRtcPlayer这个类,是流媒体服务从本地找到媒体源之后,由于客户端要将这路媒体源按照RTC协议播放,所以创建了webrtcplayer,并不是流媒体服务去其他服务器代理拉流;他的创建不是来源于addstreamproxy;

关于stun(ICE=stun+turn),DTLS ,data 交互;监听服务是在启动之初就启动好的:8000端口的UDPserver;见下图;

客户端开始获取(客户端自己的)公网IP,从stun服务,这里的stun服务就是ZLM启动的时候监听8000的UDP服务;

stun交互过程中,socket接收到数据,判断session类型为WebRtcSession,将数据传给WebRtcSession::onRecv_l;之后WebRtcTransport::inputSockData来处理 _ice_transport->processStunPacket;

包括处理DTLS都是在下边这个接口;

处理stun请求;如下;至于DTLS的交换秘钥的处理引用了mediasoap的一些源代码;不是太清楚DtlsTransport.cpp内

然后就是ZLM使用RTSP的RTP数据,通过webrtcplayer传出去;通过给rtsp媒体源的ring加一个reader,让数据能够传递给webrtctransport

 

posted on 2025-03-17 21:06  邗影  阅读(3909)  评论(0)    收藏  举报

导航