P2P技術詳解(四):P2P技術之STUN、TURN、ICE詳解

目錄

  • 1、內容概述
  • 2、STUN詳解
    • 2.1 RFC3489/STUN
      • 2.1.1 報文結構
      • 2.1.2實現原理
      • 2.1.3STUN功能舉例
    • 2.2 RFC5389/STUN
      • 2.2.1STUN用途
      • 2.2.2報文結構
    • 2.3 RFC5389與RFC3489的區別
    • 2.4 新特性介紹
      • 2.4.1指紋機制
      • 2.4.2通過DNS發現服務器機制
      • 2.4.3認證和消息完整性機制
      • 2.4.4備份服務器機制
    • 2.5 RFC5389與RFC3489的兼容
      • 2.5.1客戶端處理的改變
      • 2.5.2服務器處理的改變
  • 3、TURN詳解
    • 3.1 RFC5766/TURN
    • 3.2 操作概述
    • 3.3 術語
    • 3.4 協議交互過程詳細舉例
  • 4、ICE簡明介紹
    • 4.1 簡介
    • 4.2 應用模型
  • 5、本文總結

1、內容概述

在現實Internet網絡環境中,大多數計算機主機都位于防火墻或NAT之后,只有少部分主機能夠直接接入Internet。很多時候,我們希望網絡中的兩臺主機能夠直接進行通信,即所謂的P2P通信,而不需要其他公共服務器的中轉。由于主機可能位于防火墻或NAT之后,在進行P2P通信之前,我們需要進行檢測以確認它們之間能否進行P2P通信以及如何通信。這種技術通常稱為NAT穿透(NAT Traversal)。最常見的NAT穿透是基于UDP的技術,如RFC3489中定義的STUN協議。

STUN,首先在RFC3489中定義,作為一個完整的NAT穿透解決方案,英文全稱是Simple Traversal of UDP Through NATs,即簡單的用UDP穿透NAT。

在新的RFC5389修訂中把STUN協議定位于為穿透NAT提供工具,而不是一個完整的解決方案,英文全稱是Session Traversal Utilities for NAT,即NAT會話穿透效用。RFC5389與RFC3489除了名稱變化外,最大的區別是支持TCP穿透。

TURN,首先在RFC5766中定義,英文全稱是Traversal Using Relays around NAT:Relay Extensions to Session Traversal Utilities for NAT,即使用中繼穿透NAT:STUN的擴展。簡單的說,TURN與STURN的共同點都是通過修改應用層中的私網地址達到NAT穿透的效果,異同點是TURN是通過兩方通訊的“中間人”方式實現穿透。

ICE跟STUN和TURN不一樣,ICE不是一種協議,而是一個框架(Framework),它整合了STUN和TURN。

2、STUN詳解

了解STUN之前,我們需要了解NAT的種類。

NAT對待UDP的實現方式有4種,分別如下:

Full Cone NAT:
完全錐形NAT,所有從同一個內網IP和端口號發送過來的請求都會被映射成同一個外網IP和端口號,并且任何一個外網主機都可以通過這個映射的外網IP和端口號向這臺內網主機發送包。

Restricted Cone NAT:
限制錐形NAT,它也是所有從同一個內網IP和端口號發送過來的請求都會被映射成同一個外網IP和端口號。與完全錐形不同的是,外網主機只能夠向先前已經向它發送過數據包的內網主機發送包。

Port Restricted Cone NAT:
端口限制錐形NAT,與限制錐形NAT很相似,只不過它包括端口號。也就是說,一臺IP地址X和端口P的外網主機想給內網主機發送包,必須是這臺內網主機先前已經給這個IP地址X和端口P發送過數據包。

Symmetric NAT:
對稱NAT,所有從同一個內網IP和端口號發送到一個特定的目的IP和端口號的請求,都會被映射到同一個IP和端口號。如果同一臺主機使用相同的源地址和端口號發送包,但是發往不同的目的地,NAT將會使用不同的映射。此外,只有收到數據的外網主機才可以反過來向內網主機發送包。

2.1 RFC3489/STUN

STUN(Simple Traversal of User Datagram Protocol Through Network Address Translators),即簡單的用UDP穿透NAT,是個輕量級的協議,是基于UDP的完整的穿透NAT的解決方案。它允許應用程序發現它們與公共互聯網之間存在的NAT和防火墻及其他類型。它也可以讓應用程序確定NAT分配給它們的公網IP地址和端口號。STUN是一種Client/Server的協議,也是一種Request/Response的協議,默認端口號是3478。

2.1.1 報文結構

【? 消息頭】

所有的STUN消息都包含20個字節的消息頭,包括16位的消息類型,16位的消息長度和128位的事務ID。
在這里插入圖片描述
消息類型許可的值如下:

0x0001:捆綁請求
0x0101:捆綁響應
0x0111:捆綁錯誤響應
0x0002:共享私密請求
0x0102:共享私密響應
0x0112:共享私密錯誤響應

消息長度,是消息大小的字節數,但不包括20字節的頭部。事務ID,128位的標識符,用于隨機請求和響應,請求與其相應的所有響應具有相同的標識符。

【? 消息屬性】

消息頭之后是0或多個屬性,每個屬性進行TLV編碼,包括16位的屬性類型、16位的屬性長度和變長屬性值。
在這里插入圖片描述
屬性類型定義如下:

