SRS4.0之RTMP转WebRTC04 ---- ICE交互分析
简介
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; }
参考文档
按照时间顺序:
stun(rfc 3489) : https://tools.ietf.org/html/rfc3489
stun(rfc 5389,从rfc 3489演变来的) : https://tools.ietf.org/html/rfc5389
ice : https://tools.ietf.org/html/rfc5245
原文地址:https://www.cnblogs.com/vczf/p/15346360.html
- Java基础——IO流
- 来一点反射和Emit,让ORM的使用极度简化
- Java基础——集合框架
- Java基础——clone()方法浅析
- 【Python环境】matplotlib - 2D 与 3D 图的绘制
- 左求值表达式,堆栈,调试陷阱与ORM查询语言的设计
- Java基础——序列化
- 【Python环境】使用 scikit-learn 进行机器学习的简介
- Java基础——异常体系
- Java基础——数据类型之间的转换
- Java程序员面试宝典——重要习题整理
- Java8读文件方法代码学习
- .NET ORM 的 “SOD蜜”--零基础入门篇
- 【Spark研究】用Apache Spark进行大数据处理之入门介绍
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- sas文本挖掘案例:如何使用SAS计算Word Mover的距离
- R语言ggplot2 对Facebook用户数据可视化分析
- 如何实现一个圆弧倒计时进度条
- R语言Kaggle泰坦尼克号性别阶级模型数据分析案例
- 以图搜图系统概述
- GitHub Pages 配置 letsencrypt 开启HTTPS
- R语言中ARMA,ARIMA(Box-Jenkins),SARIMA和ARIMAX模型用于预测时间序列数据
- 以图搜图系统工程实践
- R语言线性判别分析(LDA),二次判别分析(QDA)和正则判别分析(RDA)
- 用R语言实现神经网络预测股票实例
- R语言社区主题检测算法应用案例
- C++ vector学习笔记
- 锂电池充电慢?手把手教你制作锂电池快充充电器
- c++ cin, get学习笔记
- c++ sort 学习笔记