天天看點

IPv6原理、應用與實踐

作者:騰訊 微信技術架構部 團隊

2017年11月26日,中共中央辦公廳和×××辦公廳印發了《推薦網際網路協定第六版(IPv6)規模部署行動計劃》,并發出通知,要求各地區各部門結合實際認真貫徹落實。這條新聞傳達了一個很重要的資訊:這個是推進中國IPv6發展的戰略總動員令。

本文将會從以下幾個方面進一步介紹IPv6,包括有:

         1、IPv6的基本概念

         2、IPv6在Linux作業系統下的實作

         3、IPv6的實驗

         4、IPv6的過渡技術介紹

         5、IPv6在Linux平台下socket程式設計應該注意的問題

         6、實作簡易版TGW支援IPv6雛形demo

         值得說的是,目前我們接觸得比較多的主流作業系統核心,已經很好地支援IPv6協定棧,例如:

         Windows: windows 7、windows 8.x、windows 10,預設開啟IPv6

         Linux: 核心2.6.x、核心3.x、核心4.x 已經支援IPv6(需要手動開啟)

         IOS:IOS9開始已經支援IPv6 Only,2016年蘋果已經強制要求app必須支援IPv6

        本文提到的IPv6節點,沒有特殊說明,一般指的是純IPv6節點(IPv6 Only),也就是隻支援IPv6協定棧;IPv4節點,是指純IPv4的節點,也就是隻支援IPv4協定棧;如果節點支援IPv6和IPv4雙棧,會指明是雙棧節點。

IPv6的基本概念

        衆所周知,32位的IPv4位址已經耗竭,IPv6采用128位的位址長度擁有更大的位址空間。首先我們先來認識一下IPv6到底長成什麼樣子。

初識IPv6

IPv6原理、應用與實踐

圖1 IPv6資料封包

上圖是我們最熟悉的ping的IPv6版本ICMPv6。可以看到,IPv6資料封包和IPv4有很大的差别:

1、資料鍊路層(L2)的type字段辨別為 0x86dd,表示承載的上層協定是IPv6

      IPv4對比:type字段為0x0800

2、IPv6的頭部字段,和IPv4差别巨大(可以猜測到,IPv6和IPv4無法相容)

      IPv6的封包頭部格式如下:

IPv6原理、應用與實踐

圖2 IPv6封包頭部(該圖檔來自網際網路)

        IPv6封包頭部更精簡了,字段更少了,對比起IPv4,有以下幾個地方值得注意:

        一、IPv6封包頭部是定長(固定為40位元組),IPv4封包頭部是變長的。這個意味着,寫代碼處理IPv6資料封包的效率會提高很多:)

        二、IPv6中Hop Limit字段含義類似IPv4的TTL。

        三、IPv6中的Traffic Class字段含義類似IPv4中的TOS(Type Of Service)。

        四、IPv6的封包頭部取消了校驗和字段。取消這個字段也是對IPv4協定的一個改進。當IPv4封包在網路間傳輸,每經過一個路由器轉發就是修改TTL字段,就需要重新計算校驗和,而由于資料鍊路層L2和傳輸層L4的校驗已經足夠強壯,是以IPv6取消這個字段會提高路由器的轉發效率。值得一提的是,在IPv6協定下,傳輸層L4協定UDP、TCP是強制需要進行校驗和的(IPv4是可選的)。

        五、IPv6封包頭部中的Next Header字段表示“承載上一層的協定類型”或者“擴充頭部類型”。這裡的含義與IPv4有很大的差别,需要加以解釋:

當IPv6資料封包承載的是上層協定ICMPv6、TCP、UDP等的時候,Next Header的值分别為58、6、17,這個時候和IPv4封包頭部中的Protocol字段很類似。

        當不是以上3種協定類型的時候,IPv6封包頭部緊接的是擴充頭部。擴充頭部是IPv6引入的一個新的概念,每個IPv6的資料封包可以承載0個或多個擴充頭部,擴充頭部通過連結清單的形式組織起來。當IPv6資料封包承載着擴充頭部的時候,Next Header的數值為擴充頭部的類型值。

        為什麼要引入擴充頭部這個概念,這裡也是IPv6對IPv4改進的一個方面,用擴充頭部取代了IPv4的可選項資訊,精簡了IPv6的頭部,增強了IPv6的擴充性。有同學會不會有疑問,IPv6的分片資料封包怎麼處理?其實就是使用了IPv6擴充頭部。我們來抓一個UDP分片封包來看看。

IPv6原理、應用與實踐

圖3 IPv6分片封包

        當發送一個分片IPv6資料封包的時候,IPv6使用的是擴充頭部的形式組織各個分片的資訊,如圖IPv6封包頭部Next Header字段值為44表示存在擴充頭部,擴充頭部是IPv6分片資料資訊。

        對比IPv4,分片資訊是記錄在IPv4封包頭部的分片字段中。

        IPv6的擴充頭部類型有很多種,除了上述的分片頭部,還有路由頭部、逐跳可選頭部等,具體的可以參考RFC2460。

        本章主要介紹了IPv6的一些很直覺的認識,下面逐漸介紹IPv6上的基本知識和概念。

IPv6的位址文法

        一個IPv6的位址使用冒号十六進制表示方法:128位的位址每16位分成一段,每個16位的段用十六進制表示并用冒号分隔開,例如:

        一個普通公網IPv6位址:2001:0D12:0000:0000:02AA:0987:FE29:9871

        IPv6位址支援壓縮前導零的表示方法,例如上面的位址可以壓縮表示為:

        2001:D12:0:0:2AA:987:FE29:9871

        為了進一步精簡IPv6位址,當冒号十六進制格式中出現連續幾段數值0的位段時,這些段可以壓縮為雙冒号的表示,例如上面的位址還可以進一步精簡表示為:

        2001:D12::2AA:987:FE29:9871

        又例如IPv6的位址FF80:0:0:0:FF:3BA:891:67C2可以進一步精簡表示為:

FE80::FF:3BA:891:67C2

這裡值得注意的是,雙冒号隻能出現一次。

IPv6位址的号段劃分和字首表示法

        IPv6擁有128位巨大的位址空間,對于那麼大的空間,也不是随意的劃分,而是使用按照bit位進行号段劃分(與鵝廠内部一些的64位uin改造放号的zone劃分算法)。

        IPv6的位址結構如下圖:

IPv6原理、應用與實踐

圖4 IPv6位址結構

        例如RFC4291中定義了n=48, m=16,也就是子網和接口ID與各占64位

        IPv6支援子網字首辨別方法,類似于IPv4的無分類域間路由CIDR機制(注意:IPv6沒有子網路遮罩mask的概念)。使用“IPv6位址/字首長度”表示方法,例如:

        2001:C3:0:2C6A::/64表示一個子網

        而2001:C3:0:2C6A:C9B4:FF12:48BC:1A22/64表示該子網下的一個節點位址。

        可以看到,一個IPv6的位址有子網字首+接口ID構成,子網字首由位址配置設定和管理機構定義和配置設定,而接口ID可以由各作業系統實作生成,生成算法後面的章節會介紹。

IPv6的位址類型

        IPv6位址分三種類型

        1、單點傳播,對應于IPv4的普通公網和私網位址

        2、多點傳播,對應于IPv4的多點傳播(多點傳播)位址

        3、任播,IPv6新增的位址概念類型

        IPv6沒有廣播位址,用多點傳播位址實作廣播的功能。實際上我們工作和生活最可能最多接觸的就是單點傳播位址,接下來本文重點會講解單點傳播位址的種類。多點傳播和任播位址有興趣的同學自行查閱相關RFC和文獻。

IPv6單點傳播位址

        注意,大家如果在網上搜尋IPv6的位址,可能都是千篇一律的把所有“出現過”的單點傳播位址介紹出來,其實有一些單點傳播位址類型已經在相關的RFC中被廢除或者不建議使用,而本節會指出這類位址。同時,在介紹單點傳播位址的時候,盡量與IPv4中對應的或者相類似的概念做對比,加深了解。

       IPv6單點傳播位址有以下幾種:

1、全球單點傳播位址

IPv6原理、應用與實踐

圖5 IPv6全球單點傳播位址結構

        字首2000::/3,相當于IPv4的公網位址(IPv6的誕生根本上就是為了解決IPv4公網位址耗盡的問題)。這種位址在全球的路由器間可以路由。

2、鍊路本地位址

IPv6原理、應用與實踐

圖6 鍊路本地位址結構

        字首FE80::/10,顧名思義,此類位址用于同一鍊路上的節點間的通信,主要用于自動配置位址和鄰居節點發現過程。Windows和Linux支援或開啟IPv6後,預設會給網卡接口自動配置一個鍊路本地位址。也就是說,一個接口一定有一個鍊路本地位址。如下圖:

IPv6原理、應用與實踐

圖7 Linux下檢視鍊路本地位址

IPv6原理、應用與實踐

圖8 Windows下檢視鍊路本地位址

        值得說的是,每個接口必須至少有一個鍊路本地位址;每個接口可以配置1個以上的單點傳播位址,例如一個接口可以配置一個鍊路本地位址,同時也可以配置一個全球單點傳播位址。

        注意,很容易會把鍊路本地位址和IPv4的私網/内網位址對應起來,其實鍊路本地位址對應于IPv4的APIPA位址,也就是169.254開頭的位址(典型場景就是windows開啟自動擷取位址而擷取失敗後自動配置設定一個169.254的位址)。而IPv4私網對應于IPv6的什麼位址,後面會介紹。

        特别地,在IPv6 socket程式設計中,可以使用鍊路本地位址程式設計通信,但是需要增加一些額外的參數(這是一個小坑),在後面介紹程式設計的章節會介紹。

3、唯一本地位址

IPv6原理、應用與實踐

圖9 唯一本地位址結構

        字首FC00::/7,相當于IPv4的私網位址(10.0.0.0、172.16.0.0、192.168.0.0),在RFC4193中新定義的一種解決私網需求的單點傳播位址類型,用來代替廢棄使用的站點本地位址。

        可能看到這裡,有同學會跳出來說:IPv6不是為了解決IPv4位址耗盡的問題嗎,既然IPv6的位址空間那麼大,可以為每一個網絡節點配置設定公網IPv6的節點,那為什麼IPv6還需要支援私網?這裡需要談談對IPv6下私網支援的認識。

        在IPv4中,利用NAT技術私網内的網絡節點可以使用統一的公網出口通路網際網路資源,大大節省了IPv4公網位址的消耗(IPv6推進緩慢的原因之一)。另一方面,由于預設情況下私網内節點與外界通信的發起是單向的,網絡通路僅僅能從私網内發起,外部發起的請求會被統一網關或者防火牆阻隔掉,這樣的網絡架構很好的保護了私網内的節點安全性和私密性。可以設想一下,如果鵝廠内部每台辦公電腦都配置了IPv6的公網位址上網,是多麼可怕的事情,每台辦公電腦都會面臨被黑客入侵的威脅(殭屍電腦真多)。

        是以,在安全性和私密性要求下,IPv6中同樣需要支援私網,并且也需要支援NAT。在Linux核心3.7版本開始加入對IPv6 NAT的支援,實作的方式和IPv4下的差别不大(Linux核心代碼中變量和函數的命名幾乎就是ctrl+c和ctrl+v過來的-_-||)。

4、站點本地位址

        字首FEC9::/48,以前是用來部署私網的,但RFC3879中已經不建議使用這類位址,建議使用唯一本地位址。大家知道有這麼一回事就可以了。網上還有很多文章還提到這種位址,但是沒有說明這種位址已經不再使用。

