针对苹果最新审核要求为应用兼容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。在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
那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.
综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPv6和IPv4,但是在iOS9以下会存在bug,但是苹果审核并不关心。
开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
这个网络诊断组件的主要功能如下:本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);通过TCPConnect监测到域名的连通性;通过Ping监测到目标主机的连通耗时;通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;
(1)在模拟器和真机上都会出现以FE80开头的IPv6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPv6环境下,真机测试的时候,第一个出现的是一个IPv4地址,所以在IPv4条件下第一次遇到单播地址不退出。
https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html
只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPv6的header部分也不返回TTL(TimetoLive)字段;
(2)IPv6的ICMP报文不进行checkSum的处理;
两个关键的地方需要注意:
(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的错误。关键代码如下:(完整代码参考开源组件)
果然是苹果打个哈欠,iOS行业内就得起一次风暴呀。自从5月初Apple明文规定所有开发者在6月1号以后提交新版本需要支持IPV6-Only的网络,大家便开始热火朝天的研究如何支持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
usinghigh-levelnetworkingAPIssuchasNSURLSessionandtheCFNetworkframeworksandyouconnectbyname,youshouldnotneedtochangeanythingforyourapptoworkwithIPv6addresses只说了NSURLSession和CFNetwork的API不需要改变,但是并没有提及到NSURLConnection。从上文的参考资料中,我们看到NSURLSession、NSURLConnection同属于Cocoa的urlloadingsystem,可以猜测出NSURLConnection在ios9上是支持IPV6的。应用里面的API网络请求,大家一般都会选择AFNetworking进行请求发送,由于历史原因,应用的代码基本上都深度引用了AFHTTPRequestOperation类,所以目前API网络请求均需要通过NSURLConnection发送出去,所以必须确认NSURLConnection是否支持IPV6.经过测试,NSURLConnection在最新的iOS9系统上是支持IPV6的。
那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.
综上所述,Reachability不需要做任何修改,在iOS9上就可以支持IPV6和IPV4,但是在iOS9以下会存在bug,但是苹果审核并不关心。CocoaAsyncSocket.下面我针对我们的开源网络诊断组件,说一下是如何同时支持IPV4和IPV6的。
开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
这个网络诊断组件的主要功能如下:本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);通过TCPConnect监测到域名的连通性;通过Ping监测到目标主机的连通耗时;通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;
(1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。
https://developer.apple.com/library/mac/samplecode/SimplePing/Introduction/Intro.html只是需要注意的是:
(1)返回的packet去掉了IPHeader部分,IPV6的header部分也不返回TTL(TimetoLive)字段;
(2)IPV6的ICMP报文不进行checkSum的处理;
两个关键的地方需要注意:
(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的错误。关键代码如下:(完整代码参考开源组件)
文/philon(简书作者)
原文链接:http://www.jianshu.com/p/a6bab07c4062#rd
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
检查不兼容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类型也需要做处理
一、IPv6-Only支持是啥?
首先IPv6,是对IPv4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPv4地址,但是随着运营商和企业逐渐部署IPv6DNS64/NAT64网络之后,设备被分配的地址会变成IPv6的地址,而这些网络就是所谓的IPv6-Only网络,并且仍然可以通过此网络去获取IPv4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64Server查询IPv6的地址,如果查询不到,再向DNSServer查询IPv4地址,通过DNS64Server合成一个IPv6的地址,最终将一个IPv6的地址返回给客户端。如图所示:二、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方案,参考地址如下:只是需要注意的是:
(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的地址返回给客户端。如图所示:二、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进行了特别处理,
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,可以参考谷歌的开源库开源地址:
这个网络诊断组件的主要功能如下:本地网络环境的监测(本机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方案,参考地址如下:(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
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
相关文章推荐
- Android的ListView多选删除操作实现代码
- 使用Gson解析复杂的json数据
- qt qtabwidget tab美化
- FMDB简介
- leetcode 319. Bulb Switcher
- MIC卡驱动安装
- Android深入浅出自定义控件(三)
- jboss内存查看管理 .
- sql 在not in 子查询有null值情况下经常出现的陷阱
- Qt第三方库----QCustomPlot
- excel 两列比较内容是否相同
- Oracle 中XML处理函数介绍
- TextView 控制字数
- 编程命名规则(网摘整理):帕斯卡命名法,骆驼命名法(小驼峰式&大驼峰式),匈牙利命名法
- MyBatis学习教程(二)―如何使用MyBatis对users表执行CRUD操作
- getRequestDispatcher()与sendRedirect()的区别
- Tomcat虚拟目录的配置
- Java8并发教程:Threads和Executors
- Activity管理器
- 关于Git GitLab 和以及在Android studio中的使用。