[Streaming Media Server] SRS4.0 RTMP to WebRTC ICE interaction analysis

Introduction

ICE stands for Interactive Connectivity Establishment: Interactive connectivity establishment method.
ICE is implemented according to the recommendations of RFC5245 and is a set of protocols based on the offer/answer mode to solve NAT traversal.
It comprehensively utilizes existing STUN, TURN and other protocols to establish sessions in a more efficient way.

ICE introduction

1. The role of ICE

Divided into controlling and controlled.
The offer side is the controlling role, and the answer side is the controlled role.

2. ICE mode

Divided into FULL ICE and Lite ICE:
FULL ICE: Both parties must conduct connectivity checks and complete the process.
Lite ICE: When FULL ICE and Lite ICE communicate with each other, only the FULL ICE side needs to perform a connectivity check, and the Lite side only needs to respond to the response message. This mode is more commonly used for devices deployed on the public network.

3. Candidate

The candidate addresses for media transmission form a candidate pair to perform connectivity checks and determine the transmission path. They have the following attributes:

Type type
Host: This address is a real host. The address and port in the parameters correspond to a real host address. This address comes from the address on the local physical network card or logical network card. It can be used for terminals with public network addresses or the same intranet. use.
Srvflx: This address is the type reflected through Cone NAT (Cone NAT). The address and port in the parameters are the addresses and ports allocated on the NAT when the end sends a Binding request to the STUN/TURN server through NAT.
Relay: This address is the address and port used by the TURN server for relay when the end sends an Allocate request to the TURN server. This address and port are the addresses and ports used by the TURN service to forward data between two peers. Yes A relay address port. This address is the address and port used by the TURN server for relay when the end sends an Allocate request to the TURN server (this may be a local or NAT address)
Prflx: This address is the address obtained through Binding when sending STUN Binding. Newly occurs during the connection establishment check. The address and port in the parameters are the addresses and ports assigned on the NAT when the end sends a Binding request to the STUN/TURN server through NAT. This address is the address and port assigned on the NAT when the end sends a Binding request to the peer through NAT.
Component ID
The type of transmission media, 1 represents RTP; 2 represents RTCP.
WebRTC uses the Rtcp-mux method, that is, RTP and RTCP are transmitted in the same channel, reducing ICE negotiation and channel keep-alive.
Priority
Candidate’s priority.
If you consider factors such as delay, bandwidth resources, and packet loss, the following order of Type priorities is generally recommended:
host > srvflx > prflx > relay
Base
Refers to the candidate’s base address.
The base of Srvflx address is the local host address.
The base of host address and relayed address is itself

Interactive packet capture analysis

The interaction of SRS is relatively simple. Let’s capture the packet and analyze it:


Mainly divided into two parts:
1. Realize ICE information exchange through HTTP request and SDP
2. Use STUN to send a connectivity check request

SDP ICE information

Here audio is the same as video, only audio is taken

offer:

a=ice-ufrag:PA7e
//Client username
a=ice-pwd:F1o3tHlhk6OPBtXo8IdhZCRH
//Client password
a=ice-options:trickle
//The trickle method indicates that the media information and the post-ice option information can be transmitted separately.

answer:

a=ice-lite
// SRS is Lite ICE and only needs to respond to the client's Binding request.
a=ice-ufrag:8p42d118
//SRS username
a=ice-pwd:ok61un195fg8q8083yy06247w0xg483s
//SRS side password
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]: Identifier, used to identify whether two candidates are equal
// 1 [component]: Transmission media type 1 means RTP
//ubp [protocol]: protocol type
// 2130706431 [priority] : priority
// 10.151.3.77 [ip] : ip address
// 8000 [port] : port
// host [type]: host type, indicating that this is the real host address
// generation : algebra. The initial value is 0, and then it will continue to +1. Large generations will cover the candidate addresses of low generations. When the candidate is updated, it will be + 1 to replace the old candidate.