5、特殊位址:回環位址

        0:0:0:0:0:0:0:1或::1,等同于IPv4的127.0.0.1

6、過渡位址:内嵌IPv4位址的IPv6位址

        就是在IPv6的某一些十六進制段内嵌這IPv4的位址,例如IPv6位址中64:ff9b::10.10.10.10,此IPv6位址最後4個位元組内嵌一個IPv4的位址,這類位址主要用于IPv6/IPv4的過渡技術中。

一、IPv4相容位址

        0:0:0:0:0:0:w.x.y.z或::w.x.y.z(其中w.x.y.z是點分十進制的IPv4位址)。但在RFC4291中已經不推薦使用這類位址,大家知道有這麼一回事就可以了。

二、過渡位址:IPv4映射位址

        0:0:0:0:0:FFFF:w.x.y.z或::FFFF:w.x.y.z(其中w.x.y.z是點分十進制的IPv4位址),用于IPv6位址表示IPv4位址。主要用于某些場景下IPv6節點與IPv4節點通信,Linux核心對這類位址很好地支援,在後面程式設計和核心分析的章節會分析使用過程。

三、過渡位址:特定過渡技術位址

        6to4位址、ISATAP位址、Teredo位址主要用于對應的過渡技術的位址,在後面介紹過渡技術的時候會介紹。

IPv6接口ID生成算法

        從前面的介紹中可以看出,IPv6單點傳播位址是由字首(64位)+接口ID(64位)組成。接口ID的生成算法主要有以下幾種:

        1、根據RFC4291定義,接口ID可以從EUI-64位址生成。

                  詳細算法可以檢視regli同學的PPT第14頁。

        2、為了可以具備某種程度的匿名信,接口ID可以使用一個随機配置設定的,windows作業系統預設就是使用這種生成算法,Linux下也是預設開啟這個算法。

        3、使用狀态化的自動配置技術配置設定,例如DHCPv6配置設定。

        4、手工配置。

IPv6位址配置

        前面對IPv6的位址、字首、接口等等做了介紹,接下來就是要介紹一個接口如何配置IPv6位址。IPv6一個比IPv4更厲害的方面,就是可以自動配置位址,甚至這個配置過程不需要DHCPv6(在IPv4中是DHCPv4)這樣的位址配置協定。最典型的例子就是,隻要開啟了IPv6協定棧的作業系統,每個接口就能自動配置了鍊路本地位址,這個是和IPv4最重要的差別之一。

        IPv6的位址配置有以下幾種:

        1、隻要開啟了IPv6協定棧,接口自動配置設定鍊路本地位址。

        2、無狀态自動配置位址(RFC2462),後面會有實驗示範。

        3、有狀态自動配置位址,例如DHCPv6。

        4、手動配置。

IPv6的域名解析

        由于IPv6的位址擴充為128位,比IPv4的更難書寫和記憶,是以IPv6下的DNS變得尤為重要。IPv6的的DNS資源記錄類型為AAAA(又稱作4A),用于解析指向IPv6位址的完全有效域名。下面是一個示例:

        Hostipv6.example.wechat.com IN AAAA 2001:db8:1::1

        IPv6下的域名解析可以認為是IPv4的擴充,詳細可以檢視RFC3596.

Linux核心IPv6架構簡析

        本文後面主要的分析都是基于Linux,會有涉及關于Linux核心對IPv6的實作。主要是因為,現在IPv6的參考資料不多,除了與IPv6相關的RFC之外,還有少數可以參閱的IPv6國外文獻,而Linux核心一直都與跟随着IPv6的協定更新和變化,Linux核心IPv6的實作是十分重要的參考材料之一。而且從事背景開發工作主要也是在Linux平台下,熟悉Linux下IPv6的實作也是為以後的工作做知識儲備。

        PS:用戶端開發的同學可以參考各自平台的文檔.............

        Linux在很早之前就已經開始支援IPv6,目前我們接觸最多的Linux核心版本都很好地支援IPv6,同時也是支援IPv4/IPv6雙棧體系。在Linux作業系統中,IPv4是預設必須開啟,IPv6是可選編譯和配置開啟。

        例如在編譯核心的時候,需要選擇IPv6編譯選項才支援IPv6

IPv6原理、應用與實踐

圖10 Linux核心編譯支援IPv6

        當開啟支援IPv6的Linux的核心網絡雙棧的結構,如下圖:

IPv6原理、應用與實踐

圖11 Linux核心雙棧架構

Linux核心中,IPv6協定棧與IPv4協定棧并行關系。IPv6和IPv4完全是兩套不一樣的代碼實作。IPv6完整的協定棧邏輯子產品包括:

        1、網絡層IPv6,核心邏輯:IPv6路由子系統

        2、傳輸層TCP/UDP實作:TCPv6、UDPv6

        3、控制封包協定ICMPv6,這裡值得一提的是ICMPv6在IPv6協定中的地位十分重要。

        ICMPv6不僅提供了與ICMPv4相同的服務診斷功能,例如報告資料包的錯誤和提供簡單的echo服務,ICMPv6是IPv6中鄰居發現協定的重要組成部分,用于管理鍊路上的點到點的通信。

        4、鄰居子系統的實作:鄰居發現協定NDP(對應于IPv4裡面的ARP協定)

        5、其他進階實作(IPv6 NAT、IPv6隧道、iPv6 IPSec等)

        由于我們平時的開發工作在應用層,以上1-4是将會接觸得最多。

