您的位置:首页 > 其它

针对苹果最新审核要求为应用兼容IPv6-备用

2016-05-23 15:05 302 查看
在WWDC2015上苹果宣布iOS9将支持纯IPv6的网络服务。2016年初开始所有提交到AppStore的应用必须支持IPv6。为确保现有的应用是兼容的,我们需要注意下面几点。不建议使用底层的网络API下图展示的蓝色部分的这些API都是不存在兼容性问题的,而我们平时自己用的包括那些第三方的网络库大部分都是用的这些API。NetworkingframeworksandAPIlayers大部分情况下,我们用高级的API完全能够实现我们的需求,而且高级API封装的很便于使用,很多底层的像适配IPv6的工作都已经帮我们做好了。而用底层API会有大量的工作要我们自己来做,更容易产生bug。但你如果确实需要用底层的POSIXsocketAPI,请参照这个RFC4038:ApplicationAspectsofIPv6Transition的指导。不要用IP地址比如下面这个API,nodename这个参数不要传IP地址,而应该用域名SCNetworkReachabilityCreateWithName这个方法在著名的Reachability中是用到的,我们常用的网络库AFNetworking就用了这个。所以用到的同学得好好查一下了,另外这个项目的作者几天前刚刚就这个问题有一个新的提交,不过最新的release版本中还没有加进去,可以点下面链接先去看看他都改了哪些地方。AddedsupportforIPv6toReachability#3174
https://github.com/AFNetworking/AFNetworking/pull/3174/files
检查不兼容IPv6的代码搜一下工程里有没有下面的这些API,这些都是只针对IPv4做处理的,有的话就删了。inet_addr()inet_aton()inet_lnaof()inet_makeaddr()inet_netof()inet_network()inet_ntoa()inet_ntoa_r()bindresvport()getipv4sourcefilter()setipv4sourcefilter()如果用到了下面左边的这些IPv4的类型,那么它们相应的IPv6类型也需要做处理IPv4-IPv6本地搭建IPv6测试环境最后我们来搭一个IPv6的测试环境吧,你所需要的就是一台用非Wi-Fi的方式上网的Mac电脑。我们的要做的其实就是用Mac做一个热点,然后用iPhone连接这个Wi-Fi,听起来很容易,我相信大家在公司就是这么干的吧。区别是这次我们产生的是一个本地的IPv6DNS64/NAT64网络,这项功能是OSX10.11新加的。和我们以前开启热点方式不一样的地方在于,我们在“SystemPreferences”界面选中“Sharing”的同时,要按住“Option”键。SystemPreferences之后在“Sharing”界面中,我们会看到和之前不一样的地方,就是红框所标的地方,多了一个叫“CreateNAT64Network”的选框,选中它。Sharing之后就是按照正常的创建热点的流程走完就行了。现在我们用iPhone连接上这个刚创建好的热点就可以测试了,注意此时要把iPhone设成飞行模式,以保证只用Wi-Fi上网。说法三:最近很多人都在关注支持IPv6的事情吧?我们公司也是。也有不少同行使用了我们的YTKNetwork网络库,问我们什么时候迁移到AFNetworking3.0。正在这个时候,我发现了本文。在本文中,作者经过测试发现,NSURLConnection是支持IPv6的,因此基于NSURLConnection的AFNetworking2.x也同样应该是支持IPv6,所以大家不用担心。只要网络请求是基于AFNetworking的,都不用做什么额外的事情。我向本文的作者philon申请了全文的转载授权,分享给大家。作者介绍:philon,iOS高级开发工程师,现在网易乐得彩票项目组负责技术研发工作~以下是文章正文,本文的所有打赏归philon所有。果然是苹果打个哈欠,iOS行业内就得起一次风暴呀。自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPv6-Only的网络,大家便开始热火朝天的研究如何支持IPv6,以及应用中哪些模块目前不支持IPv6。

一、IPv6-Only支持是啥?