STUN message format

Stun Header: fixed 20 bytes


STUN Message Type (14bits): message type. Define the message type as follows:

The two digits C1 and C0 represent the class encoding: 00 represents request, 01 represents indication, 10 represents success response, and 11 represents error response.

Common types:

0x0001: Binding Request
0x0101: Binding Response
0x0111: Binding Error Response

Message Length (16 bits): Message length, excluding the 20 bytes of STUN Header.
Magic Cookie (32bits): fixed value 0x2112A442, used for exclusive OR (XOR) operation of reflection address.
Transaction ID (96 bits): Transaction ID identifier, the response corresponding to the request has the same identifier.

STUN attribute type


The STUN message header is followed by multiple attributes, each attribute is TLV encoded, type is the 16-bit type, length is the 16-bit length, and value is the attribute value.

STUN connectivity request


Request:
USERNAME: User name, the rule is “the peer’s ice-ufrag: own ice-ufrag”.
ICE-CONTROLLING: Indicates the initiator. Tie breaker is used to handle role conflicts. When rushing in, the larger value is controlling.
PRIORITY: priority
MESSAGE-INTEGRITY: HMAC-SHA1 value of STUN message, 20 bytes in length, used for message integrity authentication.
FINGERPRINT: Fingerprint authentication. This attribute can appear in all STUN messages. This attribute is used to distinguish STUN data packets from packets of other protocols.


Response:

XOR-MAPPED-ADDRESS: Used to represent the client’s external IP address. If there is no NAT, the external IP address and the internal IP address are the same. The first 8 bits are reserved, and the next 8 bits are used to indicate the IP type (IPV4/6). The next 16 bits represent the port number. The IPV4 version is forced here, so the Address is 32 bits:


Family: IP type, 0x01-IPV4, 0x02-IPV6.
Port: port.
Address: IP address

SRS processing

Code processing is relatively simple

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
    message_type = stream->read_2bytes();
    // Message length (excluding header 20bytes)
    uint16_t message_len = stream->read_2bytes();
    // Fixed value 0x2112A442
    string magic_cookie = stream->read_string(4);
    // Transaction ID identifier
    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) {<!-- -->
            // Peer's ice-ufrag: own 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.
            // represents the controlled party
            case IceControlled: {<!-- -->
                ice_controlled = true;
                srs_verbose("stun ice-controlled");
                break;
            }
            //Indicates the initiator
            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 & amp; pwd, SrsBuffer* stream)
{<!-- -->
    srs_error_t err = srs_success;

    string property_username = encode_username();
    string mapped_address = encode_mapped_address();
    // Message type 0x0101
    stream->write_2bytes(BindingResponse);
    // Message length (excluding the first 20 bytes)
    stream->write_2bytes(property_username.size() + mapped_address.size());
    // Fixed value 0x2112A442
    stream->write_4bytes(kStunMagicCookie);
    //Transaction ID identifier
    stream->write_string(transcation_id);
    // username
    stream->write_string(property_username);
    //External IP address
    stream->write_string(mapped_address);
    stream->data()[2] = ((stream->pos() - 20 + 20 + 4) & amp; 0x0000FF00) >> 8;
    stream->data()[3] = ((stream->pos() - 20 + 20 + 4) & amp; 0x000000FF);
    // sha1 encryption
    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) & amp; 0x0000FF00) >> 8;
    stream->data()[3] = ((stream->pos() - 20 + 8) & amp; 0x000000FF);
    //Fingerprint authentication
    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) & amp; 0x0000FF00) >> 8;
    stream->data()[3] = ((stream->pos() - 20) & amp; 0x000000FF);
    
    return err;
}

A complete set of tutorials on audio and video streaming media development

 >>> We have compiled some audio and video streaming media development learning materials and teaching videos for free sharing. If necessary, you can add your own learning exchange group 739729163 to receive it.

Original address: https://www.cnblogs.com/vczf/p/15346360.html