IPv6實驗

        本章我們通過實驗,加深對IPv6的認識。這裡的實驗沒有使用真實作網的IPv6接入點(目前國内絕大部分接入點都是教育網),而實驗的目的主要是觀察IPv6的資料包結構、IPv6的路由配置等,是以決定自己通過搭建中間路由器、應用伺服器的方式做實驗,便于抓包和代碼分析。

       用戶端:windows 7

       路由器:中間路由器使用自己編譯和搭建的Linux系統(核心2.6.32.27)

       應用伺服器:Ubuntu16.04LTS版本。

       為什麼要使用自己編譯的Linux作為路由器?因為IPv6的實踐類能參考的文獻比較少,而Linux核心的IPv6子產品是最重要的參考資源之一,在實踐中遇到問題可以使用打LOG和分析代碼的方法解決。

1、無狀态自動配置位址實驗

        IPv6位址的擷取是最重要的環節之一。本實驗使用開源的無狀态自動配置服務radvd進行實驗。

IPv6原理、應用與實踐

圖12 IPv6無狀态自動配置

IPv6原理、應用與實踐

圖13 IPv6無狀态自動配置封包分析

        無狀态自動配置過程:

        1、由鍊路上的主機向鍊路發起“路由請求”封包,這個封包是以多點傳播協定發送,尋找鍊路上最合适的路由器。

        2、路由器收到請求會傳回“路由通告”封包,封包裡面帶着本鍊路的位址字首資訊主機将接收到的字首和自身的接口ID,組成完整的新位址。

        3、主機嘗試使用新位址發起位址重複檢測,檢測鍊路上是否有其他主機也是這個位址,如果有,就停止使用該位址;如果沒有,就啟用這個新位址。

        可以看到無狀态自動配置過程十分簡易(對比DHCPv4和DHCPv6來說),實際上,無狀态自動配置可以單獨組網使用,也可以配合有狀态自動配置一般會配合使用,加強網絡節點管理。涉及自動配置和位址檢測等更多細節,可以查閱RFC1971、RFC4861。

2、IPv6靜态路由配置實驗

        本次實驗主要是了解windows和linux的靜态路由配置。

IPv6原理、應用與實踐

圖14 IPv6典型的網絡拓撲

        由于各自的網絡字首(網段)不一緻,在不使用預設路由的情況下,我們嘗試配置路由讓用戶端可以通路到伺服器。

一、Windows 7配置靜态路由:

        去往伺服器的2001:db8:5::/64網段的路由

IPv6原理、應用與實踐

圖15 Windows配置IPv6路由

二、路由器1配置

IPv6原理、應用與實踐

圖16 Linux下配置IPv6路由

三、路由器2配置

IPv6原理、應用與實踐

圖17 Linux下配置IPv6路由

四、伺服器靜态路由配置

IPv6原理、應用與實踐

圖18 伺服器配置IPv6路由

五、結果

IPv6原理、應用與實踐

圖19 用戶端通路伺服器

        用戶端可以順利ping通伺服器。可以看到,IPv6下的路由配置,無論是windows還是linux,與IPv4的配置差别不大,熟悉IPv4各個平台路由配置的同學可以很快上手IPv6的路由配置。

3、IPv6的web服務

        複用2的架構,在伺服器端部署一個web服務,在用戶端通路該web服務。web服務沒有選擇像apache或者nginx這樣的龐然大物,而選擇了很輕量的boa。原因是boa雖然原始支援IPv6,但是我想粗暴的把所有IPv4的socket套接字都替換成IPv6版本,嘗試做一個自定義的更新。結果需要改動的代碼非常少,不超過20行,boa就能完全支援IPv6。

        配合實驗,寫了一個簡單的CGI,隻是在版面echo字元串。如下圖:

IPv6原理、應用與實踐

圖20 浏覽器使用IPv6位址通路網絡資源

這裡值得注意的是,在浏覽器中使用IPv6的位址通路web資源,IPv6的位址必須要使用中括号“[]”包起來。

IPv6原理、應用與實踐

圖21 IPv6下的http封包

        從Server端抓包看,IPv6下的Web服務http封包,除了網絡層L3的封包頭部不一樣之外,其餘的都和IPv4版本的沒有太大差别差别。

4、IPv6的過渡技術實驗

        這部分将在過渡技術介紹中一起實驗。

IPv6的過渡技術

        IPv6的提出,最重要的目的就是解決公網IPv4耗盡的問題,而且IPv6協定的設計就考慮到了更加好的效率、安全、擴充等方面,可以那麼說,IPv6是未來網絡發展的大趨勢。但為什麼IPv6已經發展了十幾年了,目前在我們的工作和生活中還是比較少接觸和使用。這裡的原因是非常的複雜,有技術上障礙,因為IPv6和IPv4是兩個完全不相容的協定(在極少數的特定場景可以實作相容),如果要從支援IPv4更新到IPv6,無論是應用程式用用戶端、伺服器程式端、路由器等等,都要同時支援IPv6才能解決問題,這個的更新改造需要花費的成本是巨大的。而且,正是由于技術上的更新花費大量的人力物力,無論是營運商還是網際網路服務商,一方面要重視使用者的體驗問題,這個肯定不能強制客戶更新換代硬體裝置和軟體,另一方面也要維護自身的投資和利益,更願意去選擇利用現有技術降低IPv4位址耗盡帶來的壓力,例如NAT的廣泛應用,就是IPv6推廣使用的一個重要的“障礙”。

        由上所述,IPv4更新到IPv6肯定不會是一蹴而就的,是需要經曆一個十分漫長的過渡階段(用我廠通用的術語說,就是IPv4更新IPv6這個灰階的時間非常長),要數十年的時間都不為過。現階段,就出現了IPv4慢慢過渡到IPv6的技術(或者叫過渡時期的技術)。過渡技術要解決最重要的問題就是,如何利用現在大規模的IPv4網絡進行IPv6的通信。

        要解決上面的問題,這裡主要介紹3種過渡技術:

        1、雙棧技術

        2、隧道技術

        3、轉換技術(有一些文獻叫做翻譯技術)

        本章節會對以上的過渡技術,選取幾個典型的、我們未來最有機會接觸到的具體的過渡技術結合實驗觀察過渡技術的具體實作和資料包的表現形式。

