IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

栏目: 服务器 · 发布时间: 5年前

内容简介:可直接点击上方蓝字(网易游戏运维平台)关注我们,获一手游戏运维方案

可直接点击上方蓝字

(网易游戏运维平台)

关注我们,获一手游戏运维方案

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

董涛,网易游戏高级运维工程师,主要工作方向为网易集团 DNS 的运维与开发。

张欣接,网易集团 DNS 团队负责人,负责网易域名系统的架构设计及生态建设。

IPV6 支持度报告和 IPv6 环境下 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 全球部署更新

随着 IPv4 地址资源的逐步枯竭,网络安全及网络服务质量的要求不断提升,世界主要国家已充分认识到现阶段部署 IPv6 的紧迫性和重要性,各国政府纷纷出台国家发展战略,制定明确的发展路线图和时间表来积极推进 IPv6 的大规模商用部署。

  • 2008 年,欧盟发布了“欧洲部署 IPv6 行动计划”

  • 2009 年,日本发布《IPv6 行动计划》

  • 2010 年,美国政府发布 IPv6 行动计划

  • 2010 年,韩国发布“下一代互联网协议(IPv6) 促进计划”

  • 2012 年,加拿大政府发布了《加拿大政府 IPv6 战略》

  • 2017 年,国务院办公厅印发《推进互联网协议第六版(IPv6)规模部署行动计划》

操作系统 IPv6 支持度

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

应用软件 IPv6 支持度

客户端软件

  1. 浏览器

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

服务器软件

  1. 程序开发软件

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

  1. 数据库

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

毋庸置疑,下一代互联网 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 记录之后,测试对目前的程序的影响。

域名解析

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

HTTP 请求

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

客户端

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

结论

  • 当在某域名原有的 A 记录类型的基础上新增 AAAA 记录后,原有的程序工作正常

2. 客户端 IPv6/v4 双栈环境下,测试程序的行为

假定用户的环境是双栈环境,假定一个服务通过域名对外提供服务,测试这种情况下程序的行为。

域名解析

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

HTTP 请求

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

客户端

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

结论

  • 当域名同时存在 A 与 AAAA 记录,并且网络类型为双栈网络时,绝大多数程序工作正常。仅有一种情况例外,即程序中使用了 gethostbyname 函数,同时 resolv.conf 中配置了 options inet6 时,此时程序会返回错误的解析结果

  • RFC 以及绝大多数实现方式,均回优先使用 IPv6 地址建立连接

  • 双栈环境下,客户端使用 IPv4 与 IPv6 缓存 DNS 服务器获取的解析结果是一致的

3. 客户端纯 IPv6 环境下,测试能否正常工作

假定用户只有 IPv6 地址,DNS 也是使用 IPv6 地址 (DNS 必须有双栈环境,因为现在很多权威服务器没有 IPv6 地址,纯 IPv6 环境下无法正常工作),假定一个服务通过域名(同时拥有 A、AAAA 记录)对外提供服务,测试服务是否可以正常访问。

域名解析

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

HTTP 请求

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

客户端

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

结论

当某域名即存在 A 记录 又存在 AAAA 记录时:

  • 如果程序中使用了 gethostbyname 时,程序可能会拿到错误的解析结果,取决于 resolv.conf 的配置(当配置了 option inet6 时,会获取到错误的解析结果)

  • Windows 在这种情况下,部分应用工作不正常。在指定使用 IPv6 socket 的情况下,程序工作正常。

  • 根据安卓官方的描述,Android 6.0 之后的版本已经支持 IPv6,但是根据对国内大多数厂商的安卓手机的调研,目前国内安卓手机很少可以原生支持 IPv6

4. DNS 解析测试

这里测试了缓存服务器和权威服务器在各种网络环境下,优先使用的解析链路。

IPv6 支持度报告和 IPv6 环境下 DNS 相关测试

结论

当权威服务器和缓存服务器均支持 ipv6 时,缓存服务器优先使用 ipv6 链路进行解析,其他情况均使用 ipv4 链路进行解析。