MAPPED-ADDRESS:MAPPED-ADDRESS屬性表示映射過的IP地址和端口。它包括8位的地址族,16位的端口號及長度固定的IP地址。RESPONSE-ADDRESS:RESPONSE-ADDRESS屬性表示響應的目的地址CHASNGE-REQUEST:客戶使用32位的CHANGE-REQUEST屬性來請求服務器使用不同的地址或端口號來發送響應。SOURCE-ADDRESS:SOURCE-ADDRESS屬性出現在捆綁響應中,它表示服務器發送響應的源IP地址和端口。CHANGED-ADDRESS:如果捆綁請求的CHANGE-REQUEST屬性中的“改變IP”和“改變端口”標志設置了,則CHANGED-ADDRESS屬性表示響應發出的IP地址和端口號。USERNAME:USERNAME屬性用于消息的完整性檢查,用于消息完整性檢查中標識共享私密。USERNAME通常出現在共享私密響應中,與PASSWORD一起。當使用消息完整性檢查時,可有選擇地出現在捆綁請求中。PASSWORD:PASSWORD屬性用在共享私密響應中,與USERNAME一起。PASSWORD的值是變長的,用作共享私密,它的長度必須是4字節的倍數,以保證屬性與邊界對齊。MESSAGE-INTEGRITY:MESSAGE-INTEGRITY屬性包含STUN消息的HMAC-SHA1,它可以出現在捆綁請求或捆綁響應中;MESSAGE-INTEGRITY屬性必須是任何STUN消息的最后一個屬性。它的內容決定了HMAC輸入的Key值。ERROR-CODE:ERROR-CODE屬性出現在捆綁錯誤響應或共享私密錯誤響應中。它的響應號數值范圍從100699。UNKNOWN-ATTRIBUTES:UNKNOWN-ATTRIBUTES屬性只存在于其ERROR-CODE屬性中的響應號為420的捆綁錯誤響應或共享私密錯誤響應中。REFLECTED-FROM:REFLECTED-FROM屬性只存在于其對應的捆綁請求包含RESPONSE-ADDRESS屬性的捆綁響應中。屬性包含請求發出的源IP地址,它的目的是提供跟蹤能力,這樣STUN就不能被用作DOS攻擊的反射器。

具體的ERROR-CODE(響應號),與它們缺省的原因語句一起,目前定義如下:

400(錯誤請求):請求變形了。客戶在修改先前的嘗試前不應該重試該請求。
401(未授權):捆綁請求沒有包含MESSAGE-INTERITY屬性。
420(未知屬性):服務器不認識請求中的強制屬性。
430(過期資格):捆綁請求沒有包含MESSAGE-INTEGRITY屬性,但它使用過期
的共享私密。客戶應該獲得新的共享私密并再次重試。
431(完整性檢查失敗):捆綁請求包含MESSAGE-INTEGRITY屬性,但HMAC驗
證失敗。這可能是潛在攻擊的表現,或者客戶端實現錯誤
432(丟失用戶名):捆綁請求包含MESSAGE-INTEGRITY屬性,但沒有
USERNAME屬性。完整性檢查中兩項都必須存在。
433(使用TLS):共享私密請求已經通過TLS(Transport Layer Security,即安全
傳輸層協議)發送,但沒有在TLS上收到。
500(服務器錯誤):服務器遇到臨時錯誤,客戶應該再次嘗試。
600(全局失敗):服務器拒絕完成請求,客戶不應該重試。

屬性空間分為可選部分與強制部分,值超過0x7fff的屬性是可選的,即客戶或服務器即使不認識該屬性也能夠處理該消息;值小于或等于0x7fff的屬性是強制理解的,即除非理解該屬性,否則客戶或服務器就不能處理該消息。

2.1.2實現原理

在這里插入圖片描述
STUN協議的完整交互過程如上,下面我們來介紹具體實現步驟。

一般情況下,客戶會配置STUN服務器提供者的域名,該域名被解析為IP地址和SRV過程的端口號。服務器名是“stun”,使用UDP協議發送捆綁請求,使用TCP協議發送共享私密請求。STUN協議的缺省端口號為3478。

若要提供完整性檢查,STUN在客戶和服務器間使用128位的共享私密,作為在捆綁請求和捆綁響應中的密匙。

首先,客戶通過發現過程獲得它將與之建立TCP連接的IP地址和端口號。客戶打開該地址和端口的連接,開始TLS協商,驗證服務器的標識。客戶發送共享私密請求。該請求沒有屬性,只有頭。服務器生成響應。

客戶會在該連接上生成多個請求,但在獲得用戶名和密碼后關閉該連接。

服務器收到共享私密請求,驗證從TLS連接上到達的該請求;如果不是通過TLS收到的請求,則生成共享私密錯誤響應,并設置ERROR-CODE屬性為響應號433;這里區分兩種情況:若通過TCP收到請求,則錯誤響應通過收到請求的相同連接發送;若通過UDP收到請求,則錯誤響應發送回請求送出的源IP和端口。

服務器檢查請求中的任何屬性,當其中有不理解的小于或等于0x7fff的值,則生成共享私密錯誤響應,設置ERROR-CODE屬性為響應號420,并包括UNKNOWN-ATTRIBUTE屬性,列出它不理解的小于或等于0x7fff的屬性的值。該錯誤響應通過TLS連接發送。

若請求正確,服務器創建共享私密響應,包含與請求中相同的事務ID,并包含USERNAME和PASSWORD屬性。用戶名在10分鐘內有效。

共享私密響應通過與收到請求的相同的TLS連接發送,服務器保持連接打開狀態,由客戶關閉它。

接著,客戶發送捆綁請求,攜帶的屬性包括:

可選屬性:RESPONSE-ADDRESS屬性和CHANGE-REQUEST屬性;
強制屬性:MESSAGE-INTEGRITY屬性和USERNAME屬性。

客戶發送捆綁請求,通過客戶重傳來提供可靠性。客戶開始用100ms的間隔重傳,每次重傳間隔加倍,直至1.6秒。之間間隔1.6秒的重傳繼續,直到收到響應或總共已經發送了9次。因此,若9500ms后,還未收到響應,客戶認為傳輸已經失敗。

服務器檢查捆綁請求的MESSAGE-INTEGRITY屬性,不存在則生成捆綁錯誤響應,設置ERROR-CODE屬性為響應號401;若存在,計算請求的HMACKey值。

服務器檢查USERNAME屬性,不存在則生成捆綁錯誤響應,設置ERROR-CODE屬性為響應號432;若存在,但不認識該USERNAME的共享私密(例如,它超時了),生成捆綁錯誤響應,設置ERROR-CODE屬性為響應號430。

若服務器知道該共享私密,但所計算的HMAC與請求的不同,生成捆綁錯誤響應,設置ERROR-CODE屬性為響應號431。

假設消息完整性檢查通過了,服務器檢查請求中的任何屬性的值,若遇到不理解的小于或等于0x7fff的值,生成捆綁錯誤響應,設置ERROR-CODE屬性為響應號420,該響應包含UNKNOWN-ATTRIBUTE屬性,并列出不理解的小于或等于0x7fff的屬性。

若請求正確,服務器生成單個捆綁響應,包含與捆綁請求相同的事務ID。服務器在捆綁響應中加入MAPPED-ADDRESS屬性,該屬性的IP地址和端口號為捆綁請求的源IP地址和端口號。