首先IPv6,是对IPv4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPv4地址,但是随着运营商和企业逐渐部署IPv6DNS64/NAT64网络之后,设备被分配的地址会变成IPv6的地址,而这些网络就是所谓的IPv6-Only网络,并且仍然可以通过此网络去获取IPv4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64Server查询IPv6的地址,如果查询不到,再向DNSServer查询IPv4地址,通过DNS64Server合成一个IPv6的地址,最终将一个IPv6的地址返回给客户端。如图所示:在MacOS10.11+的双网卡的Mac机器(以太网口+无线网卡),我们可以通过模拟构建这么一个localIPv6DNS64/NAT64的网络环境去测试应用是否支持IPv6-Only网络,大概原理如下:参考资料:
https://developer.apple.com/library/mac/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html#//apple_ref/doc/uid/TP40010220-CH213-SW1

二、Apple如何审核支持IPv6-Only?

首先第一点:这里说的支持IPv6-Only网络,其实就是说让应用在IPv6DNS64/NAT64网络环境下仍然能够正常运行。但是考虑到我们目前的实际网络环境仍然是IPv4网络,所以应用需要能够同时保证IPv4和IPv6环境下的可用性。从这点来说,苹果不会去扫描IPv4的专有API来拒绝审核通过,因为IPv4的API和IPv6的API调用都会同时存在于代码中。其次第二点:Apple官方声明iOS9开始向IPv6支持过渡,在iOS9.2+支持IPv4地址合成IPv6地址。其提供的Reachability库在iOS8系统下,当从IPv4切换到IPv6网络,或者从IPv6网络切换到IPv4,是无法监控到网络状态的变化。也有一些开发者针对这些Bug询问Apple的审核部门,给予的答复是只需要在苹果最新的系统上保证IPv6的兼容性即可最后第三点:只要应用的主流程支持IPv6,通过苹果审核即可。对于不支持IPv6的模块,考虑到我们现实IPv6网络的部署还需要一段时间,短时间内不会影响我们用户的使用。但随着4G网络IPv6的部署,这部分模块还是需要逐渐安排人力进行支持。

三、应用如何支持IPv6-Only?

对于如何支持IPv6-Only,官方给出了如下几点标准:(这里就不对其进行解释了,大家看上面的参考链接即可)
1.UseHigh-LevelNetworkingFrameworks;
2.Don’tUseIPAddressLiterals;
3.CheckSourceCodeforIPv6DNS64/NAT64Incompatibilities;
4.UseSystemAPIstoSynthesizeIPv6Addresses;

3.1NSURLConnection是否支持IPv6?

官方的这句话让我们疑惑顿生:usinghigh-levelnetworkingAPIssuchasNSURLSessionandtheCFNetworkframeworksandyouconnectbyname,youshouldnotneedtochangeanythingforyourapptoworkwithIPv6addresses只说了NSURLSession和CFNetwork的API不需要改变,但是并没有提及到NSURLConnection。从上文的参考资料中,我们看到NSURLSession、NSURLConnection同属于Cocoa的urlloadingsystem,可以猜测出NSURLConnection在iOS9上是支持IPv6的。应用里面的API网络请求,大家一般都会选择AFNetworking进行请求发送,由于历史原因,应用的代码基本上都深度引用了AFHTTPRequestOperation类,所以目前API网络请求均需要通过NSURLConnection发送出去,所以必须确认NSURLConnection是否支持IPv6.经过测试,NSURLConnection在最新的iOS9系统上是支持IPv6的。

3.2Cocoa的URLLoadingSystem从iOS哪个版本开始支持IPv6?

目前我们的应用最低版本还需要支持iOS7,虽然苹果只要求最新版本支持IPv6-Only,但是出于对用户负责的态度,我们仍然需要搞清楚在低版本上URLLoadingSystem的API是否支持IPv6.(tofixme,makesomeexperiments)待续~~~

3.3Reachability是否需要修改支持IPv6?

我们可以查到应用中大量使用了Reachability进行网络状态判断,但是在里面却使用了IPv4的专用API。
在Pods:Reachability中
AF_INETFiles:Reachability.m
structsockaddr_inFiles:Reachability.h,Reachability.m