结论

  • 经过测试与查证, gethostbyname 不支持 IPv6,使用此函数可能会拿到错误的结果或者程序抛出异常。建议使用 getaddrinfo 函数取代此函数

  • 目前已经存在 A 记录的域名,添加 AAAA 记录后,不管客户端与服务端的网络环境如何:

    • 绝大多数情况下对客户端与服务端工作正常

    • 下面一种情况下会出现工作异常:

  1. 当使用了 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)

      • 此算法的简略工作流程如下:

    1. 当客户端是双栈环境时,客户端会向缓存 DNS 服务器发起域名 A 记录与 AAAA 记录的解析请求,并受到解析结果,对应下图中的 1-4

    2. 客户端获取到解析地址后,会同时使用 IPv4 与 IPv6 两种链路尝试建立连接,对应下图中的 6-7。当 IPv6 链路比 IPv4 链路先建立连接,或者 IPv4 已经建立连接,但是在很短的时间间隔内,IPv6 也成功建立连接后,则这两种情况下客户端应该使用 IPv6 链路完成后续的网络请求,对应图中的 8-12

                       DNS Server                  Client                  Server
                       |                          |                       |
                 1.    |<--www.example.com A?-----|                       |
                 2.    |<--www.example.com AAAA?--|                       |
                 3.    |---192.0.2.1------------->|                       |
                 4.    |---2001:db8::1----------->|                       |
                 5.    |                          |                       |
                 6.    |                          |==TCP SYN, IPv6=======>|
                 7.    |                          |--TCP SYN, IPv4------->|
                 8.    |                          |<=TCP SYN+ACK, IPv6====|
                 9.    |                          |<-TCP SYN+ACK, IPv4----|
                10.    |                          |==TCP ACK, IPv6=======>|
                11.    |                          |--TCP ACK, IPv4------->|
                12.    |                          |--TCP RST, IPv4------->|

    测试方法

    解析域名

    C/ C ++

    • gethostbyname

      • Linux

            #include <stdio.h>
            #include <netdb.h>
            #include <arpa/inet.h>
            int main(void)
            {
                int i = 0;
                char str[32] = {0};
                 struct hostent* phost = NULL;
                phost = gethostbyname("IPv6test.ntes53.netease.com");
                printf("%s", inet_ntoa(*((struct in_addr*)phost->h_addr))); 
                
                return 0;
            }
        
      • Windows

            #include <winsock.h>
            #include <Windows.h>
            #include <stdio.h>
            #pragma comment (lib, "ws2_32.lib")
            int main(void) {
                WSADATA wsaData = {0,};
                struct in_addr addr = {0,};
                struct hostent *res;
                int i = 0;
                WSAStartup(MAKEWORD(2, 2), &wsaData);
                res = gethostbyname("IPv6test.ntes53.netease.com.");
                while (res->h_addr_list[i] != 0) {
                    addr.s_addr = *(u_long *) res->h_addr_list[i++];
                    printf("IP Address: %s\n", inet_ntoa(addr));
                }
                WSACleanup();
        }
        
    • 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

            #define WIN32_LEAN_AND_MEAN
            #define _WIN32_WINNT 0x501
            #include <windows.h>
            #include <winsock2.h>
            #include <stdio.h>
            #include <string.h>
            #include <stdlib.h>
            #include <sys/types.h>
            #include <ws2tcpip.h>
            #pragma comment (lib, "Ws2_32.lib")
            // int iResult;
            WSADATA wsaData;
            int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
            int inet_pton(int af, const char *src, void *dst)
            {
                struct sockaddr_storage ss;
                int size = sizeof(ss);
                char src_copy[INET6_ADDRSTRLEN+1];
                ZeroMemory(&ss, sizeof(ss));
                /* stupid non-const API */
                strncpy (src_copy, src, INET6_ADDRSTRLEN+1)
                src_copy[INET6_ADDRSTRLEN] = 0;
                if (WSAStringToAddress(src_copy, af, NULL, (struct sockaddr *)&ss, &size) == 0) {
                    switch(af) {
                        case AF_INET:
                            *(struct in_addr *)dst = ((struct sockaddr_in *)&ss)->sin_addr;
                            return 1;
                        case AF_INET6:
                            *(struct in6_addr *)dst = ((struct sockaddr_in6 *)&ss)->sin6_addr;
                            return 1;
                    }
                }
                return 0;
            }
            const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
            {
                struct sockaddr_storage ss;
                unsigned long s = size;
                ZeroMemory(&ss, sizeof(ss));
                ss.ss_family = af;
                switch(af) {
                    case AF_INET:
                        ((struct sockaddr_in *)&ss)->sin_addr = *(struct in_addr *)src;
                        break;
                    case AF_INET6:
                        ((struct sockaddr_in6 *)&ss)->sin6_addr = *(struct in6_addr *)src;
                        break;
                    default:
                        return NULL;
                }
                /* cannot direclty use &size because of strict aliasing rules */
                return (WSAAddressToString((struct sockaddr *)&ss, sizeof(ss), NULL, dst, &s) == 0)?
                       dst : NULL;
            }
            int lookup_host ()
            {
                struct addrinfo hints, *res;
                int errcode;
                char addrstr[100];
                void *ptr;
                memset (&hints, 0, sizeof (hints));
                hints.ai_family = AF_INET6;
                errcode = getaddrinfo ("IPv6test.ntes53.netease.com", NULL, &hints, &res);
                if (errcode != 0)
                {
                    perror ("getaddrinfo");
                    printf("%d",errcode);
                    return -1;
                }
                while (res)
                {
                    // inet_ntop (res->ai_family, res->ai_addr->sa_data, addrstr, 100);
                    sockaddr_in in1;
                    memcpy(&in1.sin_addr, res->ai_addr->sa_data, sizeof(res));
                    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);
                    // sockaddr_in6 in;
                    // memcpy(∈.sin6_addr, ptr, sizeof(ptr));
                    printf ("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4,
                           addrstr, res->ai_canonname);
                    //printf ("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4,
                    //        inet_ntoa(in.sin6_addr), res->ai_canonname);
                    res = res->ai_next;
                }
                return 0;
            }
            int main (void)
            {
                printf("start\n");
                lookup_host();
        }
            }
        

    Python

    • socket.gethostbyname

      import socket
      result = socket.gethostbyname("IPv6test.ntes53.netease.com")
      print result
      
    • getaddrinfo

      当不指定 socktype 时,此值默认为 socket.AF_UNSPEC

      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
      

    HTTP 请求

    Python

    requests 包

    import requests
    response = requests.get("http://IPv6test.ntes53.netease.com:8000", stream=True)
    print response.raw._fp.fp._sock.getpeername()
    

    C++

    libcurl

    #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;
    }
    

    往期精彩

    MongoDB 4.0 事务实现快速上手

    MongoDB Change streams 与数据订阅同步

    (一)深入 Openflowplugin 源码分析 Openflow 握手过程

    (二)深入 Openflowplugin 源码 Switch 生命周期对象

    人工智障入门

    IPv6 支持度报告和 IPv6 环境下 DNS 相关测试


    以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

    查看所有标签

    猜你喜欢:

    本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

    算法神探

    算法神探

    [美] 杰瑞米·库比卡 / 啊哈磊、李嘉浩 / 电子工业出版社 / 2017-2 / 65

    《算法神探:一部谷歌首席工程师写的CS小说》围绕程序设计典型算法,精心编织了一个扣人心弦又趣味横生的侦探缉凶故事。小说主人公运用高超的搜索技巧和精深的算法知识,最终识破阴谋、缉拿元凶。其间,用二分搜索搜查走私船、用搜索树跟踪间谍、用深度优先搜索逃离监狱、用优先队列开锁及用最佳优先搜索追寻线索等跌宕起伏又富含算法精要的情节,让读者在愉悦的沉浸式体验中快速提升境界,加深对程序世界的理解。《算法神探:一......一起来看看 《算法神探》 这本书的介绍吧!

    HTML 压缩/解压工具
    HTML 压缩/解压工具

    在线压缩/解压 HTML 代码

    JSON 在线解析
    JSON 在线解析

    在线 JSON 格式化工具

    UNIX 时间戳转换
    UNIX 时间戳转换

    UNIX 时间戳转换