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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM