低时延迟流媒体之WebRTC协议
低时延迟流媒体之WebRTC协议
- 低时延迟流媒体之WebRTC协议
- 前言
-
- 一、客户端与服务器交换SDP的信息
- [二、STUN协议的验证 Binding Request 0001 (USERNAME、GOOG-NETWORK-INFO、ICE-CONTROLLING、USE-CANDIDATE、PRIORITY、MESSAGE-INTEGRITY、FINGERPRINT)](#二、STUN协议的验证 Binding Request 0001 (USERNAME、GOOG-NETWORK-INFO、ICE-CONTROLLING、USE-CANDIDATE、PRIORITY、MESSAGE-INTEGRITY、FINGERPRINT))
-
- 1、STUN协议的支持类型
- [2、RFC 3489定义的 STUN Message Body](#2、RFC 3489定义的 STUN Message Body)
- 三、DTLS密钥协商流程
- [四、 发送音视频数据rtp、rtcp、application](#四、 发送音视频数据rtp、rtcp、application)
- 总结

前言
上面是WebRTC交互整连接交换的步骤的流程图
四大步骤:
- 客户端与服务端交换SDP
- STUN协议的验证 Binding Request 0001 (USERNAME、GOOG-NETWORK-INFO、ICE-CONTROLLING、USE-CANDIDATE、PRIORITY、MESSAGE-INTEGRITY、FINGERPRINT)
- DTLS密钥协商流程
- 发送音视频数据rtp、rtcp、application
一、客户端与服务器交换SDP的信息
sdp中含有的信息分五大类
- 会话描述:会话版本,会话名称,会话时长,会话发起者
- 媒体信息描述:音视频格式列表,支持的协议列表,参数说明等
- 网络连接描述:网络类型,地址类型,IP地址
- 安全描述:ice用户名和密码,fingerprint
- 服务质量描述:RTCP反馈机制
举例:
1、播放端的生成offer的SDP信息:
v=0
o=- 2123222562544546878 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0 1 2
a=extmap-allow-mixed
a=msid-semantic: WMS
m=audio 9 UDP/TLS/RTP/SAVPF 111 63 103 104 9 0 8 106 105 13 110 112 113 126
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PT+G
a=ice-pwd:rCTVsJSJN5kr/f7Q5Xzkpadd
a=ice-options:trickle
a=fingerprint:sha-256 2F:34:92:44:DF:15:37:77:15:58:53:93:F5:F0:93:6E:B0:C8:3E:6D:A5:B0:97:43:53:BD:0A:86:58:1A:E1:B0
a=setup:actpass
a=mid:0
a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=recvonly
a=rtcp-mux
a=rtpmap:111 opus/48000/2
a=rtcp-fb:111 transport-cc
a=fmtp:111 minptime=10;useinbandfec=1
a=rtpmap:63 red/48000/2
a=fmtp:63 111/111
a=rtpmap:103 ISAC/16000
a=rtpmap:104 ISAC/32000
a=rtpmap:9 G722/8000
a=rtpmap:0 PCMU/8000
a=rtpmap:8 PCMA/8000
a=rtpmap:106 CN/32000
a=rtpmap:105 CN/16000
a=rtpmap:13 CN/8000
a=rtpmap:110 telephone-event/48000
a=rtpmap:112 telephone-event/32000
a=rtpmap:113 telephone-event/16000
a=rtpmap:126 telephone-event/8000
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98 99 100 101 35 36 37 38 102 122 127 121 125 107 108 109 124 120 39 40 41 42 43 44 45 46 47 48 123 119 114 115 116 49
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:PT+G
a=ice-pwd:rCTVsJSJN5kr/f7Q5Xzkpadd
a=ice-options:trickle
a=fingerprint:sha-256 2F:34:92:44:DF:15:37:77:15:58:53:93:F5:F0:93:6E:B0:C8:3E:6D:A5:B0:97:43:53:BD:0A:86:58:1A:E1:B0
a=setup:actpass
a=mid:1
a=extmap:14 urn:ietf:params:rtp-hdrext:toffset
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:13 urn:3gpp:video-orientation
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=extmap:5 http://www.webrtc.org/experiments/rtp-hdrext/playout-delay
a=extmap:6 http://www.webrtc.org/experiments/rtp-hdrext/video-content-type
a=extmap:7 http://www.webrtc.org/experiments/rtp-hdrext/video-timing
a=extmap:8 http://www.webrtc.org/experiments/rtp-hdrext/color-space
a=extmap:4 urn:ietf:params:rtp-hdrext:sdes:mid
a=extmap:10 urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id
a=extmap:11 urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id
a=recvonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:96 VP8/90000
a=rtcp-fb:96 goog-remb
a=rtcp-fb:96 transport-cc
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtpmap:97 rtx/90000
a=fmtp:97 apt=96
a=rtpmap:98 VP9/90000
a=rtcp-fb:98 goog-remb
a=rtcp-fb:98 transport-cc
a=rtcp-fb:98 ccm fir
a=rtcp-fb:98 nack
a=rtcp-fb:98 nack pli
a=fmtp:98 profile-id=0
a=rtpmap:99 rtx/90000
a=fmtp:99 apt=98
a=rtpmap:100 VP9/90000
a=rtcp-fb:100 goog-remb
a=rtcp-fb:100 transport-cc
a=rtcp-fb:100 ccm fir
a=rtcp-fb:100 nack
a=rtcp-fb:100 nack pli
a=fmtp:100 profile-id=2
a=rtpmap:101 rtx/90000
a=fmtp:101 apt=100
a=rtpmap:35 VP9/90000
a=rtcp-fb:35 goog-remb
a=rtcp-fb:35 transport-cc
a=rtcp-fb:35 ccm fir
a=rtcp-fb:35 nack
a=rtcp-fb:35 nack pli
a=fmtp:35 profile-id=1
a=rtpmap:36 rtx/90000
a=fmtp:36 apt=35
a=rtpmap:37 VP9/90000
a=rtcp-fb:37 goog-remb
a=rtcp-fb:37 transport-cc
a=rtcp-fb:37 ccm fir
a=rtcp-fb:37 nack
a=rtcp-fb:37 nack pli
a=fmtp:37 profile-id=3
a=rtpmap:38 rtx/90000
a=fmtp:38 apt=37
a=rtpmap:102 H264/90000
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 transport-cc
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=102
a=rtpmap:127 H264/90000
a=rtcp-fb:127 goog-remb
a=rtcp-fb:127 transport-cc
a=rtcp-fb:127 ccm fir
a=rtcp-fb:127 nack
a=rtcp-fb:127 nack pli
a=fmtp:127 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42001f
a=rtpmap:121 rtx/90000
a=fmtp:121 apt=127
a=rtpmap:125 H264/90000
a=rtcp-fb:125 goog-remb
a=rtcp-fb:125 transport-cc
a=rtcp-fb:125 ccm fir
a=rtcp-fb:125 nack
a=rtcp-fb:125 nack pli
a=fmtp:125 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=rtpmap:107 rtx/90000
a=fmtp:107 apt=125
a=rtpmap:108 H264/90000
a=rtcp-fb:108 goog-remb
a=rtcp-fb:108 transport-cc
a=rtcp-fb:108 ccm fir
a=rtcp-fb:108 nack
a=rtcp-fb:108 nack pli
a=fmtp:108 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=42e01f
a=rtpmap:109 rtx/90000
a=fmtp:109 apt=108
a=rtpmap:124 H264/90000
a=rtcp-fb:124 goog-remb
a=rtcp-fb:124 transport-cc
a=rtcp-fb:124 ccm fir
a=rtcp-fb:124 nack
a=rtcp-fb:124 nack pli
a=fmtp:124 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=4d001f
a=rtpmap:120 rtx/90000
a=fmtp:120 apt=124
a=rtpmap:39 H264/90000
a=rtcp-fb:39 goog-remb
a=rtcp-fb:39 transport-cc
a=rtcp-fb:39 ccm fir
a=rtcp-fb:39 nack
a=rtcp-fb:39 nack pli
a=fmtp:39 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=4d001f
a=rtpmap:40 rtx/90000
a=fmtp:40 apt=39
a=rtpmap:41 H264/90000
a=rtcp-fb:41 goog-remb
a=rtcp-fb:41 transport-cc
a=rtcp-fb:41 ccm fir
a=rtcp-fb:41 nack
a=rtcp-fb:41 nack pli
a=fmtp:41 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=f4001f
a=rtpmap:42 rtx/90000
a=fmtp:42 apt=41
a=rtpmap:43 H264/90000
a=rtcp-fb:43 goog-remb
a=rtcp-fb:43 transport-cc
a=rtcp-fb:43 ccm fir
a=rtcp-fb:43 nack
a=rtcp-fb:43 nack pli
a=fmtp:43 level-asymmetry-allowed=1;packetization-mode=0;profile-level-id=f4001f
a=rtpmap:44 rtx/90000
a=fmtp:44 apt=43
a=rtpmap:45 AV1/90000
a=rtcp-fb:45 goog-remb
a=rtcp-fb:45 transport-cc
a=rtcp-fb:45 ccm fir
a=rtcp-fb:45 nack
a=rtcp-fb:45 nack pli
a=rtpmap:46 rtx/90000
a=fmtp:46 apt=45
a=rtpmap:47 AV1/90000
a=rtcp-fb:47 goog-remb
a=rtcp-fb:47 transport-cc
a=rtcp-fb:47 ccm fir
a=rtcp-fb:47 nack
a=rtcp-fb:47 nack pli
a=fmtp:47 profile=1
a=rtpmap:48 rtx/90000
a=fmtp:48 apt=47
a=rtpmap:123 H264/90000
a=rtcp-fb:123 goog-remb
a=rtcp-fb:123 transport-cc
a=rtcp-fb:123 ccm fir
a=rtcp-fb:123 nack
a=rtcp-fb:123 nack pli
a=fmtp:123 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=64001f
a=rtpmap:119 rtx/90000
a=fmtp:119 apt=123
a=rtpmap:114 red/90000
a=rtpmap:115 rtx/90000
a=fmtp:115 apt=114
a=rtpmap:116 ulpfec/90000
a=rtpmap:49 flexfec-03/90000
a=rtcp-fb:49 goog-remb
a=rtcp-fb:49 transport-cc
a=fmtp:49 repair-window=10000000
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:PT+G
a=ice-pwd:rCTVsJSJN5kr/f7Q5Xzkpadd
a=ice-options:trickle
a=fingerprint:sha-256 2F:34:92:44:DF:15:37:77:15:58:53:93:F5:F0:93:6E:B0:C8:3E:6D:A5:B0:97:43:53:BD:0A:86:58:1A:E1:B0
a=setup:actpass
a=mid:2
a=sctp-port:5000
a=max-message-size:262144
2、服务端根据客户端的offer中信息生成一个answer的SDP信息
v=0
o=rtc 11111111111360111 2 IN IP4 0.0.0.0
s=live/41010500002000000003
c=IN IP4 0.0.0.0
t=0 0
a=group:BUNDLE 0 1 2
a=msid-semantic: WMS live/41010500002000000003
m=audio 9 UDP/TLS/RTP/SAVPF 111
c=IN IP4 0.0.0.0
a=mid:0
a=ice-ufrag:vlARmeOs
a=ice-pwd:TNjLFSHmI79ONJEoIhWYZaomAokJtaw5
a=candidate:0 1 udp 2130706431 192.168.9.174 10001 typ host generation 0
a=fingerprint:sha-1 F8:68:61:3B:98:6C:E0:45:35:81:CC:50:F6:C2:5E:E6:73:2A:42:FF
a=fingerprint:sha-224 82:82:24:8C:1D:22:B9:A2:C3:14:D7:EC:B8:9D:69:24:2B:CA:89:55:7E:7B:26:36:A9:7F:A4:5A
a=fingerprint:sha-256 97:65:BF:A0:69:DE:2F:10:85:1D:E4:B9:36:D5:34:B7:E3:3A:E3:5F:00:2E:29:80:1D:61:6A:A6:7C:86:0A:67
a=fingerprint:sha-384 0C:47:56:DF:55:58:3E:7F:DD:E4:4F:BA:01:89:B3:7F:7D:6F:EC:59:C0:DC:3C:3D:91:83:EA:64:2B:34:0F:ED:02:BD:19:B9:0A:9E:32:84:79:A5:C5:97:03:57:BF:C3
a=fingerprint:sha-512 B3:13:3F:36:3A:16:7C:5C:5D:7A:22:4B:EA:9F:50:37:67:69:46:E8:49:24:4E:80:FB:73:F5:7A:13:E2:84:FE:1A:0E:4E:26:B2:D1:9C:0A:10:3C:D8:E5:F1:AF:F1:1D:69:41:F5:C0:87:DC:C5:54:AF:CB:BA:25:D4:CD:3A:BF
a=setup:passive
a=sendonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;stereo=1;useinbandfec=1
a=rtcp-fb:111 transport-cc
a=rtcp-fb:111 nack
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=ssrc:53142682 cname:live/41010500002000000003
a=ssrc:53142682 msid:live/41010500002000000003 live/41010500002000000003_audio
a=ssrc:53142682 mslabel:live/41010500002000000003
a=ssrc:53142682 label:live/41010500002000000003_audio
m=video 9 UDP/TLS/RTP/SAVPF 102 122
c=IN IP4 0.0.0.0
a=mid:1
a=ice-ufrag:vlARmeOs
a=ice-pwd:TNjLFSHmI79ONJEoIhWYZaomAokJtaw5
a=fingerprint:sha-1 F8:68:61:3B:98:6C:E0:45:35:81:CC:50:F6:C2:5E:E6:73:2A:42:FF
a=fingerprint:sha-224 82:82:24:8C:1D:22:B9:A2:C3:14:D7:EC:B8:9D:69:24:2B:CA:89:55:7E:7B:26:36:A9:7F:A4:5A
a=fingerprint:sha-256 97:65:BF:A0:69:DE:2F:10:85:1D:E4:B9:36:D5:34:B7:E3:3A:E3:5F:00:2E:29:80:1D:61:6A:A6:7C:86:0A:67
a=fingerprint:sha-384 0C:47:56:DF:55:58:3E:7F:DD:E4:4F:BA:01:89:B3:7F:7D:6F:EC:59:C0:DC:3C:3D:91:83:EA:64:2B:34:0F:ED:02:BD:19:B9:0A:9E:32:84:79:A5:C5:97:03:57:BF:C3
a=fingerprint:sha-512 B3:13:3F:36:3A:16:7C:5C:5D:7A:22:4B:EA:9F:50:37:67:69:46:E8:49:24:4E:80:FB:73:F5:7A:13:E2:84:FE:1A:0E:4E:26:B2:D1:9C:0A:10:3C:D8:E5:F1:AF:F1:1D:69:41:F5:C0:87:DC:C5:54:AF:CB:BA:25:D4:CD:3A:BF
a=setup:passive
a=candidate:0 1 udp 2130706431 192.168.9.174 10001 typ host generation 0
a=sendonly
a=rtcp-mux
a=rtcp-rsize
a=rtpmap:102 H264/90000
a=fmtp:102 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42001f
a=rtpmap:122 rtx/90000
a=fmtp:122 apt=102
a=rtcp-fb:102 ccm fir
a=rtcp-fb:102 goog-remb
a=rtcp-fb:102 nack
a=rtcp-fb:102 nack pli
a=rtcp-fb:102 transport-cc
a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time
a=extmap:3 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01
a=ssrc:53142683 cname:live/41010500002000000003
a=ssrc:53142683 msid:live/41010500002000000003 live/41010500002000000003_video
a=ssrc:53142684 cname:live/41010500002000000003
a=ssrc:53142684 msid:live/41010500002000000003 live/41010500002000000003_video
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:vlARmeOs
a=ice-pwd:TNjLFSHmI79ONJEoIhWYZaomAokJtaw5
a=ice-options:trickle
a=fingerprint:sha-1 F8:68:61:3B:98:6C:E0:45:35:81:CC:50:F6:C2:5E:E6:73:2A:42:FF
a=fingerprint:sha-224 82:82:24:8C:1D:22:B9:A2:C3:14:D7:EC:B8:9D:69:24:2B:CA:89:55:7E:7B:26:36:A9:7F:A4:5A
a=fingerprint:sha-256 97:65:BF:A0:69:DE:2F:10:85:1D:E4:B9:36:D5:34:B7:E3:3A:E3:5F:00:2E:29:80:1D:61:6A:A6:7C:86:0A:67
a=fingerprint:sha-384 0C:47:56:DF:55:58:3E:7F:DD:E4:4F:BA:01:89:B3:7F:7D:6F:EC:59:C0:DC:3C:3D:91:83:EA:64:2B:34:0F:ED:02:BD:19:B9:0A:9E:32:84:79:A5:C5:97:03:57:BF:C3
a=fingerprint:sha-512 B3:13:3F:36:3A:16:7C:5C:5D:7A:22:4B:EA:9F:50:37:67:69:46:E8:49:24:4E:80:FB:73:F5:7A:13:E2:84:FE:1A:0E:4E:26:B2:D1:9C:0A:10:3C:D8:E5:F1:AF:F1:1D:69:41:F5:C0:87:DC:C5:54:AF:CB:BA:25:D4:CD:3A:BF
a=setup:passive
a=mid:2
a=sctp-port:5000
a=max-message-size:262144
服务端根据客户端支持五大信息生成对应格式
二、STUN协议的验证 Binding Request 0001 (USERNAME、GOOG-NETWORK-INFO、ICE-CONTROLLING、USE-CANDIDATE、PRIORITY、MESSAGE-INTEGRITY、FINGERPRINT)

有STUN header和STUN Body组成的
- 包括20字节的STUN header
- Body中可以有0个或多个Attribute
1、STUN协议的支持类型
| 枚举 | 类型 | 含意 |
|---|---|---|
| STUN_BINDING_REQUEST | 0x0001 | 绑定信息 |
| STUN_BINDING_INDICATION | 0x0011 | |
| STUN_BINDING_RESPONSE | 0x0101 | 绑定信息响应 |
| STUN_BINDING_ERROR_RESPONSE | 0x0111 | 绑定错误 |
| GOOG_PING_REQUEST | 0x200 | |
| GOOG_PING_RESPONSE | 0x300 | |
| GOOG_PING_ERROR_RESPONSE | 0x310 |
2、RFC 3489定义的 STUN Message Body

- 信息头后有0或多个属性
- 每个塑性进行TLV编码: Type、Length、Value
Attribute的使用表里面,C代表什么?
C表示分类,M 表示方法
| 枚举 | 类型 | 含义 |
|---|---|---|
| STUN_ATTR_MAPPED_ADDRESS | 0x0001 | 获取客户端映射地址 |
| STUN_ATTR_USERNAME | 0x0006 | 指名对于MAPPED-ADDRESS的响应应该由哪儿发送 |
| STUN_ATTR_MESSAGE_INTEGRITY | 0x0008 | 消息完整性验证 |
| STUN_ATTR_ERROR_CODE | 0x0009 | 错误码 |
| STUN_ATTR_UNKNOWN_ATTRIBUTES | 0x000a | 未知属性 |
| STUN_ATTR_REALM | 0x0014 |
STUN请求

STUN Binding Request请求 携带 Attributes请求

STUN Binding Sccess Response 请求成功返回 Attributes数据

返回Attributes
- 用户名和密码
- 网络的映射地址和端口
- hmac-sha1 算法加密数据
- crc32 校验
代码实现
bool Stun::Decode(const uint8_t* data, uint32_t size)
{
type_ = (StunMessageType)ByteReader<uint16_t>::ReadBigEndian(data);
data += 2;
stun_length_ = ByteReader<uint16_t>::ReadBigEndian(data);
data += 2;
auto magic = ByteReader<uint32_t>::ReadBigEndian(data);
if (magic != kStunMagicCookie)
{
LIBRTC_LOG(LS_WARNING) << "stun magic:" << magic << " not equal kStunMagicCookie:" << kStunMagicCookie;
return false;
}
data += 4;
transcation_id_.assign((char *)data, 12);
data += 12;
LIBRTC_LOG(LS_WARNING) << "stun type:" << type_
<< " length:" << stun_length_
<< " transcation_id:" << transcation_id_;
size -= 20;
while (size >= 4)
{
uint16_t attr_type = ByteReader<uint16_t>::ReadBigEndian(data);
data += 2;
uint16_t attr_len = ByteReader<uint16_t>::ReadBigEndian(data);
data += 2;
size -= 4;
uint16_t padding_len = (4 - attr_len % 4) % 4;
if (size < padding_len + attr_len)
{
LIBRTC_LOG(LS_WARNING) << "stun attr len:" << attr_len
<< " padding:" << padding_len
<< " size:" << size;
return false;
}
switch (attr_type)
{
case kStunAttrUsername:
{
user_name_.assign((char *)data, attr_len);
LIBRTC_LOG(LS_INFO) << "stun user name:" << user_name_;
break;
}
case kStunAttrPassword:
{
password_.assign((char *)data, attr_len);
LIBRTC_LOG(LS_INFO) << "stun passwd:" << password_;
break;
}
}
data += (attr_len + padding_len);
size -= (attr_len + padding_len);
}
return true;
}
rtc::Buffer Stun::Encode()
{
rtc::Buffer packet(512);
int32_t stun_length = 0;
//PacketPtr packet = Packet::NewPacket(512);
uint8_t *data = packet.begin();//packet->Data();
/*data +=*/ ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)type_);
//data += 2;
stun_length += 2;
/*data +=*/ ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)0);
stun_length += 2;
//data += 2;
/*data +=*/ ByteWriter<uint32_t>::WriteBigEndian(data + stun_length, (uint32_t)kStunMagicCookie);
stun_length += 4;
//data += 4;
//stun_length += 6;
std::memcpy(data + stun_length, transcation_id_.c_str(), transcation_id_.size());
//data += 12;
stun_length += transcation_id_.size();
//packet.SetSize(12);
int32_t padding_bytes = (4- user_name_.size() % 4) % 4;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)kStunAttrUsername);
stun_length += 2;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)user_name_.size());
stun_length += 2;
std::memcpy(data+ stun_length, user_name_.c_str(), user_name_.size());
stun_length += user_name_.size();
if (padding_bytes != 0)
{
std::memset(data + stun_length , 0, padding_bytes);
stun_length += padding_bytes;
}
//stun_length += (4 + user_name_.size() + (uint16_t)padding_bytes);
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)kStunAttrXorMappedAddress);
stun_length += 2;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)8); //属性长度
stun_length += 2;
ByteWriter<uint8_t>::WriteBigEndian(data + stun_length, (uint8_t)0);
stun_length += 1;
ByteWriter<uint8_t>::WriteBigEndian(data + stun_length, (uint8_t)0x01);
stun_length += 1;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)mapped_port_ ^ (kStunMagicCookie >> 16));
stun_length += 2;
ByteWriter<uint32_t>::WriteBigEndian(data + stun_length, (uint32_t)(mapped_addr_ ^ kStunMagicCookie));
stun_length += 4;
//data += (4 + 8);
//stun_length += (4 + 8);
uint8_t *data_begin = packet.begin();
size_t data_bytes = stun_length - 20;
size_t paylod_len = data_bytes + ( 4+20);
ByteWriter<uint16_t>::WriteBigEndian(data_begin + 2, (uint16_t)paylod_len);
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)kStunAttrMessageIntegrity);
stun_length += 2;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)20);
stun_length += 2;
CalcHmac((char*)data + stun_length, (char*)data_begin, data_bytes + 20);
// 计算完成后,恢复实际长度
paylod_len = data_bytes + (20+4) + (4 + 4);
ByteWriter<uint16_t>::WriteBigEndian(data_begin + 2, paylod_len);
//data += (4 + 20);
stun_length += (20);
ByteWriter<uint16_t>::WriteBigEndian(data+ stun_length, (uint16_t)kStunAttrFingerprint);
stun_length += 2;
ByteWriter<uint16_t>::WriteBigEndian(data + stun_length, (uint16_t)4);
stun_length += 2;
uint32_t crc32 = rtc::ComputeCrc32(data, stun_length-4) ^ 0x5354554e;
ByteWriter<uint32_t>::WriteBigEndian(data + stun_length, crc32);
stun_length += 4;
// data += (4 + 4);
// stun_length += (4 + 4);
//packet->SetPacketSize(paylod_len + 20);
packet.SetSize(stun_length);
return std::move(packet);
}
std::string Stun::LocalUFrag()
{
auto pos = user_name_.find_first_of(':');
if (pos != std::string::npos)
{
return user_name_.substr(0, pos);
}
return std::string();
}
void Stun::SetPassword(const std::string &pwd)
{
password_ = pwd;
}
void Stun::SetMappedAddr(uint32_t addr)
{
mapped_addr_ = addr;
}
void Stun::SetMappedPort(uint16_t port)
{
mapped_port_ = port;
}
void Stun::SetMessageType(StunMessageType type)
{
type_ = type;
}
size_t Stun::CalcHmac(char *buf, const char *data, size_t bytes)
{
unsigned int digestLen;
#if OPENSSL_VERSION_NUMBER > 0x10100000L
HMAC_CTX *ctx = HMAC_CTX_new();
HMAC_Init_ex(ctx, password_.c_str(), password_.size(), EVP_sha1(), NULL);
HMAC_Update(ctx, (const unsigned char *)data, bytes);
HMAC_Final(ctx, (unsigned char *)buf, &digestLen);
HMAC_CTX_free(ctx);
#else
HMAC_CTX ctx;
HMAC_CTX_init(&ctx);
HMAC_Init_ex(&ctx, password_.c_str(), password_.size(), EVP_sha1(), NULL);
HMAC_Update(&ctx, (const unsigned char *)data, bytes);
HMAC_Final(&ctx, (unsigned char *)buf, &digestLen);
HMAC_CTX_cleanup(&ctx);
#endif
return digestLen;
}
三、DTLS密钥协商流程
那端作为服务器就接听状态、客户端就发送Client Hello