那Reachability应该如何支持IPv6呢?(1)目前Github的开源库Reachability的最新版本是3.2,苹果也出了一个SupportIPv6的Reachability的官方样例,我们比较了一下源码,跟Github上的Reachability没有什么差异。(2)我们通常都是通过一个0.0.0.0(ZeroAddress)去开启网络状态监控,经过我们测试,在iOS9以上的系统上IPv4和IPv6网络环境均能够正常使用;但是在iOS8上IPv4和IPv6相互切换的时候无法监控到网络状态的变化,可能是因为苹果在iOS8上还并没有对IPv6进行相关支持相关。(但是这仍然满足苹果要求在最新系统版本上支持IPv6的网络)。(3)当大家都在要求Reachability添加对于IPv6的支持,其实苹果在iOS9以上对ZeroAddress进行了特别处理,官方发言是这样的:reachabilityForInternetConnection:Thismonitorstheaddress0.0.0.0,
whichreachabilitytreatsasaspecialtokenthatcausesittoactually
monitorthegeneralroutingstatusofthedevice,bothIPv4andIPv6.
+(instancetype)reachabilityForInternetConnection{
structsockaddr_inzeroAddress;
bzero(&zeroAddress,sizeof(zeroAddress));
zeroAddress.sin_len=sizeof(zeroAddress);
zeroAddress.sin_family=AF_INET;
return[selfreachabilityWithAddress:(conststructsockaddr*)&zeroAddress];
}

综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPv6和IPv4,但是在iOS9以下会存在bug,但是苹果审核并不关心。

四、底层的socketAPI如何同时支持IPv4和IPv6?

由于在应用中使用了网络诊断的组件,大量使用了底层的socketAPI,所以对于IPv6支持,这块是个重头戏。如果你的应用中使用了长连接,其必然会使用底层socketAPI,这一块也是需要支持IPv6的。对于Socket如何同时支持IPv4和IPv6,可以参考谷歌的开源库CocoaAsyncSocket.下面我针对我们的开源网络诊断组件,说一下是如何同时支持IPv4和IPv6的。
开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
这个网络诊断组件的主要功能如下:本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);通过TCPConnect监测到域名的连通性;通过Ping监测到目标主机的连通耗时;通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;

4.1IP地址从二进制到符号的转化

之前我们都是通过inet_ntoa()进行二进制到符号,这个API只能转化IPv4地址。而inet_ntop()能够兼容转化IPv4和IPv6地址。写了一个公用的in6_addr的转化方法如下:
+(NSString*)formatIPv6Address:(structin6_addr)ipv6Addr{
NSString*address=nil;

chardstStr[INET6_ADDRSTRLEN];
charsrcStr[INET6_ADDRSTRLEN];
memcpy(srcStr,&ipv6Addr,sizeof(structin6_addr));
if(inet_ntop(AF_INET6,srcStr,dstStr,INET6_ADDRSTRLEN)!=NULL){
address=[NSStringstringWithUTF8String:dstStr];
}

returnaddress;
}

4.2本机IP获取支持IPv6

相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(模拟器)、pdp_ip0(真机)的ip地址。注意:
(1)在模拟器和真机上都会出现以FE80开头的IPv6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPv6环境下,真机测试的时候,第一个出现的是一个IPv4地址,所以在IPv4条件下第一次遇到单播地址不退出。
+(NSString*)deviceIPAdress
{
while(temp_addr!=NULL){
NSLog(@"ifa_name===%@",[NSStringstringWithUTF8String:temp_addr->ifa_name]);
//Checkifinterfaceisen0whichisthewificonnectionontheiPhone
if([[NSStringstringWithUTF8String:temp_addr->ifa_name]isEqualToString:@"en0"]||[[NSStringstringWithUTF8String:temp_addr->ifa_name]isEqualToString:@"pdp_ip0"])
{
//如果是IPv4地址,直接转化
if(temp_addr->ifa_addr->sa_family==AF_INET){
//GetNSStringfromCString
address=[NSStringstringWithUTF8String:inet_ntoa(((structsockaddr_in*)temp_addr->ifa_addr)->sin_addr)];
//if(address&&![addressisEqualToString:@""]&&![address.uppercaseStringhasPrefix:@"FE80"])break;
}

//如果是IPv6地址
elseif(temp_addr->ifa_addr->sa_family==AF_INET6){
address=[selfformatIPv6Address:((structsockaddr_in6*)temp_addr->ifa_addr)->sin6_addr];
if(address&&![addressisEqualToString:@""]&&![address.uppercaseStringhasPrefix:@"FE80"])break;
}
}

temp_addr=temp_addr->ifa_next;
}
}
}

