IPv6兼容解决方案

一 背景

WWDC2015苹果宣布在iOS9支持纯IPv6的网络服务,并且要求2016年提交到AppStore的应用必须兼容纯IPv6的网络,要求适配的系统版本是iOS9以上(包括iOS9),否则会有过审被拒的可能。

IPv6兼容解决方案_第1张图片

到目前为止多个阿里云客户都已提前咨询阿里云是否支持IPv6,多个阿里云客户的App送审后审核不通过。在什么场景下会有问题?是否需要阿里云公网地址及相关服务立即支持IPV6?有什么过渡方式?这就是本文档需要回答的问题。

二 简单结论

由于目前大部分服务器(包括阿里云)都没有Internet类型的IPv6地址,所以我们现在遇到的问题主要是App在IPv6-only网络下去访问IPv4-only网络的服务端,如果服务器需要兼容IPv6需要做很多软件和硬件上的全面升级。

幸好,从一开始设计IPv6就考虑到了向后兼容的问题,运营商会提供一个中间节点,使用DNS64/NAT64等技术,负责协议的转换和地址的转换,打通IPv6和IPv4之间的链路。这样我们在IPv6的环境下也是可以访问IPv4的后台资源的,我们的后台也就暂时不需要做什么变动。

对于使用域名方式访问服务端的情况,App Client端只需将网络层通信接口改造成兼容IPv6即可,App Client在IPv6-Only网络下会按照两种方式进行域名解析,如果域名配置了AAAA记录,则直接返回配置的IPv6地址,如果只有A记录,则DNS64会合成IPv6地址后返回给App Client,大部分非分区类型手机游戏和非游戏类App适合此场景。

对于使用IP方式访问服务端的情况,App Client端既需要将网络层通信接口改造成兼容IPv6,又需要实现IPv4和IPv6间的地址转换(App Client从服务器拉取的地址列表一般是IPv4类型的,连接服务端时需要用IPv6类型的IP,服务端返回包时不需要转换,因为NAT64服务已经自动做了转换),对于分区或分服类手机游戏适合此场景。

兼容IPv6的网络接口可以直接使用苹果官方提供的版本,逐一在App Client端代码中做替换即可

三 方案分析

3.1 IPV6概述

A、什么是IPV6

IPv6是Internet Protocol Version 6的缩写,简单的概括IPv6就是现行的互联网协议(IPv4)的下一代IP协议。IPv6由128位二进制数组成,可提供庞大的IP地址资源,足以让地球上每个生物乃至每厘米都能分配到一个或多个IP地址。将这128位的地址按每16位划分为一个段,将每个段转换成十六进制数字,并用冒号隔开。

IPv4地址示例:192.168.1.1

IPv6地址示例:2001:0db8:85a3:08d3:1319:8a2e:0370:7344 

B、为什么要接入IPv6

目前互联网广泛应用的IPv4技术,理论上IPv4是一个32位的二进制数的地址,可编址1600万个网络、40亿台主机。但在采用了A、B、C三类编址方式后,可用的网络地址和主机地址数目大打折扣,欧美国家掌握着核心技术,且互联网发展较早,因此拥有约3/4的IP资源。造成我国及其他发展中国家的IP地址资源不足的困局,随着中国互联网用户的不断增加和电子、网络技术的蓬勃发展,缺乏IP地址资源,将严重制约我国及其他发展中国家互联网的应用和发展。 

C、IPv6发展现状

由于从IPv4网络完全过渡到IPv6网络需要全球互联网基础设施中的网络软件和网络硬件设备以及终端设备都支持IPv6协议,这会涉及到大量的改造工作,虽然得到各国政府和各大运营商的重视和推动,但是IPv4和IPv6仍将长期共存。

D、IPV6兼容性问题

现在我们大部分服务器都是使用IPv4接入互联网的,我们要如何做兼容呢?也就是说如何做到IPv6和IPv4的兼容和相互访问?
要想使应用完全支持IPv6的环境,从协议到硬件,要做比较彻底的调整。不但客户端要做IPv6的改造,服务器也要适配IPv6,主要有以下四种对应关系,必须做好以下每一种:

    IPv4->IPv4

    IPv4->IPv6

    IPv6->IPv4

    IPv6->IPv6

要做到IPv6和IPv4完全兼容需要做很大的修改,最简单的协议上要兼容128位的IP地址,路由器、服务器等相关硬件也要升级。应苹果公司的要求,我们重点关注客户端从IPv6的网络环境访问IPv4的服务资源。

IPv6转换机制有很多种,苹果期望iOS App能够兼容DNS64/NAT64的方式。


(a) socket api支持RFC 4038—Application Aspects of IPv6 Transition

    v4 socket接口只能支持IPv4 stack

    v6 socket能支持IPv4 stack和IPv6 stack

(b) 服务器IP

    返回v4 IP

    返回v6 IP

(c) 用户本地IP stack

    IPv4-only

    IPv6-only

    IPv4-IPv6 Dual stack

(d) 各种IPv6转换机制

    NAT64/DNS64 64:ff9b::/96用于v6的本地网络通过NAT访问v4的资源:RFC6146、RFC6147。

    6to4 2002::/16用于两个拥有v4公网地址的IPv6-only子网的互相访问:RFC6343。

    Teredo tunneling 2001::/32 通过隧道的方式让两个IPv6-only子网互相访问,没有NAT问题:RFC4380。

    464XLAT用于程序只有v4地址(使用v4 socket),但是本地网络是IPv6网络,程序需要访问v4资源,类似NAT64,不过区别在于服务器是运营商提供,手机上需要安装CLAT服务:RFC6877。


还有很多兼容方案,复杂程度都很高。幸好,从一开始设计IPv6就考虑到了向后兼容的问题,运营商会提供一个中间节点,使用DNS64/NAT64等技术,负责协议的转换,打通IPv6和IPv4之间的链路。(IPv6和IPv4互通技术有很多,这里只讨论Apple要求的技术方案DNS64/NAT64)。

IPv6兼容解决方案_第2张图片

这样我们在IPv6的环境下也是可以访问IPv4的后台资源的,我们的后台也就暂时不需要做什么变动。

3.2 不同IP stack组合的处理方式

A、v4 ip + IPv4-only or IPv4-IPv6 Dual stack

在这样的情况下,我们虽然用的是v6的socket,但是必须要让socket走的是v4的协议。

::ffff:0:0/96 — This prefix is designated as an IPv4-mapped IPv6 address. With a few exceptions, this address type allows the transparent use of theTransport Layer protocols over IPv4 through the IPv6 networking application programming interface. Server applications only need to open a single listening socket to handle connections from clients using IPv6 or IPv4 protocols. IPv6 clients will be handled natively by default, and IPv4 clients appear as IPv6 clients at their IPv4-mapped IPv6 address. Transmission is handled similarly; established sockets may be used to transmit IPv4 or IPv6 datagram, based on the binding to an IPv6 address, or an IPv4-mapped address. (See also Transition mechanisms.)

从上文可以看到如果服务器地址为128.0.0.128,我们转换成IPv4-mapped IPv6 address::ffff:128.0.0.128或者纯16进制::ffff:ff00:00ff,然后赋值给sockaddr_in6.sin6_addr=”::ffff:128.0.0.128”;。这个socket虽然用了IPv6的sockaddr_in6,但实际上走的是IPv4 stack。

IPv4-mapped IPv6 address是让用户能够使用一致的socket api来访问IPv4和IPv6网络。

B、v4 ip + IPv6-only

这里我们先看看Wikipedia对NAT64/DNS64的描述:

NAT64 is a mechanism to allow IPv6 hosts to communicate with IPv4 servers. The NAT64 server is the endpoint for at least one IPv4 address and an IPv6 network segment of 32-bits, e.g., 64:ff9b::/96 (RFC 6052RFC 6146). The IPv6 client embeds the IPv4 address with which it wishes to communicate using these bits, and sends its packets to the resulting address. The NAT64 server then creates a NAT-mapping between the IPv6 and the IPv4 address, allowing them to communicate.