捆綁響應的源地址和端口號取決于捆綁請求中CHANGE-REQUEST屬性的值及捆綁請求收到的地址和端口號相關。總結如下:

在這里插入圖片描述
服務器在捆綁響應中加入SOURCE-ADDRESS屬性,包含用于發送捆綁響應的源地址和端口號;加入CHANGED-ADDRESS屬性,包含源IP地址和端口號。

如果捆綁請求中包含了USERNAME和MESSAGE-INTEGRITY屬性,則服務器在捆綁響應中加入MESSAGE-INTEGRITY屬性。

如果捆綁請求包含RESPONSE-ADDRESS屬性,則服務器在捆綁響應中加入REFLECTED-FROM屬性:如果捆綁請求使用從共享私密請求獲得的用戶名進行認證,則REFLECTED-FROM屬性包含共享私密請求到達的源IP地址和端口號;若請求中的用戶名不是使用共享私密分配的,則REFLECTED-FROM屬性包含獲得該用戶名的實體的源IP地址和端口號;若請求中沒有用戶名,且服務器愿意處理該請求,則REFLECTED-FROM屬性包含請求發出的源IP地址和端口號。

服務器不會重傳響應,可靠性通過客戶周期性地重發請求來保障,每個請求都會觸發服務器進行響應。

客戶端判斷響應的類型是捆綁錯誤響應還是捆綁響應。捆綁錯誤響應通常在請求發送的源地址和端口收到;捆綁響應通常在請求中的RESPONSE-ADDRESS屬性的地址和端口收到,若沒有該屬性,則捆綁響應將在請求發送的源地址和端口號收到。

若是捆綁錯誤響應,客戶檢查響應中的ERROR-CODE屬性的響應號:
400499之間的未知屬性按屬性400處理,500599之間的未知屬性按500處理,
600699之間的未知屬性按600處理。任何100399之間的響應都會使請求重傳中
止,但其他則忽略;若客戶收到響應的屬性類型大于0x7fff,則忽略該屬性,若小于
或等于0x7fff,則請求重傳停止,并忽略整個響應;若是捆綁響應,客戶檢查響應的MESSAGE-INTEGRITY屬性:如果不存在,
客戶在請求中加入MESSAGE-INTEGRITY屬性,并放棄該響應;如果存在,
客戶計算響應的HMAC。如果計算出的HMAC與響應中的不同,則放棄該響應,
并警告客戶可能受到了攻擊;若計算出的HMAC與響應中的匹配,則過程繼續;不論收到捆綁響應還是捆綁錯誤響應,都將中止該請求的重傳。客戶在第一次
響應后繼續監聽捆綁請求的響應10秒鐘,如果這期間它收到任何消息類型不同的響應
或不同的MAPPED-ADDRESS屬性,它將警告用戶可能受到攻擊;并且,如果客戶收到
的捆綁響應次數超過它發送的捆綁請求數的兩倍,它將警告用戶可能受到攻擊;
若捆綁響應經過認證,上述攻擊并未導致客戶丟棄MAPPED-ADDRESS,則客戶可以
使用該MAPPED-ADDRESS和SOURCE-ADDRESS屬性。

2.1.3STUN功能舉例

客戶通過帶外方式獲得STUN服務器信息后,就打開對應的地址和端口的連接,并開始與STUN服務器進行TLS協商。一旦打開了連接,客戶就通過TCP協議發送共享私密請求,服務器生成共享私密響應。STUN在客戶和服務器間使用共享私密,用作捆綁請求和捆綁響應中的密匙。之后,客戶使用UDP協議向STUN服務器發送捆綁請求,當捆綁請求消息到達服務器的時候,它可能經過了一個或者多個NAT。結果是STUN服務器收到的捆綁請求消息的源IP地址被映射成最靠近STUN服務器的NAT的IP地址,STUN服務器把這個源IP地址和端口號復制到一個捆綁響應消息中,發送回擁有這個IP地址和端口號的客戶端。

當STUN客戶端收到捆綁響應消息之后,它會將自己發送捆綁請求時綁定的本地IP地址和端口號同捆綁響應消息中的IP地址和端口號進行比較,如果不匹配,就表示客戶端正處于一個或者多個NAT的前面。

在Full-Cone NAT的情況下,在捆綁響應消息中的IP地址和端口是屬于公網的,公網上的任何主機都可以使用這個IP地址和端口號向這個應用程序發送數據包,應用程序只需要在剛才發送捆綁請求的IP地址和端口上監聽即可。

當然,客戶可能并不在一個Full-Cone NAT的前面,實際上,它并不知道自己在一個什么類型的NAT的前面。為了確定NAT的類型,客戶端使用附加的捆綁請求。具體過程是很靈活的,但一般都會像下面這樣工作:客戶端再發送一個捆綁請求,這次發往另一個IP地址,但是使用的是跟上一次同一個源IP地址和源端口號,如果返回的數據包里面的IP地址和端口號和第一次返回的數據包中的不同,客戶端就會知道它是在一個對稱NAT的前面。客戶端為了確認自己是否在一個完全錐形NAT的前面,客戶端可以發送一個帶有標志的捆綁請求,這個標志告訴服務器使用另一個IP地址和端口發送捆綁響應。換句話說,如果客戶端使X/Y的IP地址端口對向A/B的IP地址端口對發送捆綁請求,服務器就會使用源IP地址和源端口號為C/D的地址端口對向X/Y發送捆綁響應。如果客戶端收到了這個響應,它就知道它是在一個Full-Cone NAT前面。

STUN協議允許客戶端請求服務器從收到捆綁請求的IP地址往回發捆綁響應,但是要使用不同的端口號。這可以用來檢查客戶端是否在Port Restricted Cone NAT的前面還是在Restricted Cone NAT的前面。

2.2 RFC5389/STUN

STUN協議在RFC5389中被重新命名為Session Traversal Utilities for NAT,即NAT會話穿透效用。在這里,NAT會話穿透效用被定位為一個用于其他解決NAT穿透問題協議的協議。它可以用于終端設備檢查由NAT分配給終端的IP地址和端口號。同時,它也被用來檢查兩個終端之間的連接性,好比是一種維持NAT綁定表項的保活協議。STUN可以用于多種NAT類型,并不需要它們提供特殊的行為。

STUN本身不再是一種完整的NAT穿透解決方案,它相當于是一種NAT穿透解決方案中的工具。這是與RFC3489/STUN版本相比最重要的改變。