4.3设备网关地址获取获取支持IPv6

其实是在IPv4获取网关地址的源码的基础上进行了修改,初开把AF_INET->AF_INET6,sockaddr->sockaddr_in6之外,还需要注意如下修改,就是拷贝的地址字节数。去掉了ROUNDUP的处理。(解析出来的地址老是少了4个字节,结果是偏移量搞错了,纠结了半天),具体参考源码库。
/*net.route.0.inet.flags.gateway*/
intmib[]={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";
}

....

//forIPv4
for(i=0;i<RTAX_MAX;i++){
if(rt->rtm_addrs&(1<<i)){
sa_tab[i]=sa;
sa=(structsockaddr*)((char*)sa+ROUNDUP(sa->sa_len));
}else{
sa_tab[i]=NULL;
}
}

//forIPv6
for(i=0;i<RTAX_MAX;i++){
if(rt->rtm_addrs&(1<<i)){
sa_tab[i]=sa;
sa=(structsockaddr_in6*)((char*)sa+sa->sin6_len);
}else{
sa_tab[i]=NULL;
}
}

4.4设备DNS地址获取支持IPv6

IPv4时只需要通过res_ninit进行初始化就可以获取,但是在IPv6环境下需要通过res_getservers()接口才能获取。
+(NSArray*)outPutDNSServers{
res_stateres=malloc(sizeof(struct__res_state));
intresult=res_ninit(res);

NSMutableArray*servers=[[NSMutableArrayalloc]init];
if(result==0){
unionres_9_sockaddr_union*addr_union=malloc(res->nscount*sizeof(unionres_9_sockaddr_union));
res_getservers(res,addr_union,res->nscount);

for(inti=0;i<res->nscount;i++){
if(addr_union[i].sin.sin_family==AF_INET){
charip[INET_ADDRSTRLEN];
inet_ntop(AF_INET,&(addr_union[i].sin.sin_addr),ip,INET_ADDRSTRLEN);
NSString*dnsIP=[NSStringstringWithUTF8String:ip];
[serversaddObject:dnsIP];
NSLog(@"IPv4DNSIP:%@",dnsIP);
}elseif(addr_union[i].sin6.sin6_family==AF_INET6){
charip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6,&(addr_union[i].sin6.sin6_addr),ip,INET6_ADDRSTRLEN);
NSString*dnsIP=[NSStringstringWithUTF8String:ip];
[serversaddObject:dnsIP];
NSLog(@"IPv6DNSIP:%@",dnsIP);
}else{
NSLog(@"Undefinedfamily.");
}
}
}
res_nclose(res);
free(res);

return[NSArrayarrayWithArray:servers];
}

4.4域名DNS地址获取支持IPv6

在IPv4网络下我们通过gethostname获取,而在IPv6环境下,通过新的gethostbyname2函数获取。
//ipv4
phot=gethostbyname(hostN);

//ipv6
phot=gethostbyname2(hostN,AF_INET6);

4.5ping方案支持IPv6

Apple的官方提供了最新的支持IPv6的ping方案,参考地址如下:https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html
只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPv6的header部分也不返回TTL(TimetoLive)字段;
(2)IPv6的ICMP报文不进行checkSum的处理;

4.6traceRoute方案支持IPv6

其实是通过创建socket套接字模拟ICMP报文的发送,以计算耗时;
两个关键的地方需要注意:
(1)IPv6中去掉IP_TTL字段,改用跳数IPv6_UNICAST_HOPS来表示;
(2)sendto方法可以兼容支持IPv4和IPv6,但是需要最后一个参数,制定目标IP地址的大小;因为前一个参数只是指明了IP地址的开始地址。千万不要用统一的sizeof(structsockaddr),因为sockaddr_in和sockaddr都是16个字节,两者可以通用,但是sockaddr_in6的数据结构是28个字节,如果不显式指定,sendto方法就会一直返回-1,erroNo报22Invalidargument的错误。关键代码如下:(完整代码参考开源组件)
//构造通用的IP地址结构stucksockaddr

