IPv6兼容解決方案
一 背景
WWDC2015蘋果宣布在iOS9支持純IPv6的網絡服務,並且要求2016年提交到AppStore的應用必須兼容純IPv6的網絡,要求適配的係統版本是iOS9以上(包括iOS9),否則會有過審被拒的可能。
到目前為止多個阿裏雲客戶都已提前谘詢阿裏雲是否支持IPv6,多個阿裏雲客戶的App送審後審核不通過。在什麼場景下會有問題?是否需要阿裏雲公網地址及相關服務立即支持IPV6?有什麼過渡方式?這就是本文檔需要回答的問題。
。
IPv6地址示例:2001:0db8:85a3:08d3:1319:8a2e:0370:7344
現在我們大部分服務器都是使用IPv4接入互聯網的,我們要如何做兼容呢?也就是說如何做到IPv6和IPv4的兼容和相互訪問?
要想使應用完全支持IPv6的環境,從協議到硬件,要做比較徹底的調整。不但客戶端要做IPv6的改造,服務器也要適配IPv6,主要有以下四種對應關係,必須做好以下每一種:
(a) socket api支持RFC 4038—Application Aspects of IPv6 Transition
v4 socket接口隻能支持IPv4 stack
v6 socket能支持IPv4 stack和IPv6 stack
(b) 服務器IP
返回v4 IP
返回v6 IP
IPv4-only
IPv6-only
IPv4-IPv6 Dual stack
464XLAT用於程序隻有v4地址(使用v4 socket),但是本地網絡是IPv6網絡,程序需要訪問v4資源,類似NAT64,不過區別在於服務器是運營商提供,手機上需要安裝CLAT服務:RFC6877。
這樣我們在IPv6的環境下也是可以訪問IPv4的後台資源的,我們的後台也就暫時不需要做什麼變動。
::ffff:0:0/96 — This prefix is designated as an IPv4-mapped IPv6 address. With a few exceptions, this address type 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網絡。
這裏我們先看看Wikipedia對NAT64/DNS64的描述:
NAT64 is a mechanism to allow IPv6 hosts to communicate with IPv4 servers. The NAT64 server is the endpoint for at least one IPv4 address 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 this transition mechanism:
- It only works for cases where DNS is used to find the
remote host address, if IPv4 literals are used the DNS64 server will never be
involved.
- Because the DNS64 server needs to return records not specified by the domain owner, DNSSEC validation against the root will fail in cases where the DNS server doing the translation is not the domain owner's server.
NAT64是一種有狀態的網絡地址與協議轉換技術,一般隻支持通過IPv6網絡側用戶發起連接訪問IPv4側網絡資源。但NAT64也支持通過手工配置靜態映射關係,實現IPv4網絡主動發起連接訪問IPv6網絡。NAT64可實現TCP、UDP、ICMP協議下的IPv6與IPv4網絡地址和協議轉換。
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,然後返回給客戶端。
//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);
- IPv6主機發起www.abc.com的AAAA域名解析到DNS64(主機配置的DNS地址是DNS64);
- DNS64觸發AAAA到DNS AAAA中查詢;
- DNS AAAA返回NULL的信息到DNS64;
- DNS64然後觸發A的申請到DNS A中查詢;
- DNS A返回www.abc.com的A記錄(1.1.1.1);
- DNS64合成IPv6地址(64:ff9b:1.1.1.1),返回AAAA
response給IPv6主機;
- IPv6主機發起目的地址為64:ff9b:1.1.1.1的IPv6數據包,由於NAT64在IPv6域內通告配置的IPv6Prefix,因此這個數據包轉發到NAT64設備上;
- NAT64執行地址轉換和協議轉換,目的地址轉換為192.0.2.1,源地址根據地址狀態轉換(64:ff9b:1.1.1.1,1500)>(1.1.1.1,2000)在IPv4域內路由到IPv4 Server;
- 數據包返回,目的地址和端口為1.1.1.1,2000;
- NAT64根據已有記錄進行轉換,目的地址轉換為2001:db8::1,源地址為加了IPv6前綴的IPv4Server地址64:ff9b:1.1.1.1,發送到IPv6主機。
注:AAAA記錄(AAAA Record)是用來將域名解析到IPv6地址的DNS記錄,用戶可以將一個域名解析到IPv6地址上,也可以將子域名解析到IPv6地址上。
這裏一般connect的時候會返回錯誤碼network is unreachable,因為根本沒有v6的協議棧,就像沒有硬件設備一樣,但是不排除會有係統會返回no route to host。當然,如果服務器的地址是Teredo tunneling 2001::/32,可以客戶端直接做隧道。如果是6to4 2002::/16,並且客戶端有RAW socket權限加上非NAT網絡,這種情況下可以客戶端自己做6to4的路由。
iOS設備需要連接Mac機創建的NAT64/DNS64的Wi-Fi,就是傳說中的IPv6的網絡環境,再通過有線網絡、路由器,訪問到IPv4的資源,就可以做到IPv6->IPv4的連接。
需要一台用非Wi-Fi方式上網的Mac電腦(10.11以上的係統)、iOS9以上(包含iOS9)設備和互聯網有線接口。目的是用Mac作一個熱點,然後用iPhone連接這個Wi-Fi。我們產生的是一個本地的IPv6 DNS64/NAT64網絡,這項功能是OS X 10.11新加的。
a 在“係統偏好設置”界麵選中“共享”的同時,要按住“option”鍵。
b 不要鬆開“option”鍵,選擇“Internet Sharing”;
c 鬆開“option”鍵,選擇“Create NAT64 Network”選框;
d 選擇提供網絡連接的網絡接口設備;
e 選擇“Wi-Fi”選框;
f 點擊“Wi-Fi Options”並配置網絡名和安全選項;
g 選擇“Internet Sharing”選框以生效本地網絡;
h 點擊“Start”開始網絡共享;
現在可以用iPhone連接上這個剛創建好的熱點開始測試了,注意此時要把iPhone設成飛行模式,以保證隻用Wi-Fi上網。
測試重點:
a IPv4和IPv6網絡環境判斷是否正確
b UDP和TCP的切換是否正確。
四 對開發同學的建議
SCNetworkReachabilityCreateWithName
代碼中以上對應類型都要處理。

這些API都是隻針對IPv4做處理的,換用兼容IPv4及IPv6的API。
判斷當前客戶端是處於IPv4-only、IPv6-only還是IPv4和IPv6並存的環境,然後分別使用不同的網絡API。
客戶端做不同的處理的前提是需要知道客戶端可用的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的處理。
//gateway
in6_addr addr6_gateway = {0};
if (0 != getdefaultgateway6(&addr6_gateway))
return EIPv4;
if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway))
return EIPv4;
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)
return EIPv6;
//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
最後更新:2017-11-11 23:35:10