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

  • 相关阅读:
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法提高 P0404
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    Java实现 蓝桥杯VIP 算法训练 排列问题
    关于模态/非模态对话框不响应菜单的UPDATE_COMMAND_UI消息(对对WM_INITMENUPOPUP消息的处理)
  • 原文地址:https://www.cnblogs.com/vczf/p/15346360.html
Copyright © 2011-2022 走看看