一、背景
? ? WWDC2015蘋果宣布在iOS9支持純IPv6的網絡服務,并且要求2016年提交到AppStore的應用必須兼容純IPv6的網絡,要求適配的系統版本是iOS9以上(包括iOS9),否則會有過審被拒的可能,貼別是子2016年6月1日起,國內陸陸續續出現了大量App無法通過IPv6審核;
二、簡單結論
由于目前大部分服務器(包括阿里云)都沒有Internet類型的IPv6地址,所以我們現在遇到的問題主要是App在IPv6-only網絡下去訪問IPv4-only網絡的服務端,如果服務器需要兼容IPv6需要做很多軟件和硬件上的全面升級。
幸好,從一開始設計IPv6就考慮到了向后兼容的問題,運營商會提供一個中間節點,使用DNS64/NAT64等技術,負責協議的轉換和地址的轉換,打通IPv6和IPv4之間的鏈路。這樣我們在IPv6的環境下也是可以訪問IPv4的后臺資源的,我們的后臺也就暫時不需要做什么變動。
對于使用域名方式訪問服務端的情況,App
Client端只需將網絡層通信接口改造成兼容IPv6即可,App Client在IPv6-Only網絡下會按照兩種方式進行域名解析,如果域名配置了AAAA記錄,則直接返回配置的IPv6地址,如果只有A記錄,則DNS64會合成IPv6地址后返回給App Client,大部分非分區類型手機游戲和非游戲類App適合此場景。
對于使用IP方式訪問服務端的情況,App
Client端既需要將網絡層通信接口改造成兼容IPv6,又需要實現IPv4和IPv6間的地址轉換(App Client從服務器拉取的地址列表一般是IPv4類型的,連接服務端時需要用IPv6類型的IP,服務端返回包時不需要轉換,因為NAT64服務已經自動做了轉換),對于分區或分服類手機游戲適合此場景。
兼容IPv6的網絡接口可以直接使用蘋果官方提供的版本,逐一在App Client端代碼中做替換即可。
可以參考如下文檔,示例了如果在App
Client中進行代碼修改以適應這個問題。
http://www.atatech.org/articles/54871
因為上述鏈接文檔是從代碼和協議層面對原理進行深入剖析,可能不太適合一些部署類的用戶,因此接下來將從相對概要的層面對這個問題進行分析。
三、方案分析
3.1 IPV6概述
A、什么是IPV6
IPv6是Internet
Protocol Version 6的縮寫,簡單的概括IPv6就是現行的互聯網協議(IPv4)的下一代IP協議。IPv6由128位二進制數組成,可提供龐大的IP地址資源,足以讓地球上每個生物乃至每厘米都能分配到一個或多個IP地址。將這128位的地址按每16位劃分為一個段,將每個段轉換成十六進制數字,并用冒號隔開。
??? IPv4地址示例:192.168.1.1
??? IPv6地址示例:2001:0db8:85a3:08d3:1319:8a2e:0370:7344
B、為什么要接入IPv6
??? 目前互聯網廣泛應用的IPv4技術,理論上IPv4是一個32位的二進制數的地址,可編址1600萬個網絡、40億臺主機。但在采用了A、B、C三類編址方式后,可用的網絡地址和主機地址數目大打折扣,歐美國家掌握著核心技術,且互聯網發展較早,因此擁有約3/4的IP資源。造成我國及其他發展中國家的IP地址資源不足的困局,隨著中國互聯網用戶的不斷增加和電子、網絡技術的蓬勃發展,缺乏IP地址資源,將嚴重制約我國及其他發展中國家互聯網的應用和發展。
C、IPv6發展現狀
??? 由于從IPv4網絡完全過渡到IPv6網絡需要全球互聯網基礎設施中的網絡軟件和網絡硬件設備以及終端設備都支持IPv6協議,這會涉及到大量的改造工作,雖然得到各國政府和各大運營商的重視和推動,但是IPv4和IPv6仍將長期共存。
IP
D、IPV6兼容性問題
??? 現在我們大部分服務器都是使用IPv4接入互聯網的,我們要如何做兼容呢?也就是說如何做到IPv6和IPv4的兼容和相互訪問?
??? 要想使應用完全支持IPv6的環境,從協議到硬件,要做比較徹底的調整。不但客戶端要做IPv6的改造,服務器也要適配IPv6,主要有以下四種對應關系,必須做好以下每一種:
??? IPv4->IPv4
??? IPv4->IPv6
??? IPv6->IPv4
??? IPv6->IPv6
要做到IPv6和IPv4完全兼容需要做很大的修改,最簡單的協議上要兼容128位的IP地址,路由器,服務器等相關硬件也要升級。應蘋果公司的要求,我們重點關注客戶端從IPv6的網絡環境訪問IPv4的服務資源。
IPv6轉換機制有很多種,蘋果期望iOS
App能夠兼容DNS64/NAT64的方式。
(a) socket api支持RFC 4038—ApplicationAspects of IPv6 Transition
? v4 socket接口只能支持IPv4 stack
? v6 socket能支持IPv4 stack和IPv6 stack
(b) 服務器IP
? 返回v4 IP
? 返回v6 IP
(c) 用戶本地IP stack
? IPv4-only
? IPv6-only
? IPv4-IPv6 Dual stack
(d) 各種IPv6轉換機制
NAT64/DNS64
64:ff9b::/96用于v6的本地網絡通過NAT訪問v4的資源:RFC6146、RFC6147。
6to4 2002::/16用于兩個擁有v4公網地址的IPv6-only子網的互相訪問:RFC6343。
Teredo tunneling
2001::/32 通過隧道的方式讓兩個IPv6-only子網互相訪問,沒有NAT問題:RFC4380。
464XLAT用于程序只有v4地址(使用v4
socket),但是本地網絡是IPv6網絡,程序需要訪問v4資源,類似NAT64,不過區別在于服務器是運營商提供,手機上需要安裝CLAT服務:RFC6877。
還有很多兼容方案,復雜程度都很高。
??? 幸好,從一開始設計IPv6就考慮到了向后兼容的問題,運營商會提供一個中間節點,使用DNS64/NAT64等技術,負責協議的轉換,打通IPv6和IPv4之間的鏈路。(IPv6和IPv4互通技術有很多,這里只討論Apple要求的技術方案DNS64/NAT64)。
3.2不同IP stack組合的處理方式
A、v4 ip + IPv4-only or IPv4-IPv6 Dual stack
??? 在這樣的情況下,我們雖然用的是v6的socket,但是必須要讓socket走的是v4的協議。
??? ::ffff:0:0/96 — This prefix is designatedas anIPv4-mapped IPv6 address. With a few exceptions, this addresstype allows the transparent use of theTransport
Layer?protocols
over IPv4 through the IPv6 networking?application
programming interface. Server applications only need to open a single
listening?socket?to handle
connections from clients using IPv6 or IPv4 protocols. IPv6 clients will be
handled natively by default, and IPv4 clients appear as IPv6 clients at their
IPv4-mapped IPv6 address. Transmission is handled similarly; established
sockets may be used to transmit IPv4 or IPv6 datagram, based on the binding to
an IPv6 address, or an IPv4-mapped address. (See also?Transition
mechanisms.)
從上文可以看到如果服務器地址為128.0.0.128,我們轉換成IPv4-mapped
IPv6 address::ffff:128.0.0.128或者純16進制::ffff:ff00:00ff,然后賦值給sockaddr_in6.sin6_addr=”::ffff:128.0.0.128”;。這個socket雖然用了IPv6的sockaddr_in6,但實際上走的是IPv4
stack。
IPv4-mapped IPv6
address是讓用戶能夠使用一致的socket api來訪問IPv4和IPv6網絡。
B、v4 ip + IPv6-only
這里我們先看看Wikipedia對NAT64/DNS64的描述:
??? NAT64is a mechanism to allow IPv6 hosts to communicatewith IPv4 servers. The NAT64 server is the endpoint for at least one IPv4address and an IPv6 network segment of 32-bits, e.g.,64:ff9b::/96(RFC 6052,?RFC
6146). The
IPv6 client embeds the IPv4 address with which it wishes to communicate using
these bits, and sends its packets to the resulting address. The NAT64 server
then creates a?NAT-mapping between the IPv6
and the IPv4 address, allowing them to communicate.
??? DNS64?describes a?DNS server?that
when asked for a domain's?AAAA records,
but only finds?A records,
synthesizes the AAAA records from the A records. The first part of the
synthesized IPv6 address points to an IPv6/IPv4 translator and the second part
embeds the IPv4 address from the A record. The translator in question is
usually a NAT64 server. The standard-track specification of DNS64 is in?RFC 6147.[10]
??? There are two noticeable issues with thistransition mechanism:
[if !supportLists]l?? [endif]It only works for cases where DNS is usedto find the remote host address, if IPv4 literals are used the DNS64 serverwill never be involved.
[if !supportLists]l?? [endif]Because the DNS64 server needs to return
records not specified by the domain owner,?DNSSEC?validation against the?rootwill fail in cases where the DNS server doing thetranslation is not the domain owner's server.
NAT64是一種有狀態的網絡地址與協議轉換技術,一般只支持通過IPv6網絡側用戶發起連接訪問IPv4側網絡資源。但NAT64也支持通過手工配置靜態映射關系,實現IPv4網絡主動發起連接訪問IPv6網絡。NAT64可實現TCP、UDP、ICMP協議下的IPv6與IPv4網絡地址和協議轉換。
DNS64則主要是配合NAT64工作,主要是將DNS查詢信息中的A記錄(IPv4地址)合成到AAAA記錄(IPv6地址)中,返回合成的AAAA記錄給IPv6側用戶。NAT64一般與DNS64協同工作,而不需要在IPv6客戶端或IPv4服務器端做任何修改。
這里大概描述一下NAT64的工作流程,首先局域網內有一個NAT64的路由設備并且有DNS64的服務。
a、客戶端進行getaddrinfo的域名解析
b、DNS返回結果,如果返回的IP里面只有v4地址,并且當前網絡是IPv6-only網路,DNS64服務器會把v4地址加上64:ff9b::/96的前綴,例如64:ff9b::14.17.32.211。如果當前網絡是IPv4-only或IPv4-IPv6,DNS64不會做任何事情。
c、客戶端拿到IPv6地址進行connect。
d、路由器發現地址的前綴為64:ff9b::/96,知道這個是NAT64的映射,是需要訪問14.17.32.211。這個時候需要進行NAT64映射,因為到外網需要轉換成IPv4 stack。
e、當數據返回的時候,按照NAT映射,IPv4回包重新加上前綴64:ff9b::/96,然后返回給客戶端。
Apple的文檔里面也有很詳細的描述:
//NAT64 address sample
//address init
const char* ipv6_str = “64:ff9b::14.17.32.211”;
in6_addr ipv6_addr = {0};
int v6_r = inet_pton(AF_INET6, ipv6_str, &ipv6_addr);
sockaddr_in6 v6_addr = {0};
v6_addr.sin6_family = AF_INET6;
v6_addr.sin6_port = htons(80);
v6_addr.sin6_addr = ipv6_addr;
//socket connect
int v6_sock = socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP);
std::string v6_error;
if (0 != connect(v6_sock, (sockaddr*)&v6_addr, 28))
{
????? v6_error =strerror(errno);
}
//get local ip
sockaddr_in6 v6_local_addr = {0};
socklen_t v6_local_addr_len = 28;
char v6_str_local_addr[64] = {0};
getpeername(v6_sock, (sockaddr*)&v6_local_addr,&v6_local_addr_len);
inet_ntop(v6_local_addr.sin_family, &v6_local_addr.sin6_addr,v6_str_local_addr, 64);
close(v6_sock);
舉個例子:
1、IPv6主機發起www.abc.com的AAAA域名解析到DNS64(主機配置的DNS地址是DNS64);
2、DNS64觸發AAAA到DNS AAAA中查詢;
3、DNS AAAA返回NULL的信息到DNS64;
4、 DNS64然后觸發A的申請到DNS A中查詢;
5、DNS A返回www.abc.com的A記錄(1.1.1.1);
6、 DNS64合成IPv6地址(64:ff9b:1.1.1.1),返回AAAA response給IPv6主機;
7、IPv6主機發起目的地址為64:ff9b:1.1.1.1的IPv6數據包,由于NAT64在IPv6域內通告配置的IPv6Prefix,因此這個數據包轉發到NAT64設備上;
8、NAT64執行地址轉換和協議轉換,目的地址轉換為192.0.2.1,源地址根據地址狀態轉換(64:ff9b:1.1.1.1,1500)>(1.1.1.1,2000)在IPv4域內路由到IPv4 Server;
9、 數據包返回,目的地址和端口為1.1.1.1,2000;
10、 NAT64根據已有記錄進行轉換,目的地址轉換為2001:db8::1,源地址為加了IPv6前綴的IPv4Server地址64:ff9b:1.1.1.1,發送到IPv6主機;
這里討論比較坑的地方,按照NAT64的規則,客戶端如果沒有做DNS域名解析的話,客戶端就需要完成DNS64的工作。這里的關鍵點是,發現網絡是IPv6-only的NAT64網絡的情況下,我們可以自己補充上前綴64:ff9b::/96,然后進行正常的訪問。
注:AAAA記錄(AAAA
Record)是用來將域名解析到IPv6地址的DNS記錄,用戶可以將一個域名解析到IPv6地址上,也可以將子域名解析到IPv6地址上。
C、v6 ip + IPv4-only
這里一般connect的時候會返回錯誤碼network
is unreachable,因為根本沒有v6的協議棧,就像沒有硬件設備一樣,但是不排除會有系統會返回no route to host。當然,如果服務器的地址是Teredo tunneling 2001::/32,可以客戶端直接做隧道。如果是6to4
2002::/16,并且客戶端有RAW socket權限加上非NAT網絡,這種情況下可以客戶端自己做6to4的路由。
D、v6 ip + IPv6-only or IPv4-IPv6
這里只要沒有配置上,是可以直接通訊的。當然這里會涉及到一個問題,如果DNS返回上文說的6to4或Teredo tunneling或pure native IPv6 address,這樣的情況下我們怎么做IP的選擇呢,可以參照RFC
3484 – Default Address Selection for Internet Protocol version 6(IPv6)。
[if !supportLists]三、??[endif]對開發同學的建議
4.1 不建議使用底層的網絡API
下圖展示的藍色部分的這些API都是不存在兼容性問題的,而我們平時自己用的包括那些第三方的網絡庫大部分都是用的這些API:
大部分情況下,我們用高級的API完全能夠實現我們的需求,而且高級API封裝得很便于使用,很多底層的像適配IPv6的工作都已經完成,而用底層API會有大量的工作要我們自己來做,更容易產生BUG,如果確實需要用底層的POSIX Socket API,請參照RFC 4038:Application Aspects of IPv6 Transition的指導。
4.2 不要用IP地址
SCNetworkReachabilityCreateWithName
比如這個API的nodename參數不要傳IP地址,而應該是域名;
4.3使用合適的數據容器
[if !vml]
[endif]
代碼中以上對應類型都要處理;
4.4檢查不兼容IPv6 DNS64/NAT64的代碼
?????????[if !vml]
[endif]
?????????這些API都是只針對IPv4做處理的,換用兼容IPv4及IPv6的API。
?????????判斷當前客戶端是處于IPv4-only、IPv6-only還是IPv4和IPv6并存的環境,然后分別使用不同的網絡API。
4.5使用系統API去合成IPv6地址
4.6使用socket及connect進行的聯網操作換用Apple提供的API
換用CoreFoundationframework及以上Apple提供的API,這樣,即使我們填的是IPv4的地址,系統會在不同的網絡環境自動幫我們進行地址的轉換,我們不需要額外的工作(例如CFStreamCreatePairWithSocketToHost、NSURLSession)。
四、 協議棧判別方法
5.1 判斷客戶端可用的IP stack
??? 客戶端做不同的處理的前提是需要知道客戶端可用的IP協議棧。可用的IP stack類型分別是IPv4-only、IPv6-only、IPv4-IPv6 Dual stack。
我們先定義客戶端可用的IP協議棧的含義:獲取客戶端當前能使用的IP協議棧。例如iOS在NAT64 Wi-Fi連接上的情況下,Mobile的網雖然存在IPv4的協議,但是系統是不允許使用的。iOS只能使用Wi-Fi的協議棧,在NAT64 Wi-Fi的情況下就是IPv6-only網絡了。如果遇到IPv6-only網絡,需要把它當作NAT64來處理,在v4 IP前添加前綴64:ff9b::/64。但是NAT64和IPv6-only不是等價的。IPv6-only網絡可能支持NAT64,能訪問v4的互聯網資源,但是IPv6-only能訪問v6的互聯網資源,不支持NAT64。這里假設IPv6-only的網絡都是支持NAT64的,對v4 IP進行64:ff9b::/96的處理。
5.2 DNS方案
??? 這里的方案是直接做DNS解析,然后判斷返回的IP有沒有帶上64:ff9b前綴來確定當前的IP協議棧。這也是唯一能夠判斷IPv6-only網絡是否支持NAT64的方案。
//gateway
in6_addr addr6_gateway = {0};
if (0 !=
getdefaultgateway6(&addr6_gateway))
?????? returnEIPv4;
if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway))
?????? returnEIPv4;
in_addr addr_gateway = {0};
if (0 !=
getdefaultgateway(&addr_gateway))
return EIPv6;
if (INADDR_NONE ==
addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr)
?????? returnEIPv6;
//getaddrinfo
struct addrinfo hints, *res, *res0;
memset(*hints, 0, sizeof(hints));
hints.ai_family
= PF_INET6;
六參考文獻
(1)wikipedia Transition from IPv4?:leftwards_arrow_with_hook:
(2) wikipedia NAT64?:leftwards_arrow_with_hook:
(3) wikipedia DNS64?:leftwards_arrow_with_hook:
(4)wikipedia IPv6 address?:leftwards_arrow_with_hook:
(5)https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html
IPv6審核互助群:112019540(群文件共享部分資料)