2.2.1STUN用途

目前定義了三種STUN用途:

Interactive Connectivity Establishment(ICE)[MMUSIC-ICE],交互式連接建立Client-initiated connections for SIP [SIP-OUTBOUND],用于SIP的客戶端初始化連接NAT Behavior Discovery [BEHAVE-NAT],NAT行為發現

2.2.2報文結構

【? 消息頭】

STUN消息頭為20字節,后面緊跟0或多個屬性。STUN頭部包含一STUN消息類型、magic cookie、事務ID和消息長度。
在這里插入圖片描述
每個STUN消息的最高位前2位必須為0。當STUN協議為多個協議多路復用時若使用的是同一個端口,這可以用于與其他協議區分STUN數據包。消息類型確定消息的類別(如請求、成功回應、失敗回應、標志)。雖然這里有四種消息類型,但可以分為2類事務:請求/響應事務、標志事務。

消息類型字段可進一步劃分為下面結構:
在這里插入圖片描述
消息類型定義如下:

0b00,表示請求
0b01,表示標志
0b10,表示成功響應
0b11,表示錯誤響應

魔術字域必須包含固定的值0x2112A442。在RFC3489中,該域是事務ID的一部分。配置魔術字允許服務器檢測客戶是否理解某些在改進的版本中增加的屬性。另外,還可用于STUN多路復用時與其他協議的包進行區分。

96位的事務ID用于唯一的識別STUN事務。對于請求/響應事務,事務ID由STUN客戶端來選擇;對于標志事務,由代理(代理指支持STUN的客戶端或服務器)來選擇并發送。它主要服務于與請求相關的響應,因此它也扮演著一個幫助阻止確定類型的攻擊的角色。服務器使用事務ID來唯一的標識出所有客戶端的每一個事務。事務ID本身必須是唯一的,并且隨機的從0到2的96-1次方中選擇。重新發送相同的請求時,也必須使用新的事務ID。成功或錯誤響應必須攜帶與相對應的請求相同的事務ID。

消息長度字段不包括20字節的STUN頭部。所有的STUN屬性必須填充為4字節的倍數。消息長度字段的最后2位總是為0,這為區分STUN包與其他協議的包提供了另外一種方法。

【? 消息屬性】

STUN頭之后是0或多個屬性。每個屬性都采用TLV編碼,16位的類型、16位的長度及可變長度的值。每個STUN屬性必須是4字節邊界對齊。
在這里插入圖片描述
屬性空間被劃分為2個范圍。屬性的類型值在0x0000到0x7fff是強制理解屬性,這意味著除非STUN代理能夠理解這些屬性,否則將不能正常處理包含該屬性的消息;屬性的類型值在0x8000到0xffff范圍是可選理解屬性,這意味著如果STUN代理不能理解它們的話這些屬性可以被忽略。

2.3 RFC5389與RFC3489的區別

RFC5389與RFC3489的不同點如下:

去掉STUN是一種完整的NAT穿透方案的概念,現在是一種用于提供NAT穿透解決方案的工具。因而,協議的名稱變為NAT會話穿透效用;定義了STUN的用途;去掉了STUN關于NAT類型檢測和綁定生命期發現的用法,去掉了RESPONSE-ADDRESS、CHANGED-ADDRESS、CHANGE-REQUEST、SOURCE-ADDRESS和REFLECTED-FROM屬性;增加了一個固定的32位的魔術字字段,事務ID字段減少了32位長度;增加了XOR-MAPPED-ADDRESS屬性,若魔術字在捆綁請求中出現時,該屬性包括在捆綁響應中。否則,RFC3489中的行為是保留的(換句話說,捆綁響應中包括MAPPED-ADDRESS);介紹了消息類型字段的正式結構,帶有一對明確的位來標識Request、Response、Error-Response或Indication消息。因此,消息類型字段被劃分為類別和方法兩部分;明確的指出了STUN的最高2位是0b00,當用于ICE時可以簡單的與RTP包區分開來;增加指紋屬性來提供一種明確的方法來檢測當STUN協議多路復用時,STUN與其他協議之間的差異;增加支持IPv6,IPv4客戶端可以獲取一個IPv6映射地址,反之亦然;增加一個long-term-credential-based認證機制;增加了SOFTWARE、REALM、NONCE和ALTERNATE-SERVER屬性;去掉了共享密匙方法,因此PASSWORD屬性也去掉了;去掉了使用連續10秒偵聽STUN響應來識別一個攻擊的做法;改變事務計時器來增加TCP友好性;去掉了STUN例子如集中分離控制和媒體面,代替的,在使用STUN協議時提供了更多的信息;定義了一類填充機制來改變長度屬性的說明;REALM、SERVER、原因語句和NONCE限制在127個字符,USERNAME限制在513個字節以內;為TCP和TLS改變了DNS SRV規程,UDP仍然和以前保持一致。

2.4 新特性介紹

2.4.1指紋機制

FINGERPRINT機制是一種可選的用于其他協議多路復用STUN時發送給相同的傳輸地址時區分STUN數據包的機制,該機制不支持與RFC3489相兼容。

在一些用途中,基于相同的傳輸地址時多個協議會多路復用STUN消息,例如RTP協議。STUN消息必須首先和應用報文分離開。目前,在STUN報頭中有3種固定的字段可以用于該目的。盡管如此,在一些案例中,三種固定字段仍然不能充分的區別開。

當擴展的指紋機制被使用時,STUN代理在發送給其他STUN代理的消息中包括FINGERPRINT屬性。當其他STUN代理收到時,除基本的檢查之外,還將檢查是否包含FINGERPRINT屬性及它是否包含正確的值,至此,它將相信這是一個STUN消息。指紋機制幫助STUN代理檢查其他協議那些看起來像是STUN消息的消息。

2.4.2通過DNS發現服務器機制

STUN客戶端可以使用DNS來發現STUN服務器的IP地址和端口。客戶端必須知道服務器的域名。

當客戶端希望找出服務器在公網上的位置就采用捆綁請求/響應事務,SRV(資源記錄表)中服務器名稱是“stun”。當通過TLS會話采用捆綁請求/響應事務,SRV中服務器名稱為“stuns”。STUN用戶可以定義額外的DNS資源記錄服務名稱。

STUN請求的默認端口是3478,用于TCP和UDP。STUN在TLS上的默認端口是5349。服務器能夠在TLS上運行STUN與STUN在TCP上時使用相同的端口,只有服務器軟件支持決定初始消息是否是TLS或STUN消息。

