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

一、IPV6-Only支持是啥?

首先IPV6,是对IPV4地址空间的扩充。目前当我们用iOS设备连接上Wifi、4G、3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IPV6 DNS64/NAT64网络之后,设备被分配的地址会变成IPV6的地址,而这些网络就是所谓的IPV6-Only网络,并且仍然可以通过此网络去获取IPV4地址提供的内容。客户端向服务器端请求域名解析,首先通过DNS64 Server查询IPv6的地址,如果查询不到,再向DNS Server查询IPv4地址,通过DNS64 Server合成一个IPV6的地址,最终将一个IPV6的地址返回给客户端。如图所示:

 
 
在Mac OS 10.11+的双网卡的Mac机器(以太网口+无线网卡),我们可以通过模拟构建这么一个local IPv6 DNS64/NAT64 的网络环境去测试应用是否支持IPV6-Only网络,大概原理如下:

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

首先第一点:这里说的支持IPV6-Only网络,其实就是说让应用在 IPv6 DNS64/NAT64 网络环境下仍然能够正常运行。但是考虑到我们目前的实际网络环境仍然是IPV4网络,所以应用需要能够同时保证IPV4和IPV6环境下的可用性。从这点来说,苹果不会去扫描IPV4的专有API来拒绝审核通过,因为IPV4的API和IPV6的API调用都会同时存在于代码中(不过为了减小审核被拒风险,建议将IPV4专有API通过IPV6的兼容API来替换)。其次第二点:Apple官方声明iOS9开始向IPV6支持过渡,在iOS9.2+支持通过getaddrInfo方法将IPV4地址合成IPV6地址(The ability to synthesize IPv6 addresses was added to getaddrinfo in iOS 9.2 and OS X 10.11.2)。其提供的Reachability库在iOS8系统下,当从IPV4切换到IPV6网络,或者从IPV6网络切换到IPV4,是无法监控到网络状态的变化。也有一些开发者针对这些Bug询问Apple的审核部门,给予的答复是只需要在苹果最新的系统上保证IPV6的兼容性即可最后第三点:只要应用的主流程支持IPV6,通过苹果审核即可。对于不支持IPV6的模块,考虑到我们现实IPV6网络的部署还需要一段时间,短时间内不会影响我们用户的使用。但随着4G网络IPV6的部署,这部分模块还是需要逐渐安排人力进行支持。追加第四点:如果应用一直直接使用IPV4地址通过NSURLConenction或者NSURLSession进行网络请求(一般需要服务器允许,且客户端需要在header中伪装host);经测试,IPV6网络环境下,直接使用IPV4地址在iOS9及以上的系统仍然能够正常访问;在iOS8.4及以下不能正常访问;这一点苹果的解释和建议是这样的:


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.


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

对于如何支持IPV6-Only,官方给出了如下几点标准:(这里就不对其进行解释了,大家看上面的参考链接即可)

  1. 1. Use High-Level Networking Frameworks;
  2. 2. Dont Use IP Address Literals;
  3. 3. Check Source Code for IPv6 DNS64/NAT64 Incompatibilities;
  4. 4. Use System APIs to Synthesize IPv6 Addresses;

3.1 NSURLConnection是否支持IPV6?

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

3.2 Cocoa的URL Loading System从iOS哪个版本开始支持IPV6?

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

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

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

  1. Pods:Reachability
  2. AF_INET Files:Reachability.m
  3. struct sockaddr_in Files:Reachability.h , Reachability.m

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


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.

  1. + (instancetype)reachabilityForInternetConnection {
  2. struct sockaddr_in zeroAddress;
  3. bzero(&zeroAddress, sizeof(zeroAddress));
  4. zeroAddress.sin_len = sizeof(zeroAddress);
  5. zeroAddress.sin_family = AF_INET;
  6. return [self reachabilityWithAddress: (const struct sockaddr *) &zeroAddress];
  7. }

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

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

由于在应用中使用了网络诊断的组件,大量使用了底层的 socket API,所以对于IPV6支持,这块是个重头戏。如果你的应用中使用了长连接,其必然会使用底层socket API,这一块也是需要支持IPV6的。 对于Socket如何同时支持IPV4和IPV6,可以参考谷歌的开源库CocoaAsyncSocket.下面我针对我们的开源 网络诊断组件, 说一下是如何同时支持IPV4和IPV6的。 开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git 这个网络诊断组件的主要功能如下:

  • 本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);
  • 通过TCP Connect监测到域名的连通性;
  • 通过Ping 监测到目标主机的连通耗时;
  • 通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;

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