雙棧技術

        這種技術其實很好了解,就是通信節點同時支援IPv4和IPv6雙棧。例如在同一個交換機下面有2個Linux的節點,2個節點都是IPv4/IPv6雙棧,節點間原來使用IPv4上的UDP協定通信傳輸,現在需要更新為IPv6上的UDP傳輸。由于2個節點都支援IPv6,那隻要修改應用程式為IPv6的socket通信基本達到目的了。

        上面的例子在區域網路通信的改造是很容易的。但是在廣域網,問題就變得十分複雜了。因為主要問題是在廣域網上的2個節點間往往經過多個路由器,按照雙棧技術的部署要求,之間的所有節點都要支援IPv4/IPv6雙棧,并且都要配置了IPv4的公網IP才能正常工作,這裡就無法解決IPv4公網位址匮乏的問題。是以,雙棧技術一般不會直接部署到網絡中,而是配合其他過渡技術一起使用,例如在隧道技術中,在隧道的邊界路由器就是雙棧的,其他參與通信的節點不要求是雙棧的。

隧道技術

        目前的網絡是以IPv4為主,是以盡可能地充分利用IPv4網絡進行IPv6通信是十分好的手段之一。隧道技術就是這樣子的一種過渡技術。

        隧道将IPv6的資料封包封裝在IPv4的封包頭部後面(IPv6的資料封包是IPv4的載荷部分),IPv6通信節點之間傳輸的IPv6資料包就可以穿越IPv4網絡進行傳輸。隧道技術的一個很重要的優點是透明性,通過隧道進行通信的兩個IPv6節點(或者節點上的應用程式)幾乎感覺不到隧道的存在。

IPv6原理、應用與實踐

圖22 IPv6典型的隧道

        上圖是一種典型的隧道技術:路由器-路由器隧道,兩個IPv6網絡中的主機通過隧道方式穿越了IPv4進行通信。其中C節點和D節點被稱為邊界路由器,邊界路由器必須要支援IPv4-IPv6雙棧。當IPv6網絡1的主機A将IPv6資料包發給邊界路由器C,C對IPv6資料包進行IPv4封裝,然後在IPv4網絡上進行傳輸,發送到邊界路由器D,D收到IPv4的資料包後剝掉IPv4的標頭,還原IPv6的資料包,發送到IPv6網絡2的主機B。

        根據隧道的出口入口的構成,隧道可以分為路由器-路由器,主機-路由器隧道、路由器-主機、主機-主機隧道等類型。

        隧道的類型也分為手動配置類型和自動配置類型兩種,手動配置是指點對點的隧道是手動加以配置,例如手動配置點對點隧道外層的IPv4位址才能建立起隧道;自動配置是指隧道的建立和解除安裝是動态的,一般會把隧道外層的IPv4位址内嵌到資料包的目的IPv6位址裡面,在隧道路由器擷取該IPv6位址時候取出内嵌IPv4位址進而使用該IPv4位址作為隧道的對端來建立隧道。

        下面就介紹幾種我們很可能會接觸到的具體的隧道技術。

         在介紹具體的隧道技術前,特别要說明一下,Linux核心原生支援一種叫做sit(Simple Internet Transition)隧道。這個隧道專門用于IPv6-in-IPv4的資料封裝解封和傳輸,應用十分之廣泛,現在很多主流的IPv6隧道技術都能基于sit隧道實作。關于sit隧道的技術實作,可以查閱Linux核心源碼 net/ipv6/sit.c 。

1、6to4隧道

        6to4是目前使用得比較廣泛的一種自動配置隧道技術,這種技術采用特殊的IPv6位址,稱為6to4位址,這種位址是以2002開頭,接着後面的32位就是内嵌的隧道對端的IPv4位址。當邊界路由器收到這類目的位址,取出IPv4位址建立隧道。

        6to4隧道一般用在路由器-路由器、主機-路由器、路由器-主機場景,典型的應用場景是兩個IPv6的站點内主機通過6to4隧道進行互相通路。

        6to4隧道的一個限制是内嵌的IPv4位址必須是公網位址。

6to4隧道實驗

        如下圖,就是本次6to4實驗中使用的隧道架構,該架構是典型的路由器-路由器隧道,隧道兩側的IPv6網絡對隧道的存在無感覺。

IPv6原理、應用與實踐

圖23 6to4路由器-路由器隧道

        在Linux下的sit隧道可以自适應為6to4隧道。

IPv6原理、應用與實踐

圖24 Linux下配置sit隧道(6to4)

        上圖就是在路由器上配置sit隧道的指令,因為是使用6to4隧道,隧道的目的端點位址是從目的位址中擷取,是以隻需要配置本地端點即可。

IPv6原理、應用與實踐

圖25 浏覽器通過隧道通路web服務

        配置完隧道後,使用用戶端通路web服務,可以正常通路。

IPv6原理、應用與實踐

圖26 web伺服器端抓取http封包

        在web服務端抓取http封包,可以看到,web服務擷取到就是一個普通的http請問封包。

IPv6原理、應用與實踐

圖27 隧道内抓取http封包

        在隧道内抓取http封包,可以看到裡面的乾坤。這個不是一般的http封包,它比服務端抓取到的多了一層IPv4封包頭部,是隧道的外出通信協定,隧道内層IPv6才是真正的資料。IPv4封包頭部中的協定字段,不是我們熟悉的TCP(6)/UDP(17)協定,而是IPv6-in-IPv4專屬的隧道協定類型。

        可以看到,經過隧道的資料封包,在隧道兩端的邊界路由器分别完成了隧道協定的封包和解包,在真正擷取到資料的節點看來,幾乎不感覺隧道的存在。

