php gethostbyname ipv6,支持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));

你可能感兴趣的:(php,gethostbyname,ipv6)