之前我们都是通过inet_ntoa()进行二进制到符号,这个API只能转化IPV4地址。而inet_ntop()能够兼容转化IPV4和IPV6地址。 写了一个公用的in6_addr的转化方法如下:

  1. //for IPV6
  2. +(NSString *)formatIPV6Address:(struct in6_addr)ipv6Addr{
  3. NSString *address = nil;
  4. char dstStr[INET6_ADDRSTRLEN];
  5. char srcStr[INET6_ADDRSTRLEN];
  6. memcpy(srcStr, &ipv6Addr, sizeof(struct in6_addr));
  7. if(inet_ntop(AF_INET6, srcStr, dstStr, INET6_ADDRSTRLEN) != NULL){
  8. address = [NSString stringWithUTF8String:dstStr];
  9. }
  10. return address;
  11. }
  12. //for IPV4
  13. +(NSString *)formatIPV4Address:(struct in_addr)ipv4Addr{
  14. NSString *address = nil;
  15. char dstStr[INET_ADDRSTRLEN];
  16. char srcStr[INET_ADDRSTRLEN];
  17. memcpy(srcStr, &ipv4Addr, sizeof(struct in_addr));
  18. if(inet_ntop(AF_INET, srcStr, dstStr, INET_ADDRSTRLEN) != NULL){
  19. address = [NSString stringWithUTF8String:dstStr];
  20. }
  21. return address;
  22. }

4.2 本机IP获取支持IPV6

相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(Wifi)、pdp_ip0(移动网络)的ip地址。注意: (1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。 (2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。

  1. + (NSString *)deviceIPAdress
  2. {
  3. while (temp_addr != NULL) {
  4. NSLog(@"ifa_name===%@",[NSString stringWithUTF8String:temp_addr->ifa_name]);
  5. // Check if interface is en0 which is the wifi connection on the iPhone
  6. if ([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"] || [[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"pdp_ip0"])
  7. {
  8. //如果是IPV4地址,直接转化
  9. if (temp_addr->ifa_addr->sa_family == AF_INET){
  10. // Get NSString from C String
  11. address = [self formatIPV4Address:((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr];
  12. }
  13. //如果是IPV6地址
  14. else if (temp_addr->ifa_addr->sa_family == AF_INET6){
  15. address = [self formatIPV6Address:((struct sockaddr_in6 *)temp_addr->ifa_addr)->sin6_addr];
  16. if (address && ![address isEqualToString:@""] && ![address.uppercaseString hasPrefix:@"FE80"]) break;
  17. }
  18. }
  19. temp_addr = temp_addr->ifa_next;
  20. }
  21. }
  22. }

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

其实是在IPV4获取网关地址的源码的基础上进行了修改,初开把AF_INET->AF_INET6, sockaddr -> sockaddr_in6之外,还需要注意如下修改,就是拷贝的地址字节数。去掉了ROUNDUP的处理。 (解析出来的地址老是少了4个字节,结果是偏移量搞错了,纠结了半天),具体参考源码库。

  1. /* net.route.0.inet.flags.gateway */
  2. int mib[] = {CTL_NET, PF_ROUTE, 0, AF_INET6, NET_RT_FLAGS, RTF_GATEWAY};
  3. if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &l, 0, 0) < 0 xss=removed xss=removed>rtm_addrs & (1 << i xss=removed xss=removed>sa_len));
  4. } else {
  5. sa_tab[i] = NULL;
  6. }
  7. }
  8. //for IPV6
  9. for (i = 0; i < RTAX>rtm_addrs & (1 << i xss=removed xss=removed>sin6_len);
  10. } else {
  11. sa_tab[i] = NULL;
  12. }
  13. }
  14. 4.4 设备DNS地址获取支持IPV6
  15. IPV4时只需要通过res_ninit进行初始化就可以获取,但是在IPV6环境下需要通过res_getservers()接口才能获取。
  16. +(NSArray *)outPutDNSServers{
  17. res_state res = malloc(sizeof(struct __res_state));
  18. int result = res_ninit(res);
  19. NSMutableArray *servers = [[NSMutableArray alloc] init];
  20. if (result == 0) {
  21. union res_9_sockaddr_union *addr_union = malloc(res->nscount * sizeof(union res_9_sockaddr_union));
  22. res_getservers(res, addr_union, res->nscount);
  23. for (int i = 0; i < res>nscount; i++) {
  24. if (addr_union[i].sin.sin_family == AF_INET) {
  25. char ip[INET_ADDRSTRLEN];
  26. inet_ntop(AF_INET, &(addr_union[i].sin.sin_addr), ip, INET_ADDRSTRLEN);
  27. NSString *dnsIP = [NSString stringWithUTF8String:ip];
  28. [servers addObject:dnsIP];
  29. NSLog(@"IPv4 DNS IP: %@", dnsIP);
  30. } else if (addr_union[i].sin6.sin6_family == AF_INET6) {
  31. char ip[INET6_ADDRSTRLEN];
  32. inet_ntop(AF_INET6, &(addr_union[i].sin6.sin6_addr), ip, INET6_ADDRSTRLEN);
  33. NSString *dnsIP = [NSString stringWithUTF8String:ip];
  34. [servers addObject:dnsIP];
  35. NSLog(@"IPv6 DNS IP: %@", dnsIP);
  36. } else {
  37. NSLog(@"Undefined family.");
  38. }
  39. }
  40. }
  41. res_nclose(res);
  42. free(res);
  43. return [NSArray arrayWithArray:servers];
  44. }

