一、Apple声明
从6月起,所有的APP都要支持IPv6-only网络。鉴于此,做为开发人员,肯定又要行动起来,那么首先要知道 IPv6-only Networks是什么。
二、Ipv6-only Networks引申
a.With IPv4 address pool exhaustion imminent, enterprise and cellular providers are increasingly deploying IPv6 DNS64 and NAT64 networks.A DNS64/NAT64 network is an IPv6-only network that continues to provide access to IPv4 content through translation.
可以看到IPv6-only Networks是让IPv6 Client通过NAT64和DNS64的转换去访问ipv4资源的一种途径。下面摘自网上的一段介绍:
NAT64是一种有状态的网络地址与协议转换技术,一般只支持通过IPv6网络侧用户发起连接访问IPv4侧网络资源。但NAT64也支持通过手工配置静态映射关系,实现IPv4网络主动发起连接访问IPv6网络。NAT64可实现TCP、UDP、ICMP协议下的IPv6与IPv4网络地址和协议转换。DNS64则主要是配合NAT64工作,主要是将DNS查询信息中的A记录(IPv4地址)合成到AAAA记录(IPv6地址)中,返回合成的AAAA记录用户给IPv6侧用户。
IPv6终端用户发出的DNS请求通过DNS64设备进行域名解析。
如果存在请求域名的IPv6 DNS记录(AAAA记录),解析结果将直接返回给IPv6终端用户,IPv6终端用户使用该地址进行访问。
如果对应域名记录不是基于IPv6而是基于IPv4的DNS记录(A记录),DNS64设备使用其NAT64做前缀将A记录转换为AAAA记录并转发给IPv6终端用户,IPv6终端用户经NAT64设备进行NAT转换访问相应的IPv4服务器。
如果我们想访问IPv6和IPv4网络资源,那就如下图这样:
但是网络运营网不想再提供Ipv4网络,但又不能让用户访问不到IPv4资源,为了解决这个问题,布署NAT64/DNS64去解决,这时访问网络的流程就会是:
对于DNS64工作流程如下图:
在获取到IPv4 mapping IPv6后,client通过NAT64去访问资源
总而言之,appstore上的app,必须能让IPv6-only Network环境也就是IPv6的iPhone能通过NAT64/DNS64去访问IPv4的网络资源。
三、改造支持IPv6
苹果推荐用比较上层的framework去做网络连接,但我们游戏一般都是用低层的socket去处理网络链接的,所以需要自己去做这个处理。
其实改动也很简单,一般主要是涉及三个地方
DNS解析,socket创建,socket连接
对于DNS解析,不可以再用只适用于IPv4的gethostbyname方法去获取地址,而改用getaddrinfo方法,对此用苹果的示例代码如下:
#include <arpa/inet.h>
#include <err.h>
uint8_t ipv4[4] = {192, 0, 2, 1};
struct addrinfo hints, *res, *res0;
int error, s;
const char *cause = NULL;
char ipv4_str_buf[INET_ADDRSTRLEN] = { 0 };
const char *ipv4_str = inet_ntop(AF_INET, &ipv4, ipv4_str_buf, sizeof(ipv4_str_buf));
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
error = getaddrinfo(ipv4_str, "http", &hints, &res0);
if (error) {
errx(1, "%s", gai_strerror(error));
/*NOTREACHED*/
}
s = -1;
for (res = res0; res; res = res->ai_next) {
s = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (s < 0) {
cause = "socket";
continue;
}
if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
cause = "connect";
close(s);
s = -1;
continue;
}
break; /* okay we got one */
}
if (s < 0) {
err(1, "%s", cause);
/*NOTREACHED*/
}
freeaddrinfo(res0);
对于getaddrinfo方法,函数的参数可以都按苹果推荐的参数来,
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_DEFAULT;
这里注意一点是,通过getaddrinfo(ipv4_str, "http", &hints, &res0);这样获取到的addr是没有端口数据的,一般都需要我们手工去设置一下,如:
addr->sin_port = htons(DEFAULT_HOST_PORT);
对于IPv4和IPv6有一个比较关键的点是,用于存储地址的数据结构,一共涉及到四种:
a、IPv4 : struct sockaddr_in, 16 个字节
b、IPv6: struct sockaddr_in6, 28 个字节
c、通用结构体1: struct sockaddr, 16个字节
d、通用结构体2: struct sockaddr_storage,128个字节
由于像socket,connect方法都是用的sockaddr *,及地址长度做为参数,而这个struct是同IPv4的长度一致的,所以对于IPv6的28个字节,显然是不够用的,所以如果要用一个通用的结构来存储IPv4和IPv6的地址,就得用sockaddr_storage了。
这四个结构的family字段的位置是一样的,所以对于sockaddr_storage可以通过,判断family字段来进行指针的转换。
比如对于connect ,可以这样写:
if (addr->ss_family == AF_INET)
{
connect(mSocketID, addr, sizeof(socketaddr_in))
}else if (addr->ss_family == AF_INET6)
{
connect(mSocketID, addr, sizeof(socketaddr_in6);
}
如果不使用底层的API,也可以对于APPLE平台编译apple比较上层API,更容易处理一些。
如使用CFStreamCreatePairWithSocketToCFHost 处理tcp连接会更简单一些。
最后,苹果帮我们列出了哪些常用的已经不适用于IPv6的结构及接口:
inet_addr()
inet_aton()
inet_lnaof()
inet_makeaddr()
inet_netof()
inet_network()
inet_ntoa()
inet_ntoa_r()
bindresvport()
getipv4sourcefilter()
setipv4sourcefilter()
这些函数都不适用于IPv6