如果SRV中沒有記錄可查,客戶端執行A或AAAA記錄查找域名。結果將會是1張IP地址表,每一個都可以使用TCP或UDP采用默認端口號連接。通常要求使用TLS,客戶端使用STUN在TLS上的默認端口號連接其中一個IP地址。

2.4.3認證和消息完整性機制

短期證書機制
短期證書機制假設在STUN事務之前,客戶端和服務器已經使用了其他協議來交換了證書,以username和password形式。這個證書是有時間限制的。例如,在ICE用途中,兩個終端使用帶外方式交換信息來對username和password達成一致,并在媒體會話期間使用。這個證書被用來進行消息完整性檢查,用于每個請求和多個響應中。與長期證書機制相比,沒有挑戰和響應方式,因此,這種證書的時間限制特性的優點是可以阻止重播。

長期證書機制
長期證書機制依賴于一個長期證書,username和password在客戶端和服務器中是共用的。這個證書從它提供給 用戶開始將一直是有效的,直到該用戶不再是該系統的用戶。這本質上是一個提供給用戶username和password的傳統的登入方式。

客戶端初始發送一個請求,沒有提供任何證書和任何完整性檢測。服務器拒絕這個請求,并提供給用戶一個范圍(用于指導用戶或代理選擇username和password)和一個nonce。這個nonce提供重放保護。它是一個cookie,由服務器選擇,以這樣一種方式來標示有效時間或客戶端身份是有效的。客戶端重試這個請求,這次包括它的username和realm和服務器提供的nonce來回應。服務器確認這個nonce和檢查這個message integrity。如果它們匹配,請求則通過認證。如果這個nonce不再有效,即過期了,服務器就拒絕該請求,并提供一個新的nonce。

在隨后的到同一服務器的請求,客戶端重新使用這個nonce、username和realm,和先前使用的password。這樣,隨后的請求不會被拒絕直到這個nonce變成無效的。需要注意的是,長期證書機制不能用來保護Indications,由于Indications不能被改變,因此,使用Indications時要么使用短期證書,要么就省略認證和消息完整性。因為長期證書機制對離線字典攻擊敏感,部署的時候應該使用很難猜測的密碼。

2.4.4備份服務器機制

服務器使用增強的重定向功能將一個客戶端轉向另一個服務器,通過回應一個錯誤響應號為300(嘗試備份)的錯誤響應。服務器在錯誤響應中攜帶一個ALTERNATE-SERVER屬性。

客戶端收到錯誤響應號為300的錯誤響應后,在該響應中查找ALTERNATE-SERVER屬性。若找到一個,客戶端就會將當前的事務作廢,并重新嘗試發送請求到該屬性中列出的服務器。請求報文若已經通過認證,則必須使用與先前發送給執行重定向操作的服務器同樣的證書。如果客戶端在最后5分鐘里已經重試發送請求時已經重定向到了一個服務器,它必須忽略重定向操作并將當前的事務作廢,這是為了防止無限的重定向循環。

2.5 RFC5389與RFC3489的兼容

在RFC3489中:

UDP是唯一支持的傳輸協議
RFC5389中的魔術字字段是RFC3489中事務ID的一部分,事務ID長128位
沒有XOR-MAPPED-ADDRESS屬性,綁定方法是使用MAPPED-ADDRESS屬性代替
有3個需要強制理解的屬性,分別是:RESPONSE-ADDRESS、CHANGE-REQUEST、CHANGED-ADDRESS屬性,而RFC5389中不再支持這些屬性。

2.5.1客戶端處理的改變

客戶端想要與RFC3489的服務器互操作,應發送一個使用綁定方法的請求消息,不包含任何消息,使用UDP協議發送給服務器。如果成功,將收到服務器發回的包含MAPPED-ADDRESS屬性而不是XOR-MAPPED-ADDRESS屬性的成功響應。客戶端試圖與基于RFC3489的應用服務器互操作必須準備好接收任意一個屬性。此外,客戶端必須忽略任何在響應中出現的保留的強制理解的屬性。RFC3489中規定保留屬性中的0x0002、0x0004、0x0005和0x000B可能出現在綁定響應中。

2.5.2服務器處理的改變

服務器能夠察覺由RFC3489中的客戶端發送的攜帶有不正確的魔術字的捆綁請求消息。當服務器察覺到RFC3489中的客戶端,它應該將捆綁請消息中魔術字域中的值拷貝到捆綁響應中的魔術字字段中,并且插入一個MAPPED-ADDRESS屬性代替XOR-MAPPED-ADDRESS屬性。

客戶端在極少的環境下可能包括RESPONSE-ADDRESS或CHANGE-REQUEST屬性中的一個。在這些情況下,服務器把這些屬性看做是一個不認識的強制理解的屬性,并回應一個錯誤響應。RFC3489版本中的STUN缺少魔術字和指紋屬性這兩種能夠高可靠性的正確標識其他協議多路復用時的STUN消息。因此,STUN執行與RFC3489兼容時不應該被用于多個協議。

3、TURN詳解

3.1 RFC5766/TURN

TURN,在RFC5766中定義,英文全稱Traversal Using Relays around NAT(TURN):Relay Extensions to Session Traversal Utilities for NAT(STUN),即使用中繼穿透NAT:STUN的中繼擴展。簡單的說,TURN與STUN的共同點都是通過修改應用層中的私網地址達到NAT穿透的效果,異同點是TURN是通過兩方通訊的“中間人”方式實現穿透。

如果一個主機位于NAT的后面,在某些情況下它不能夠與其他主機點對點直接連接。在這些情況下,它需要使用中間網點提供的中繼連接服務。TURN協議就是用來允許主機控制中繼的操作并且使用中繼與對端交換數據。TURN與其他中繼控制協議不同的是它能夠允許一個客戶端使用一個中繼地址與多個對端連接。

TURN協議被設計為ICE的一部分,用于NAT穿越,雖然如此,它也可以在沒有ICE的地方單獨使用。

3.2 操作概述

在這里插入圖片描述
在一個典型組網中,一個TURN客戶端連接在一個私有網絡中,通過一個或多個NAT來連接到公網。在公網中有一個TURN服務器。在因特網的別處有一個或多個對端是這個TURN客戶端希望通訊的。這些對端也有可能是在一個或多個NAT的后面。該客戶端使用服務器作為一個中繼來發送數據包 到這些對端去,并且從這些對端接收數據包。