DNS64 describes a DNS server that when asked for a domain's AAAA records, but only finds A records, synthesizes the AAAA records from the A records. The first part of the synthesized IPv6 address points to an IPv6/IPv4 translator and the second part embeds the IPv4 address from the A record. The translator in question is usually a NAT64 server. The standard-track specification of DNS64 is in RFC 6147.[10]

There are two noticeable issues with this transition mechanism:

  • It only works for cases where DNS is used to find the remote host address, if IPv4 literals are used the DNS64 server will never be involved.
  • Because the DNS64 server needs to return records not specified by the domain owner, DNSSEC validation against the root will fail in cases where the DNS server doing the translation is not the domain owner's server.

NAT64是一种有状态的网络地址与协议转换技术,一般只支持通过IPv6网络侧用户发起连接访问IPv4侧网络资源。但NAT64也支持通过手工配置静态映射关系,实现IPv4网络主动发起连接访问IPv6网络。NAT64可实现TCP、UDP、ICMP协议下的IPv6与IPv4网络地址和协议转换。

DNS64则主要是配合NAT64工作,主要是将DNS查询信息中的A记录(IPv4地址)合成到AAAA记录(IPv6地址)中,返回合成的AAAA记录给IPv6侧用户。NAT64一般与DNS64协同工作,而不需要在IPv6客户端或IPv4服务器端做任何修改。

这里大概描述一下NAT64的工作流程,首先局域网内有一个NAT64的路由设备并且有DNS64的服务。

    a 客户端进行getaddrinfo的域名解析

    b DNS返回结果,如果返回的IP里面只有v4地址,并且当前网络是IPv6-only网路,DNS64服务器会把v4地址加上64:ff9b::/96的前缀,例如 64:ff9b::14.17.32.211。如果当前网络是IPv4-only或IPv4-IPv6,DNS64不会做任何事情。

    c 客户端拿到IPv6地址进行connect。

    d 路由器发现地址的前缀为64:ff9b::/96,知道这个是NAT64的映射,是需要访问14.17.32.211。这个时候需要进行NAT64映射,因为到外网需要转换成IPv4 stack。

    e 当数据返回的时候,按照NAT映射,IPv4回包重新加上前缀64:ff9b::/96,然后返回给客户端。

Apple的文档里面也有很详细的描述:

IPv6兼容解决方案_第3张图片

IPv6兼容解决方案_第4张图片

//NAT64 address sample

//address init

const char* ipv6_str = “64:ff9b::14.17.32.211”;

in6_addr ipv6_addr = {0};

int v6_r = inet_pton(AF_INET6, ipv6_str, &ipv6_addr);

sockaddr_in6 v6_addr = {0};

v6_addr.sin6_family = AF_INET6;

v6_addr.sin6_port = htons(80);

v6_addr.sin6_addr = ipv6_addr;

 

//socket connect

int v6_sock = socket(AF_INET6,SOCK_STREAM,IPPROTO_TCP);

std::string v6_error;

if (0 != connect(v6_sock, (sockaddr*)&v6_addr, 28))

{

      v6_error = strerror(errno);

}

 

//get local ip

sockaddr_in6 v6_local_addr = {0};

socklen_t v6_local_addr_len = 28;

char v6_str_local_addr[64] = {0};

getpeername(v6_sock, (sockaddr*)&v6_local_addr, &v6_local_addr_len);

inet_ntop(v6_local_addr.sin_family, &v6_local_addr.sin6_addr, v6_str_local_addr, 64);

close(v6_sock);