2、ISATAP隧道

        ISATAP全稱是站點内自動隧道尋址協定(Intra-Site Automatic Tunnel Addressing Protocol),用來為IPv4網絡中的IPv6雙棧節點可以跨越IPv4網絡通路外部的IPv6節點。

        ISATAP隧道一般用于主機-主機、主機-路由器的場景。

ISATAP隧道實驗

        如下圖就是本次實驗使用的架構,是一種典型的主機-路由器場景。實驗中需要在路由器2上部署radvd服務,用于用戶端進行無狀态自動配置位址。Linux下的ISATAP隧道也是可以使用sit隧道實作。

IPv6原理、應用與實踐

圖28 ISATAP主機-路由器隧道

IPv6原理、應用與實踐

圖29 Windows下配置ISATAP隧道

        實驗用的用戶端使用windows 7,原生支援ISATAP隧道,如上圖,需要進入netsh開啟并且設定ISATAP的路由器位址(支援域名)。

IPv6原理、應用與實踐

圖30 ISATAP隧道中的無狀态自動配置

        當用戶端設定完router後,隧道已經建立,用戶端便發起了無狀态自動配置流程,可以看到上面的截圖路由器通過隧道将字首資訊下發給用戶端,用戶端完成無狀态自動配置,擷取到公網IP位址。

IPv6原理、應用與實踐

圖31 ISATAP隧道接口位址

        在windows 7上檢視ISATAP接口,擷取到公網位址。這個位址類型是ISATAP專用的位址結構,由64位全球單點傳播路由字首:200(0):5e5f:w.x.y.z組成(w.x.y.z是用戶端的IPv4位址)。

IPv6原理、應用與實踐

圖32 使用ISATAP隧道通路web服務

        如上圖,使用ISATAP隧道通路web服務,在隧道内的資料抓包,可以看到和6to4的類似,這裡就不再深入闡述。

3、Teredo隧道

        前面的隧道技術,主要是在IPv4的資料封包承載着IPv6的資料封包,這是一種特殊的資料包格式(IPV6-in-IPv4),不同于我們熟悉的TCP、UDP等傳輸層協定。而我們平常接觸到的網絡都存在于NAT架構中(例如我們的辦公網絡和家庭網絡),在這種網絡架構中,路由器僅對于TCP、UDP等傳輸層協定做NAT處理,而無法正确處理IPv6-in-IPv4這種封包,例如使用ISATAP隧道,IPv6雙棧節點與ISATAP路由器之前如果存在NAT,ISATAP建立隧道失敗;6to4隧道也會遇到同樣的問題。

        Teredo隧道是有微軟公司主導的一項隧道技術,主要用于在NAT網絡架構下建立穿越NAT的隧道。

        Teredo隧道的核心思路,是将IPv6的資料封裝成IPv4的UDP資料包,利用NAT對IPv4的UDP支援進行穿越NAT的傳輸,當UDP包到達隧道的另外一端後,再把IPv4的標頭、UDP標頭剝離,還原IPv6的資料包,再進行下一步的IPv6資料通信轉發。Teredo節點會配置設定一個以2001::/32的字首,而且位址中還包含Teredo的伺服器、标志位和用戶端外部映射模糊位址和端口号等資訊。

        Teredo的實作還會遇到NAT的類型不同而被限制的問題。NAT的類型有錐形NAT、受限制的NAT、對稱NAT幾種,Teredo隻能在錐形NAT和受限制的NAT的環境下正常工作,而且在這兩種NAT需要處理的邏輯又是不一樣的。是以Teredo整體的實作會比較複雜。