客戶端通過一個IP地址和端口的組合來與服務器建立會話。客戶端使用TURN命令在服務器上創建和操作一個ALLOCATION。一旦這個allocation創建好了,客戶端能夠在數據發往哪個對端的指示下發送應用數據到這個服務器,服務器將中繼這些數據到合適的對端。客戶端發送的應用數據包含在TURN消息中,服務器將數據提取出來,并以UDP數據包方式發送給對端。反向上,對端以UDP數據包方式發送應用數據到這個allocation提供的中繼傳輸地址。因為TURN消息總是包含客戶端與哪些對端通訊的指示,客戶端能夠使用單一的allocation來與多個對端通訊。

3.3 術語

TURN client:遵循RFC5766的STUN客戶端。TURN server:遵循RFC5766的STUN服務器。Peer:TURN客戶端希望連接的主機。TURN服務器為TURN客戶端和它的對端中繼流量,但Peer并不與TURN服務器使用TURN協議進行交互,它接收從TURN服務器發送過來的數據,并向TURN服務器發送數據。Transport Address:IP地址與端口號的組合。Host Transport Address:客戶端或對端的傳輸地址。Server-Reflexive Transport Address:NAT公網側的傳輸地址,該地址由NAT分配,相當于一個特定的主機傳輸地址。Relayed Transport Address:TURN服務器上的傳輸地址,用于客戶端和對端中繼數據。TURN Server Transport Address:TURN服務器上的傳輸地址,用于客戶端發送STUN消息給服務器。Peer Transport Address:服務器看到的對端的傳輸地址,當對端是在NAT后面,則是對端的服務器反射傳輸地址。Allocation:通過Allocate請求將中繼傳輸地址提供給客戶端,除了中繼狀態外,還有許可和超時定時器等。5-tuple:五元組,包括客戶端IP地址和端口,服務器IP地址和端口和傳輸協議(包括UDP、TCP、TLS)的組合。Channel:通道號與對端傳輸地址的關聯,一旦一個通道號與一個對端的傳輸地址綁定,客戶端和服務器就能夠利用帶寬效應更大的通道數據消息來交換數據。Permission:一個對端允許使用它的IP地址和傳輸協議來發送數據到TURN服務器,服務器只為從對端發來的并且匹配一個已經存在的許可的流量中繼到相應的客戶端。Realm:服務器內用于描述服務器或內容的一個字符串,這個realm告訴客戶端哪些用戶名和密碼的組合可用于認證請求。Nonce:服務器隨機選擇的一個字符串,包含在報文摘要中。為了防止中繼攻擊,服務器應該有規律的改變這個nonce。

3.4 協議交互過程詳細舉例

以上圖為例進行講解,每個消息中,多個屬性包含在消息中并顯示它們的值。為了方便閱讀,值以人們可讀的格式來顯示
在這里插入圖片描述
客戶端使用10.1.1.2:49271作為傳輸地址向服務器的傳輸地址發送Allocate請求。客戶端隨機選擇一個96位的事務ID。該Allocate請求消息包括SOFTWARE屬性來提供客戶端的軟件版本信息;包括LIFETIME屬性,指明客戶端希望該allocation具有1小時的生命期而非缺省的10分鐘;包括REQUESTED-TRANSPORT屬性來告訴服務器與對端之間采用UDP協議來傳輸;包括DONT-FRAGMENT屬性因為客戶端希望在隨后的Send indications中使用DON’T-FRAGMENT屬性。

服務器需要任何請求必須是經過認證的,因此服務器拒絕了該最初的Allocation請求,并且回應了攜帶有錯誤響應號為401(未授權)的Allocate錯誤響應;該響應包括一個REALM屬性,指明認證的域;還包括一個NONCE屬性和一個SOFTWARE屬性。

客戶端收到了錯誤響應號為401的Allocate錯誤響應,將重新嘗試發送Allocate請求,此時將包括認證屬性。客戶端在新的請求中重新選擇一個新的事務ID。客戶端包括一個USERNAME屬性,使用從服務器那收到的realm值來幫助它決定使用哪個值;請求還包括REALM和NONCE屬性,這兩個屬性是從收到的錯誤響應中拷貝出來的。最后,客戶端包括一個MESSAGE-INTEGRITY屬性。

服務器收到認證的Allocate請求后,檢查每個屬性是否正確;然后,產生一個allocation,并給客戶端回應Allocate成功響應。服務器在該成功響應中攜帶一個LIFETIME屬性,本例中服務器將客戶端請求的1小時生命期減小為20分鐘,這是因為這個特定的服務器可能不允許超過20分鐘的生命期;該響應包括XOR-RELAYED-ADDRESS屬性,值為該allocation的中繼傳輸地址;該響應還包括XOR-MAPPED-ADDRESS屬性,值為客戶端的server-reflexive地址;該響應也包含一個SOFTWARE屬性;最后,包括一個MESSAGE-INTEGRITY屬性來證明該響應,確保它的完整性。

在這里插入圖片描述
接著,客戶端為了準備向對端A發送一些應用數據而創建一個permission。這里通過一個CreatePermission請求來做到。該請求攜帶XOR-PEER-ADDRESS屬性包含有確定的請求的IP地址,這里為對端A的地址;需要注意的是,屬性中地址的端口號被設置為0在CreatePermission請求中,并且客戶端使用的是對端A的server-reflexive地址而不是它的主機地址(私網地址);客戶端在該請求中攜帶與之前的Allocate請求中一樣的username、realm和nonce值,因此該請求被服務器認可。此時在該請求中,客戶端沒有攜帶SOFTWARE屬性。

服務器收到該CreatePermission請求,產生一個相應的許可,并以CreatePermission成功響應來回應。該響應中只包含了Transaction-ID和MESSAGE-INTEGRITY屬性。
在這里插入圖片描述
現在客戶端使用Send indication來發送應用數據到對端A。對端的server-reflexive傳輸地址包含在XOR-PEER-ADDRESS屬性中,應用數據包含在DATA屬性中。客戶端已經在應用層上執行了路徑MTU發現功能,因此通過DON’T-FRAGMENT屬性來告知服務器當通過UDP方式來向對端發送數據時應設置DF位。Indications不能使用長期證書機制來認證,所以該消息中沒有MESSAGE-INTEGRITY屬性。