NSString*ipAddr0=[serverDNSsobjectAtIndex:0];
//设置server主机的套接口地址
NSData*addrData=nil;
BOOLisIPv6=NO;
if([ipAddr0rangeOfString:@":"].location==NSNotFound){
isIPv6=NO;
structsockaddr_innativeAddr4;
memset(&nativeAddr4,0,sizeof(nativeAddr4));
nativeAddr4.sin_len=sizeof(nativeAddr4);
nativeAddr4.sin_family=AF_INET;
nativeAddr4.sin_port=htons(udpPort);
nativeAddr4.sin_addr.s_addr=inet_addr([ipAddr0UTF8String]);
addrData=[NSDatadataWithBytes:&nativeAddr4length:sizeof(nativeAddr4)];
}else{
isIPv6=YES;
structsockaddr_in6nativeAddr6;
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=[NSDatadataWithBytes:&nativeAddr6length:sizeof(nativeAddr6)];
}

structsockaddr*destination;
destination=(structsockaddr*)[addrDatabytes];

//创建socket
if((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)

//设置sender套接字的ttl
if((isIPv6?
setsockopt(send_sock,IPPROTO_IPv6,IPv6_UNICAST_HOPS,&ttl,sizeof(ttl)):
setsockopt(send_sock,IPPROTO_IP,IP_TTL,&ttl,sizeof(ttl)))<0)

//发送成功返回值等于发送消息的长度
ssize_tsentLen=sendto(send_sock,cmsg,sizeof(cmsg),0,
(structsockaddr*)destination,
isIPv6?sizeof(structsockaddr_in6):sizeof(structsockaddr_in));

说法4:

果然是苹果打个哈欠,iOS行业内就得起一次风暴呀。自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPV6-Only的网络,大家便开始热火朝天的研究如何支持IPV6,以及应用中哪些模块目前不支持IPV6。

一、IPV6-Only支持是啥?

首先IPV6,是对IPV4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IPV6DNS64/NAT64网络之后,设备被分配的地址会变成IPV6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64Server查询IPv6的地址,如果查询不到,再向DNSServer查询IPv4地址,通过DNS64Server合成一个IPV6的地址,最终将一个IPV6的地址返回给客户端。如图所示:NAT64-DNS64-ResolutionOfIPv4_2x.png在MacOS10.11+的双网卡的Mac机器(以太网口+无线网卡),我们可以通过模拟构建这么一个localIPv6DNS64/NAT64的网络环境去测试应用是否支持IPV6-Only网络,大概原理如下: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

二、Apple如何审核支持IPV6-Only?

首先第一点:这里说的支持IPV6-Only网络,其实就是说让应用在IPv6DNS64/NAT64网络环境下仍然能够正常运行。但是考虑到我们目前的实际网络环境仍然是IPV4网络,所以应用需要能够同时保证IPV4和IPV6环境下的可用性。从这点来说,苹果不会去扫描IPV4的专有API来拒绝审核通过,因为IPV4的API和IPV6的API调用都会同时存在于代码中。其次第二点:Apple官方声明iOS9开始向IPV6支持过渡,在iOS9.2+支持IPV4地址合成IPV6地址。其提供的Reachability库在iOS8系统下,当从IPV4切换到IPV6网络,或者从IPV6网络切换到IPV4,是无法监控到网络状态的变化。也有一些开发者针对这些Bug询问Apple的审核部门,给予的答复是只需要在苹果最新的系统上保证IPV6的兼容性即可最后第三点:只要应用的主流程支持IPV6,通过苹果审核即可。对于不支持IPV6的模块,考虑到我们现实IPV6网络的部署还需要一段时间,短时间内不会影响我们用户的使用。但随着4G网络IPV6的部署,这部分模块还是需要逐渐安排人力进行支持。

三、应用如何支持IPV6-Only?

对于如何支持IPV6-Only,官方给出了如下几点标准:(这里就不对其进行解释了,大家看上面的参考链接即可)
1.UseHigh-LevelNetworkingFrameworks;
2.Don’tUseIPAddressLiterals;
3.CheckSourceCodeforIPv6DNS64/NAT64Incompatibilities;
4.UseSystemAPIstoSynthesizeIPv6Addresses;

3.1NSURLConnection是否支持IPV6?

官方的这句话让我们疑惑顿生:
usinghigh-levelnetworkingAPIssuchasNSURLSessionandtheCFNetworkframeworksandyouconnectbyname,youshouldnotneedtochangeanythingforyourapptoworkwithIPv6addresses只说了NSURLSession和CFNetwork的API不需要改变,但是并没有提及到NSURLConnection。从上文的参考资料中,我们看到NSURLSession、NSURLConnection同属于Cocoa的urlloadingsystem,可以猜测出NSURLConnection在ios9上是支持IPV6的。应用里面的API网络请求,大家一般都会选择AFNetworking进行请求发送,由于历史原因,应用的代码基本上都深度引用了AFHTTPRequestOperation类,所以目前API网络请求均需要通过NSURLConnection发送出去,所以必须确认NSURLConnection是否支持IPV6.经过测试,NSURLConnection在最新的iOS9系统上是支持IPV6的。

3.2Cocoa的URLLoadingSystem从iOS哪个版本开始支持IPV6?

目前我们的应用最低版本还需要支持iOS7,虽然苹果只要求最新版本支持IPV6-Only,但是出于对用户负责的态度,我们仍然需要搞清楚在低版本上URLLoadingSystem的API是否支持IPV6.(tofixme,makesomeexperiments)待续~~~

3.3Reachability是否需要修改支持IPV6?

我们可以查到应用中大量使用了Reachability进行网络状态判断,但是在里面却使用了IPV4的专用API。
在Pods:Reachability中
AF_INETFiles:Reachability.m
structsockaddr_inFiles:Reachability.h,Reachability.m

那Reachability应该如何支持IPV6呢?
(1)目前Github的开源库Reachability的最新版本是3.2,苹果也出了一个SupportIPV6的Reachability的官方样例,我们比较了一下源码,跟Github上的Reachability没有什么差异。
(2)我们通常都是通过一个0.0.0.0(ZeroAddress)去开启网络状态监控,经过我们测试,在iOS9以上的系统上IPV4和IPV6网络环境均能够正常使用;但是在iOS8上IPV4和IPV6相互切换的时候无法监控到网络状态的变化,可能是因为苹果在iOS8上还并没有对IPV6进行相关支持相关。(但是这仍然满足苹果要求在最新系统版本上支持IPV6的网络)。
(3)当大家都在要求Reachability添加对于IPV6的支持,其实苹果在iOS9以上对ZeroAddress进行了特别处理,官方发言是这样的:reachabilityForInternetConnection:Thismonitorstheaddress0.0.0.0,
whichreachabilitytreatsasaspecialtokenthatcausesittoactually
monitorthegeneralroutingstatusofthedevice,bothIPv4andIPv6.
+(instancetype)reachabilityForInternetConnection{
structsockaddr_inzeroAddress;
bzero(&zeroAddress,sizeof(zeroAddress));
zeroAddress.sin_len=sizeof(zeroAddress);
zeroAddress.sin_family=AF_INET;
return[selfreachabilityWithAddress:(conststructsockaddr*)&zeroAddress];
}

综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPV6和IPV4,但是在iOS9以下会存在bug,但是苹果审核并不关心。

四、底层的socketAPI如何同时支持IPV4和IPV6?

由于在应用中使用了网络诊断的组件,大量使用了底层的socketAPI,所以对于IPV6支持,这块是个重头戏。如果你的应用中使用了长连接,其必然会使用底层socketAPI,这一块也是需要支持IPV6的。对于Socket如何同时支持IPV4和IPV6,可以参考谷歌的开源库CocoaAsyncSocket.下面我针对我们的开源网络诊断组件,说一下是如何同时支持IPV4和IPV6的。
开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
这个网络诊断组件的主要功能如下:本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);通过TCPConnect监测到域名的连通性;通过Ping监测到目标主机的连通耗时;通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;