客户端:
void RtcConsumer::MayRunDtls()
{
dtls_.SetRemoteFingerprint(sdp_.GetRemoteFingerprint());
// client and server
std::string role = sdp_.GetRemoteRole();
//// role = "active" / "passive" / "actpass" / "holdconn"
/*a = setup 主要是表示dtls的协商过程中角色的问题,谁是客户端,谁是服务器
a = setup:actpass 既可以是客户端,也可以是服务器
a = setup : active 客户端
a = setup : passive 服务器
由客户端先发起client hello*/
libmedia_transfer_protocol::libssl::Role local_role = libmedia_transfer_protocol::libssl::Role::SERVER;
if (role == "actpass" || role == "active")
{
local_role = libmedia_transfer_protocol::libssl::Role::SERVER;
}
else if (role == "passive")
{
//远端是服务时候就需要自动发送Hello
local_role = libmedia_transfer_protocol::libssl::Role::CLIENT;
}
GBMEDIASERVER_LOG(LS_INFO) << "remote role:"<< role <<" , local_role :" << (int32_t)local_role;
dtls_.Run(local_role);
}
Dtls类中方法
void Dtls::Run(Role local_role)
{
RTC_ASSERT(
local_role == Role::CLIENT || local_role == Role::SERVER,
"local DTLS role must be 'client' or 'server'");
Role previousLocalRole = this->local_role_;
if (local_role == previousLocalRole)
{
LIBSSL_LOG_T_F(LS_INFO)<<("same local DTLS role provided, doing nothing");
return;
}
// If the previous local DTLS role was 'client' or 'server' do reset.
if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
{
LIBSSL_LOG_T_F(LS_INFO) << "resetting DTLS due to local role change";
Reset();
}
// Update local role.
this->local_role_ = local_role;
// Set state and notify the listener.
this->state_ = DtlsState::CONNECTING;
//this->listener->OnDtlsTransportConnecting(this);
SignalDtlsConnecting(this);
switch (this->local_role_)
{
case Role::CLIENT:
{
//MS_DEBUG_TAG(dtls, "running [role:client]");
LIBSSL_LOG(LS_INFO) << "running [role:client]";
/// ????????????????????????????? dtls ???? 交换协商的流程
SSL_set_connect_state(this->ssl_);
SSL_do_handshake(this->ssl_);
SendPendingOutgoingDtlsData();
SetTimeout();
break;
}
case Role::SERVER:
{
//MS_DEBUG_TAG(dtls, "running [role:server]");
LIBSSL_LOG(LS_INFO) << "running [role:server]";
SSL_set_accept_state(this->ssl_);
SSL_do_handshake(this->ssl_);
break;
}
default:
{
//RTC_CHECK(
// local_role_ == Role::CLIENT || local_role_ == Role::SERVER,
// "local DTLS role must be 'client' or 'server'");
RTC_ABORT( "invalid local DTLS role");
}
}
}
真正客户端调用函数
SSL_set_connect_state(this->ssl_);
SSL_do_handshake(this->ssl_);
SendPendingOutgoingDtlsData();
SetTimeout();
服务端 操作函数 设置accept 等等客户端连接上来进行握手
SSL_set_accept_state(this->ssl_);
SSL_do_handshake(this->ssl_);
具体的Dtls类的实现
namespace
{
inline static unsigned int OnSslDtlsTimer(SSL* /*ssl*/, unsigned int timerUs)
{
if (timerUs == 0)
{
return 100000;
}
else if (timerUs >= 4000000)
{
return 4000000;
}
//else
return 2 * timerUs;
}
}
//Dtls::Dtls(/*DtlsHandler *handler*/)
// //:handler_(handler)
//{
Dtls::Dtls(webrtc::TaskQueueFactory * task_queue_factory)
: dtls_queue_(task_queue_factory->CreateTaskQueue("dtls_queue", webrtc::TaskQueueFactory::Priority::NORMAL))
{
//InitSSL();
ssl_ = SSL_new(DtlsCerts::GetInstance().GetSslCtx());
if (!ssl_)
{
LIBRTC_LOG_T_F(LS_WARNING) << "SSL_new failed.";
goto error;
}
//注册数据回调
SSL_set_ex_data(ssl_, 0, static_cast<void*>(this));
bio_read_ = BIO_new(BIO_s_mem());
if (!bio_read_)
{
LIBSSL_LOG_T_F(LS_ERROR) << ("BIO_new() failed");
SSL_free(ssl_);
goto error;
}
bio_write_ = BIO_new(BIO_s_mem());
if (!bio_write_)
{
LIBSSL_LOG_T_F(LS_ERROR) << ("BIO_new() failed");
BIO_free(bio_write_);
SSL_free(ssl_);
goto error;
}
SSL_set_bio(ssl_, bio_read_, bio_write_);
SSL_set_mtu(ssl_, kDtlsMtu);
DTLS_set_link_mtu(ssl_, kDtlsMtu);
// Set callback handler for setting DTLS timer interval.
DTLS_set_timer_cb(ssl_, OnSslDtlsTimer);
//dtls_queue_.PostDelayedTask([this]() { /*RTC_DCHECK_RUN_ON(&dtls_queue_);*/ OnTimer(); }, timeoutMs);
//SSL_set_accept_state(ssl_);
return ;
error:
// NOTE: At this point SSL_set_bio() was not called so we must free BIOs as
// well.
if (bio_read_)
BIO_free(bio_read_);
if (bio_write_)
BIO_free(bio_write_);
if (this->ssl_)
SSL_free(this->ssl_);
// NOTE: If this is not catched by the caller the program will abort, but
// this should never happen.
LIBSSL_LOG_T_F(LS_ERROR) << ("DtlsTransport instance creation failed");
return ;
}
//}
Dtls::~Dtls()
{
#if SAMPLE_SSL
if (ssl_context_)
{
SSL_CTX_free(ssl_context_);
ssl_context_ = nullptr;
}
if (ssl_)
{
SSL_free(ssl_);
ssl_ = nullptr;
bio_read_ = nullptr;
bio_write_ = nullptr;
}
#else
if (IsRunning())
{
// Send close alert to the peer.
SSL_shutdown(this->ssl_);
SendPendingOutgoingDtlsData();
}
if (this->ssl_)
{
SSL_free(this->ssl_);
this->ssl_ = nullptr;
this->bio_read_ = nullptr;
this->bio_write_ = nullptr;
}
#endif //
}
void Dtls::Run(Role local_role)
{
RTC_ASSERT(
local_role == Role::CLIENT || local_role == Role::SERVER,
"local DTLS role must be 'client' or 'server'");
Role previousLocalRole = this->local_role_;
if (local_role == previousLocalRole)
{
LIBSSL_LOG_T_F(LS_INFO)<<("same local DTLS role provided, doing nothing");
return;
}
// If the previous local DTLS role was 'client' or 'server' do reset.
if (previousLocalRole == Role::CLIENT || previousLocalRole == Role::SERVER)
{
LIBSSL_LOG_T_F(LS_INFO) << "resetting DTLS due to local role change";
Reset();
}
// Update local role.
this->local_role_ = local_role;
// Set state and notify the listener.
this->state_ = DtlsState::CONNECTING;
//this->listener->OnDtlsTransportConnecting(this);
SignalDtlsConnecting(this);
switch (this->local_role_)
{
case Role::CLIENT:
{
//MS_DEBUG_TAG(dtls, "running [role:client]");
LIBSSL_LOG(LS_INFO) << "running [role:client]";
/// ????????????????????????????? dtls ???? 交换协商的流程
SSL_set_connect_state(this->ssl_);
SSL_do_handshake(this->ssl_);
SendPendingOutgoingDtlsData();
SetTimeout();
break;
}
case Role::SERVER:
{
//MS_DEBUG_TAG(dtls, "running [role:server]");
LIBSSL_LOG(LS_INFO) << "running [role:server]";
SSL_set_accept_state(this->ssl_);
SSL_do_handshake(this->ssl_);
break;
}
default:
{
//RTC_CHECK(
// local_role_ == Role::CLIENT || local_role_ == Role::SERVER,
// "local DTLS role must be 'client' or 'server'");
RTC_ABORT( "invalid local DTLS role");
}
}
}
void Dtls::OnRecv(const uint8_t *data, int32_t size)
{
#if SAMPLE_SSL
BIO_reset(bio_read_);
BIO_reset(bio_write_);
BIO_write(bio_read_, data, size);
SSL_do_handshake(ssl_);
NeedPost();
//if (is_done_)
//{
// GetSrtpKey();
//
// SignalDtlsHandshakeDone(this);
// return;
//}
int32_t read = SSL_read(ssl_, buffer_, 65535);
if (!CheckStatus(read))
{
return;
}
// application data reccvide not if
// datachannel
// Application data received. Notify to the listener.
if (read > 0)
{
// It is allowed to receive DTLS data even before validating remote fingerprint.
if (!handshake_done_)
{
//MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
LIBRTC_LOG(LS_WARNING) << "ignoring application data received while DTLS handshake not done";
return;
}
// Notify the listener.
//this->listener->OnDtlsTransportApplicationDataReceived(
//this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast<size_t>(read));
}
#else
int written;
int read;
if (!IsRunning())
{
LIBSSL_LOG_T_F(LS_ERROR) << ("cannot process data while not running");
return;
}
// Write the received DTLS data into the sslBioFromNetwork.
written =
BIO_write(this->bio_read_, static_cast<const void*>(data), static_cast<int>(size));
if (written != static_cast<int>(size))
{
LIBSSL_LOG_T_F(LS_WARNING) <<
"OpenSSL BIO_write() wrote less ("<< written <<" bytes) than given data ("<< size <<" bytes)" ;
}
// Must call SSL_read() to process received DTLS data.
read = SSL_read(this->ssl_, static_cast<void*>(ssl_read_buffer_), kSsslReadBufferSize);
// Send data if it's ready.
SendPendingOutgoingDtlsData();
// Check SSL status and return if it is bad/closed.
if (!CheckStatus(read))
return;
// Set/update the DTLS timeout.
if (!SetTimeout())
return;
// Application data received. Notify to the listener.
if (read > 0)
{
// It is allowed to receive DTLS data even before validating remote fingerprint.
if (!this->handshake_done_)
{
//MS_WARN_TAG(dtls, "ignoring application data received while DTLS handshake not done");
LIBSSL_LOG_T_F(LS_WARNING) << "ignoring application data received while DTLS handshake not done";
return;
}
// Notify the listener.
//this->listener->OnDtlsTransportApplicationDataReceived(
// this, (uint8_t*)DtlsTransport::sslReadBuffer, static_cast<size_t>(read));
SignalDtlsApplicationDataReceived(this,
(uint8_t*)ssl_read_buffer_, static_cast<size_t>(read));
}
#endif //
}
void Dtls::SendApplicationData(const uint8_t* data, size_t len)
{
LIBRTC_LOG_T_F(LS_INFO);
//MS_TRACE();
// We cannot send data to the peer if its remote fingerprint is not validated.
if (this->state_ != DtlsState::CONNECTED)
{
LIBSSL_LOG_T_F(LS_WARNING) << "cannot send application data while DTLS is not fully connected";
return;
}
if (len == 0)
{
LIBSSL_LOG_T_F(LS_WARNING) <<( "ignoring 0 length data");
return;
}
int written;
written = SSL_write(this->ssl_, static_cast<const void*>(data), static_cast<int>(len));
if (written < 0)
{
LIBSSL_LOG_T_F(LS_ERROR) <<("SSL_write() failed");
if (!CheckStatus(written))
{
return;
}
}
else if (written != static_cast<int>(len))
{
LIBSSL_LOG_T_F(LS_WARNING) << "OpenSSL SSL_write() wrote less ("<< written <<" bytes) than given data ("<<len<<" bytes)";
//MS_WARN_TAG(
// dtls, "OpenSSL SSL_write() wrote less (%d bytes) than given data (%zu bytes)", written, len);
}
// Send data.
SendPendingOutgoingDtlsData();
}
bool Dtls::SetRemoteFingerprint(Fingerprint fingerprint)
{
RTC_ASSERT(
fingerprint.algorithm != FingerprintAlgorithm::NONE, "no fingerprint algorithm provided");
this->remote_fingerprint_ = fingerprint;
// The remote fingerpring may have been set after DTLS handshake was done,
// so we may need to process it now.
if (this->handshake_done_ && this->state_ != DtlsState::CONNECTED)
{
LIBSSL_LOG(LS_INFO)<< "handshake already done, processing it right now";
return ProcessHandshake();
}
return true;
}
bool Dtls::CheckStatus(int returnCode)
{
int err;
bool wasHandshakeDone = handshake_done_;
err = SSL_get_error(ssl_, returnCode);
switch (err)
{
case SSL_ERROR_NONE:
break;
case SSL_ERROR_SSL:
//LOG_OPENSSL_ERROR("SSL status: SSL_ERROR_SSL");
LIBRTC_LOG(LS_ERROR) << "SSL status: SSL_ERROR_SSL";
break;
case SSL_ERROR_WANT_READ:
break;
case SSL_ERROR_WANT_WRITE:
//MS_WARN_TAG(dtls, "SSL status: SSL_ERROR_WANT_WRITE");
LIBRTC_LOG(LS_WARNING) << "SSL status: SSL_ERROR_WANT_WRITE";
break;
case SSL_ERROR_WANT_X509_LOOKUP:
LIBRTC_LOG(LS_ERROR) << "SSL status: SSL_ERROR_WANT_X509_LOOKUP";
break;
case SSL_ERROR_SYSCALL:
LIBRTC_LOG(LS_WARNING) << ("SSL status: SSL_ERROR_SYSCALL");
break;
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_CONNECT:
LIBRTC_LOG(LS_WARNING) << "SSL status: SSL_ERROR_WANT_CONNECT";
break;
case SSL_ERROR_WANT_ACCEPT:
LIBRTC_LOG(LS_WARNING) << "SSL status: SSL_ERROR_WANT_ACCEPT";
break;
default:
LIBRTC_LOG(LS_WARNING) << "SSL status: unknown error";
}
// Check if the handshake (or re-handshake) has been done right now.
if (handshake_done_now_)
{
handshake_done_now_ = false;
handshake_done_ = true;
// Stop the timer.
//this->timer->Stop();
// Process the handshake just once (ignore if DTLS renegotiation).
if (!wasHandshakeDone && this->remote_fingerprint_.algorithm != FingerprintAlgorithm::NONE)
{
return ProcessHandshake();
}
return true;
}
// Check if the peer sent close alert or a fatal error happened.
else if (((SSL_get_shutdown(ssl_) & SSL_RECEIVED_SHUTDOWN) != 0) || err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL)
{
if ( this->state_ == DtlsState::CONNECTED)
{
//MS_DEBUG_TAG(dtls, "disconnected");
LIBRTC_LOG(LS_INFO) << "disconnected";
Reset();
handshake_done_ = false;
// Set state and notify the listener.
this->state_ = DtlsState::CLOSED;
//this->listener->OnDtlsTransportClosed(this);
SignalDtlsClose(this);
}
else
{
LIBRTC_LOG(LS_INFO) << "connection failed";
//MS_WARN_TAG(dtls, "connection failed");
Reset();
// Set state and notify the listener.
this->state_ = DtlsState::FAILED;
SignalDtlsFailed(this);
//this->listener->OnDtlsTransportFailed(this);
}
return false;
}
else
{
return true;
}
}
void Dtls::OnSslInfo(int32_t where, int32_t ret)
{
//Dtls *dtls = static_cast<Dtls*>(SSL_get_ex_data(ssl, 0));
int w = where & ~SSL_ST_MASK;
const char* role;
if (w & SSL_ST_CONNECT)
{
//dtls->SetClient(true);
//SetClient(true);
role = "client";
}
else if (w & SSL_ST_ACCEPT)
{
// SetClient(false);
role = "server";
}
else
{
// SetClient(false);
role = "undefined";
}
if ((where & SSL_CB_LOOP) != 0)
{
LIBRTC_LOG(LS_INFO) << "[role: " << role << ", action:'" << SSL_state_string_long(ssl_) << "']";
//MS_DEBUG_TAG(dtls, "[role:%s, action:'%s']", role, SSL_state_string_long(ssl));
}
else if ((where & SSL_CB_ALERT) != 0)
{
const char* alertType;
switch (*SSL_alert_type_string(ret))
{
case 'W':
alertType = "warning";
break;
case 'F':
alertType = "fatal";
break;
default:
alertType = "undefined";
}
if ((where & SSL_CB_READ) != 0)
{
//MS_WARN_TAG(dtls, "received DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
LIBRTC_LOG(LS_INFO) << "[received DTLS " << alertType << ", alert:'" << SSL_alert_desc_string_long(ret) << "']";
}
else if ((where & SSL_CB_WRITE) != 0)
{
//MS_DEBUG_TAG(dtls, "sending DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
LIBRTC_LOG(LS_INFO) << "[sending DTLS " << alertType << ", alert:'" << SSL_alert_desc_string_long(ret) << "']";
}
else
{
//MS_DEBUG_TAG(dtls, "DTLS %s alert: %s", alertType, SSL_alert_desc_string_long(ret));
LIBRTC_LOG(LS_INFO) << "[ DTLS " << alertType << ", alert:'" << SSL_alert_desc_string_long(ret) << "']";
}
}
else if ((where & SSL_CB_EXIT) != 0)
{
if (ret == 0)
{
//MS_DEBUG_TAG(dtls, "[role:%s, failed:'%s']", role, SSL_state_string_long(this->ssl));
LIBRTC_LOG(LS_INFO) << "[ role: " << role << ", failed:'" << SSL_state_string_long(ssl_) << "']";
}
else if (ret < 0)
{
//MS_DEBUG_TAG(dtls, "role: %s, waiting:'%s']", role, SSL_state_string_long(this->ssl));
LIBRTC_LOG(LS_INFO) << "[ role: " << role << ", waiting:'" << SSL_state_string_long(ssl_) << "']";
}
}
else if ((where & SSL_CB_HANDSHAKE_START) != 0)
{
//MS_DEBUG_TAG(dtls, "DTLS handshake start");
LIBRTC_LOG(LS_INFO) << "DTLS handshake start";
}
else if ((where & SSL_CB_HANDSHAKE_DONE) != 0)
{
//MS_DEBUG_TAG(dtls, "DTLS handshake done");
LIBRTC_LOG(LS_INFO) << "DTLS handshake done";
//SetDone();
//this->handshakeDoneNow = true;
handshake_done_now_ = true;
}
}
void Dtls::OnTimer()
{
// Workaround for https://github.com/openssl/openssl/issues/7998.
if (this->handshake_done_)
{
LIBSSL_LOG_T_F(LS_ERROR) << ("handshake is done so return");
return;
}
DTLSv1_handle_timeout(this->ssl_);
// If required, send DTLS data.
SendPendingOutgoingDtlsData();
// Set the DTLS timer again.
SetTimeout();
}
void Dtls::SendPendingOutgoingDtlsData()
{
if (BIO_eof(this->bio_write_))
{
return;
}
int64_t read;
char* data{ nullptr };
read = BIO_get_mem_data(this->bio_write_, &data); // NOLINT
if (read <= 0)
return;
//MS_DEBUG_DEV("%" PRIu64 " bytes of DTLS data ready to sent to the peer", read);
LIBSSL_LOG(LS_INFO) << read << " bytes of DTLS data ready to sent to the peer";
// Notify the listener.
// send data
//this->listener->OnDtlsTransportSendData(
// this, reinterpret_cast<uint8_t*>(data), static_cast<size_t>(read));
//const char *, size_t, Dtls*
SignalDtlsSendPakcet(this, reinterpret_cast<const uint8_t *>(data), static_cast<size_t>(read));
// Clear the BIO buffer.
// NOTE: the (void) avoids the -Wunused-value warning.
(void)BIO_reset(this->bio_write_);
}
bool Dtls::SetTimeout()
{
RTC_ASSERT(
this->state_ == DtlsState::CONNECTING || this->state_ == DtlsState::CONNECTED,
"invalid DTLS state");
int64_t ret;
//uv_timeval_t dtlsTimeout{ 0, 0 };
struct timeval dtlsTimeout { 0, 0 } ;
uint64_t timeoutMs;
// NOTE: If ret == 0 then ignore the value in dtlsTimeout.
// NOTE: No DTLSv_1_2_get_timeout() or DTLS_get_timeout() in OpenSSL 1.1.0-dev.
ret = DTLSv1_get_timeout(this->ssl_, static_cast<void*>(&dtlsTimeout)); // NOLINT
if (ret == 0)
{
return true;
}
timeoutMs = (dtlsTimeout.tv_sec * static_cast<uint64_t>(1000)) + (dtlsTimeout.tv_usec / 1000);
if (timeoutMs == 0)
{
return true;
}
else if (timeoutMs < 30000)
{
//MS_DEBUG_DEV("DTLS timer set in %" PRIu64 "ms", timeoutMs);
//this->timer->Start(timeoutMs);
LIBSSL_LOG(LS_INFO) << "DTLS timer set in " << timeoutMs << "ms";
/*
[this]() {
RTC_DCHECK_RUN_ON(&task_queue_);
if (!Process()) {
next_process_ms_.reset();
}
}
*/
dtls_queue_.PostDelayedTask( [this]() { /*RTC_DCHECK_RUN_ON(&dtls_queue_);*/ OnTimer(); }, timeoutMs);
return true;
}
// NOTE: Don't start the timer again if the timeout is greater than 30 seconds.
else
{
LIBSSL_LOG(LS_INFO) << "DTLS timeout too high ( " << timeoutMs << "ms), resetting DLTS" ;
Reset();
// Set state and notify the listener.
this->state_ = DtlsState::FAILED;
//this->listener->OnDtlsTransportFailed(this);
SignalDtlsFailed(this);
return false;
}
}
void Dtls::Reset()
{
int ret;
if (!IsRunning())
{
return;
}
//MS_WARN_TAG(dtls, "resetting DTLS transport");
LIBSSL_LOG(LS_INFO) << "resetting DTLS transport";
// Stop the DTLS timer.
//this->timer->Stop();
//dtls_queue_.~TaskQueue();
// We need to reset the SSL instance so we need to "shutdown" it, but we
// don't want to send a Close Alert to the peer, so just don't call
// SendPendingOutgoingDTLSData().
SSL_shutdown(this->ssl_);
this->local_role_ = Role::NONE;
this->state_ = DtlsState::NONE;
this->handshake_done_ = false;
this->handshake_done_now_ = false;
// Reset SSL status.
// NOTE: For this to properly work, SSL_shutdown() must be called before.
// NOTE: This may fail if not enough DTLS handshake data has been received,
// but we don't care so just clear the error queue.
ret = SSL_clear(this->ssl_);
if (ret == 0)
{
ERR_clear_error();
}
}
bool Dtls::ProcessHandshake()
{
RTC_ASSERT(this->handshake_done_, "handshake not done yet");
RTC_ASSERT(
this->remote_fingerprint_.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
// Validate the remote fingerprint.
if (!CheckRemoteFingerprint())
{
Reset();
// Set state and notify the listener.
this->state_ = DtlsState::FAILED;
//this->listener->OnDtlsTransportFailed(this);
SignalDtlsFailed(this);
return false;
}
// Get the negotiated SRTP crypto suite.
libsrtp::CryptoSuite srtpCryptoSuite = GetNegotiatedSrtpCryptoSuite();
if (srtpCryptoSuite != libsrtp::CryptoSuite::NONE)
{
// Extract the SRTP keys (will notify the listener with them).
ExtractSrtpKeys(srtpCryptoSuite);
return true;
}
// NOTE: We assume that "use_srtp" DTLS extension is required even if
// there is no audio/video.
LIBSRTP_LOG(LS_INFO) << "SRTP crypto suite not negotiated";
Reset();
// Set state and notify the listener.
this->state_ = DtlsState::FAILED;
SignalDtlsFailed(this);
return false;
}
bool Dtls::CheckRemoteFingerprint()
{
RTC_ASSERT(
this->remote_fingerprint_.algorithm != FingerprintAlgorithm::NONE, "remote fingerprint not set");
X509* certificate;
uint8_t binaryFingerprint[EVP_MAX_MD_SIZE];
unsigned int size{ 0 };
char hexFingerprint[(EVP_MAX_MD_SIZE * 3) + 1];
const EVP_MD* hashFunction;
int ret;
certificate = SSL_get_peer_certificate(this->ssl_);
if (!certificate)
{
//MS_WARN_TAG(dtls, "no certificate was provided by the peer");
LIBSSL_LOG_T_F(LS_WARNING) << "no certificate was provided by the peer";
return false;
}
switch (remote_fingerprint_.algorithm)
{
case FingerprintAlgorithm::SHA1:
hashFunction = EVP_sha1();
break;
case FingerprintAlgorithm::SHA224:
hashFunction = EVP_sha224();
break;
case FingerprintAlgorithm::SHA256:
hashFunction = EVP_sha256();
break;
case FingerprintAlgorithm::SHA384:
hashFunction = EVP_sha384();
break;
case FingerprintAlgorithm::SHA512:
hashFunction = EVP_sha512();
break;
default:
RTC_ABORT( "unknown algorithm");
}
// Compare the remote fingerprint with the value given via signaling.
ret = X509_digest(certificate, hashFunction, binaryFingerprint, &size);
if (ret == 0)
{
LIBSSL_LOG_T_F(LS_ERROR)<<( "X509_digest() failed");
X509_free(certificate);
return false;
}
// Convert to hexadecimal format in uppercase with colons.
for (unsigned int i{ 0 }; i < size; ++i)
{
std::sprintf(hexFingerprint + (i * 3), "%.2X:", binaryFingerprint[i]);
}
hexFingerprint[(size * 3) - 1] = '\0';
if ( remote_fingerprint_.value != hexFingerprint)
{
LIBSSL_LOG_T_F(LS_WARNING) << "fingerprint in the remote certificate (" << hexFingerprint
<< ") does not match the announced one ("<< remote_fingerprint_.value <<")";
X509_free(certificate);
return false;
}
//MS_DEBUG_TAG(dtls, "valid remote fingerprint");
LIBSSL_LOG(LS_INFO) << "valid remote fingerprint";
// Get the remote certificate in PEM format.
BIO* bio = BIO_new(BIO_s_mem());
// Ensure the underlying BUF_MEM structure is also freed.
// NOTE: Avoid stupid "warning: value computed is not used [-Wunused-value]" since
// BIO_set_close() always returns 1.
(void)BIO_set_close(bio, BIO_CLOSE);
ret = PEM_write_bio_X509(bio, certificate);
if (ret != 1)
{
LIBSSL_LOG(LS_ERROR) << ("PEM_write_bio_X509() failed");
X509_free(certificate);
BIO_free(bio);
return false;
}
BUF_MEM* mem;
BIO_get_mem_ptr(bio, &mem); // NOLINT[cppcoreguidelines-pro-type-cstyle-cast]
if (!mem || !mem->data || mem->length == 0u)
{
LIBSSL_LOG(LS_ERROR) << ("BIO_get_mem_ptr() failed");
X509_free(certificate);
BIO_free(bio);
return false;
}
remote_cert_ = std::string(mem->data, mem->length);
X509_free(certificate);
BIO_free(bio);
return true;
}
void Dtls::ExtractSrtpKeys(libsrtp::CryptoSuite srtpCryptoSuite)
{
size_t srtpKeyLength{ 0 };
size_t srtpSaltLength{ 0 };
size_t srtpMasterLength{ 0 };
switch (srtpCryptoSuite)
{
case libsrtp::CryptoSuite::AES_CM_128_HMAC_SHA1_80:
case libsrtp::CryptoSuite::AES_CM_128_HMAC_SHA1_32:
{
srtpKeyLength = libsrtp::kSrtpMasterKeyLength;
srtpSaltLength = libsrtp::kSrtpMasterSaltLength;
srtpMasterLength = libsrtp::kSrtpMasterLength;
break;
}
case libsrtp::CryptoSuite::AEAD_AES_256_GCM:
{
srtpKeyLength = libsrtp::kSrtpAesGcm256MasterKeyLength;
srtpSaltLength = libsrtp::kSrtpAesGcm256MasterSaltLength;
srtpMasterLength = libsrtp::kSrtpAesGcm256MasterLength;
break;
}
case libsrtp::CryptoSuite::AEAD_AES_128_GCM:
{
srtpKeyLength = libsrtp::kSrtpAesGcm128MasterKeyLength;
srtpSaltLength = libsrtp::kSrtpAesGcm128MasterSaltLength;
srtpMasterLength = libsrtp::kSrtpAesGcm128MasterLength;
break;
}
default:
{
RTC_ABORT( "unknown SRTP crypto suite");
}
}
auto* srtpMaterial = new uint8_t[srtpMasterLength * 2];
uint8_t* srtpLocalKey{ nullptr };
uint8_t* srtpLocalSalt{ nullptr };
uint8_t* srtpRemoteKey{ nullptr };
uint8_t* srtpRemoteSalt{ nullptr };
auto* srtpLocalMasterKey = new uint8_t[srtpMasterLength];
auto* srtpRemoteMasterKey = new uint8_t[srtpMasterLength];
int ret;
ret = SSL_export_keying_material(
this->ssl_, srtpMaterial, srtpMasterLength * 2, "EXTRACTOR-dtls_srtp", 19, nullptr, 0, 0);
RTC_ASSERT(ret != 0, "SSL_export_keying_material() failed");
switch ( local_role_)
{
case Role::SERVER:
{
srtpRemoteKey = srtpMaterial;
srtpLocalKey = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteSalt + srtpSaltLength;
break;
}
case Role::CLIENT:
{
srtpLocalKey = srtpMaterial;
srtpRemoteKey = srtpLocalKey + srtpKeyLength;
srtpLocalSalt = srtpRemoteKey + srtpKeyLength;
srtpRemoteSalt = srtpLocalSalt + srtpSaltLength;
break;
}
default:
{
RTC_ABORT( "no DTLS role set");
}
}
// Create the SRTP local master key.
std::memcpy(srtpLocalMasterKey, srtpLocalKey, srtpKeyLength);
std::memcpy(srtpLocalMasterKey + srtpKeyLength, srtpLocalSalt, srtpSaltLength);
// Create the SRTP remote master key.
std::memcpy(srtpRemoteMasterKey, srtpRemoteKey, srtpKeyLength);
std::memcpy(srtpRemoteMasterKey + srtpKeyLength, srtpRemoteSalt, srtpSaltLength);
// Set state and notify the listener.
this->state_ = DtlsState::CONNECTED;
SignalDtlsConnected(this,
srtpCryptoSuite,
srtpLocalMasterKey,
srtpMasterLength,
srtpRemoteMasterKey,
srtpMasterLength,
remote_cert_);
delete[] srtpMaterial;
delete[] srtpLocalMasterKey;
delete[] srtpRemoteMasterKey;
}
libsrtp::CryptoSuite Dtls::GetNegotiatedSrtpCryptoSuite()
{
libsrtp::CryptoSuite negotiatedSrtpCryptoSuite = libsrtp::CryptoSuite::NONE;
// Ensure that the SRTP crypto suite has been negotiated.
// NOTE: This is a OpenSSL type.
SRTP_PROTECTION_PROFILE* sslSrtpCryptoSuite = SSL_get_selected_srtp_profile(this->ssl_);
if (!sslSrtpCryptoSuite)
return negotiatedSrtpCryptoSuite;
// Get the negotiated SRTP crypto suite.
for (auto& srtpCryptoSuite : libsrtp::kSrtpCryptoSuites)
{
libsrtp::SrtpCryptoSuiteMapEntry* cryptoSuiteEntry = std::addressof(srtpCryptoSuite);
if (std::strcmp(sslSrtpCryptoSuite->name, cryptoSuiteEntry->name) == 0)
{
//MS_DEBUG_2TAGS(dtls, srtp, "chosen SRTP crypto suite: %s", cryptoSuiteEntry->name);
LIBSRTP_LOG(LS_INFO) << "chosen SRTP crypto suite:" << cryptoSuiteEntry->name;
negotiatedSrtpCryptoSuite = cryptoSuiteEntry->crypto_suite;
}
}
RTC_ASSERT(
negotiatedSrtpCryptoSuite != libsrtp::CryptoSuite::NONE,
"chosen SRTP crypto suite is not an available one");
return negotiatedSrtpCryptoSuite;
}
详细可以参考WebRTC的ICE之Dtls/SSL/TLSv1.x协议详解:https://blog.csdn.net/Poisx/article/details/124918704
四、 发送音视频数据rtp、rtcp、application
总结
librtc 源码地址:https://github.com/chensongpoixs/libmedia_transfer_protocol/tree/master/librtc