本文轉自 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)); |