實驗環境搭建:

        在Linux平台下有開源的Teredo實作版本:miredo。由于時間和文章篇幅的原因,而且部署miredo比較複雜,是以這裡的實驗等以後有機會再補充。:(

轉換技術(有一些文獻叫做:翻譯技術)

        隧道技術是比較好地解決了在很長期一段時間内還是IPv4網絡是主流的情況下IPv6節點(或者雙棧節點)間的通信問題。但是由于IPv4到IPv6的過渡是十分漫長的,是以也需要解決IPv6節點與IPv4節點通信的問題。協定轉換技術可以用來解決這個問題。

        協定轉換技術根據協定在網絡中位置的不同,分為網絡層協定轉換、傳輸層協定轉換和應用層協定轉換等。協定轉換技術的核心思路就是在IPv4和IPv6通信節點之間部署中間層,将IPv4和IPv6互相映射轉換。

        我們非常熟悉的NAT也是一種典型的協定轉換技術,是将私網IPv4位址映射轉換為公網IPv4位址,這種轉換技術又稱為NAT44。而我們接着要重點介紹的名為NAT64/DNS64的協定轉換技術。

NAT64/DNS64

        提到NAT64/DNS64,相信做iOS用戶端開發的同學一定非常熟悉。在2016年中開始,蘋果要求app必須支援IPv6網絡。而蘋果官方提供的過渡解決方案正是NAT64/DNS64。

以下是蘋果提供的技術圖:

IPv6原理、應用與實踐

圖33 蘋果提供的過渡技術解決方案

        NAT64/DNS64分為NAT64、DNS64兩大方面,兩者需要結合使用。

        DNS64在RFC6147中明确定義,将IPv6的位址記錄AAAA DNS查詢消息轉換為IPv4的位址記錄查詢。當IPv6節點發起DNS請求,NAT64/DNS64中間層同時發起A域名查詢和AAAA域名查詢。如果僅有A域名查詢的IPv4位址響應,表明IPv6節點需要通路一個IPv4的節點,NAT64/DNS64中間層将回應的IPv4位址轉換為IPv6位址,傳回給IPv6節點。

        IPv6節點使用擷取到的IPv6服務端位址進行通路,資料包會經過NAT64/DNS64中間層,中間層将IPv6位址映射轉換為IPv4的位址進行通路。

Linux平台下有多個NAT64的開源軟體,實作方式各有不同,有純核心态實作的ecdysis,也有使用者态實作的tayga。

        DNS64的實作可以使用著名的開源DNS服務BIND就可以很好地支援,詳細可以檢視上面2個開源軟體的搭建說明。

        時間的原因,還沒有把NAT64/DNS64的開源軟體研究透徹,是以這裡的實踐等以後有機會再補上。

        PS:在研究tayga和miredo源碼的時候,發現了在Linux平台上面有一些有趣的東西,如下圖,是tayga的軟體實作架構。

IPv6原理、應用與實踐

圖34 Linux下的一個有趣的虛拟裝置

        Linux核心自帶了一個軟體虛拟裝置,也是一種隧道的實作(/dev/net/tun),該裝置可以實作将核心态的網絡資料發送到使用者态,使用者态修改後再傳回給核心态,使用者态的程序負責完成NAT64這一次“偷龍轉鳳”操作。

        關于/dev/net/tun裝置的實作,可以查閱Linux核心源碼drivers/net/tun.c,一些著名的×××軟體例如openvpn等,都是以它作為實作基礎。

        本章隻介紹了一些典型的過渡技術,其實過渡技術種類還有很多,有一些在實驗室階段,有一些已經商用,有一些已經被廢棄,但是總的來說,每一種過渡技術都是在解決特定時期特定場景下的過渡問題。

IPv6 Socket程式設計應該注意的問題

        在《IPv6 Socket程式設計》一文中,ray已經很詳細介紹了IPv6下的socket程式設計細節和應該注意的問題。本章作為一個補充,介紹一下IPv6 socket程式設計可能還會遇到的問題。

1、IPv6位址編碼

        IPv4位址本質是一個32位整數,是以一般無論是存儲層還是邏輯層,都經常将點分制的IPv4字元串位址轉為32位整數使用。而在IPv6,情況就複雜多了(可能也有同學就想到,光是原子性就很難保證了)。

        舉一個典型的例子,現在有個需求,分别統計每個IP的通路頻次。

        在IPv4的情況下,最簡單就是STL用std::map搞定(單線程),土豪一點的可以開個16G的數組用空間換時間。

        但是在IPv6的場景下,那就尴尬了,IPv6可是個128位整數,可以用map嗎?可能會有人直接将原始的字元串類型的IPv6位址作為key來累計。一旦那麼用,就要十分注意了。由于IPv6是支援前導0和連續0的壓縮表示方式,而且支援英文字母大小寫,例如:

        2001:db8:4::41

        2001:db8:4:0:0:0:0:0:41

        2001:0db8:4::41

        2001:DB8:4::41

        這4個都是合法的IPv6位址,如果将輸入毫無修改地作為key來累計,那必須會将累計邏輯分散了,最終得不到正确的頻率結果。類似的問題也在MAC位址(BSSID)上面,由于MAC位址分号間的數字前導0可以省略,并且也是支援大小寫英文字母,是以也是會同樣的問題。在微信安全中心,MAC位址的邏輯統一轉為64位整數處理,情況相對還好。

        但是到了IPv6有木有更好的解決辦法呢?答案是肯定的,但是需要具體問題具體分析。

        在上面的頻率例子比較優雅的做法,依然用map的話,可以利用自定義key類型解決,這個方法需要重載自定義類型的比較符号’<’:

IPv6原理、應用與實踐

圖35 自定義IPv6位址結構

        其中struct in6_addr就是一個128位的IPv6位址結構體。

IPv6原理、應用與實踐

圖36 使用std::map實作IPv6頻率

        其實還有更優雅的方式,直接将IPv6的位址強制轉為2個64位整數來比較,if else會寫得更少一些,效率更高一些。

        上面說到2個64位整數,微信安全中心有一些靜态的key-value資料查詢(批量寫,多次讀),其中key是MD5,我們将MD5也是作為2個64位整數來對待,将2個64位整數聯合排序,寫入記憶體,然後使用兩次二分查找的方式搜尋,效率非常高。在這種場景下面,IPv6也是可以用類似的方法處理。

         IPv6位址結構,以後很可能會給我們的程式設計或多或少帶來一些“未知”的坑-_-||。

2、IPv6 socket“相容”IPv4的情況

        在IPv4和IPv6共存的一個很長的時間裡,在socket程式設計上不得不面對的就是IPv6和IPv4一定程度的“相容問題”。而在文章前面有提到,IPv6和IPv4和完全不相容的兩種協定,但是IPv6協定的位址空間更大,是可以使用IPv6的位址表示IPv4位址,例如IPv4映射位址,是以,在很特殊的情況下,IPv4和IPv6可以實作“相容”,但是這種相容是很有限的。在Linux平台下,這種“相容性”是如何表現的,我們這裡來分析一下。

        在Linux下面,以IPv6下的UDP Socket舉例:

        有個UDP協定的Server改造IPv6,該Server機器上有一個網卡并且同時配置IPv6和IPv4位址,支援雙棧。Server程序建立IPv6 UDP socket套接字,綁定Server本地任意位址(IPv4和IPv6都是以全0位址為綁定任意位址)。用戶端是IPv4,向這個Server發送UDP請求資料包。

IPv6原理、應用與實踐

圖37 IPv6服務收到IPv4封包

        可以看到的是,IPv6的socket會正常收到用戶端的資料封包,并且會将IPv4位址轉化為映射位址,為了明确這個邏輯,我們分析Linux核心的實作。

IPv6原理、應用與實踐

圖38 IPv6下UDP socket收到IPv4資料包核心實作

        IPv6的socket收到資料包,如果是IPv4協定,則将來源IPv4的IP位址轉為IPv6的IPv4映射位址。與實驗的結果很一緻。

        如果Server的IPv6 socket按照這個來源位址傳回資料包,那麼核心又是如何處理的呢?

IPv6原理、應用與實踐

圖39 IPv6下UDP socket發送IPv4資料包核心實作

        首先核心會判斷目的位址是否為IPv6的IPv4映射位址,如果是映射位址,那麼要發送的資料是IPv4資料,直接以IPv4協定棧的形式發送該資料(udp_sendmsg是IPv4 udp發送接口)。

        可以看到,Linux核心本身對這類雙棧上的改造做了一定的适配,我們可以根據核心的這種特性去進行改造工作。

3、使用鍊路本地位址

        從前面的章節可以知道,IPv6具有自動配置位址的能力。鍊路本地位址是IPv6要求在每個接口預設自動配置生成的位址,用于鍊路上的通信,路由器不能轉發鍊路本地位址。除了以上提到的特征外,鍊路本地位址就是一個普通的IPv6位址,我們可以使用這類位址做socket程式設計通信。

        但是我們在IPv6 Socket程式設計的時候使用鍊路本地位址,有一個細節需要注意。

IPv6原理、應用與實踐

圖40 IPv6位址結構

        在IPv6位址結構中(對應于IPv4的struct sockaddr_in),有一個我們非常陌生的字段scope_id,這個字段在我們使用鍊路本地位址來程式設計的時候是必須要使用的,這個字段表示我們需要選擇接口ID。為什麼需要需要有這麼一個字段,那是因為鍊路本地位址的特殊性,一個網絡節點可以有多個網絡接口,多個網絡接口可以有相同的鍊路本地位址,例如我們需要bind一個本地鍊路位址,這個時候就會有沖突,作業系統無法決策需要綁定的是哪個接口的本地鍊路位址。

        又例如,如果我們在直連的2個主機之間直接用鍊路本地位址ping的話,會ping失敗。

        是以IPv6引入了scope_id來解決這個問題,scope_id指定了使用哪個網絡接口。

        如何檢視這個網絡接口(網卡)的scope_id是多少?

一、在Linux下檢視網絡接口的scope_id:

IPv6原理、應用與實踐

圖41 Linux下檢視網絡接口scope id

        使用ip addr指令可以檢視每個接口的scope_id,如圖第一列的數字就是scope_id。

二、在windows下檢視scope_id:

IPv6原理、應用與實踐

圖42 Windows下檢視網路接口scope id

        最後的百分号%後面的數字就是該網絡接口的scope_id。

        Windows下也可以使用route print -6檢視接口清單,清單第一列數字就是scope_id。

        是以,在使用鍊路本地位址程式設計的時候,需要把這個scope_id指派到sin6_scope_id字段。

        而在使用ping指令的時候,需要在位址後面加上%和scope_id才能ping成功,如圖:

IPv6原理、應用與實踐

圖43 使用鍊路本地位址ping

        關于這個scope id,詳細可以檢視RFC2553

總結:

        本文主要科普介紹了IPv6的基本内容,配合各種實驗分析比較清晰認識了IPv6的各種基本概念;也介紹一些“超綱”的内容(我們的工作中很可能不會接觸到),但是我覺得這類内容在技術實作上十分有趣,可以在一些技術的方法和思路上面可能會給我們一些通用的啟示,例如NAT64/DNS64就是使用中間層來處理IPv4和IPv6互通的問題,我們的工作中也确實經常遇到類似的技術問題。

        IPv6本身是一個很龐大的體系,還有很多進階内容沒有介紹(IPv6-IPSec、移動IPv6等等)。而且檢視和IPv6相關的RFC,不斷在做修正,Linux核心的IPv6子產品代碼也不斷有配合新的RFC修改來做調整,引入新的邏輯,以适應各種場景的實際需求。有興趣的同學可以一直留意RFC的變化和緊跟Linux核心的版本釋出。

        本文是我在結合各種文獻和實驗對IPv6了解的一個總結歸納,難免會有了解偏差和手抖的地方,希望各位同學熟悉的話能幫忙指出其中的錯誤,并且提供修改建議和意見,謝謝:)。