服務器收到Send indication后,提取出應用數據封裝成UDP格式發給對端A;UDP報文的源傳輸地址為中繼傳輸地址,并設置DF位。

對端A回應它自己的包含有應用數據的UDP包給服務器。目的地址為服務器的中繼傳輸地址。當服務器收到后,將生成Data indication消息給客戶端,攜帶有XOR-PEER-ADDRESS屬性。應用數據包含在DATA屬性中。
在這里插入圖片描述
客戶端現在若要綁定一個通道到對端B,將指定一個空閑的通道號(本例中為0x4000)包含在CHANNEL-NUMBER屬性中,對端B的傳輸地址包含在XOR-PEER-ADDRESS屬性中。與以前一樣,客戶端再次利用上次請求中的username、realm和nonce。

當服務器收到該請求后,服務器綁定這個對端的通道號,為對端B的IP地址安裝一個permission,然后給客戶端回應一個ChannelBind成功響應消息。
在這里插入圖片描述
客戶端現在發送一個ChannelData消息給服務器,攜帶有發送給對端B的數據。這個消息不是一個STUN消息,因此沒有事務ID。它之有3個字段:通道號、數據、數據長度;服務器收到后,檢查通道號后發現當前已經綁定了,就以UDP方式發送數據給對端B。

接著,對端B發送UDP數據包回應給服務器的中繼傳輸地址。服務器收到后,回應給客戶端ChannelData消息,包含UDP數據包中的數據。服務器知道是給哪個客戶端發送ChannelData消息,這是因為收到的UDP數據包中的目的地址(即服務器的中繼傳輸地址),并且知道使用的是哪個通道號,這是因為通道已經與相應的傳輸地址綁定了。
在這里插入圖片描述
有時候,20分鐘的生命期已經到了,客戶端需要刷新allocation。此時通過發送Refresh請求來進行。該請求包含最后一次使用的username、realm和nonce,還包含SOFTWARE屬性。當服務器收到這個Refresh請求時,它注意到這個nonce值已經超期了,則給客戶端回應一個錯誤響應號為438(過期Nonce)的Refresh錯誤響應,并提供一個新的nonce值。可護端將重試該請求,此時攜帶新的nonce值。若第二次嘗試被接受,服務器將回應一個成功響應。需要注意的是,此時客戶端在請求中沒有攜帶LIFETIME屬性,所以服務器刷新客戶端的allocation時采用缺省的10分鐘生命期。

4、ICE簡明介紹

4.1 簡介

ICE的全稱Interactive Connectivity Establishment(互動式連接建立),由IETF的MMUSIC工作組開發出來的,它所提供的是一種框架,使各種NAT穿透技術可以實現統一。ICE跟STUN和TURN不一樣,ICE不是一種協議,而是一個框架(Framework),它整合了STUN和TURN。

4.2 應用模型

在這里插入圖片描述
如上圖所示,如果A想與B通信,那么其過程如下:

1)A收集所有的IP地址,并找出其中可以從STUN服務器和TURN服務器收到流量的地址;
2)A向STUN服務器發送一份地址列表,然后按照排序的地址列表向B發送啟動信息,目的是實現節點間的通信;
3)B向啟動信息中的每一個地址發送一條STUN請求;
4)A將第一條接收到的STUN請求的回復信息發送給B;
5)B接到STUN回復后,從中找出那些可在A和B之間實現通信的地址;
6)利用列表中的排序列最高的地址進一步的設備間通信。

由于該技術是建立在多種NAT穿透協議的基礎之上,并且提供了一個統一的框架,所以ICE具備了所有這些技術的優點,同時還避免了任何單個協議可能存在的缺陷。因此,ICE可以實現在未知網絡拓撲結構中實現的設備互連,而且不需要進行對手配置。另外,由于該技術不需要為VoIP流量手動打開防火墻,所以也不會產生潛在的安全隱患。

5、本文總結

在現實Internet網絡環境中,大多數計算機主機都位于防火墻或NAT之后,只有少部分主機能夠直接接入Internet。很多時候,我們希望網絡中的兩臺主機能夠直接進行通信(即所謂的P2P通信),而不需要其它公共服務器的中轉。由于主機可能位于防火墻或NAT之后,在進行P2P通信之前,我們需要進行檢測以確認它們之間能否進行P2P通信以及如何通信。這種技術通常被稱為NAT穿透(NAT Traversal)。

RFC3489中定義的STUN,即簡單地用UDP穿過NAT(STUN)是個輕量級的協議。它允許應用發現它們與公共互聯網之間存在的NAT和防火墻及其他類型。它還為應用提供判斷NAT給它們分配的公共網際協議(IP)地址。STUN可工作在許多現存NAT上,并且不需要它們做任何特別的行為。它允許廣泛的各類的應用穿越現存的NAT設施。

RFC5389中對STUN協議進行了修訂,將其定位于為穿透NAT提供工具,即NAT會話穿透效用是一個用于其他解決NAT穿透問題協議的協議。它可以用于終端設備檢查由NAT分配給終端的IP地址和端口號。同時,它也被用來檢查兩個終端之間的連接性,好比是一種維持NAT綁定表項的保活協議。STUN本身并不是一種完整的NAT穿透解決方案。它相當于是一種NAT穿透解決方案中的工具。這是與先前的版本相比最重要的改變。之前的RFC3489中定義的STUN是一個完整的穿透NAT解決方案。此外,最大的區別是支持TCP穿透。

RFC5766中對STUN協議再次進行了擴展,即中繼穿透NAT:STUN的擴展。TURN與STUN的共同點都是通過修改應用層中的私網地址達到NAT穿透的效用,異同點是TUN采用了兩方通訊的“中間人”方式實現穿透,突破了原先STUN協議無法在兩臺主機不能夠點對點直接連接下提供作用的限制。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/378840.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/378840.shtml
英文地址,請注明出處:http://en.pswp.cn/news/378840.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

比較兩張大小相同的照片的差異,返回數值

比較兩張大小相同的照片的差異,返回數值 from PIL import Image import math import operator from functools import reducedef image_contrast(img1, img2):image1 Image.open(img1)image2 Image.open(img2)h1 image1.histogram()h2 image2.histogram()resul…

poj2115C Looooops

http://poj.org/problem?id2115 參考人家的 如下 如i65534,當i3時,i1 其實就是 i(655343)%(2^16)1 有了這些思想,設對于某組數據要循環x次結束,那么本題就很容易得到方程: x[(B-A2^k)%2^k] /C 即 Cx(B-A)(mod 2^k) 此…