4.1IP地址从二进制到符号的转化

之前我们都是通过inet_ntoa()进行二进制到符号,这个API只能转化IPV4地址。而inet_ntop()能够兼容转化IPV4和IPV6地址。写了一个公用的in6_addr的转化方法如下:
//forIPV6
+(NSString*)formatIPV6Address:(structin6_addr)ipv6Addr{
NSString*address=nil;

chardstStr[INET6_ADDRSTRLEN];
charsrcStr[INET6_ADDRSTRLEN];
memcpy(srcStr,&ipv6Addr,sizeof(structin6_addr));
if(inet_ntop(AF_INET6,srcStr,dstStr,INET6_ADDRSTRLEN)!=NULL){
address=[NSStringstringWithUTF8String:dstStr];
}

returnaddress;
}

//forIPV4
+(NSString*)formatIPV4Address:(structin_addr)ipv4Addr{
NSString*address=nil;

chardstStr[INET_ADDRSTRLEN];
charsrcStr[INET_ADDRSTRLEN];
memcpy(srcStr,&ipv4Addr,sizeof(structin_addr));
if(inet_ntop(AF_INET,srcStr,dstStr,INET_ADDRSTRLEN)!=NULL){
address=[NSStringstringWithUTF8String:dstStr];
}

returnaddress;
}

