2021/09/13

ICE: Interactive Connectivity Establishment

ICE 是以 P2P 連線為前提的一種協調方法,主要是 VOIP、P2P視訊在使用。因為現在的網路終端,通常都在 Firewall 裡面,透過 NAT 上網,導致 P2P 連線的困難度大增,因此 ICE 透過 Stun 的技術,判讀 NAT 類型,當遇到特殊的 NAT 類型,無法處理 P2P 時,就搭配 Turn Server 為兩個終端進行資料中繼傳輸。

ICE

ref: Introduction to WebRTC protocols

主要用在 UDP 多媒體 session,為了解決 NAT 的問題,ICE 可嘗試找到多個 candidates,取得最佳的連線方法。

ICE 透過 stun server 查詢 NAT 類型,以及取得自己對外的 IP:Port。

如果無法直接 p2p 連線,就會改用 Turn Server 中繼,互相傳遞資料。

SDP

P2P通訊標準協議ICE

RFC4566 SDP 是用來協調 session 傳輸所要交換的資訊,內容可能會有這些

會話描述:
     v=  (protocol version)
     o=  (originator and session identifier)
     s=  (session name)
     i=* (session information)
     u=* (URI of description)
     e=* (email address)
     p=* (phone number)
     c=* (connection information -- not required if included in
          all media)
     b=* (zero or more bandwidth information lines)
     One or more time descriptions ("t=" and "r=" lines; see below)
     z=* (time zone adjustments)
     k=* (encryption key)
     a=* (zero or more session attribute lines)
     Zero or more media descriptions

時間資訊描述:
     t=  (time the session is active)
     r=* (zero or more repeat times)

多媒體資訊描述(如果有的話):
     m=  (media name and transport address)
     i=* (media title)
     c=* (connection information -- optional if included at
          session level)
     b=* (zero or more bandwidth information lines)
     k=* (encryption key)
     a=* (zero or more media attribute lines)

ex:

      v=0
      o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
      s=SDP Seminar
      i=A Seminar on the session description protocol
      u=http://www.example.com/seminars/sdp.pdf
      e=j.doe@example.com (Jane Doe)
      c=IN IP4 224.2.17.12/127
      t=2873397496 2873404696
      a=recvonly
      m=audio 49170 RTP/AVP 0
      m=video 51372 RTP/AVP 99
      a=rtpmap:99 h263-1998/90000

NAT

依據 NAT的限制方式,可分為四種:

  • Full Cone NAT(one-to-one)

    • 只是單純的做位址轉換,並未對進出的封包設限
  • Address-Restricted Cone NAT

    • 從內部送出之封包的目的地 IP 位址會被記住。只有這些曾經收過這些封包的位址可以送封包進入內部 IP。由其他位址送進來的封包,都會被拒絕。
  • Port-Restricted cone NAT

    • 封包進出比 Restricted Cone 增加了一個限制, 從內部送出之封包的目的地的IP 位址及 Port Number 會被記住。 由外部送進來的封包,除了由那些接收過內部所送出的封包的IP 位址及 Port Number 所送來的封包之外,都會被拒絕。
  • Symmetric NAT

    • 前三種NAT在做位址轉換時,無論封包是送往哪裡, NAT內部同一內部位址都對應到同一個外部位址:Port,但 Symmetric NAT 內則每一內部位址對於不同的目的地,會對應到不同的外部 IP address:Port。
    • Symmetric NAT只允許先由私有網域內的機器,發送封包到網際網路中的接收端可以回傳封包

判斷方法

NAT Test 提供一種方式,可以判斷是否為 Symmetric NAT,因為 Understanding Different NAT Types and Hole-Punching 的說明,只要有一端為 Symmetric NAT,就無法直接 p2p 連線。

判斷的方法是利用 RTCPeerConnection 對 google stun server 建立連線,由 onicecandidate 判讀, port 跟 rport 是否一樣。

// JScript File

function parseCandidate(line) {
    console.log("parseCandidate="+line);
    // candidate:842163049 1 udp 1677729535 220.132.127.162 42280 typ srflx raddr 0.0.0.0 rport 0 generation 0 ufrag 1cTw network-cost 999

    var parts;
    // Parse both variants.
    // 兩種:a=candidate:  or  candidate:
    if (line.indexOf('a=candidate:') === 0) {
        parts = line.substring(12).split(' ');
    } else {
        parts = line.substring(10).split(' ');
    }

    var candidate = {
        foundation: parts[0],
        component: parts[1],
        protocol: parts[2].toLowerCase(),
        priority: parseInt(parts[3], 10),
        ip: parts[4],
        port: parseInt(parts[5], 10),
        // skip parts[6] == 'typ'
        type: parts[7]
    };

    for (var i = 8; i < parts.length; i += 2) {
        switch (parts[i]) {
            case 'raddr':
                candidate.relatedAddress = parts[i + 1];
                break;
            case 'rport':
                candidate.relatedPort = parseInt(parts[i + 1], 10);
                break;
            case 'tcptype':
                candidate.tcpType = parts[i + 1];
                break;
            default: // Unknown extensions are silently ignored.
                break;
        }
    }

    console.log("candidate=",candidate);
    // {
    //     "foundation": "842163049",
    //     "component": "1",
    //     "protocol": "udp",
    //     "priority": 1677729535,
    //     "ip": "220.132.127.162",
    //     "port": 42280,
    //     "type": "srflx",
    //     "relatedAddress": "0.0.0.0",
    //     "relatedPort": 0
    // }
    return candidate;
};