4.4 域名DNS地址获取支持IPV6

在IPV4网络下我们通过gethostname获取,而在IPV6环境下,通过新的gethostbyname2函数获取。

  1. //ipv4
  2. phot = gethostbyname(hostN);
  3. //ipv6
  4. phot = gethostbyname2(hostN, AF_INET6);

4.5 ping方案支持IPV6

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报文不进行checkSum的处理;

4.6 traceRoute方案支持IPV6

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

  1. //构造通用的IP地址结构stuck sockaddr
  2. NSString *ipAddr0 = [serverDNSs objectAtIndex:0];
  3. //设置server主机的套接口地址
  4. NSData *addrData = nil;
  5. BOOL isIPV6 = NO;
  6. if ([ipAddr0 rangeOfString:@":"].location == NSNotFound) {
  7. isIPV6 = NO;
  8. struct sockaddr_in nativeAddr4;
  9. memset(&nativeAddr4, 0, sizeof(nativeAddr4));
  10. nativeAddr4.sin_len = sizeof(nativeAddr4);
  11. nativeAddr4.sin_family = AF_INET;
  12. nativeAddr4.sin_port = htons(udpPort);
  13. inet_pton(AF_INET, ipAddr0.UTF8String, &nativeAddr4.sin_addr.s_addr);
  14. addrData = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];
  15. } else {
  16. isIPV6 = YES;
  17. struct sockaddr_in6 nativeAddr6;
  18. memset(&nativeAddr6, 0, sizeof(nativeAddr6));
  19. nativeAddr6.sin6_len = sizeof(nativeAddr6);
  20. nativeAddr6.sin6_family = AF_INET6;
  21. nativeAddr6.sin6_port = htons(udpPort);
  22. inet_pton(AF_INET6, ipAddr0.UTF8String, &nativeAddr6.sin6_addr);
  23. addrData = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];
  24. }
  25. struct sockaddr *destination;
  26. destination = (struct sockaddr *)[addrData bytes];
  27. //创建socket
  28. if ((recv_sock = socket(destination->sa_family, SOCK_DGRAM, isIPV6?IPPROTO_ICMPV6:IPPROTO_ICMP)) < 0 xss=removed>sa_family, SOCK_DGRAM, 0)) &lt; 0)
  29. //设置sender 套接字的ttl
  30. if ((isIPV6?
  31. setsockopt(send_sock,IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ttl, sizeof(ttl)):
  32. setsockopt(send_sock, IPPROTO_IP, IP_TTL, &ttl, sizeof(ttl))) &lt; 0)
  33. //发送成功返回值等于发送消息的长度
  34. ssize_t sentLen = sendto(send_sock, cmsg, sizeof(cmsg), 0,
  35. (struct sockaddr *)destination,
  36. isIPV6?sizeof(struct sockaddr_in6):sizeof(struct sockaddr_in));
 

