一、背景

? ? WWDC2015蘋(píng)果宣布在iOS9支持純IPv6的網(wǎng)絡(luò)服務(wù),并且要求2016年提交到AppStore的應(yīng)用必須兼容純IPv6的網(wǎng)絡(luò),要求適配的系統(tǒng)版本是iOS9以上(包括iOS9),否則會(huì)有過(guò)審被拒的可能,貼別是子2016年6月1日起,國(guó)內(nèi)陸陸續(xù)續(xù)出現(xiàn)了大量App無(wú)法通過(guò)IPv6審核;
二、簡(jiǎn)單結(jié)論
由于目前大部分服務(wù)器(包括阿里云)都沒(méi)有Internet類(lèi)型的IPv6地址,所以我們現(xiàn)在遇到的問(wèn)題主要是App在IPv6-only網(wǎng)絡(luò)下去訪問(wèn)IPv4-only網(wǎng)絡(luò)的服務(wù)端,如果服務(wù)器需要兼容IPv6需要做很多軟件和硬件上的全面升級(jí)。
幸好,從一開(kāi)始設(shè)計(jì)IPv6就考慮到了向后兼容的問(wèn)題,運(yùn)營(yíng)商會(huì)提供一個(gè)中間節(jié)點(diǎn),使用DNS64/NAT64等技術(shù),負(fù)責(zé)協(xié)議的轉(zhuǎn)換和地址的轉(zhuǎn)換,打通IPv6和IPv4之間的鏈路。這樣我們?cè)贗Pv6的環(huán)境下也是可以訪問(wèn)IPv4的后臺(tái)資源的,我們的后臺(tái)也就暫時(shí)不需要做什么變動(dòng)。
對(duì)于使用域名方式訪問(wèn)服務(wù)端的情況,App
Client端只需將網(wǎng)絡(luò)層通信接口改造成兼容IPv6即可,App Client在IPv6-Only網(wǎng)絡(luò)下會(huì)按照兩種方式進(jìn)行域名解析,如果域名配置了AAAA記錄,則直接返回配置的IPv6地址,如果只有A記錄,則DNS64會(huì)合成IPv6地址后返回給App Client,大部分非分區(qū)類(lèi)型手機(jī)游戲和非游戲類(lèi)App適合此場(chǎng)景。
對(duì)于使用IP方式訪問(wèn)服務(wù)端的情況,App
Client端既需要將網(wǎng)絡(luò)層通信接口改造成兼容IPv6,又需要實(shí)現(xiàn)IPv4和IPv6間的地址轉(zhuǎn)換(App Client從服務(wù)器拉取的地址列表一般是IPv4類(lèi)型的,連接服務(wù)端時(shí)需要用IPv6類(lèi)型的IP,服務(wù)端返回包時(shí)不需要轉(zhuǎn)換,因?yàn)镹AT64服務(wù)已經(jīng)自動(dòng)做了轉(zhuǎn)換),對(duì)于分區(qū)或分服類(lèi)手機(jī)游戲適合此場(chǎng)景。
兼容IPv6的網(wǎng)絡(luò)接口可以直接使用蘋(píng)果官方提供的版本,逐一在App Client端代碼中做替換即可。
可以參考如下文檔,示例了如果在App
Client中進(jìn)行代碼修改以適應(yīng)這個(gè)問(wèn)題。
http://www.atatech.org/articles/54871
因?yàn)樯鲜鲦溄游臋n是從代碼和協(xié)議層面對(duì)原理進(jìn)行深入剖析,可能不太適合一些部署類(lèi)的用戶(hù),因此接下來(lái)將從相對(duì)概要的層面對(duì)這個(gè)問(wèn)題進(jìn)行分析。
三、方案分析
3.1 IPV6概述
A、什么是IPV6
IPv6是Internet
Protocol Version 6的縮寫(xiě),簡(jiǎn)單的概括IPv6就是現(xiàn)行的互聯(lián)網(wǎng)協(xié)議(IPv4)的下一代IP協(xié)議。IPv6由128位二進(jìn)制數(shù)組成,可提供龐大的IP地址資源,足以讓地球上每個(gè)生物乃至每厘米都能分配到一個(gè)或多個(gè)IP地址。將這128位的地址按每16位劃分為一個(gè)段,將每個(gè)段轉(zhuǎn)換成十六進(jìn)制數(shù)字,并用冒號(hào)隔開(kāi)。
??? IPv4地址示例:192.168.1.1
??? IPv6地址示例:2001:0db8:85a3:08d3:1319:8a2e:0370:7344
B、為什么要接入IPv6
??? 目前互聯(lián)網(wǎng)廣泛應(yīng)用的IPv4技術(shù),理論上IPv4是一個(gè)32位的二進(jìn)制數(shù)的地址,可編址1600萬(wàn)個(gè)網(wǎng)絡(luò)、40億臺(tái)主機(jī)。但在采用了A、B、C三類(lèi)編址方式后,可用的網(wǎng)絡(luò)地址和主機(jī)地址數(shù)目大打折扣,歐美國(guó)家掌握著核心技術(shù),且互聯(lián)網(wǎng)發(fā)展較早,因此擁有約3/4的IP資源。造成我國(guó)及其他發(fā)展中國(guó)家的IP地址資源不足的困局,隨著中國(guó)互聯(lián)網(wǎng)用戶(hù)的不斷增加和電子、網(wǎng)絡(luò)技術(shù)的蓬勃發(fā)展,缺乏IP地址資源,將嚴(yán)重制約我國(guó)及其他發(fā)展中國(guó)家互聯(lián)網(wǎng)的應(yīng)用和發(fā)展。
C、IPv6發(fā)展現(xiàn)狀
??? 由于從IPv4網(wǎng)絡(luò)完全過(guò)渡到IPv6網(wǎng)絡(luò)需要全球互聯(lián)網(wǎng)基礎(chǔ)設(shè)施中的網(wǎng)絡(luò)軟件和網(wǎng)絡(luò)硬件設(shè)備以及終端設(shè)備都支持IPv6協(xié)議,這會(huì)涉及到大量的改造工作,雖然得到各國(guó)政府和各大運(yùn)營(yíng)商的重視和推動(dòng),但是IPv4和IPv6仍將長(zhǎng)期共存。
IP
D、IPV6兼容性問(wèn)題
??? 現(xiàn)在我們大部分服務(wù)器都是使用IPv4接入互聯(lián)網(wǎng)的,我們要如何做兼容呢?也就是說(shuō)如何做到IPv6和IPv4的兼容和相互訪問(wèn)?
??? 要想使應(yīng)用完全支持IPv6的環(huán)境,從協(xié)議到硬件,要做比較徹底的調(diào)整。不但客戶(hù)端要做IPv6的改造,服務(wù)器也要適配IPv6,主要有以下四種對(duì)應(yīng)關(guān)系,必須做好以下每一種:
??? IPv4->IPv4
??? IPv4->IPv6
??? IPv6->IPv4
??? IPv6->IPv6
要做到IPv6和IPv4完全兼容需要做很大的修改,最簡(jiǎn)單的協(xié)議上要兼容128位的IP地址,路由器,服務(wù)器等相關(guān)硬件也要升級(jí)。應(yīng)蘋(píng)果公司的要求,我們重點(diǎn)關(guān)注客戶(hù)端從IPv6的網(wǎng)絡(luò)環(huán)境訪問(wèn)IPv4的服務(wù)資源。
IPv6轉(zhuǎn)換機(jī)制有很多種,蘋(píng)果期望iOS
App能夠兼容DNS64/NAT64的方式。
(a) socket api支持RFC 4038—ApplicationAspects of IPv6 Transition
? v4 socket接口只能支持IPv4 stack
? v6 socket能支持IPv4 stack和IPv6 stack
(b) 服務(wù)器IP
? 返回v4 IP
? 返回v6 IP
(c) 用戶(hù)本地IP stack
? IPv4-only
? IPv6-only
? IPv4-IPv6 Dual stack
(d) 各種IPv6轉(zhuǎn)換機(jī)制
NAT64/DNS64
64:ff9b::/96用于v6的本地網(wǎng)絡(luò)通過(guò)NAT訪問(wèn)v4的資源:RFC6146、RFC6147。
6to4 2002::/16用于兩個(gè)擁有v4公網(wǎng)地址的IPv6-only子網(wǎng)的互相訪問(wèn):RFC6343。
Teredo tunneling
2001::/32 通過(guò)隧道的方式讓兩個(gè)IPv6-only子網(wǎng)互相訪問(wèn),沒(méi)有NAT問(wèn)題:RFC4380。
464XLAT用于程序只有v4地址(使用v4
socket),但是本地網(wǎng)絡(luò)是IPv6網(wǎng)絡(luò),程序需要訪問(wèn)v4資源,類(lèi)似NAT64,不過(guò)區(qū)別在于服務(wù)器是運(yùn)營(yíng)商提供,手機(jī)上需要安裝CLAT服務(wù):RFC6877。
還有很多兼容方案,復(fù)雜程度都很高。
??? 幸好,從一開(kāi)始設(shè)計(jì)IPv6就考慮到了向后兼容的問(wèn)題,運(yùn)營(yíng)商會(huì)提供一個(gè)中間節(jié)點(diǎn),使用DNS64/NAT64等技術(shù),負(fù)責(zé)協(xié)議的轉(zhuǎn)換,打通IPv6和IPv4之間的鏈路。(IPv6和IPv4互通技術(shù)有很多,這里只討論Apple要求的技術(shù)方案DNS64/NAT64)。