举个例子:

  1. IPv6主机发起www.abc.com的AAAA域名解析到DNS64(主机配置的DNS地址是DNS64);
  2. DNS64触发AAAA到DNS AAAA中查询;
  3. DNS AAAA返回NULL的信息到DNS64;
  4. DNS64然后触发A的申请到DNS A中查询;
  5. DNS A返回www.abc.com的A记录(1.1.1.1);
  6. DNS64合成IPv6地址(64:ff9b:1.1.1.1),返回AAAA response给IPv6主机;
  7. IPv6主机发起目的地址为64:ff9b:1.1.1.1的IPv6数据包,由于NAT64在IPv6域内通告配置的IPv6Prefix,因此这个数据包转发到NAT64设备上;
  8. NAT64执行地址转换和协议转换,目的地址转换为192.0.2.1,源地址根据地址状态转换(64:ff9b:1.1.1.1,1500)>(1.1.1.1,2000)在IPv4域内路由到IPv4 Server;
  9. 数据包返回,目的地址和端口为1.1.1.1,2000;
  10. NAT64根据已有记录进行转换,目的地址转换为2001:db8::1,源地址为加了IPv6前缀的IPv4Server地址64:ff9b:1.1.1.1,发送到IPv6主机。

这里讨论比较坑的地方,按照NAT64的规则,客户端如果没有做DNS域名解析的话,客户端就需要完成DNS64的工作。这里的关键点是,发现网络是IPv6-only的NAT64网络的情况下,我们可以自己补充上前缀64:ff9b::/96,然后进行正常的访问。

注:AAAA记录(AAAA Record)是用来将域名解析到IPv6地址的DNS记录,用户可以将一个域名解析到IPv6地址上,也可以将子域名解析到IPv6地址上。

C、v6 ip + IPv4-only

这里一般connect的时候会返回错误码network is unreachable,因为根本没有v6的协议栈,就像没有硬件设备一样,但是不排除会有系统会返回no route to host。当然,如果服务器的地址是Teredo tunneling 2001::/32,可以客户端直接做隧道。如果是6to4 2002::/16,并且客户端有RAW socket权限加上非NAT网络,这种情况下可以客户端自己做6to4的路由。 

D、v6 ip + IPv6-only or IPv4-IPv6

这里只要没有配置上,是可以直接通讯的。当然这里会涉及到一个问题,如果DNS返回上文说的6to4或Teredo tunneling或pure native IPv6 address,这样的情况下我们怎么做IP的选择呢,可以参照RFC 3484 – Default Address Selection for Internet Protocol version 6(IPv6)。

3.3 IPv6测试环境搭建

整体原理如下:

IPv6兼容解决方案_第5张图片

iOS设备需要连接Mac机创建的NAT64/DNS64的Wi-Fi,就是传说中的IPv6的网络环境,再通过有线网络、路由器,访问到IPv4的资源,就可以做到IPv6->IPv4的连接。

需要一台用非Wi-Fi方式上网的Mac电脑(10.11以上的系统)、iOS9以上(包含iOS9)设备和互联网有线接口。目的是用Mac作一个热点,然后用iPhone连接这个Wi-Fi。我们产生的是一个本地的IPv6 DNS64/NAT64网络,这项功能是OS X 10.11新加的。


a 在“系统偏好设置”界面选中“共享”的同时,要按住“option”键。

IPv6兼容解决方案_第6张图片

b 不要松开“option”键,选择“Internet Sharing”;

IPv6兼容解决方案_第7张图片

c 松开“option”键,选择“Create NAT64 Network”选框;

IPv6兼容解决方案_第8张图片

d 选择提供网络连接的网络接口设备;

IPv6兼容解决方案_第9张图片

e 选择“Wi-Fi”选框;

IPv6兼容解决方案_第10张图片

f 点击“Wi-Fi Options”并配置网络名和安全选项;

IPv6兼容解决方案_第11张图片

IPv6兼容解决方案_第12张图片

选择“Internet Sharing”选框以生效本地网络;

IPv6兼容解决方案_第13张图片

点击“Start”开始网络共享;

IPv6兼容解决方案_第14张图片

现在可以用iPhone连接上这个刚创建好的热点开始测试了,注意此时要把iPhone设成飞行模式,以保证只用Wi-Fi上网。

测试重点:

 IPv4和IPv6网络环境判断是否正确 

UDP和TCP的切换是否正确。

四  对开发同学的建议

4.1 不建议使用底层的网络API