參考文獻:

1、《深入解析IPv6(第3版)》

2、《TCP/IP詳解-卷1協定(第2版)》

3、《TCP/IP協定原理與應用(第4版)》

4、《精通Linux核心網絡》

5、RFC4380 “Teredo: Tunneling IPv6 over UDP through Network Address Translations”

6、RFC3596 “DNS Extensions to support IP version 6”

7、RFC4193 “Unique Local IPv6 Unicast Address”

8、RFC3879 “Deprecating Site Local Address”

9、RFC2553 “Basic Socket Interface Extensions for IPv6”

10、RFC4214 “Intra-Site Automatic Tunnel Address Protocol(ISATAP)”

11、RFC6147 “DNS Extensions for Network Address Translation from IPv6 Clients to IPv4 Servers”

12、RFC6052 “IPv6 Addressing of IPv4/IPv6 Translators”

13、RFC6146 “Stateful NAT64: Network Address and Protocol Translation from IPv6 Clients to IPv4 Servers”

14、RFC3068 “An Anycast Prefix for 6to4 Deployment”

15、RFC2460 “Internet Protocol, Version 6 Specification”

16、RFC4291 “IP Version 6 Addressing Architecture”

17、RFC1971 “IPv6 Stateless Address Autoconfiguration”

18、RFC4861 “Neighbor Discovery for IP version6”

19、https://developer.apple.com/library/content/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1

文中使用到的開源軟體清單:

1、Linux-2.6.32.27源碼 (www.kernel.org)

2、Linux-3.10.108源碼 (www.kernel.org)

3、Linux-4.9.75源碼   (www.kernel.org)

4、無狀态自動配置服務radvd (http://www.litech.org/radvd/)

5、NAT64服務tayga(http://www.litech.org/tayga/)

6、NAT64/DNS64服務ecdysis (http://ecdysis.viagenie.ca/)

繼續閱讀