董涛,网易游戏高级运维工程师,主要工作方向为网易集团 DNS 的运维与开发。
张欣接,网易集团 DNS 团队负责人,负责网易域名系统的架构设计及生态建设。
一、IPv6 支持度报告
IPv6 简介
IPv6(Internet Protocol version 6,互联网通信协议第 6 版)是用于数据包交换互联网络的网络层协议,是 IETF(互联网工程任务小组 Internet Engineering Task Force,简称 IETF)设计的用来替代 IPv4 协议的互联网协议版本。
随着电子技术及网络技术的发展,计算机网络已经与人们的生活密切相关,可能身边的每一样电子设备都需要连入网络,IPv4 的地址数量已经无法满足。IPv6 的应用将彻底解决这些问题。IPv6 由 128 比特位构成,单从数量级上来说,IPv6 所拥有的地址容量是 IPv4 的约 8×10 28 倍,达到 2 128(约 3.4 × 10 38)个。这不但解决了网络地址资源数量的问题,同时也为物联网的发展提供了基础。
IPv6 地址的表达形式采用 32 个十六进制数,由两个逻辑部分组成:一个 64 位的网络前缀和一个 64 位的主机地址,主机地址通常根据物理地址自动生成,叫做 EUI-64(或者 64- 位扩展唯一标识)。例如 2001:0db8:85a3:08d3:1319:8a2e:0370:7344 是一个合法的 IPv6 地址。
IPv6 全球部署更新
-
2008 年,欧盟发布了“欧洲部署 IPv6 行动计划”
-
2009 年,日本发布《IPv6 行动计划》
-
2010 年,美国政府发布 IPv6 行动计划
-
2010 年,韩国发布“下一代互联网协议(IPv6) 促进计划”
-
2012 年,加拿大政府发布了《加拿大政府 IPv6 战略》
-
2017 年,国务院办公厅印发《推进互联网协议第六版(IPv6)规模部署行动计划》
操作系统 IPv6 支持度
应用软件 IPv6 支持度
客户端软件
1、浏览器
服务器软件
1、程序开发软件
2、数据库
总结
毋庸置疑,下一代互联网 IPv6 是万物互连,智能化时代基础网络的重要支撑协议,但是从一个只拥有 IPv4 协议的巨型网络要全面、平稳地过渡到一个纯 IPv6 网络需要一段极为漫长的时间。从报告统计的数据来看,各种基础软件和应用软件都已基本支持 IPv6。现在在国内的环境下,IPv6 的基础环境还需要完善,为此工信部也发布了
《推进互联网协议第六版(IPv6)规模部署行动计划》(http://www.miit.gov.cn/n1146290/n4388791/c6166476/content.html)
推动各单位加快支持 IPv6。
IPv6 支持度报告的数据来源是:下一代国家互联网中心在 2017 年 11 月发布的 IPv6 支持度报告
(https://www.ipv6ready.org.cn/public/download/ipv6.pdf), 感兴趣的同学可以查看原文。
二、IPv6 环境下 DNS 相关测试
背景介绍
名词简介
-
A 记录
A 记录是一个域名指向 IPv4 地址的解析结果,即最常见的记录类型, 例如 ipv6test.ntes53.netease.com. 1800 IN A 123.58.166.70
-
AAAA 记录
AAAA 是一个域名指向 IPv6 地址的解析结果。如果想要一个域名解析到 IPv6 地址,则需要设置此种类型的解析结果。同一个域名可以同时有 A 与 AAAA 两种记录类型, 例如 ipv6test.ntes53.netease.com. 1800 IN AAAA 2403:c80:100:3000::7b3a:a646
-
缓存 DNS 服务器
用户直接使用的 DNS 服务器,各种平台、操作系统上直接设置的 DNS 服务器,常见的有 8.8.8.8, 114.114.114.114
-
权威 DNS 服务器
用于域名的管理。权威 DNS 服务器只对自己所拥有的域名进行域名解析,对于不是自己的域名则拒绝应答。例如网易的权威 DNS 服务器只会响应网易域名的请求,对于其他域名,则拒绝应答。
-
双栈网络环境
双栈网络环境即客户端或服务器同时拥有 IPv4、IPv6 两种网络环境,可以简单的理解为机器上既有 IPv4 地址又有 IPv6 地址
测试场景
下文中所有测试使用的程序均为测试方法中的程序
1.目前纯 IPv4 环境下,仅新增 AAAA(IPv6) 记录之后,对已有程序的影响
假定已经存在了一个程序(C 程序、python 程序、浏览器等),通过域名访问某个服务,现在在 IPv4 环境下一切工作正常。当给这个域名增加了 AAAA 记录之后,测试对目前的程序的影响。
域名解析
HTTP 请求
客户端
结论
-
当在某域名原有的 A 记录类型的基础上新增 AAAA 记录后,原有的程序工作正常
2.客户端 IPv6/v4 双栈环境下,测试程序的行为
假定用户的环境是双栈环境,假定一个服务通过域名对外提供服务,测试这种情况下程序的行为。
域名解析
HTTP 请求
客户端
结论
-
当域名同时存在 A 与 AAAA 记录,并且网络类型为双栈网络时,绝大多数程序工作正常。仅有一种情况例外,即程序中使用了 gethostbyname 函数,同时 resolv.conf 中配置了 options inet6 时,此时程序会返回错误的解析结果
-
RFC 以及绝大多数实现方式,均回优先使用 IPv6 地址建立连接
-
双栈环境下,客户端使用 IPv4 与 IPv6 缓存 DNS 服务器获取的解析结果是一致的
3. 客户端纯 IPv6 环境下,测试能否正常工作
假定用户只有 IPv6 地址,DNS 也是使用 IPv6 地址 (DNS 必须有双栈环境,因为现在很多权威服务器没有 IPv6 地址,纯 IPv6 环境下无法正常工作),假定一个服务通过域名(同时拥有 A、AAAA 记录)对外提供服务,测试服务是否可以正常访问。
域名解析
HTTP 请求
客户端
结论
当某域名即存在 A 记录 又存在 AAAA 记录时:
-
如果程序中使用了 gethostbyname 时,程序可能会拿到错误的解析结果,取决于 resolv.conf 的配置(当配置了 option inet6 时,会获取到错误的解析结果)
-
Windows 在这种情况下,部分应用工作不正常。在指定使用 IPv6 socket 的情况下,程序工作正常。
-
根据安卓官方的描述,Android 6.0 之后的版本已经支持 IPv6,但是根据对国内大多数厂商的安卓手机的调研,目前国内安卓手机很少可以原生支持 IPv6
4. DNS 解析测试
这里测试了缓存服务器和权威服务器在各种网络环境下,优先使用的解析链路。
结论
当权威服务器和缓存服务器均支持 ipv6 时,缓存服务器优先使用 ipv6 链路进行解析,其他情况均使用 ipv4 链路进行解析。
结论
-
经过测试与查证,
gethostbyname
不支持 IPv6,使用此函数可能会拿到错误的结果或者程序抛出异常。建议使用getaddrinfo
函数取代此函数 -
目前已经存在 A 记录的域名,添加 AAAA 记录后,不管客户端与服务端的网络环境如何:
-
绝大多数情况下对客户端与服务端工作正常
-
下面一种情况下会出现工作异常:
当使用了 C 的 gethostbyname 并且在 resolv.conf 中配置了 options inet6时,此函数返回错误的结果
-
-
经过测试,双栈网络下 IPv4 与 IPv6 的优先级:
-
优先使用 IPv6 发起解析请求
-
优先使用 IPv6 请求建立连接 (TCP, UDP)
-
优先解析 A 地址记录
-
参考资料
-
Windows 8 IPv4 与 IPv6 选择的方法:Connecting with IPv6 in Windows8
(https://blogs.msdn.microsoft.com/b8/2012/06/05/connecting-with-ipv6-in-windows-8/) -
Windows 当 IPv6 不可用后的回退机制:Is there any setting for connection timeout when IPv6 fallback to IPv4?
(https://social.technet.microsoft.com/Forums/en-US/d09e938a-a594-4766-8898-3926a81fc5dc/is-there-any-setting-for-connection-timeout-when-ipv6-fallback-to-ipv4?forum=w7itpronetworking) -
目前广泛使用的 IPv4 与 IPv6 优先选择算法为 Happy Eyeballs
(https://en.wikipedia.org/wiki/Happy_Eyeballs):-
目前使用此算法的项目有:Chrome, Opera 12.10, Firefox version 13, OS X, cURL
-
此算法会优先选择 IPv6 链路使用
-
此算法的原理可参考 RFC 6555(Happy Eyeballs: Success with Dual-Stack Hosts)
(https://tools.ietf.org/html/rfc6555) -
此算法的简略工作流程如下:
-
当客户端是双栈环境时,客户端会向缓存 DNS 服务器发起域名 A 记录与 AAAA 记录的解析请求,并受到解析结果,对应下图中的 1-4
-
客户端获取到解析地址后,会同时使用 IPv4 与 IPv6 两种链路尝试建立连接,对应下图中的 6-7。当 IPv6 链路比 IPv4 链路先建立连接,或者 IPv4 已经建立连接,但是在很短的时间间隔内,IPv6 也成功建立连接后,则这两种情况下客户端应该使用 IPv6 链路完成后续的网络请求,对应图中的 8-12
-
测试方法
解析域名
C/ C ++
-
gethostbyname
Linux
Windows
getaddrinfo
- #include <stdio.h>
- #include <string.h>
- #include <stdlib.h>
- #include <netdb.h>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <arpa/inet.h>
- int lookup_host ()
- {
- struct addrinfo hints, *res;
- int errcode;
- char addrstr[100];
- void *ptr;
- memset (&hints, 0, sizeof (hints));
- hints.ai_family = AF_INET;
- errcode = getaddrinfo ("IPv6test.ntes53.netease.com", NULL, &hints, &res);
- if (errcode != 0)
- {
- perror ("getaddrinfo");
- return -1;
- }
- while (res)
- {
- inet_ntop (res->ai_family, res->ai_addr->sa_data, addrstr, 100);
- switch (res->ai_family)
- {
- case AF_INET:
- ptr = &((struct sockaddr_in *) res->ai_addr)->sin_addr;
- break;
- case AF_INET6:
- ptr = &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr;
- break;
- }
- inet_ntop (res->ai_family, ptr, addrstr, 100);
- printf ("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4,
- addrstr, res->ai_canonname);
- res = res->ai_next;
- }
- return 0;
- }
- int main (void)
- {
- lookup_host();
- }
windows
Python
-
socket.gethostbyname
- import socket
- result = socket.gethostbyname("IPv6test.ntes53.netease.com")
- print result
- getaddrinfo
- import socket
- result = socket.getaddrinfo("IPv6test.ntes53.netease.com", 0, socket.AF_INET6)
- print result
- result = socket.getaddrinfo("IPv6test.ntes53.netease.com", 0, socket.AF_INET)
- print result
- result = socket.getaddrinfo("IPv6test.ntes53.netease.com", 0, socket.AF_UNSPEC)
- print result
当不指定 socktype 时,此值默认为 socket.AF_UNSPEC
。
HTTP 请求
Python
requests 包
C++
- #include <stdio.h>
- #include <curl/curl.h>
- int main(void)
- {
- CURL *curl;
- CURLcode res;
- curl = curl_easy_init();
- if(curl) {
- curl_easy_setopt(curl, CURLOPT_URL, "http://IPv6test.ntes53.netease.com:8000");
- /* example.com is redirected, so we tell libcurl to follow redirection */
- curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
- // curl_easy_setopt(curl, CURL_IPRESOLVE_V6, 1L); // 使用 IPv6 地址
- // curl_easy_setopt(curl, CURL_IPRESOLVE_V4, 1L); // 使用 IPv4 地址
- // curl_easy_setopt(curl, CURL_IPRESOLVE_WHATEVER, 1L); // 获取系统允许的 IPv4 或者 IPv6 地址
- /* Perform the request, res will get the return code */
- res = curl_easy_perform(curl);
- /* Check for errors */
- if(res != CURLE_OK)
- fprintf(stderr, "curl_easy_perform() failed: %s\n",
- curl_easy_strerror(res));
- /* always cleanup */
- curl_easy_cleanup(curl);
- }
- return 0;
- }