本文转自 http://www.code4app.com/forum.php?mod=viewthread&tid=8427&highlight=ipv6
一、IPV6-Only支持是啥?
 
 NAT64-DNS64-ResolutionOfIPv4_2x.png
 
 
 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?
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;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
 
3.2 Cocoa的URL Loading System从iOS哪个版本开始支持IPV6?
 
3.3 Reachability是否需要修改支持IPV6?
| 1 2 3 | 在Pods:Reachability中AF_INET                  Files:Reachability.mstructsockaddr_in       Files:Reachability.h , Reachability.m | 
 
(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进行了特别处理,官方发言是这样的:
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 2 3 4 5 6 7 | + (instancetype)reachabilityForInternetConnection {    structsockaddr_in zeroAddress;    bzero(&zeroAddress, sizeof(zeroAddress));    zeroAddress.sin_len = sizeof(zeroAddress);    zeroAddress.sin_family = AF_INET;    return[selfreachabilityWithAddress: (conststructsockaddr *) &zeroAddress];} | 
 
 
四、底层的socket API如何同时支持IPV4和IPV6?
 
开源地址:https://github.com/Lede-Inc/LDNetDiagnoService_IOS.git
这个网络诊断组件的主要功能如下:
- 本地网络环境的监测(本机IP+本地网关+本地DNS+域名解析);
- 通过TCP Connect监测到域名的连通性;
- 通过Ping 监测到目标主机的连通耗时;
- 通过traceRoute监测设备到目标主机中间每一个路由器节点的ICMP耗时;
 
4.1 IP地址从二进制到符号的转化
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | //for IPV6+(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;}//for IPV4+(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
(1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。
(2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | + (NSString*)deviceIPAdress{        while(temp_addr != NULL) {            NSLog(@"ifa_name===%@",[NSStringstringWithUTF8String:temp_addr->ifa_name]);            // Check if interface is en0 which is the wifi connection on the iPhone            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){                    // Get NSString from C String                   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 && ![address isEqualToString:@""] && ![address.uppercaseString hasPrefix:@"FE80"]) break;                }            }            temp_addr = temp_addr->ifa_next;        }    }} | 
 
4.3 设备网关地址获取获取支持IPV6
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | /* 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";  }  ....  //for IPV4  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;              }          }//for IPV6   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
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | +(NSArray*)outPutDNSServers{    res_state res = 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];                [servers addObject:dnsIP];                NSLog(@"IPv4 DNS IP: %@", 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];                [servers addObject:dnsIP];                NSLog(@"IPv6 DNS IP: %@", dnsIP);            } else{                NSLog(@"Undefined family.");            }        }    }    res_nclose(res);    free(res);    return[NSArrayarrayWithArray:servers];} | 
 
4.4 域名DNS地址获取支持IPV6
| 1 2 3 4 5 | //ipv4phot = gethostbyname(hostN);//ipv6 phot = gethostbyname2(hostN, AF_INET6); | 
 
4.5 ping方案支持IPV6
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
两个关键的地方需要注意:
(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的错误。
 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | //构造通用的IP地址结构stuck sockaddr NSString*ipAddr0 = [serverDNSs objectAtIndex:0];    //设置server主机的套接口地址    NSData*addrData = nil;    BOOLisIPV6 = NO;    if([ipAddr0 rangeOfString:@":"].location == NSNotFound) {        isIPV6 = NO;        structsockaddr_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 = [NSDatadataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)];    } else{        isIPV6 = YES;        structsockaddr_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 = [NSDatadataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)];    }    structsockaddr *destination;    destination = (structsockaddr *)[addrData bytes];//创建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)//设置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)//发送成功返回值等于发送消息的长度ssize_t sentLen = sendto(send_sock, cmsg, sizeof(cmsg), 0, (structsockaddr *)destination, isIPV6?sizeof(structsockaddr_in6):sizeof(structsockaddr_in)); |