您好,登錄后才能下訂單哦!
以下是手機(jī),解決方法其實(shí)很簡(jiǎn)單,將所有游戲服的ip變?yōu)橛蛎?,即可達(dá)到解析的效果。
果然是蘋(píng)果打個(gè)哈欠,iOS行業(yè)內(nèi)就得起一次風(fēng)暴呀。自從5月初Apple明文規(guī)定所有開(kāi)發(fā)者在6月1號(hào)以后提交新版本需要支持IPV6-Only的網(wǎng)絡(luò),大家便開(kāi)始熱火朝天的研究如何支持IPV6,以及應(yīng)用中哪些模塊目前不支持IPV6。
首先IPV6,是對(duì)IPV4地址空間的擴(kuò)充。目前當(dāng)我們用iOS設(shè)備連接上Wifi、4G、3G等網(wǎng)絡(luò)時(shí),設(shè)備被分配的地址均是IPV4地址,但是隨著運(yùn)營(yíng)商和企業(yè)逐漸部署IPV6 DNS64/NAT64網(wǎng)絡(luò)之后,設(shè)備被分配的地址會(huì)變成IPV6的地址,而這些網(wǎng)絡(luò)就是所謂的IPV6-Only網(wǎng)絡(luò),并且仍然可以通過(guò)此網(wǎng)絡(luò)去獲取IPV4地址提供的內(nèi)容??蛻?hù)端向服務(wù)器端請(qǐng)求域名解析,首先通過(guò)DNS64 Server查詢(xún)IPv6的地址,如果查詢(xún)不到,再向DNS Server查詢(xún)IPv4地址,通過(guò)DNS64 Server合成一個(gè)IPV6的地址,最終將一個(gè)IPV6的地址返回給客戶(hù)端。如圖所示:
NAT64-DNS64-ResolutionOfIPv4_2x.png
在Mac OS 10.11+的雙網(wǎng)卡的Mac機(jī)器(以太網(wǎng)口+無(wú)線(xiàn)網(wǎng)卡),我們可以通過(guò)模擬構(gòu)建這么一個(gè)local IPv6 DNS64/NAT64 的網(wǎng)絡(luò)環(huán)境去測(cè)試應(yīng)用是否支持IPV6-Only網(wǎng)絡(luò),大概原理如下:
local_ipv6_dns64_nat64_network_2x.png
參考資料:
https://developer.apple.com/library/mac/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1
首先第一點(diǎn):這里說(shuō)的支持IPV6-Only網(wǎng)絡(luò),其實(shí)就是說(shuō)讓?xiě)?yīng)用在 IPv6 DNS64/NAT64 網(wǎng)絡(luò)環(huán)境下仍然能夠正常運(yùn)行。但是考慮到我們目前的實(shí)際網(wǎng)絡(luò)環(huán)境仍然是IPV4網(wǎng)絡(luò),所以應(yīng)用需要能夠同時(shí)保證IPV4和IPV6環(huán)境下的可用性。從這點(diǎn)來(lái)說(shuō),蘋(píng)果不會(huì)去掃描IPV4的專(zhuān)有API來(lái)拒絕審核通過(guò),因?yàn)镮PV4的API和IPV6的API調(diào)用都會(huì)同時(shí)存在于代碼中(不過(guò)為了減小審核被拒風(fēng)險(xiǎn),建議將IPV4專(zhuān)有API通過(guò)IPV6的兼容API來(lái)替換)。
其次第二點(diǎn):Apple官方聲明iOS9開(kāi)始向IPV6支持過(guò)渡,在iOS9.2+支持通過(guò)getaddrInfo方法將IPV4地址合成IPV6地址(The ability to synthesize IPv6 addresses was added to getaddrinfo in iOS 9.2 and OS X 10.11.2)。其提供的Reachability庫(kù)在iOS8系統(tǒng)下,當(dāng)從IPV4切換到IPV6網(wǎng)絡(luò),或者從IPV6網(wǎng)絡(luò)切換到IPV4,是無(wú)法監(jiān)控到網(wǎng)絡(luò)狀態(tài)的變化。也有一些開(kāi)發(fā)者針對(duì)這些Bug詢(xún)問(wèn)Apple的審核部門(mén),給予的答復(fù)是只需要在蘋(píng)果最新的系統(tǒng)上保證IPV6的兼容性即可。
最后第三點(diǎn):只要應(yīng)用的主流程支持IPV6,通過(guò)蘋(píng)果審核即可。對(duì)于不支持IPV6的模塊,考慮到我們現(xiàn)實(shí)IPV6網(wǎng)絡(luò)的部署還需要一段時(shí)間,短時(shí)間內(nèi)不會(huì)影響我們用戶(hù)的使用。但隨著4G網(wǎng)絡(luò)IPV6的部署,這部分模塊還是需要逐漸安排人力進(jìn)行支持。
追加第四點(diǎn):如果應(yīng)用一直直接使用IPV4地址通過(guò)NSURLConenction或者NSURLSession進(jìn)行網(wǎng)絡(luò)請(qǐng)求(一般需要服務(wù)器允許,且客戶(hù)端需要在header中偽裝host);經(jīng)測(cè)試,IPV6網(wǎng)絡(luò)環(huán)境下,直接使用IPV4地址在iOS9及以上的系統(tǒng)仍然能夠正常訪(fǎng)問(wèn);在iOS8.4及以下不能正常訪(fǎng)問(wèn);這一點(diǎn)蘋(píng)果的解釋和建議是這樣的:
Note: In iOS 9 and OS X 10.11 and later, NSURLSession and CFNetwork automatically synthesize IPv6 addresses from IPv4 literals locally on devices operating on DNS64/NAT64 networks. However, you should still work to rid your code of IP address literals.
對(duì)于如何支持IPV6-Only,官方給出了如下幾點(diǎn)標(biāo)準(zhǔn):(這里就不對(duì)其進(jìn)行解釋了,大家看上面的參考鏈接即可)
1. Use High-Level Networking Frameworks;2. Don’t Use IP Address Literals;3. Check Source Code for IPv6 DNS64/NAT64 Incompatibilities;4. Use System APIs to Synthesize IPv6 Addresses;
官方的這句話(huà)讓我們疑惑頓生:
using high-level networking APIs such as NSURLSession and the CFNetwork frameworks and you connect by name, you should not need to change anything for your app to work with IPv6 addresses
只說(shuō)了NSURLSession和CFNetwork的API不需要改變,但是并沒(méi)有提及到NSURLConnection。 從上文的參考資料中,我們看到NSURLSession、NSURLConnection同屬于Cocoa的url loading system,可以猜測(cè)出NSURLConnection在ios9上是支持IPV6的。
應(yīng)用里面的API網(wǎng)絡(luò)請(qǐng)求,大家一般都會(huì)選擇AFNetworking進(jìn)行請(qǐng)求發(fā)送,由于歷史原因,應(yīng)用的代碼基本上都深度引用了AFHTTPRequestOperation類(lèi),所以目前API網(wǎng)絡(luò)請(qǐng)求均需要通過(guò)NSURLConnection發(fā)送出去,所以必須確認(rèn)NSURLConnection是否支持IPV6. 經(jīng)過(guò)測(cè)試,NSURLConnection在最新的iOS9系統(tǒng)上是支持IPV6的。
目前我們的應(yīng)用最低版本還需要支持iOS7,雖然蘋(píng)果只要求最新版本支持IPV6-Only,但是出于對(duì)用戶(hù)負(fù)責(zé)的態(tài)度,我們?nèi)匀恍枰闱宄诘桶姹旧蟄RL Loading System的API是否支持IPV6.
(to fix me, make some experiments)待續(xù)~~~
我們可以查到應(yīng)用中大量使用了Reachability進(jìn)行網(wǎng)絡(luò)狀態(tài)判斷,但是在里面卻使用了IPV4的專(zhuān)用API。
在Pods:Reachability中AF_INET Files:Reachability.m struct sockaddr_in Files:Reachability.h , Reachability.m
那Reachability應(yīng)該如何支持IPV6呢?
(1)目前Github的開(kāi)源庫(kù)Reachability的最新版本是3.2,蘋(píng)果也出了一個(gè)Support IPV6 的Reachability的官方樣例,我們比較了一下源碼,跟Github上的Reachability沒(méi)有什么差異。
(2)我們通常都是通過(guò)一個(gè)0.0.0.0 (ZeroAddress)去開(kāi)啟網(wǎng)絡(luò)狀態(tài)監(jiān)控,經(jīng)過(guò)我們測(cè)試,在iOS9以上的系統(tǒng)上IPV4和IPV6網(wǎng)絡(luò)環(huán)境均能夠正常使用;但是在iOS8上IPV4和IPV6相互切換的時(shí)候無(wú)法監(jiān)控到網(wǎng)絡(luò)狀態(tài)的變化,可能是因?yàn)樘O(píng)果在iOS8上還并沒(méi)有對(duì)IPV6進(jìn)行相關(guān)支持相關(guān)。(但是這仍然滿(mǎn)足蘋(píng)果要求在最新系統(tǒng)版本上支持IPV6的網(wǎng)絡(luò))。
(3)當(dāng)大家都在要求Reachability添加對(duì)于IPV6的支持,其實(shí)蘋(píng)果在iOS9以上對(duì)Zero Address進(jìn)行了特別處理,官方發(fā)言是這樣的:
reachabilityForInternetConnection: This monitors the address 0.0.0.0,
which reachability treats as a special token that causes it to actually
monitor the general routing status of the device, both IPv4 and IPv6.
+ (instancetype)reachabilityForInternetConnection { struct sockaddr_in zeroAddress; bzero(&zeroAddress, sizeof(zeroAddress)); zeroAddress.sin_len = sizeof(zeroAddress); zeroAddress.sin_family = AF_INET; return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress]; }
綜上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPV6和IPV4,但是在iOS9以下會(huì)存在bug,但是蘋(píng)果審核并不關(guān)心。
由于在應(yīng)用中使用了網(wǎng)絡(luò)診斷的組件,大量使用了底層的 socket API,所以對(duì)于IPV6支持,這塊是個(gè)重頭戲。如果你的應(yīng)用中使用了長(zhǎng)連接,其必然會(huì)使用底層socket API,這一塊也是需要支持IPV6的。 對(duì)于Socket如何同時(shí)支持IPV4和IPV6,可以參考谷歌的開(kāi)源庫(kù)CocoaAsyncSocket.
下面我針對(duì)我們的開(kāi)源 網(wǎng)絡(luò)診斷組件, 說(shuō)一下是如何同時(shí)支持IPV4和IPV6的。
開(kāi)源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
這個(gè)網(wǎng)絡(luò)診斷組件的主要功能如下:
本地網(wǎng)絡(luò)環(huán)境的監(jiān)測(cè)(本機(jī)IP+本地網(wǎng)關(guān)+本地DNS+域名解析);
通過(guò)TCP Connect監(jiān)測(cè)到域名的連通性;
通過(guò)Ping 監(jiān)測(cè)到目標(biāo)主機(jī)的連通耗時(shí);
通過(guò)traceRoute監(jiān)測(cè)設(shè)備到目標(biāo)主機(jī)中間每一個(gè)路由器節(jié)點(diǎn)的ICMP耗時(shí);
之前我們都是通過(guò)inet_ntoa()進(jìn)行二進(jìn)制到符號(hào),這個(gè)API只能轉(zhuǎn)化IPV4地址。而inet_ntop()能夠兼容轉(zhuǎn)化IPV4和IPV6地址。 寫(xiě)了一個(gè)公用的in6_addr的轉(zhuǎn)化方法如下:
//for IPV6+(NSString *)formatIPV6Address:(struct in6_addr)ipv6Addr{ NSString *address = nil; char dstStr[INET6_ADDRSTRLEN]; char srcStr[INET6_ADDRSTRLEN]; memcpy(srcStr, &ipv6Addr, sizeof(struct in6_addr)); if(inet_ntop(AF_INET6, srcStr, dstStr, INET6_ADDRSTRLEN) != NULL){ address = [NSString stringWithUTF8String:dstStr]; } return address; }//for IPV4+(NSString *)formatIPV4Address:(struct in_addr)ipv4Addr{ NSString *address = nil; char dstStr[INET_ADDRSTRLEN]; char srcStr[INET_ADDRSTRLEN]; memcpy(srcStr, &ipv4Addr, sizeof(struct in_addr)); if(inet_ntop(AF_INET, srcStr, dstStr, INET_ADDRSTRLEN) != NULL){ address = [NSString stringWithUTF8String:dstStr]; } return address; }
相當(dāng)于我們?cè)诮K端中輸入ifconfig命令獲取字符串,然后對(duì)ifconfig結(jié)果字符串進(jìn)行解析,獲取其中en0(Wifi)、pdp_ip0(移動(dòng)網(wǎng)絡(luò))的ip地址。
注意:
(1)在模擬器和真機(jī)上都會(huì)出現(xiàn)以FE80開(kāi)頭的IPV6單播地址影響我們判斷,所以在這里進(jìn)行特殊的處理(當(dāng)?shù)谝淮斡龅讲皇菃尾サ刂返腎P地址即為本機(jī)IP地址)。
(2)在IPV6環(huán)境下,真機(jī)測(cè)試的時(shí)候,第一個(gè)出現(xiàn)的是一個(gè)IPV4地址,所以在IPV4條件下第一次遇到單播地址不退出。
+ (NSString *)deviceIPAdress { while (temp_addr != NULL) { NSLog(@"ifa_name===%@",[NSString stringWithUTF8String:temp_addr->ifa_name]); // Check if interface is en0 which is the wifi connection on the iPhone if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"] || [[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"pdp_ip0"]) { //如果是IPV4地址,直接轉(zhuǎn)化 if (temp_addr->ifa_addr->sa_family == AF_INET){ // Get NSString from C String address = [self formatIPV4Address:((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr]; } //如果是IPV6地址 else if (temp_addr->ifa_addr->sa_family == AF_INET6){ address = [self formatIPV6Address:((struct sockaddr_in6 *)temp_addr->ifa_addr)->sin6_addr]; if (address && ![address isEqualToString:@""] && ![address.uppercaseString hasPrefix:@"FE80"]) break; } } temp_addr = temp_addr->ifa_next; } } }
其實(shí)是在IPV4獲取網(wǎng)關(guān)地址的源碼的基礎(chǔ)上進(jìn)行了修改,初開(kāi)把AF_INET->AF_INET6, sockaddr -> sockaddr_in6之外,還需要注意如下修改,就是拷貝的地址字節(jié)數(shù)。去掉了ROUNDUP的處理。 (解析出來(lái)的地址老是少了4個(gè)字節(jié),結(jié)果是偏移量搞錯(cuò)了,糾結(jié)了半天),具體參考源碼庫(kù)。
/* net.route.0.inet.flags.gateway */ int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY}; if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &l, 0, 0) < 0) { address = @"192.168.0.1"; } .... //for IPV4 for (i = 0; i < RTAX_MAX; i++) { if (rt->rtm_addrs & (1 << i)) { sa_tab[i] = sa; sa = (struct sockaddr *)((char *)sa + ROUNDUP(sa->sa_len)); } else { sa_tab[i] = NULL; } } //for IPV6 for (i = 0; i < RTAX_MAX; i++) { if (rt->rtm_addrs & (1 << i)) { sa_tab[i] = sa; sa = (struct sockaddr_in6 *)((char *)sa + sa->sin6_len); } else { sa_tab[i] = NULL; } }
IPV4時(shí)只需要通過(guò)res_ninit進(jìn)行初始化就可以獲取,但是在IPV6環(huán)境下需要通過(guò)res_getservers()接口才能獲取。
+(NSArray *)outPutDNSServers{ res_state res = malloc(sizeof(struct __res_state)); int result = res_ninit(res); NSMutableArray *servers = [[NSMutableArray alloc] init]; if (result == 0) { union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union)); res_getservers(res, addr_union, res->nscount); for (int i = 0; i < res->nscount; i++) { if (addr_union[i].sin.sin_family == AF_INET) { char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN); NSString *dnsIP = [NSString stringWithUTF8String:ip]; [servers addObject:dnsIP]; NSLog(@"IPv4 DNS IP: %@", dnsIP); } else if (addr_union[i].sin6.sin6_family == AF_INET6) { char ip[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN); NSString *dnsIP = [NSString stringWithUTF8String:ip]; [servers addObject:dnsIP]; NSLog(@"IPv6 DNS IP: %@", dnsIP); } else { NSLog(@"Undefined family."); } } } res_nclose(res); free(res); return [NSArray arrayWithArray:servers]; }
在IPV4網(wǎng)絡(luò)下我們通過(guò)gethostname獲取,而在IPV6環(huán)境下,通過(guò)新的gethostbyname2函數(shù)獲取。
//ipv4 phot = gethostbyname(hostN);//ipv6 phot = gethostbyname2(hostN, AF_INET6);
Apple的官方提供了最新的支持IPV6的ping方案,參考地址如下:
https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html
只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPV6的header部分也不返回TTL(Time to Live)字段;
(2)IPV6的ICMP報(bào)文不進(jìn)行checkSum的處理;
其實(shí)是通過(guò)創(chuàng)建socket套接字模擬ICMP報(bào)文的發(fā)送,以計(jì)算耗時(shí);
兩個(gè)關(guān)鍵的地方需要注意:
(1)IPV6中去掉IP_TTL字段,改用跳數(shù)IPV6_UNICAST_HOPS來(lái)表示;
(2)sendto方法可以兼容支持IPV4和IPV6,但是需要最后一個(gè)參數(shù),制定目標(biāo)IP地址的大??;因?yàn)榍耙粋€(gè)參數(shù)只是指明了IP地址的開(kāi)始地址。千萬(wàn)不要用統(tǒng)一的sizeof(struct sockaddr), 因?yàn)閟ockaddr_in 和 sockaddr都是16個(gè)字節(jié),兩者可以通用,但是sockaddr_in6的數(shù)據(jù)結(jié)構(gòu)是28個(gè)字節(jié),如果不顯式指定,sendto方法就會(huì)一直返回-1,erroNo報(bào)22 Invalid argument的錯(cuò)誤。
關(guān)鍵代碼如下:(完整代碼參考開(kāi)源組件)
//構(gòu)造通用的IP地址結(jié)構(gòu)stuck sockaddr NSString *ipAddr0 = [serverDNSs objectAtIndex:0]; //設(shè)置server主機(jī)的套接口地址 NSData *addrData = nil; BOOL isIPV6 = NO; if ([ipAddr0 rangeOfString:@":"].location == NSNotFound) { isIPV6 = NO; struct sockaddr_in nativeAddr4; memset(&nativeAddr4, 0, sizeof(nativeAddr4)); nativeAddr4.sin_len = sizeof(nativeAddr4); nativeAddr4.sin_family = AF_INET; nativeAddr4.sin_port = htons(udpPort); inet_pton(AF_INET, ipAddr0.UTF8String, &nativeAddr4.sin_addr.s_addr); addrData = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; } else { isIPV6 = YES; struct sockaddr_in6 nativeAddr6; memset(&nativeAddr6, 0, sizeof(nativeAddr6)); nativeAddr6.sin6_len = sizeof(nativeAddr6); nativeAddr6.sin6_family = AF_INET6; nativeAddr6.sin6_port = htons(udpPort); inet_pton(AF_INET6, ipAddr0.UTF8String, &nativeAddr6.sin6_addr); addrData = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; } struct sockaddr *destination; destination = (struct sockaddr *)[addrData bytes];//創(chuàng)建socketif ((recv_sock = socket(destination->sa_family, SOCK_DGRAM, isIPV6?IPPROTO_ICMPV6:IPPROTO_ICMP)) < 0)if ((send_sock = socket(destination->sa_family, SOCK_DGRAM, 0)) < 0)//設(shè)置sender 套接字的ttlif ((isIPV6? setsockopt(send_sock,IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)): setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))) < 0)//發(fā)送成功返回值等于發(fā)送消息的長(zhǎng)度ssize_t sentLen = sendto(send_sock, cmsg, sizeof(cmsg), 0, (struct sockaddr *)destination, isIPV6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in));
文/philon(簡(jiǎn)書(shū)作者)
原文鏈接:http://www.jianshu.com/p/a6bab07c4062#rd
著作權(quán)歸作者所有,轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),并標(biāo)注“簡(jiǎn)書(shū)作者”。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。