本文来自OPPO互联网基础技术团队,转载请注名作者。同时欢迎关注我们的公众号:OPPO_tech,与你分享OPPO前沿互联网技术及活动。
IPv6推广已久,基础体系建设也日趋完善。加之工信部的响应号召,我们做应用体系的v6地址迁移适配势在必行。但整个迁移过程也不是一蹴而就,期间也面临着不少疑难问题亟待解决。
从协议层看,IPv4和IPv6的兼容性并不好,部署改造困难。其次,由于国内数量庞大的IPv4用户和设备,以及广泛的NAT技术应用,也使得网络结构更加复杂,对IPv6升级带来不少挑战。
最重要的是,当前并不算完备的IPv6基础环境,意味着基于v6网络而建设的服务体系,在用户使用体验层面相比v4有着较为明显可感知的劣势,这对各大应用服务商来说,是不能接受的,这也使得IPv6推进改造的动力严重不足。
所以我们从以下几个点了引入和讲解OPPO迁移适配历程:
IPv6是什么,为什么要升级IPv6
软硬件支持程度如何,该怎样去迁移适配IPv6
在IPv6网络不友好的情况下,如何降级和改善使用体验
在阐述升级IPv6的必要性之前,我们先了解下IPv6
IPv6(Internet Protocol version 6)是用于数据包交换互联网络的网络层协议,由互联网工程任务小组(IETF)于1998年设计的用来替代 IPv4 协议的互联网协议版本。相较于IPv4使用的32位,IPv6采用128位构成,其所拥有的地址容量是 IPv4 的约 8×10^28 倍,能够极大的满足网络地址资源数量问题。
那么除了量级方面,IPv6还有哪些优点呢?
IPv6 在解决了 Pv4 的地址匮乏问题的同时,还在许多方面提出了改进。与 传统的 IPv4 相比,IPv6 具有以下几方面特点及优势:
既然IPv6有如此大的优势和普及需求,那么推广和部署目标该如何达成呢?
当前阶段来看,在v6与v4共存的大环境下,IPv6的迁移适配将会是一个繁杂而又漫长的过程,我们需要一个平稳的过渡和转换步骤,以最低影响来完成升级改造。
为了向IPv6网络逐步演进,IETF提出了三种转换机制来过渡IPv4到IPv6。
双栈技术是指涉及到业务交互的从用户侧到网络侧的所有软硬件设备同时支持IPv4和IPv6两个协议栈,即双栈节点使用IPv4协议栈与IPv4节点进行通讯,使用IPv6协议栈与IPv6节点进行通讯。
双栈技术的优点是改造彻底,适用性广、用户互通性好,缺点是投资大、周期长。
隧道技术通过对报文的封装、解封装,使得两个同构网络能够在一个异构网络的两边桥接起来从而实现相互通信。简单来说,隧道机制就是在必要时将IPv6数据包作为数据封装在IPv4数据包里,使IPv6数据包能在已有的IPv4基础设施(主要是指IPv4路由器)上传输的机制。
隧道技术的优点是App应用只需要新增一个IPv6隧道服务器,应用系统本身基本不影响,方便快速部署,缺点是需用户安装相应IPv6隧道软件,普适性和方便性都有局限。
翻译类技术有NAT64、SPACE6等。翻译技术是在IPv6用户和IPv4移动端App应用之间部署协议转换设备,建立IPv6/IPv4之间地址和端口的映射关系,以实现透明的IPv6和IPv4互访。翻译技术具有改动小、部署快、投资小的优点。
针对移动设备来说,双栈技术是最适合推广和改造的,因为客户端设备硬件迭代快,随着设备的更替和系统升级,APN双栈的支持会很快推广普及开。
苹果在2015年即宣布IOS9开始支持IPv6,从16年6月开始对AppStore提交规范做了限制,至2017年6月,要求App使用IPv6才能上架。自IOS12.1开始,苹果默认开启APN双栈支持。
相对开放的Android来说,APN协议设置都是支持编辑的。并且5.0以后默认缺省设置为双栈模式。其他场景下,也可以在移动网络访问网络时,通过修改接入点(APN)中“APN协议/APN漫游协议”属性设置为“IPv4/IPv6”双栈网络使设备支持双栈。
注:是否支持编辑 APN 协议,具体需要根据不同机型来侦测判断。
关于其他设备软硬件对IPv6的支持度情况,可以参见互联网国家中心2018年软硬件关于IPv6支持度的调查报告[1]。
对于已支持双栈的手机设备,需要在应用层面也适配IPv6地址升级,通过v6 协议来完成和服务端的数据交互。
移动端App应用IPv6升级,是指经过软件层面适配IPv6, 使得在原来仅支持用户通过IPv4协议访问并获取服务的应用,同样能够通过IPv6协议来访问并获取服务。改造过程主要包括:
由于Linux内核的TCP/IP协议栈在2.2版本就已经支持IPv6了, Android是基于2.6之后的,所以默认是能够使用IPv6地址与后台交互的。
应用后台的IPv6的演进升级不做赘述,目前常见采用双栈云主机、DNS64+NAT64、SPACE6翻译等多种技术完成IPv6的升级改造,在不改变现有应用服务架构的情况下,实现移动端App应用服务全面支持IPv6。
那如何知道DNS能否返回IPv4/IPv6地址呢?
当客户端是双栈环境时,客户端在向DNS服务器请求地址解析时,会发起域名A记录和AAAA记录的解析请求,如果后台支持双栈,就拿到对应的两条解析地址结果。
如图,DNS查询基本是同时发起的 A 和 AAAA 记录的查询,然后先后获得地址解析结果。
名词解释:
A记录: 一个域名指向 IPv4 地址的解析结果,即最常见的记录类型
AAAA记录:是一个域名指向 IPv6 地址的解析结果。如果想要一个域名解析到 IPv6 地址,则需要设置此种类型的解析结果。同一个域名可以同时有 A 与 AAAA 两种记录类型
DNS 服务器:是进行域名(domain name)和与之相对应的IP地址(IP address)转换的服务器。将域名解析为ip
至此,我们解决了开头提出的前两个问题,即升级IPv6的必要性和如何在软硬件层面兼容适配IPv6。
那么,能够解析到并处理IPv6地址建链是否就意味我们完成了整个v6的升级改造工作吗?
由于当前IPv6基础建设尚未完善,连通性问题和可靠性问题不能得到有效保证,链接超时或失败会导致出现加载等待、页面错误等情况,造成用户可感知的负面体验,所以还是需要使用IPv4协议适当的做ip降级或者兜底策略。
针对 IPv6 的回退和降级策略,IETF于12年和17年分别发布了两版RFC算法来描述了关于在域名解析、地址排序和连接尝试阶段v4配合v6升级适配的详细方案,该方案成为 HappyEyeball。
HappyEyeballs 分为两版,分别是 Cisco于2012年提出的 RFC6555 版和 Apple于2017年提出的 RFC8305 版。这里参考v2版详细解读一下 从域名解析到地址排序和连接建立的过程。
先从Apple在IETF上关于 HappyEyeballs v2的简要介绍来了解一下算法概貌[2]:
The updated implementation performs the following:
- Query the DNS resolver for A and AAAA. If the DNS records are not in the cache, the requests are sent back to back on the wire, AAAA first.
- If the first reply we get is AAAA, we send out the v6 SYN immediately
- If the first reply we get is A and we're expecting a AAAA, we start a 25ms timer
- If the timer fires, we send out the v4 SYN
- If we get the AAAA during that 25ms window, we move on to address selection
- When we have a list of IP addresses (either from the DNS cache or by receiving them close together with v4 before v6), we perform our own address selection algorithm to sort them. This algorithm uses historical RTT data to prefer addresses that have lower latency
- but has a 25ms leeway: if the historical RTT of two compared address are within 25ms of each other, we use RFC3484 to pick the best one.
- Once the list is sorted, we send out the SYN for the first address and start timers based on average and variance of the historical TCP RTT. Roughly speaking, we start the second address around the same time we send out a SYN retransmission for the first address.
- The first address to reply with a SYN-ACK wins the race, we then cancel the other TCP connection attempts.
整个过程如下:
我们从 RFC8305 中摘选几个关键步骤【地址解析排序和建链】的过程来深入探究一下:
目前已经使用上述算法的项目包括:
Chrome
Openra 12.10
FireFox 13
CURL
这里选取CURL对整个HappyEyeball做个功能验证。
对 google做一下域名解析,可以看到有 IPv4 和 IPv6地址返回
nslookup -type=AAAA google.com 复制代码
curl "https://google.com" -v 复制代码
如图,双栈情况下,同时发起A记录和AAAA记录解析域名,即使A记录先返回,建链也是优先v6地址。
基于网络库Okhttp,制定一套策略来适配ip地址竞速过程,即网络层实现HappyEyeballs。
在手机端,我们如果想提前判断当前设备是否支持双栈,由此来感知是否支持IPv4/IPv6协议栈,是一个很棘手的难题。因为这个判定结果,直接影响到后续dns解析和竞速建链过程的算法抉择。
能否直接先建立连接然后来确认地址和端口呢? 答案是肯定的。
但是,如果使用TCP,就会面临连接connect过程前必须先进行三次握手建链,造成无用的网络损耗。而UDP的 connect方法就给我们的IP协议栈确认提供了可能。 因为在UDP发生真实网络数据请求和数据响应之前,他的connect方法本身只是用于检测端口是否可用,地址是否准确,并记录这些信息,返回给调用者。
//UDP接口 { connect(InetAddress, port) //尝试bind地址和端口后,并赋值。 可以通过连接本地 ipv6地址FE80::xxxx来验证协议支持 send(DatagramPacket) // c sendto() //请求数据 recvfrom() //响应数据 } 复制代码
详细实现参见 IPv6可用协议栈检测 [3]
所以我们可以通过上述方式,来提前感知设备所支持的IP协议栈,做后续DNS请求和连接竞速优化。当然,由于设备APN配置变化频率不高,我们可以将整个探测结果做本地缓存,避免重复的协议栈检测。
OKHttp的域名解析提供了dns()
方法配置,有默认解析和自定义解析两种:
InetAddress.getAllByName(host)
, 根据运营商配置会返回指定域名对应的IPv4和IPv6地址。我们可以在lookup阶段对结果做一次干预和过滤,配合网络库EventListener接口收集来的IP连接成功率和协议类型对IPv4和IPv6地址做交错排序。通过修改okhttp的请求并发控制,在StreamAllocation.findConnection()
过程增加IP建链并发竞速过程即可。
具体为,在连接的查找和尝试阶段,从连接池或地址列表首位,取v6地址发起握手建链,同时,开启一个延迟250ms后发起v4的建连的任务,如果在该时间段内v6建连成功,延迟任务直接终止,流程结束。若以外会将列表进行交换调整,使v4排在首位,发起v4连接,此时将处于竞争模式,当哪一个连接先完成建立,就将那个作为目标地址,并取消其他握手建链过程,调整列表顺序,并执行后续数据交换操作。否则,顺序遍历列表其他v6/v4组合,重复上述步骤。
其他网络连接可参照上述过程。此处附上Github上的一套HappyEyeballs C实现方案[4]。
IPv6的普及推广是一个繁重而又漫长却十分值得的过程,为保证过渡的顺利和体验层面的提升,HappyEyeballs作为一种平滑策略是非常值得肯定的。这里从IPv6说起到客户端适配的过程,尽量按照笔者的认知去尽力做详尽描述和总结提炼,但也难免存在谬误或者认知不足之处。欢迎交流沟通或者提出优化意见或建议。
[1]. 互联网国家中心2018年软硬件关于IPv6支持度的调查报告 www.ipv6ready.org.cn/public/download/ipv6.pdf
[2]. Apple关于HappyEyeballs讲解 mailarchive.ietf.org/arch/msg/v6…
[3]. IPv6可用协议栈检测 cloud.tencent.com/developer/a…
[4]. GitHub上的HappyEyeballs C实现方案 github.com/shtrom/happ…
[5]. HappyEyeballs最佳实践 segmentfault.com/a/119000002…
[6]. 谈一谈IPv6和HappyEyeballs fukun.org/post/201901…