支持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的转化方法如下:

//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

相当于我们在终端中输入ifconfig命令获取字符串,然后对ifconfig结果字符串进行解析,获取其中en0(Wifi)、pdp_ip0(移动网络)的ip地址。

注意:

(1)在模拟器和真机上都会出现以FE80开头的IPV6单播地址影响我们判断,所以在这里进行特殊的处理(当第一次遇到不是单播地址的IP地址即为本机IP地址)。

(2)在IPV6环境下,真机测试的时候,第一个出现的是一个IPV4地址,所以在IPV4条件下第一次遇到单播地址不退出。

+ (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

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

/* 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

IPV4时只需要通过res_ninit进行初始化就可以获取,但是在IPV6环境下需要通过res_getservers()接口才能获取。

+(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

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

//ipv4

phot = gethostbyname(hostN);

//ipv6

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的错误。

关键代码如下:(完整代码参考开源组件)

//构造通用的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));

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容