4.2本机IP获取支持IPV6

相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(模拟器)、pdp_ip0(真机)的ip地址。注意:
(1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。
+(NSString*)deviceIPAdress
{
while(temp_addr!=NULL){
NSLog(@"ifa_name===%@",[NSStringstringWithUTF8String:temp_addr->ifa_name]);
//Checkifinterfaceisen0whichisthewificonnectionontheiPhone
if([[NSStringstringWithUTF8String:temp_addr->ifa_name]isEqualToString:@"en0"]||[[NSStringstringWithUTF8String:temp_addr->ifa_name]isEqualToString:@"pdp_ip0"])
{
//如果是IPV4地址,直接转化
if(temp_addr->ifa_addr->sa_family==AF_INET){
//GetNSStringfromCString
address=[selfformatIPV4Address:((structsockaddr_in*)temp_addr->ifa_addr)->sin_addr];
}

//如果是IPV6地址
elseif(temp_addr->ifa_addr->sa_family==AF_INET6){
address=[selfformatIPV6Address:((structsockaddr_in6*)temp_addr->ifa_addr)->sin6_addr];
if(address&&![addressisEqualToString:@""]&&![address.uppercaseStringhasPrefix:@"FE80"])break;
}
}

temp_addr=temp_addr->ifa_next;
}
}
}

4.3设备网关地址获取获取支持IPV6

其实是在IPV4获取网关地址的源码的基础上进行了修改,初开把AF_INET->AF_INET6,sockaddr->sockaddr_in6之外,还需要注意如下修改,就是拷贝的地址字节数。去掉了ROUNDUP的处理。(解析出来的地址老是少了4个字节,结果是偏移量搞错了,纠结了半天),具体参考源码库。
/*net.route.0.inet.flags.gateway*/
intmib[]={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";
}

....

//forIPV4
for(i=0;i<RTAX_MAX;i++){
if(rt->rtm_addrs&(1<<i)){
sa_tab[i]=sa;
sa=(structsockaddr*)((char*)sa+ROUNDUP(sa->sa_len));
}else{
sa_tab[i]=NULL;
}
}

//forIPV6
for(i=0;i<RTAX_MAX;i++){
if(rt->rtm_addrs&(1<<i)){
sa_tab[i]=sa;
sa=(structsockaddr_in6*)((char*)sa+sa->sin6_len);
}else{
sa_tab[i]=NULL;
}
}

4.4设备DNS地址获取支持IPV6

