網(wǎng)絡(luò)編程之IPv4與IPv6相互操作
由于互聯(lián)網(wǎng)終端不斷增加,IPv4 地址長(zhǎng)度(32位)已不能夠滿足要求,所以出現(xiàn)了 IPv6地址(128位),但是現(xiàn)有應(yīng)用程序大部分還是采用 IPv4 地址形式,所以必須解決 IPv4 與 IPv6 之間的相互操作,使現(xiàn)有基于 IPv4 的應(yīng)用程序能夠與基于 IPv6 的應(yīng)用程序相互通信。那么我們?cè)趺磳?shí)現(xiàn) IPv4 客戶端與 IPv6 服務(wù)器、IPv6 客戶端與 IPv4 服務(wù)器之間的通信。
IPv4 客戶端與 IPv6 服務(wù)器
假設(shè)我們主機(jī)是運(yùn)行雙棧,即存在 IPv4 協(xié)議棧和 IPv6 協(xié)議棧,雙棧主機(jī)上的 IPv6 服務(wù)器既能處理 IPv4 客戶端,也能處理 IPv6 客戶端,因?yàn)?IPv4 可以映射成 IPv6 地址。下圖是 IPv4 客戶端與 IPv6 服務(wù)器之間的通信過程:
IPv6 服務(wù)器程序創(chuàng)建的套接字綁定到 IPv6 通配地址和 TCP 端口號(hào) 9999。假設(shè)客戶端和服務(wù)器主機(jī)都處于同一個(gè)以太網(wǎng),當(dāng)左側(cè)兩個(gè)客戶端都發(fā)送 SYN 報(bào)文段請(qǐng)求與服務(wù)器建立連接時(shí),IPv4 客戶端主機(jī)在一個(gè) IPv4 數(shù)據(jù)報(bào)中載送 SYN,IPv6 客戶端主機(jī)在一個(gè) IPv6 數(shù)據(jù)報(bào)中載送 SYN。在以太網(wǎng)線上包含以太網(wǎng)首部、IP 首部、TCP 首部以及 TCP 數(shù)據(jù),根據(jù)以太網(wǎng)首部中包含的類型字段區(qū)分 IP 類型是為 IPv4 還是 IPv6,因此 IP 首部中的目的 IP 地址格式根據(jù)以太網(wǎng)類型字段分為 IPv4 地址和 IPv6 地址。兩者的 TCP 首部是一樣的,TCP 首部中包含目的端口號(hào)(即 IPv6 服務(wù)器的端口號(hào) 9999)。
服務(wù)器的接收數(shù)據(jù)鏈路通過查看以太網(wǎng)類型字段把每幀傳遞給相應(yīng)的 IP 模塊。IPv4 模塊結(jié)合其上的 TCP 模塊檢測(cè)到 IPv4 數(shù)據(jù)報(bào)的目的端口對(duì)應(yīng)的是一個(gè) IPv6 套接字,于是把該數(shù)據(jù)報(bào) IPv4 首部中的源 IPv4 地址轉(zhuǎn)換成一個(gè)等價(jià)的 IPv4 映射的 IPv6 地址。當(dāng) accept 系統(tǒng)調(diào)用把這個(gè)已經(jīng)接受的 IPv4 客戶端連接返回給服務(wù)器進(jìn)程時(shí),這個(gè)映射后的地址將作為客戶的 IPv6 地址返回給服務(wù)器的 IPv6 套接字(也就是說服務(wù)器根本不知道自己是在跟 IPv4 客戶端通信,客戶端也不知道自己和 IPv6 的服務(wù)器通信),該連接上其余的數(shù)據(jù)報(bào)都是 IPv4 數(shù)據(jù)報(bào)。對(duì)于 IPv6 客戶端,當(dāng) accept 系統(tǒng)調(diào)用把接受的 IPv6 客戶端連接返回給服務(wù)器進(jìn)程時(shí),該客戶的 IPv6 地址就是原來 IPv6 首部中的源地址,不需要進(jìn)行映射,該連接上其余的數(shù)據(jù)報(bào)都是 IPv6 數(shù)據(jù)報(bào)。
IPv4 的 TCP 客戶端與 IPv6 的 TCP 服務(wù)器之間通信的步驟如下:
首先啟動(dòng) IPv6 服務(wù)器,創(chuàng)建一個(gè) IPv6 的監(jiān)聽套接字,并且該服務(wù)器把通配地址和端口號(hào) 9999 綁定到該套接字上;
IPv4 客戶端調(diào)用 gethostbyname 函數(shù)找到服務(wù)器主機(jī)的一個(gè) A 記錄,服務(wù)器同時(shí)包含 A 記錄和 AAAA 記錄,即同時(shí)支持 IPv4 和 IPv6,對(duì)于 IPv4 客戶端來說只需要 A 記錄即可;
IPv4 客戶端調(diào)用 connect 函數(shù)向服務(wù)器發(fā)出連接請(qǐng)求,即客戶端主機(jī)向服務(wù)器主機(jī)發(fā)送一個(gè) IPv4 的 SYN 數(shù)據(jù)報(bào)(該 IPv4 的 SYN 中的目的地是 IPv6 套接字);
服務(wù)器主機(jī)接收到來自客戶端的 IPv4 的 SYN 數(shù)據(jù)報(bào)后,設(shè)置一個(gè)標(biāo)志指示本連接應(yīng)使用 IPv4 映射的 IPv6 地址,并響應(yīng)一個(gè) IPv4 的SYN 和 ACK 數(shù)據(jù)報(bào)。當(dāng)該鏈接建立后,由 accept 函數(shù)把這個(gè) IPv4 映射的 IPv6 地址返回給服務(wù)器;
當(dāng)服務(wù)器主機(jī)往這個(gè) IPv4 映射的 IPv6 地址發(fā)送 TCP 報(bào)文段時(shí),其 IP 棧產(chǎn)生目的地址為所映射 IPv4 地址的 IPv4 載送數(shù)據(jù)報(bào)。即客戶端和服務(wù)器之間所有通信都使用 IPv4 的載送數(shù)據(jù)報(bào);
IPv6 客戶端與 IPv4 服務(wù)器
IPv6 的 TCP 客戶端與 IPv4 的 TCP 服務(wù)器之間通信的步驟如下:
首先啟動(dòng) IPv4 服務(wù)器,創(chuàng)建一個(gè) IPv4 的監(jiān)聽套接字;
IPv6 客戶端調(diào)用 getaddrinfo 函數(shù)查找 IPv6 地址;
IPv6 客戶端在作為函數(shù)參數(shù)的 IPv6 套接字地址結(jié)構(gòu)中設(shè)置這個(gè) IPv4 映射的 IPv6 地址后調(diào)用 connect 函數(shù)向服務(wù)器發(fā)出連接請(qǐng)求,內(nèi)核檢測(cè)到這個(gè)映射地址后,自動(dòng)向服務(wù)器主機(jī)發(fā)送一個(gè) IPv4 的 SYN 數(shù)據(jù)報(bào);
服務(wù)器主機(jī)接收到來自客戶端的 IPv4 的 SYN 數(shù)據(jù)報(bào)后,響應(yīng)一個(gè) IPv4 的SYN 和 ACK 數(shù)據(jù)報(bào)。連接通過使用 IPv4 數(shù)據(jù)報(bào)建立;
總結(jié)
雙棧主機(jī)上的 IPv6 服務(wù)器既能服務(wù)于 IPv4 客戶,又能服務(wù)于 IPv6 客戶。IPv4 客戶發(fā)送給這種服務(wù)器的仍然是 IPv4 數(shù)據(jù)報(bào),不過服務(wù)器的協(xié)議棧會(huì)把客戶主機(jī)的地址轉(zhuǎn)換成一個(gè) IPv4 映射的 IPv6 地址。類似地,雙棧主機(jī)上的 IPv6 客戶能夠與 IPv4 服務(wù)器通信,客戶的解析器會(huì)把服務(wù)器主機(jī)所有的 A 記錄作為 IPv4 映射的 IPv6 地址返回給客戶,而客戶指定這些地址之一調(diào)用 connect 將會(huì)使雙棧發(fā)送一個(gè) IPv4 的 SYN 數(shù)據(jù)報(bào)。為了使套接字編程具有可移植性,在編程實(shí)現(xiàn)過程中,盡量避免使用 gethostbyname 和 gethostbyaddr 函數(shù),而應(yīng)該使用 getaddrinfo 和 getnameinfo 函數(shù)。