function testNat() {
    //reset lblResult
    document.getElementById("MainContent_lblResult").style.color = "#696969";
    document.getElementById("MainContent_lblResult").innerHTML = "Test started " + new Date().toLocaleString() + "<br>";

    var candidates = {};
    var pc = new RTCPeerConnection({
        iceServers: [
            { urls: 'stun:stun1.l.google.com:19302' },
            { urls: 'stun:stun2.l.google.com:19302' }
        ]
    });
    document.getElementById("MainContent_lblResult").innerHTML += "Create data channel to Google STUN servers<br>";
    pc.createDataChannel("foo");
    document.getElementById("MainContent_lblResult").innerHTML += "Testing...Please Wait...<br>";
    pc.onicecandidate = function (e) {
        console.log(e);

        if (e.candidate && e.candidate.candidate.indexOf('srflx') !== -1) {
            console.log("e.candidate.candidate="+e.candidate.candidate);

            var cand = parseCandidate(e.candidate.candidate);

            // console.log("candidates[cand.relatedPort]=",candidates[cand.relatedPort]);
            if (!candidates[cand.relatedPort]) candidates[cand.relatedPort] = [];
            // console.log("candidates[cand.relatedPort]=",candidates[cand.relatedPort]);
            candidates[cand.relatedPort].push(cand.port);
            // console.log("candidates[cand.relatedPort]=",candidates[cand.relatedPort]);

        } else if (!e.candidate) {
            // All ICE candidates have been sent
            console.log("candidates=",candidates);
            // {
            //     "0": [
            //         42280
            //     ]
            // }
            if (Object.keys(candidates).length === 1) {
                var ports = candidates[Object.keys(candidates)[0]];
                console.log("ports", ports);

                //window.alert(ports.length === 1 ? 'normal nat' : 'symmetric nat');
                if (ports.length === 1) {
                    document.getElementById("MainContent_lblResult2").innerHTML = "Completed: Normal NAT";
                } else {
                    document.getElementById("MainContent_lblResult2").innerHTML = "Completed: Symmetric NAT";
                }
                document.getElementById("MainContent_lblResult2").style.color = "green";
            }
        }
    };
    pc.createOffer()
        .then(offer => pc.setLocalDescription(offer))

};

另一個判斷的 TypeScript: isNormalNat.ts

ICE协议下NAT穿越的实现(STUN&TURN) 這邊有說明,stun server 是如何判斷 NAT 類型的

candidate type

ref: RFC 5245

ref: WebRTC connectivity

udp 有四種:host, srflx, prflx, relay

  • host:實體 IP,可直接連線

  • srflx:Server Reflexive and Relayed Candidates

    host 因為經過 NAT,透過 Stun/Turn server 產生的 candidate

  • prflx: Peer Reflexive Candidates

    其中一端來自 symmetric NAT

  • relay

    由 Turn Server 產生的中繼 IP:Port

tcp 有三種:active, passive, so

  • active

    會打開 outbound connection,且不接受 incoming connection request。這是最常見的類型

  • passive

    只會接受 incoming connection

  • so

    會嘗試同時在兩個終端直接打開連線

References

互動式連接建立

P2P通訊標準協議ICE

ICE (Interactive Connectivity Establishment)

你需要知道关于ICE的三件事

30-29之 WebRTC 的 P2P 打洞術 ( ICE )

[知識篇]WebRTC - ICE(STUN/TURN)

Peer-to-Peer Communication Across Network Address Translators

about 82% of the NATs tested support hole punching for UDP, and about 64% support hole punching for TCP streams

Trickle ICE

RFC 5389 - STUN 協定介紹

[筆記] WebRTC 網路影音-通訊協定篇(protocol of media, video and audio)

NAT 及防火牆之來源

Understanding Different NAT Types and Hole-Punching

30-28之 WebRTC 連線前傳 - 為什麼 P2P 連線很麻煩 ? ( NAT )

NAT -- 基本概念

WebRTC ICE 狀態與提名處理

測試 NAT 類型

SIP穿越NAT的rport機制

WebRTC ICE candidate里面的raddr和rport表示什么?

Network Configuration for VoIP Providers

ICE介绍 (RFC 5245)

rfc 5245 笔记

C ICE implementation library

libice

libnice

WebRtc音视频实时通信--libnice库介绍

How to play libnice-ly with your NAT

libnice for iOS and Android.

libjuice

沒有留言:

張貼留言