本文转自 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.m struct sockaddr_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 { 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]; } |
四、底层的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:( 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; } |
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===%@" ,[ 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地址,直接转化 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; } } } |
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 */ 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 ; } } |
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)); 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]; } |
4.4 域名DNS地址获取支持IPV6
1 2 3 4 5 | //ipv4 phot = 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 ; 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]; //创建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_t sentLen = sendto(send_sock, cmsg, sizeof (cmsg), 0, ( struct sockaddr *)destination, isIPV6? sizeof ( struct sockaddr_in6): sizeof ( struct sockaddr_in)); |