IPV4时只需要通过res_ninit进行初始化就可以获取,但是在IPV6环境下需要通过res_getservers()接口才能获取。
+(NSArray*)outPutDNSServers{
res_stateres=malloc(sizeof(struct__res_state));
intresult=res_ninit(res);

NSMutableArray*servers=[[NSMutableArrayalloc]init];
if(result==0){
unionres_9_sockaddr_union*addr_union=malloc(res->nscount*sizeof(unionres_9_sockaddr_union));
res_getservers(res,addr_union,res->nscount);

for(inti=0;i<res->nscount;i++){
if(addr_union[i].sin.sin_family==AF_INET){
charip[INET_ADDRSTRLEN];
inet_ntop(AF_INET,&(addr_union[i].sin.sin_addr),ip,INET_ADDRSTRLEN);
NSString*dnsIP=[NSStringstringWithUTF8String:ip];
[serversaddObject:dnsIP];
NSLog(@"IPv4DNSIP:%@",dnsIP);
}elseif(addr_union[i].sin6.sin6_family==AF_INET6){
charip[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6,&(addr_union[i].sin6.sin6_addr),ip,INET6_ADDRSTRLEN);
NSString*dnsIP=[NSStringstringWithUTF8String:ip];
[serversaddObject:dnsIP];
NSLog(@"IPv6DNSIP:%@",dnsIP);
}else{
NSLog(@"Undefinedfamily.");
}
}
}
res_nclose(res);
free(res);

return[NSArrayarrayWithArray:servers];
}

4.4域名DNS地址获取支持IPV6

在IPV4网络下我们通过gethostname获取,而在IPV6环境下,通过新的gethostbyname2函数获取。
//ipv4
phot=gethostbyname(hostN);

//ipv6
phot=gethostbyname2(hostN,AF_INET6);

4.5ping方案支持IPV6

Apple的官方提供了最新的支持IPV6的ping方案,参考地址如下:
https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPV6的header部分也不返回TTL(TimetoLive)字段;
(2)IPV6的ICMP报文不进行checkSum的处理;

4.6traceRoute方案支持IPV6

其实是通过创建socket套接字模拟ICMP报文的发送,以计算耗时;
两个关键的地方需要注意:
(1)IPV6中去掉IP_TTL字段,改用跳数IPV6_UNICAST_HOPS来表示;
(2)sendto方法可以兼容支持IPV4和IPV6,但是需要最后一个参数,制定目标IP地址的大小;因为前一个参数只是指明了IP地址的开始地址。千万不要用统一的sizeof(structsockaddr),因为sockaddr_in和sockaddr都是16个字节,两者可以通用,但是sockaddr_in6的数据结构是28个字节,如果不显式指定,sendto方法就会一直返回-1,erroNo报22Invalidargument的错误。关键代码如下:(完整代码参考开源组件)
//构造通用的IP地址结构stucksockaddr

NSString*ipAddr0=[serverDNSsobjectAtIndex:0];
//设置server主机的套接口地址
NSData*addrData=nil;
BOOLisIPV6=NO;
if([ipAddr0rangeOfString:@":"].location==NSNotFound){
isIPV6=NO;
structsockaddr_innativeAddr4;
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=[NSDatadataWithBytes:&nativeAddr4length:sizeof(nativeAddr4)];
}else{
isIPV6=YES;
structsockaddr_in6nativeAddr6;
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=[NSDatadataWithBytes:&nativeAddr6length:sizeof(nativeAddr6)];
}

structsockaddr*destination;
destination=(structsockaddr*)[addrDatabytes];

//创建socket
if((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)

//设置sender套接字的ttl
if((isIPV6?
setsockopt(send_sock,IPPROTO_IPV6,IPV6_UNICAST_HOPS,&ttl,sizeof(ttl)):
setsockopt(send_sock,IPPROTO_IP,IP_TTL,&ttl,sizeof(ttl)))<0)

//发送成功返回值等于发送消息的长度
ssize_tsentLen=sendto(send_sock,cmsg,sizeof(cmsg),0,
(structsockaddr*)destination,
isIPV6?sizeof(structsockaddr_in6):sizeof(structsockaddr_in));


文/philon(简书作者)
原文链接:http://www.jianshu.com/p/a6bab07c4062#rd
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: