简介
ICE全称Interactive Connectivity Establishment:交互式连通建立方式。
ICE参照RFC5245建议实现,是一组基于offer/answer模式解决NAT穿越的协议集合。
它综合利用现有的STUN,TURN等协议,以更有效的方式来建立会话。
ICE介绍
1. ICE的角色
分为 controlling和controlled。
Offer 一方为controlling角色,answer一方为controlled角色。
2. ICE的模式
分为FULL ICE和Lite ICE:
FULL ICE:是双方都要进行连通性检查,完成的走一遍流程。
Lite ICE: 在FULL ICE和Lite ICE互通时,只需要FULL ICE一方进行连通性检查, Lite一方只需回应response消息。这种模式对于部署在公网的设备比较常用。
3. Candidate
媒体传输的候选地址,组成candidate pair做连通性检查,确定传输路径,有如下属性:
Type 类型
Host: 这个地址是一个真实的主机,参数中的地址和端口对应一个真实的主机地址, 这个地址来源于本地的物理网卡或逻辑网卡上的地址,对于具有公网地址或者同一内网的端可以用。
Srvflx:这个地址是通过Cone NAT(锥形NAT)反射的类型,参数中的地址和端口是端发送 Binding 请求到 STUN/TURN server 经过NAT时,NAT 上分配的地址和端口。
Relay:这个地址是端发送 Allocate 请求到 TURN server ,由 TURN server 用于中继的地址和端口,该地址和端口是 TURN 服务用于在两个对等点之间转发数据的地址和端口,是一个中继地址端口。这个地址是端发送 Allocate 请求到 TURN server ,由 TURN server 用于中继的地址和端口(这个可能是本机或 NAT 地址)
Prflx:这个地址是通过 发送STUN Binding时,通过Binding获取到的地址。在建连检查期间新发生,参数中的地址和端口是端发送 Binding 请求到 STUN/TURN server 经过 NAT 时,NAT 上分配的地址和端口。这个地址是端发送 Binding 请求到对等端经过 NAT 时,NAT 上分配的地址和端口
Componet ID
传输媒体的类型,1代表RTP;2代表 RTCP。
WebRTC采用Rtcp-mux方式,也就是RTP和RTCP在同一通道内传输,减少ICE的协商和通道的保活。
Priority
Candidate的优先级。
如果考虑延时,带宽资源,丢包的因素,Type优先级高低一般建议如下顺序:
host > srvflx > prflx > relay
Base
是指candidate 的基础地址。
Srvflx address 的base 是本地host address。
host address和 relayed address 的base 是自身
交互抓包分析
SRS的交互相对比较简单,我们抓包分析一下:
主要分为两个部分:
1.通过HTTP请求,通过SDP实现ICE信息交互
2.使用STUN发送连通性检查请求
SDP的ICE信息
这里audio和video一样,只取audio
offer:
a=ice-ufrag:PA7e
// 客户端用户名
a=ice-pwd:F1o3tHlhk6OPBtXo8IdhZCRH
// 客户端密码
a=ice-options:trickle
// trickle方式表示媒体信息和ice后选项的信息可以分开传输
answer:
a=ice-lite
// SRS是Lite ICE,只需要响应客户端的Binding请求
a=ice-ufrag:8p42d118
// SRS端用户名
a=ice-pwd:ok61un195fg8q8083yy06247w0xg483s
// SRS端密码
a=candidate:0 1 udp 2130706431 10.151.3.77 8000 typ host generation 0
// {foundation} {component} {protocol} {priority} {ip} {port} typ {type} {generation}
// 0 [foundation] : 标识符,用来识别两个candidate是否相等
// 1 [component] : 传输媒体类型 1表示RTP
// ubp [protocol] : 协议类型
// 2130706431 [priority] : 优先级
// 10.151.3.77 [ip] : ip地址
// 8000 [port] : 端口
// host [type] : host类型,表示这是真实的主机地址
// generation : 代数。初始值是0,然后会不断+1,大的代数会覆盖掉低代数的候选地址。更新candidate的时候会+1,替换老的candidate
STUN消息格式
Stun Header:固定20个字节
STUN Message Type(14bits):消息类型。定义消息类型如下:
C1和C0两位表示类的编码:00表示request 01表示indication 10表示success response 11表示error response
常见类型:
0x0001 : Binding Request
0x0101 : Binding Response
0x0111 : Binding Error Response
Message Length(16bits):消息长度,不包含STUN Header的20个字节。
Magic Cookie(32bits):固定值0x2112A442,用于反射地址的异或(XOR)运算。
Transaction ID(96bits):事务ID标识符,请求对应的响应具有相同的标识符。
STUN属性类型
STUN 消息头后跟着多个属性,每个属性都采用 TLV 编码,type 为 16 位的类型、lenght 为 16 位的长度、value 为属性值。
STUN的连通性请求
Request:
USERNAME:用户名,规则为“对端的ice-ufrag : 自己的ice-ufrag”。
ICE-CONTROLLING: 表示发起方,Tie breaker用来处理角色冲突,当冲入时,这个值大的为controlling
PRIORITY:优先级
MESSAGE-INTEGRITY:STUN 消息的 HMAC-SHA1 值,长度 20 字节,用于消息完整性认证。
FINGERPRINT:指纹认证,此属性可以出现在所有的 STUN 消息中,该属性用于区分 STUN 数据包与其他协议的包。
Response:
XOR-MAPPED-ADDRESS: 用于表示客户端外部IP地址,如果没有NAT,那么外部IP地址和内部IP地址是相同的。前8位保留,之后8位用于表示IP类型(IPV4/6)。之后16位表示端口号。这里强制使用IPV4版本,所以Address是32位:
Family:IP类型,0x01-IPV4、0x02-IPV6。
Port:端口。
Address:IP地址
SRS处理
代码处理比较简单
Request:
srs_error_t SrsStunPacket::decode(const char* buf, const int nb_buf)
{
srs_error_t err = srs_success;
SrsBuffer* stream = new SrsBuffer(const_cast<char*>(buf), nb_buf);
SrsAutoFree(SrsBuffer, stream);
if (stream->left() < 20) {
return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, size=%d", stream->size());
}
// 消息类型
message_type = stream->read_2bytes();
// 消息长度(不包含header 20bytes)
uint16_t message_len = stream->read_2bytes();
// 固定值 0x2112A442
string magic_cookie = stream->read_string(4);
// 事务ID标识符
transcation_id = stream->read_string(12);
if (nb_buf != 20 + message_len) {
return srs_error_new(ERROR_RTC_STUN, "invalid stun packet, message_len=%d, nb_buf=%d", message_len, nb_buf);
}
while (stream->left() >= 4) {
uint16_t type = stream->read_2bytes();
uint16_t len = stream->read_2bytes();
if (stream->left() < len) {
return srs_error_new(ERROR_RTC_STUN, "invalid stun packet");
}
string val = stream->read_string(len);
// padding
if (len % 4 != 0) {
stream->read_string(4 - (len % 4));
}
switch (type) {
// 对端的ice-ufrag : 自己的ice-ufrag
case Username: {
username = val;
size_t p = val.find(":");
if (p != string::npos) {
local_ufrag = val.substr(0, p);
remote_ufrag = val.substr(p + 1);
}
srs_trace("stun recv:%s", username.c_str());
break;
}
case UseCandidate: {
use_candidate = true;
srs_verbose("stun use-candidate");
break;
}
// @see: https://tools.ietf.org/html/draft-ietf-ice-rfc5245bis-00#section-5.1.2
// One agent full, one lite: The full agent MUST take the controlling
// role, and the lite agent MUST take the controlled role. The full
// agent will form check lists, run the ICE state machines, and
// generate connectivity checks.
// 表示受控方
case IceControlled: {
ice_controlled = true;
srs_verbose("stun ice-controlled");
break;
}
// 表示发起方
case IceControlling: {
ice_controlling = true;
srs_verbose("stun ice-controlling");
break;
}
default: {
srs_verbose("stun type=%u, no process", type);
break;
}
}
}
return err;
}
Response:
srs_error_t SrsStunPacket::encode_binding_response(const string& pwd, SrsBuffer* stream)
{
srs_error_t err = srs_success;
string property_username = encode_username();
string mapped_address = encode_mapped_address();
// 消息类型0x0101
stream->write_2bytes(BindingResponse);
// 消息长度(不包含头20字节)
stream->write_2bytes(property_username.size() + mapped_address.size());
// 固定值0x2112A442
stream->write_4bytes(kStunMagicCookie);
// 事务ID标识符
stream->write_string(transcation_id);
// 用户名
stream->write_string(property_username);
// 外部IP地址
stream->write_string(mapped_address);
stream->data()[2] = ((stream->pos() - 20 + 20 + 4) & 0x0000FF00) >> 8;
stream->data()[3] = ((stream->pos() - 20 + 20 + 4) & 0x000000FF);
// sha1加密
char hmac_buf[20] = {0};
unsigned int hmac_buf_len = 0;
if ((err = hmac_encode("sha1", pwd.c_str(), pwd.size(), stream->data(), stream->pos(), hmac_buf, hmac_buf_len)) != srs_success) {
return srs_error_wrap(err, "hmac encode failed");
}
string hmac = encode_hmac(hmac_buf, hmac_buf_len);
stream->write_string(hmac);
stream->data()[2] = ((stream->pos() - 20 + 8) & 0x0000FF00) >> 8;
stream->data()[3] = ((stream->pos() - 20 + 8) & 0x000000FF);
// 指纹认证
uint32_t crc32 = srs_crc32_ieee(stream->data(), stream->pos(), 0) ^ 0x5354554E;
string fingerprint = encode_fingerprint(crc32);
stream->write_string(fingerprint);
stream->data()[2] = ((stream->pos() - 20) & 0x0000FF00) >> 8;
stream->data()[3] = ((stream->pos() - 20) & 0x000000FF);
return err;
}
>>> 整理了一些音视频流媒体开发学习资料、教学视频 免费分享有需要的可以自行添加学习交流群 739729163 领取
原文地址:https://www.cnblogs.com/vczf/p/15346360.html