iOS应用支持IPV6,就那点事儿的更多相关文章

  1. iOS应用支持IPV6

    一.IPV6-Only支持是啥? 首先IPV6,是对IPV4地址空间的扩充.目前当我们用iOS设备连接上Wifi.4G.3G等网络时,设备被分配的地址均是IPV4地址,但是随着运营商和企业逐渐部署IP ...

  2. iOS应用适配IPV6

    网络收集,连接如下: 针对苹果iOS最新审核要求为应用兼容IPv6 iOS应用支持IPV6,就那点事儿 iOS 适配iPV6的修改(AF及其他第三方库)

  3. iOS 支持 IPv6

    苹果的规定:2016年6月1日提交到App Store必须支持IPv6-only网络. 官方文档:https://developer.apple.com/library/mac/documentati ...

  4. iOS 上线因iPv6被拒,查询服务器是否支持iPv6,mac设置iPv6网络,手机测试iPv6

    一. iOS----如何检查域名是否支持ipv6 iOS----------如何检查域名是否支持ipv6 1.检查你所用到的库,像af 3.0以上什么的(不用改),其他的库自己去搜下是否支持ipv6吧 ...

  5. iOS----------如何检查域名是否支持ipv6

    http://ipv6-test.com/validate.php  这个地址  也可以检测到! 1.检查你所用到的库,像af 3.0以上什么的(不用改),其他的库自己去搜下是否支持ipv6吧. 2. ...

  6. 配置阿里云ECS支持IPv6,解决苹果app审核失败问题

    前几天iOS的App提交给苹果审核没通过,给出的原因是:该应用在 IPv6 的环境下无法使用.检查发现:阿里云优化过的系统没有启用IPv6协议,需要配置启用一下,但是只单独启用IPv6也是无法直接提供 ...

  7. linux centos6.5支持ipv6

    1.用ifconfig查看有没有inet6 addr,我的这个已经支持了,如果不支持请看第二步. 2.vim /etc/sysconfig/network 把这句改成:NETWORKING_IPV6= ...

  8. 用尽洪荒之力解决Apple Store ipv6审核通关---linux服务器支持ipv6

         强势的库克时代到来,苹果开启了强制IPV6审核,大家也知道中国现在的情况,除了教育网实验性的支持IPV6,ISP运营商还不支持,想必大家都陆陆续续的遭受到了苹果无情的拒绝,以前开个加急,审核 ...

  9. ios app 支持 ipv6-only

    最近苹果公司发布声明:自今年6月1日开始,所有提交至苹果App Store的应用申请必须要兼容面向硬件识别和网络路由的最新互联网协议--IPv6-only标准. 那么问题来了,目前的app是否支持ip ...

随机推荐

  1. centos安装

    转:http://www.cnblogs.com/Johness/archive/2012/12/03/2800126.html 在已经安装了Win7的系统下安装CentOS 注意:1.由于涉及到对硬 ...

  2. _beginThreadex创建多线程解读【转】

    _beginThreadex创建多线程解读 一.需要的头文件支持 #include <process.h>         // for _beginthread() 需要的设置:Proj ...

  3. PYTHON 迭代器

    可以走直接作用于for循环的对象统称为可迭代对象使用:Iterable 使用isinstance()判断一个对象是否是Iterable对象: from collections import Itera ...

  4. PHP开发神器——phpstorm

    常用快捷键 快捷键 说明 ctrl+j 插入活动代码提示 ctrl+alt+t 当前位置插入环绕代码 alt+insert 生成代码菜单 Shift + Enter 新一行 ctrl+q 查看代码注释 ...

  5. JDBC的应用

    JDBC的开发步骤: 1.  引入JDBC驱动架包 2.  程序中引入JDBC驱动类     3.  创建java与数据库的连接 4.  跟数据库交互:发送sql语句,接收数据库对sql语句的执行结果 ...

  6. VB中 ByRef与ByVal区别

    函数调用的参数传递有"值传递"和"引用传递"两种传递方式.如果采用"值传递",在函数内部改变了参数的值,主调程序的对应变量的值不会改变:如果 ...

  7. Extjs 组件共用(单例)问题

    说明: 将store初始化在类定义时便创建, store实例将成为该类的单例 代码: 测试: 说明: 将store初始化放入initComponent函数中.  每次都将创建一个新的实例. 代码: 测 ...

  8. JAVA 回调机制(callback)

    序言 最近学习java,接触到了回调机制(CallBack).初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义.当然了,我在理解了回 ...

  9. struts2 配置 struts.xml 提示

    1.这个提示通常是在 连网络的时候才可以看到 2.当没有网路的时候我们该如何配置呢? window -->preferences -->xml catelog -->user.... ...

  10. Linux-PAM认证模块

    Linux-PAM认证模块     用户访问服务器的时候,服务器的某一个服务程序把用户的谁请求发送到PAM模块进行认证.对于不同的服务器应用程序所对应的PAM模块也是不同的.如果想查看某个程序是否支持 ...