低时延迟流媒体之WebRTC协议

低时延迟流媒体之WebRTC协议

低时延迟流媒体之WebRTC协议


前言

上面是WebRTC交互整连接交换的步骤的流程图

四大步骤:

  1. 客户端与服务端交换SDP
  2. STUN协议的验证 Binding Request 0001 (USERNAME、GOOG-NETWORK-INFO、ICE-CONTROLLING、USE-CANDIDATE、PRIORITY、MESSAGE-INTEGRITY、FINGERPRINT)
  3. DTLS密钥协商流程
  4. 发送音视频数据rtp、rtcp、application

一、客户端与服务器交换SDP的信息

sdp中含有的信息分五大类

  1. 会话描述:会话版本,会话名称,会话时长,会话发起者
  2. 媒体信息描述:音视频格式列表,支持的协议列表,参数说明等
  3. 网络连接描述:网络类型,地址类型,IP地址
  4. 安全描述:ice用户名和密码,fingerprint
  5. 服务质量描述: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组成的

  1. 包括20字节的STUN header
  2. 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

  1. 信息头后有0或多个属性
  2. 每个塑性进行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

  1. 用户名和密码
  2. 网络的映射地址和端口
  3. hmac-sha1 算法加密数据
  4. 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

相关推荐
恪愚4 小时前
webRTC:流程和socket搭建信令服务器
运维·服务器·webrtc
赖small强18 小时前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC SDK 音视频传输技术分析
音视频·webrtc·nack·pli·twcc·带宽自适应
赖small强20 小时前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC Data Plane REST API 深度解析
https·webrtc·data plane rest·sigv4 签名
赖small强1 天前
【ZeroRange WebRTC】Kinesis Video Streams WebRTC 三大平面职责与协同关系总结
websocket·webrtc·control plane·data plane
赖small强1 天前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC Control Plane API 深度解析
https·webrtc·control plane
赖small强1 天前
【ZeroRange WebRTC】Kinesis Video Streams WebRTC Data Plane WebSocket API 深度解析
websocket·webrtc·sdp·offer/answer·master/viewer
赖small强1 天前
【ZeroRnge WebRTC】RFC 8445:ICE 协议规范(中文整理与译注)
webrtc·ice·rfc 8445
赖small强2 天前
【ZeroRange WebRTC】RFC 5766:TURN 协议规范(中文整理与译注)
webrtc·turn·ice·rfc 5766
赖small强2 天前
【ZeroRange WebRTC】ICE 服务器列表解析(KVS WebRTC)
webrtc·stun·turn·ice