ASP.NET調用javascript腳本的常見方法小結

http://www.codesky.net/article/doc/201004/2010041706872.htm轉載于:https://www.cnblogs.com/ZC_Mo-Blog/archive/2010/11/23/1885125.html

python wait方法_Python條件類| 帶有示例的wait()方法

python wait方法Python Condition.wait()方法 (Python Condition.wait() Method) wait() is an inbuilt method of the Condition class of the threading module in Python. wait()是Python中線程模塊的Condition類的內置方法。 Condition class implements condition variab…

return編程python_python3 第二十一章 - 函數式編程之return函數和閉包

我們來實現一個可變參數的求和。通常情況下,求和的函數是這樣定義的:def calc_sum(*args):ax0for n inargs:ax ax nreturn ax但是,如果不需要立刻求和,而是在后面的代碼中,根據需要再計算怎么辦?可以不返回…

黑色背景下,計算照片白色的區域面積和周長

黑色背景下,計算照片白色的區域面積和周長 import cv2 img cv2.imread(E:\Python-workspace\OpenCV\OpenCV/beyond.png,1)#第一個參數為選擇照片的路徑,注意照片路徑最后一個為正斜杠其他都為反斜杠;第二個參數,其中1表示所選照…

php連接mssql數據庫的幾種方式

數據庫查詢不外乎4個步驟,1、建立連接。2、輸入查詢代碼。3、建立查詢并取出數據。4、關閉連接。 php連接mssql數據庫有幾個注意事項,尤其mssql的多個版本、32位、64位都有區別。 首先,php.ini文件中;extensionphp_pdo_mssql.dll ;extensionp…

通俗易懂:快速理解P2P技術中的NAT穿透原理

目錄1、基礎知識1.1、什么是NAT?1.2、為什么會有NAT?1.3、NAT有什么優缺點?2、NAT的實現方式2.1、靜態NAT2.2、NAPT3、NAT的主要類型3.1、完全錐型NAT(Full Cone NAT,后面簡稱FC)3.2、受限錐型NAT&#xff…

duration java_Java Duration類| toNanos()方法與示例

duration javaDuration Class toNanos()方法 (Duration Class toNanos() method) toNanos() method is available in java.time package. toNanos()方法在java.time包中可用。 toNanos() method is used to convert this Duration into the number of nanoseconds. toNanos()方…

java 負載均衡_java負載均衡 - 歲月靜好I的個人空間 - OSCHINA - 中文開源技術交流社區...

作用對系統的高可用,網絡壓力的緩解,處理能力擴容的重要手段之一。服務器負載我們通常所說的負載是指:服務器負載軟硬件負載服務器負載又分為:軟件負載--硬件負載軟件負載:通過在服務器上安裝一些具有負載功能或模塊的…

b tree和b+tree_B TREE實施

b tree和btreeB TREE及其操作簡介 (Introduction to B TREE and its operations) A B tree is designed to store sorted data and allows search, insertion and deletion operation to be performed in logarithmic time. As In multiway search tree, there are so many nod…

黑色背景下,將照片內封閉空心圖案的空心區域染成Cyan并保存

在黑色背景下,將照片內封閉空心圖案的空心區域染色 import cv2 import numpy as np img cv2.imread(E:\Python-workspace\OpenCV\OpenCV/beyond.png,1)#第一個參數為選擇照片的路徑,注意照片路徑最后一個為正斜杠其他都為反斜杠;第二個參數…

Ubuntu輸入su提示認證失敗的解決方法

Ubuntu輸入su提示認證失敗的解決方法 啟動ubuntu服務時竟然提示權限不夠,用su切換,輸入密碼提示認證失敗,這下搞了吧,后來一經查閱原來Ubuntu安裝后,root用戶默認是被鎖定了的,不允許登錄,也不允…

SDP協議基本分析(RTSP、WebRTC使用)

目錄一、介紹二、標準 SDP 規范1. SDP 的格式2. SDP 的結構(1)會話描述(2)媒體描述三、WebRTC 中的 SDP一、介紹 SDP(Session Description Protocal)以文本描述各端(PC 端、Mac 端、Android 端…

MFC六大關鍵技術(第四部分)——永久保存(串行化)

MFC 六大關鍵技術 ( 第四部分 ) ——永久保存(串行化) 先用一句話來說明永久保存的重要:弄懂它以后,你就越來越像個程序員了! 如果我們的程序不需要永久保存,那幾乎可以肯定是一個小玩兒。那怕我們的記事本…

在網絡中配置思科交換機

By default, all ports of a switch are enabled. As we are talking about layer 2 switching, there is no need to configure IP address or any routing protocol on the switch. In such a situation, the configuration is not focused on the switch. 缺省情況下&#…

黑色背景下,描繪照片的輪廓形狀并保存

描繪照片的輪廓形狀并保存 import cv2 from matplotlib import pyplot as plt # 1.先找到輪廓 img cv2.imread(E:\Python-workspace\OpenCV\OpenCV/beyond.png, 0) _, thresh cv2.threshold(img, 0, 255, cv2.THRESH_BINARY cv2.THRESH_OTSU) image, conturs, hierarchy c…

java pdf合并_Java 合并、拆分PDF文檔

本文將介紹如何在Java程序中合并及拆分PDF文檔,合并文檔時,包括合并多個不同PDF文檔為一個文檔,以及合并PDF文檔的不同頁面為一頁;拆分文檔是,包括將PDF文檔按每一頁拆分,以及按指定頁數范圍來拆分。下面將…

HDU4405 期望

對于期望,首先,對于這個公式中p表示概率,x表示隨機變量 展開則為 ex p1*x1p2*x2p3*x3....... 對于本題 假設 ex[ i ]表示當前 i 走到 n 的期望值。所以若 i 處沒有飛機,ex[ i ]sigma(1/6*ex[ik])1 其中(k1...6) (1表示…

調用本地電腦攝像頭并進行按P進行捕獲照片并保存,按下Q退出

調用本地電腦攝像頭并進行按P進行捕獲照片并保存,按下Q退出 灰度攝像頭顯示: import cv2 cap cv2.VideoCapture(0) if not cap.isOpened():print("Cannot open camera")exit() while True:# 逐幀捕獲ret, frame cap.read()# 如果正確讀取幀…