3.2不同IP stack組合的處理方式
A、v4 ip + IPv4-only or IPv4-IPv6 Dual stack
??? 在這樣的情況下,我們雖然用的是v6的socket,但是必須要讓socket走的是v4的協(xié)議。
??? ::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.)
從上文可以看到如果服務(wù)器地址為128.0.0.128,我們轉(zhuǎn)換成IPv4-mapped
IPv6 address::ffff:128.0.0.128或者純16進(jìn)制::ffff:ff00:00ff,然后賦值給sockaddr_in6.sin6_addr=”::ffff:128.0.0.128”;。這個(gè)socket雖然用了IPv6的sockaddr_in6,但實(shí)際上走的是IPv4
stack。
IPv4-mapped IPv6
address是讓用戶(hù)能夠使用一致的socket api來(lái)訪問(wèn)IPv4和IPv6網(wǎng)絡(luò)。
B、v4 ip + IPv6-only
這里我們先看看Wikipedia對(duì)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是一種有狀態(tài)的網(wǎng)絡(luò)地址與協(xié)議轉(zhuǎn)換技術(shù),一般只支持通過(guò)IPv6網(wǎng)絡(luò)側(cè)用戶(hù)發(fā)起連接訪問(wèn)IPv4側(cè)網(wǎng)絡(luò)資源。但NAT64也支持通過(guò)手工配置靜態(tài)映射關(guān)系,實(shí)現(xiàn)IPv4網(wǎng)絡(luò)主動(dòng)發(fā)起連接訪問(wèn)IPv6網(wǎng)絡(luò)。NAT64可實(shí)現(xiàn)TCP、UDP、ICMP協(xié)議下的IPv6與IPv4網(wǎng)絡(luò)地址和協(xié)議轉(zhuǎn)換。
DNS64則主要是配合NAT64工作,主要是將DNS查詢(xún)信息中的A記錄(IPv4地址)合成到AAAA記錄(IPv6地址)中,返回合成的AAAA記錄給IPv6側(cè)用戶(hù)。NAT64一般與DNS64協(xié)同工作,而不需要在IPv6客戶(hù)端或IPv4服務(wù)器端做任何修改。
這里大概描述一下NAT64的工作流程,首先局域網(wǎng)內(nèi)有一個(gè)NAT64的路由設(shè)備并且有DNS64的服務(wù)。
a、客戶(hù)端進(jìn)行g(shù)etaddrinfo的域名解析
b、DNS返回結(jié)果,如果返回的IP里面只有v4地址,并且當(dāng)前網(wǎng)絡(luò)是IPv6-only網(wǎng)路,DNS64服務(wù)器會(huì)把v4地址加上64:ff9b::/96的前綴,例如64:ff9b::14.17.32.211。如果當(dāng)前網(wǎng)絡(luò)是IPv4-only或IPv4-IPv6,DNS64不會(huì)做任何事情。
c、客戶(hù)端拿到IPv6地址進(jìn)行connect。
d、路由器發(fā)現(xiàn)地址的前綴為64:ff9b::/96,知道這個(gè)是NAT64的映射,是需要訪問(wèn)14.17.32.211。這個(gè)時(shí)候需要進(jìn)行NAT64映射,因?yàn)榈酵饩W(wǎng)需要轉(zhuǎn)換成IPv4 stack。
e、當(dāng)數(shù)據(jù)返回的時(shí)候,按照NAT映射,IPv4回包重新加上前綴64:ff9b::/96,然后返回給客戶(hù)端。
Apple的文檔里面也有很詳細(xì)的描述:


