从2016年6月1日起,iOS 应用必须支持 IPv6,否则审核将被拒。详见 Supporting IPv6 DNS64/NAT64 Networks。本文是翻译稿。从本文中可以学到有关 IPv6 过度时期的网络架构和具体 IOS 应用如何兼容的知识。
原文链接: http://www.pchou.info/ios/2016/06/05/ios-supporting-ipv6.html?utm_source=tuicool&utm_medium=referral
IPV4 地址枯竭迫在眉睫,企业和移动运营商都纷纷部署 IPv6 DNS64 和 NAT64 网络。所谓 DNS64/NAT64 网络其实就是一个 IPv6-only 的网络,通过转换,我们可以继续访问 IPv4 的内容。 根据应用的不同,我们需要不同的处理方式来适配 IPv6:
NSURLSession
,或者你使用 CFNetwork 框架,使用 hostname 来连接的话,你就不需要特意为 IPv6 做什么。但是如果你没有使用 hostname 来连接的话,那就要做下处理,具体参见 Avoid Resolving DNS Names Before Connecting to a Host 。一些主要的网络服务提供商,包括美国主要的移动运营商,都在积极推动和部署 IPv6 网络。主要原因如下:
Note: World IPv6 Launch is an organization that tracks deployment activity at a global scale. To see recent trends, visit the World IPv6 Launch website.
几十年来,众所周知,IPv4 地址最终会被耗尽。 我们通过技术手段,比如无类别域间路由(CIDR)和 网络地址转换(NAT)推迟耗尽的时间。然而在2011年1月31日,IANA(The Internet Assigned Numbers Authority,互联网数字分配机构)可分配 IPv4 地址耗尽。 美国 Internet 号码注册中心 (ARIN) 也预计在 2015 年夏天用完 IPv4 地址—去这里看倒数!(特么都 2016 年了。。)
除了要解决 IPv4 耗尽的问题,IPv6 也比 IPv4 更加高效。比如:
第四代移动通信技术 (4G) 仅基于包交换,由于 IPv4 地址的限制,为了保证 4G 开发的扩展性,需要 IPv6 的支持
IP Multimedia Core Network Subsystem (IMS) 允许一些服务通过 IP 传输,例如多媒体 SMS 消息和 VoLTE。 有些服务提供商使用 IMS 时仅支持 IPv6。
业界在向 IPv6 迁移的过程中,需要继续支持古老的 IPv4 网络,这使运营商产生了额外的操作和维护成本。
为了缓解 IPv4 地址的耗尽,许多 IPv4 网络采用 NAT 技术。尽管这种方案临时奏效,但是实践证明耗资巨大并且不够可靠。如今,随着越来越多的设备使用 IPv6,运营商必须同时支持 IPv4 和 IPv6,这种努力却是花费巨大的。
图 10-1 蜂窝移动网络分别提供 IPv4 和 IPv6 链接
理想情况下,运营商希望丢掉对 IPv4 的支持。然而,这么做会导致客户端无法访问基于 IPv4 的服务器,而 IPv4 的服务器依然是网络的重要组成部分。为了解决这个问题,大多数的网络供应商实现了一个叫 DNS64/NAT64 的转换流程。这是个纯 IPv6 网络,并通过转换也可继续访问 IPv4 的内容。
图 10-2 蜂窝移动网络用 DNS64 和 NAT64 来部署一个 IPv6 网络
在这个流程中,如果客户端向 DNS64 服务器发起一个 DNS 查询,当 DNS 找到一个基于 IPv6 的地址后,立刻返回客户端。如果无法找到对应的 IPv6 地址,DNS64 服务器将请求 IPv4 地址,然后 DNS64 服务器将 IPv4 作为前缀合成一个 IPv6 地址,并且将其返回给客户端。这样,客户端将总是获得一个 IPv6 目标地址,见图 10-3。
图 10-3 DNS64 IPv4 到 IPv6 转换过程
当客户端向服务端发送请求时,目标地址为合成后的 IPv6 地址会自动由 NAT64 网关路由过去。对于请求,网关作的是 IPv6 到 IPv4 的转换。同样的,对于服务器响应,网关作的是 IPv4 到 IPv6 的转换。见图 10-4
图 10-4 DNS64/NAT64 转化方案的流程
对 IPv6 DNS64/NAT64 网络的兼容性,将是 App Store 的提交时的必须条件,所以兼容对于 app 来说是相当重要的。好消息是,大多数 app 已经是 IPv6 兼容的了。对于这些 app,进行定期的回归测试依旧是必要的。对于那些 IPv6 不兼容的应用在面对 DNS64/NAT64 网路时可能遇到麻烦。幸运的是,解决问题通常很简单,下面章节会讨论这个问题。
有几个导致应用无法支持 IPv6 的场景。本节描述如何解决这些问题。
SIP
,FTP
,WebSockets
,P2PP
,都可能在协议的报文中包含了 IP 地址。例如,FTP
参数命令DATA
PORT
PASSIVE
的交换信息中包含了 IP 地址。类似的,IP 地址值可能出现在SIP
的头部,像To
FROM
Contact
Record-Route
以及Via
。参见Use High-Level Networking Frameworks和Don’t Use IP Address Literals
socket
和其他的低层次网络 API,比如gethostbyname
gethostbyname2
和inet_aton
。这些 API 很容易因为错误使用而仅支持 IPv4。比如,域名解析时使用AF_INET
地址簇,而不是AF_UNSPEC
地址簇。参见Use High-Level Networking Frameworks
unit32
,in_addr
,sockaddr_in
这种 32 位或更小的容器来存储地址。参见Use Appropriately Sized Storage Containers
附上下面的指导来确保 IPv6 DNS64/NAT64 的兼容性。
app 请求网络时,可以构建在高层次的网络框架上,也可以使用底层的POSIX
兼容的socket
接口。在多数情况下,相比底层接口,高层次的接口效率高一些,兼容性好,容易使用,不容易掉入通常的编程错误陷阱中。
图 10-5 网络框架和 API 层次
WebKit
。此框架提供一系列的类用来在窗口上显示 web 内容,而且实现了浏览器特性,诸如:链接、前进后退管理、最近访问历史。WebKit 将加载网页的流程简化了,包括异步地从 HTTP 服务器上请求网页内容,这些服务器响应的数据包可能一点点送达,也可能以随机的顺序到达,甚至可能由于网络错误收不全。详见WebKit Framework Reference
Cocoa URL loading system
。这个系统用于简单地通过网络发送和接收数据,却不需要提供显示的 IP 地址。数据的发送和接收使用这几个类中的一个:NSURLSession
NSURLRequest
NSURLConnection
,这些类使用NSURL
对象。NSURL
对象允许你操作 URL。创建一个NSURL
对象时使用initWithString:
方法,并传入一个指定的 URL。调用NSURL
类的checkResourceIsReachableAndReturnError:
方法检测目标主机的可达性。详见URL Session Programming Guide
CFNetwork
。这个核心服务框架提供了一个抽象网络协议的库。这个库提供了大量易用的网络操作,比如 BSD socket,DNS 解析,处理 HTTP/HTTPS。调动CFHostCreateWithName
方法,避免显示的使用 IP 地址来标识主机。调用CFStreamCreatePairWithSocketToCFHost
与主机建立 TCP 链接。详见CFNetwork Programming Guide中的CFNetwork Concepts
如果你需要使用低层次的 socket 接口,参看如下指导:RFC4038: Application Aspects of IPv6 Transition
Getting Started with Networking, Internet, and Web和Networking Overview提供详细的网络框架 API 的说明
在许多 API 中请确保不再使用点分十进制表示的 IPv4 地址,例如getaddrinfo
或SCNetworkReachabilityCreateWithName
。取而代之,应该使用高层次网络框架和地址无关的 API,例如在使用getaddrinfo
和getnameinfo
时,传入主机名或域名。详见:getaddrinfo(3) Mac OS X Developer Tools Manual Page 和 getnameinfo(3) Mac OS X Developer Tools Manual Page。
从 IOS9 何 OSX10.11 开始,
NSURLSession
和CFNetwork
会在本地自动将 IPv4 的地址合成 IPv6 地址,便于与 DNS64/NAT64 通信。不过,你依旧不该使用 IP 地址串。
检测网络可达性的 API(参见SCNetworkReachability Reference) 用来在遇到连接异常时进行诊断。许多 app 错误的使用了 API,它们往往通过调用SCNetworkReachabilityCreateWithAddress方法,并将 IPv4 地址0.0.0.0
作为参数传入,来不断检查网络连接,实际表示是否至少可达一个路由 (which indicates that there is a router on the network)。然而,即使有这样的路由也不保证互联网的连接存在。总之,避免进行网络可达性的检测。只需要直接进行连接,并且优雅的处理失败的情况。如果你确实需要检测网络可用性,需避免使用SCNetworkReachabilityCreateWithAddress,而是调用SCNetworkReachabilityCreateWithName,并传入主机名。
有些 app 还在调用SCNetworkReachabilityCreateWithAddress的时候传入 IPv4 地址169.254.0.0
(一个自动分配的本地 IP),试图检测 Wi-Fi 连接。若要检测 Wi-Fi 或蜂窝移动网络连接,参见网络可达标识kSCNetworkReachabilityFlagsIsWWAN
。
使用 Storage Container 结构,如sockaddr_storage
,用以有足够的空间存放 IPv6 地址。
查找并删除 IPv4 相关的 API,如:
如果你处理的 IPv4 的类型,去报同时处理对应的 IPv6 类型
IPv4 | IPv6 |
---|---|
AF_INET | AF_INET6 |
PF_INET | PF_INET6 |
struct in_addr | struct in_addr6 |
struct sockaddr_in | struct sockaddr_in6 |
kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6 |
如果你的 app 需要连接到仅支持 IPv4 的服务器,且不使用 DNS 域名解析,请使用getaddrinfo
处理 IPv4 地址串 (译注:getaddrinfo 可通过传入一个 IPv4 或 IPv6 地址,得到一个 sockaddr 结构链表)。如果当前的网络接口不支持 IPv4,仅支持 IPv6,NAT64 和 DNS64,这样做可以得到一个合成的 IPv6 地址。
代码 10-1 展示了如何用getaddrinfo
处理 IPv4 地址串。假设你内存中有一个 4 个字节的 IPv4 地址串 (如{192,0,2,1}),这个示例代码将之转化为字符串 (“192.0.2.1”),使用getaddrinfo
合成一个 IPv6 地址结构 (struct sockaddr_in6
包含 IPv6 地址串为” 64:ff9b::192.0.2.1”),然后尝试连接到这个 IPv6 地址。
代码 10-1 使用getaddrinfo
处理 IPv4 地址串
#include <sys/socket.h>
#include <netdb.h>
#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);
从 IOS9.2 和 OSX10.11.2 开始合成 IPv6 地址的功能才被加入到
getaddrinfo
。不过,这么用不会对旧的系统产生兼容性问题。参见 getaddrinfo(3) Mac OS X Developer Tools Manual Page.
大多数蜂窝移动供应商已经开始部署 IPv6 DNS64/NAT64 网络,测试这种网络最简单的的方法是用 Mac 建立一个本地的 IPv6 DNS64/NAT64 网络。你可以将其他设备链接到这个网络来测试。见图 10-6
提示:IPv6 DNS64/NAT64 网络仅在 OSX 10.11 及更高版本上可以设置。除此之外,基于 Mac 来建立的 IPv6 DNS64/NAT64 网络仅与支持RFC6106: IPv6 Router Advertisement Options for DNS Configuration的客户端设备兼容。如果你的设备不是 iOS 或 OSX 设备,请确保它支持 RFC。还需注意的是:不同于运营商提供的 DNS64/NAT64 网络,基于 Mac 系统的 IPv6 DNS64/NAT64 总是返回合成后的 IPv6 地址。因此,它不能用于访问你本地网络以外的纯 IPv6 网络。
图 10-6 本地的基于 Mac 的 IPv6 DNS64/NAT64 网络
使用你的 Mac 建立本地的 IPv6 Wi-Fi 网络
确保你的 Mac 连接到互联网,但不是通过Wi-Fi
启动System Preferences
按住Option
键 (标准键盘是 Alt 键) 点击Sharing
,不要放开 Option 键。
图 10-7 打开 Sharing preferences
在共享列表中选择Internet Sharing
图 10-8 配置 Internet Sharing
放开Option
键
勾选Create NAT64 Network
复选框
图 10-9 启用一个本地 IPv6 NAT64 网络
选择你用于互联连接的网络接口,例如蓝牙局域网 (译者注:通常这里 mac 用以太网连接互联网,很少有用蓝牙的)
图 10-10 选择共享的网络接口
选择 Wi-Fi 复选框
图 10-11 通过 Wi-Fi 开启共享
点击Wi-Fi Options
,配置你网络的网络名和安全选项
图 10-12 设置 Wi-Fi 网络选项
勾选Internet Sharing
复选框启动你的本地网络
图 10-14 启动网络共享
当弹出确认是否开启共享时,点击Start
图 10-15 开启网络共享
一旦共享启动后,你应该可以看到一个绿色的状态指示灯和一段话说明共享已开启。在 Wi-Fi 菜单中,你同样将看到一个小的向上的箭头,表示网络共享已经开启。现在你拥有了一个 IPv6 NAT64 的网络,其他设备可以连接这个网络来测试 app。
图 10-16 网络共享开启图标
提示:为了确保测试时严格使用本地的 IPv6 网络,请确认测试设备没有其他的网络接口正在使用。例如,如果你在测试 iOS 设备,确保蜂窝移动网络服务是禁用的,这样才能确保通过 Wi-Fi 连接。