下图展示的蓝色部分的这些API都是不存在兼容性问题的,而我们平时自己用的包括那些第三方的网络库大部分都是用的这些API:

IPv6兼容解决方案_第15张图片

大部分情况下,我们用高级的API完全能够实现我们的需求,而且高级API封装得很便于使用,很多底层的像适配IPv6的工作都已经完成,而用底层API会有大量的工作要我们自己来做,更容易产生BUG,如果确实需要用底层的POSIX Socket API,请参照RFC 4038:Application Aspects of IPv6 Transition的指导。

4.2 不要用IP地址

SCNetworkReachabilityCreateWithName 比如这个API的nodename参数不要传IP地址,而应该是域名;

4.3使用合适的数据容器

IPv6兼容解决方案_第16张图片

代码中以上对应类型都要处理。

4.4检查不兼容IPv6 DNS64/NAT64的代码

IPv6兼容解决方案_第17张图片

这些API都是只针对IPv4做处理的,换用兼容IPv4及IPv6的API。
判断当前客户端是处于IPv4-only、IPv6-only还是IPv4和IPv6并存的环境,然后分别使用不同的网络API。

4.5使用系统API去合成IPv6地址

4.6使用socket及connect进行的联网操作换用Apple提供的API

换用CoreFoundationframework及以上Apple提供的API,这样,即使我们填的是IPv4的地址,系统会在不同的网络环境自动帮我们进行地址的转换,我们不需要额外的工作(例如CFStreamCreatePairWithSocketToHost、NSURLSession)。

五 协议栈判别方法

5.1 判断客户端可用的IP stack

客户端做不同的处理的前提是需要知道客户端可用的IP协议栈。可用的IP stack类型分别是IPv4-only、IPv6-only、IPv4-IPv6 Dual stack。
我们先定义客户端可用的IP协议栈的含义:获取客户端当前能使用的IP协议栈。例如iOS在NAT64 Wi-Fi连接上的情况下,Mobile的网虽然存在IPv4的协议,但是系统是不允许使用的。iOS只能使用Wi-Fi的协议栈,在NAT64 Wi-Fi的情况下就是IPv6-only网络了。如果遇到IPv6-only网络,需要把它当作NAT64来处理,在v4 IP前添加前缀64:ff9b::/64。但是NAT64和IPv6-only不是等价的。IPv6-only网络可能支持NAT64,能访问v4的互联网资源,但是IPv6-only能访问v6的互联网资源,不支持NAT64。这里假设IPv6-only的网络都是支持NAT64的,对v4 IP进行64:ff9b::/96的处理。

5.2 DNS方案

这里的方案是直接做DNS解析,然后判断返回的IP有没有带上64:ff9b前缀来确定当前的IP协议栈。这也是唯一能够判断IPv6-only网络是否支持NAT64的方案。 


//gateway

in6_addr addr6_gateway = {0};

if (0 != getdefaultgateway6(&addr6_gateway))

       return EIPv4;

if (IN6_IS_ADDR_UNSPECIFIED(&addr6_gateway))

       return EIPv4;

in_addr addr_gateway = {0};

if (0 != getdefaultgateway(&addr_gateway))

return EIPv6;

if (INADDR_NONE == addr_gateway.s_addr || INADDR_ANY == addr_gateway.s_addr)

       return EIPv6;

//getaddrinfo

struct addrinfo hints, *res, *res0;

memset(*hints, 0, sizeof(hints));

hints.ai_family = PF_INET6;

 

六 参考文献

(1) wikipedia Transition from IPv4 :leftwards_arrow_with_hook:

(2) wikipedia NAT64 :leftwards_arrow_with_hook:

(3) wikipedia DNS64 :leftwards_arrow_with_hook:

(4) wikipedia IPv6 address :leftwards_arrow_with_hook:

(5)https://developer.apple.com/library/prerelease/content/documentation/NetworkingInternetWeb/Conceptual/NetworkingOverview/UnderstandingandPreparingfortheIPv6Transition/UnderstandingandPreparingfortheIPv6Transition.html


你可能感兴趣的:(IPv6兼容解决方案)