//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);
舉個(gè)例子:
1、IPv6主機(jī)發(fā)起www.abc.com的AAAA域名解析到DNS64(主機(jī)配置的DNS地址是DNS64);
2、DNS64觸發(fā)AAAA到DNS AAAA中查詢(xún);
3、DNS AAAA返回NULL的信息到DNS64;
4、 DNS64然后觸發(fā)A的申請(qǐng)到DNS A中查詢(xún);
5、DNS A返回www.abc.com的A記錄(1.1.1.1);
6、 DNS64合成IPv6地址(64:ff9b:1.1.1.1),返回AAAA response給IPv6主機(jī);
7、IPv6主機(jī)發(fā)起目的地址為64:ff9b:1.1.1.1的IPv6數(shù)據(jù)包,由于NAT64在IPv6域內(nèi)通告配置的IPv6Prefix,因此這個(gè)數(shù)據(jù)包轉(zhuǎn)發(fā)到NAT64設(shè)備上;
8、NAT64執(zhí)行地址轉(zhuǎn)換和協(xié)議轉(zhuǎn)換,目的地址轉(zhuǎn)換為192.0.2.1,源地址根據(jù)地址狀態(tài)轉(zhuǎn)換(64:ff9b:1.1.1.1,1500)>(1.1.1.1,2000)在IPv4域內(nèi)路由到IPv4 Server;
9、 數(shù)據(jù)包返回,目的地址和端口為1.1.1.1,2000;
10、 NAT64根據(jù)已有記錄進(jìn)行轉(zhuǎn)換,目的地址轉(zhuǎn)換為2001:db8::1,源地址為加了IPv6前綴的IPv4Server地址64:ff9b:1.1.1.1,發(fā)送到IPv6主機(jī);
這里討論比較坑的地方,按照NAT64的規(guī)則,客戶(hù)端如果沒(méi)有做DNS域名解析的話,客戶(hù)端就需要完成DNS64的工作。這里的關(guān)鍵點(diǎn)是,發(fā)現(xiàn)網(wǎng)絡(luò)是IPv6-only的NAT64網(wǎng)絡(luò)的情況下,我們可以自己補(bǔ)充上前綴64:ff9b::/96,然后進(jìn)行正常的訪問(wèn)。
注:AAAA記錄(AAAA
Record)是用來(lái)將域名解析到IPv6地址的DNS記錄,用戶(hù)可以將一個(gè)域名解析到IPv6地址上,也可以將子域名解析到IPv6地址上。
C、v6 ip + IPv4-only
這里一般connect的時(shí)候會(huì)返回錯(cuò)誤碼network
is unreachable,因?yàn)楦緵](méi)有v6的協(xié)議棧,就像沒(méi)有硬件設(shè)備一樣,但是不排除會(huì)有系統(tǒng)會(huì)返回no route to host。當(dāng)然,如果服務(wù)器的地址是Teredo tunneling 2001::/32,可以客戶(hù)端直接做隧道。如果是6to4
2002::/16,并且客戶(hù)端有RAW socket權(quán)限加上非NAT網(wǎng)絡(luò),這種情況下可以客戶(hù)端自己做6to4的路由。
D、v6 ip + IPv6-only or IPv4-IPv6
這里只要沒(méi)有配置上,是可以直接通訊的。當(dāng)然這里會(huì)涉及到一個(gè)問(wèn)題,如果DNS返回上文說(shuō)的6to4或Teredo tunneling或pure native IPv6 address,這樣的情況下我們?cè)趺醋鯥P的選擇呢,可以參照RFC
3484 – Default Address Selection for Internet Protocol version 6(IPv6)。
[if !supportLists]三、??[endif]對(duì)開(kāi)發(fā)同學(xué)的建議
4.1 不建議使用底層的網(wǎng)絡(luò)API
下圖展示的藍(lán)色部分的這些API都是不存在兼容性問(wèn)題的,而我們平時(shí)自己用的包括那些第三方的網(wǎng)絡(luò)庫(kù)大部分都是用的這些API:
大部分情況下,我們用高級(jí)的API完全能夠?qū)崿F(xiàn)我們的需求,而且高級(jí)API封裝得很便于使用,很多底層的像適配IPv6的工作都已經(jīng)完成,而用底層API會(huì)有大量的工作要我們自己來(lái)做,更容易產(chǎn)生BUG,如果確實(shí)需要用底層的POSIX Socket API,請(qǐng)參照RFC 4038:Application Aspects of IPv6 Transition的指導(dǎo)。
4.2 不要用IP地址
SCNetworkReachabilityCreateWithName
比如這個(gè)API的nodename參數(shù)不要傳IP地址,而應(yīng)該是域名;
4.3使用合適的數(shù)據(jù)容器
[if !vml]
[endif]
代碼中以上對(duì)應(yīng)類(lèi)型都要處理;
4.4檢查不兼容IPv6 DNS64/NAT64的代碼
?????????[if !vml]
[endif]
?????????這些API都是只針對(duì)IPv4做處理的,換用兼容IPv4及IPv6的API。
?????????判斷當(dāng)前客戶(hù)端是處于IPv4-only、IPv6-only還是IPv4和IPv6并存的環(huán)境,然后分別使用不同的網(wǎng)絡(luò)API。
4.5使用系統(tǒng)API去合成IPv6地址
4.6使用socket及connect進(jìn)行的聯(lián)網(wǎng)操作換用Apple提供的API
換用CoreFoundationframework及以上Apple提供的API,這樣,即使我們填的是IPv4的地址,系統(tǒng)會(huì)在不同的網(wǎng)絡(luò)環(huán)境自動(dòng)幫我們進(jìn)行地址的轉(zhuǎn)換,我們不需要額外的工作(例如CFStreamCreatePairWithSocketToHost、NSURLSession)。
四、 協(xié)議棧判別方法
5.1 判斷客戶(hù)端可用的IP stack
??? 客戶(hù)端做不同的處理的前提是需要知道客戶(hù)端可用的IP協(xié)議棧??捎玫腎P stack類(lèi)型分別是IPv4-only、IPv6-only、IPv4-IPv6 Dual stack。
我們先定義客戶(hù)端可用的IP協(xié)議棧的含義:獲取客戶(hù)端當(dāng)前能使用的IP協(xié)議棧。例如iOS在NAT64 Wi-Fi連接上的情況下,Mobile的網(wǎng)雖然存在IPv4的協(xié)議,但是系統(tǒng)是不允許使用的。iOS只能使用Wi-Fi的協(xié)議棧,在NAT64 Wi-Fi的情況下就是IPv6-only網(wǎng)絡(luò)了。如果遇到IPv6-only網(wǎng)絡(luò),需要把它當(dāng)作NAT64來(lái)處理,在v4 IP前添加前綴64:ff9b::/64。但是NAT64和IPv6-only不是等價(jià)的。IPv6-only網(wǎng)絡(luò)可能支持NAT64,能訪問(wèn)v4的互聯(lián)網(wǎng)資源,但是IPv6-only能訪問(wèn)v6的互聯(lián)網(wǎng)資源,不支持NAT64。這里假設(shè)IPv6-only的網(wǎng)絡(luò)都是支持NAT64的,對(duì)v4 IP進(jìn)行64:ff9b::/96的處理。
5.2 DNS方案
??? 這里的方案是直接做DNS解析,然后判斷返回的IP有沒(méi)有帶上64:ff9b前綴來(lái)確定當(dāng)前的IP協(xié)議棧。這也是唯一能夠判斷IPv6-only網(wǎng)絡(luò)是否支持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;
六參考文獻(xiàn)
(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(群文件共享部分資料)