zoukankan      html  css  js  c++  java
  • SRS4.0之RTMP转WebRTC05 ---- 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

  • 相关阅读:
    详解EBS接口开发之采购申请导入
    EBS HRMS数据表
    会计期间
    帐套和会计科目的理解
    oracle中动态SQL详解
    不同币种汇率转换
    API创建/更新员工联系电话
    API创建/更新员工薪水
    Android 圆形、圆角图片ImageView
    Knowledge Generation Model for Visual Analytics
  • 原文地址:https://www.cnblogs.com/vczf/p/15346360.html
Copyright © 2011-2022 走看看