手机支持双栈吗?

IPv6 在相当长一段时间内没能够在公众网中普及,很重要的一个原因就是各方的动力不足,虽然一直在宣传 IPv4 地址不够用了,但缝缝补补还是让互联网走了这么多年。如果抛开动力不足来看,IPv6 的普及其实是一个系统工程,需要的是端、管、云,三方的协同支持,那么我们先看下端,也就是手机的支持情况。

首先是苹果 iPhone,对于 v6 苹果早在几年前就强推 APP 对于 IPv6 only 的支持,如果不通过这个功能审核是不能上架 App Store 的。但当时对于 APP 开发者来说最为郁闷的是在国内很难找到一个商用的 IPv6 Only 环境进行测试,更多是 Mac 热点或者 WiFi APP 来模拟进行网络库的逻辑测试,在移动网下是没有办法做测试的。而这样的问题在目前 IPv6 改造期间一样面临,即在国内苹果 iPhone 在移动网下只能获得 v4 地址,没有 v6 地址。为什么呢,因为 iPhone 里面的 APN 设置是不能修改的,而内置 APN 中对于地址请求所携带的字段仅仅是 IPv4 类型,这样即使网络支持双栈,iPhone 还是只能获得v4地址,下图就是一个苹果手机在移动网内的信令请求:

 

android ipv6 网络请求 android app ipv6_首部

上面标黄的地方显示 PDN type 为 IPv4。如果双栈的话,这里的 type 类型是IPv4IPv6。在国家机构,运营商的联合推动下,苹果手机从 iOS12.1 开始已经开启了默认双栈的支持。

下面该谈到 Android 了,安卓相对来说开放一些的,大多数的手机都可以支持 APN协议编辑,并且部分手机已经缺省设置变成了 IPv4IPv6 双栈协议支持,如下截图:

 

android ipv6 网络请求 android app ipv6_首部_02

但在这里还有几个坑需要告知:

  1. 不是所有的安卓手机都可以编辑 APN 协议类型。
  2. 即使可以编辑 APN 协议类型,也不是所有的手机都会把 IPv4/IPv6 作为缺省协议。
  3. 有些手机尽管是支持双栈的,但从系统 API 里面是看不到所获得的 IPv6 地址,但如果你同时开了 WiFi,奇迹出现了,v6 地址又出来了。

碎片化的安卓带来了碎片化的双栈支持,这对客户端进行当前网络环境判断带来了很大挑战。

移动运营商现在支持双栈吗,手机得到的地址有什么玄机?

说完了端,下一步就需要看看管,即运营商到底对于 v6支持的现状如何,策略如何?如开篇所说抛开固网不谈,在移动网的场景下,三大运营商都已经开通了IPv4IPv6 的双栈支持。不过需要说明的是,这里的双栈支持管道特指下图中从空口到移动核心网这里,至于骨干网和阿里网络的双栈支持要根据各个运营商的互联情况来看。

 

android ipv6 网络请求 android app ipv6_android ipv6 网络请求_03

下面看下在某运营商网络下终端拿到的地址信息:

 

android ipv6 网络请求 android app ipv6_运营商_04

一般用户回拿到 IPv4v6 双栈地址,v6地址的 DNS 不是必须的有些省份可能没有,但在双栈情况下只要 DNS 能支持 AAAA 记录的解析查询即可。

下面就是要进入重要的 IPv6 地址获得环节了,即以上的2409开头的 v6地址客户端是怎么获得到呢,这个地址有什么玄机吗?移动网内手机和网络通讯有两个面,一个是控制面也就是俗称的信令面,这个层面 APP 是感知不到的,另一个是用户面,即APP 正常的业务数据流都走在这里。在 IPv4 only 的场景下,手机地址的获得单纯通过控制面的信令交互即可,但在 IPv4 IPv6 双栈场景下,流程就发生了一些变化。先来看信令面:

 

android ipv6 网络请求 android app ipv6_首部_05

Create Session Request 是手机发给核心网的,里面携带了几个重要的信息:

 

android ipv6 网络请求 android app ipv6_首部_06

PDN Type 字段需要是 IPv4/IPv6, PDN Address and Prefix(IPv6) 是全零。
Create Session Response 是核心网对手机的响应。

 

android ipv6 网络请求 android app ipv6_字段_07

里面包含了类似 PDN AddressPrefix 和 DNS Server 的信息,但你可能会很奇怪发现这个 Prefix 不是一个真正的 Prefix,也和手机获得到的地址格式有很多差异。

这时候就需要再看一下用户面的消息:

 

android ipv6 网络请求 android app ipv6_字段_08

根据 IPv6 地址分配规则,FE80开头的是链路单播地址,FF02::1是所有开启了 IPv6组播的主机,再来看下 Router Advertisement 消息:

 

android ipv6 网络请求 android app ipv6_字段_09

这个消息就包含了重要的 Prefix 字段,这是基于 IPv6 stateless Autonomous Address-Configuration(SLACC) 的实现,从后续的数据流可以看到,手机收到了这个64位的前缀后补充了后面的64位组合成完整的128位IPv6地址作为源地址进行正常的业务访问。不过稍微有一点疑问的是这后面的64位地址是怎么生成的,从多次测试来看每次后64位都是变化的,由于在手机上构造包需要 root,所以后续条件具备情况下会进行通过构造不同后64位的数据报文来进行测试,看看网络是不是仅靠前64位来识别用户的。

接着就面临到了最后一个问题,即这个地址有玄机吗?128位的地址一方面让人很难记,另一方面也给地址扫描造成了巨大的难度,如果这些地址都是完全随机的,那么对于那些依赖地址信息的后端业务来说将是巨大的灾难。不过运营商帮我们在一定程度上解决了这个问题,但他们的出发点是为了更好的监管,也就是在文章片头的那句世界上的每一粒沙子都有一个地址后面要加上世界上每一粒沙子都是可被追踪的。

根据工信部2014年发布的《YD/T 2682-2014 IPv6接入地址编址编码技术要求》,其中对用户设备接入地址结构的指导性意见为:

 

android ipv6 网络请求 android app ipv6_android ipv6 网络请求_10

其中:

  • PB 为 IPv6 接入地址块前缀,长度为 n 的比特串。
  • AI 是编址标识符,长度为 s+t 的比特串,包括长度为 s 的省份标识符和长度为 t 的接入类型表示符两部分。其中接入类型包括固网动态接入、固网静态接入、移动蜂窝接入。
  • CC 是区/县编码,长度为 8 位。在工信部的文件附录,明确了全国各区县的具体8位编码。
  • SSI 是子网标识符,长度为56-n-s-t。
  • IID 是接口标识符,长度64位。

国内三大运营商都基于此技术要求做了进一步的细化,这里不再描述细节,但从地址大段上来看中国移动使用的2409:8000::/20IPv6地址,中国联通使用的2408:8000::/20 IPv6地址,中国电信则有240E::/24、240E::/20、2001:0C68 ::/32、2001:07FA:0010::/48、2402:8800::/32 五块地址。

不过这里需要说明的是目前双栈仅在 4G 下开启,也就是说回到 2G 会变成 IPv4 only,这又该客户端对于当前网络环境判断增加了变数;还有就是由于目前地址具有了一定的位置属性,那么跨区县移动场景下的地址分配怎么处理还暂不明确。

Happy EyeBall 问题

在手机,网络和服务端全链路支持双栈的场景下,手机首先要面对的就是一个选择问题,即一个域名会解析出来 A 和 AAAA 记录,简化来说就是两个地址,同时返回两个地址,怎么选择?一个快,一个慢怎么选择?一个出问题怎么选择?地址选择好了才能有后续的建连,才会有业务发生,所以这个选择策略很重要。如果从体验角度出发,谁快连谁这是最简单的逻辑,但在当前 v4 网络好于 v6 的大环境下这样的逻辑只会让 v6 的普及变得更为艰难,因此针对这个问题,IET F先起了一个名字叫 Happy Eyeball,接着发布了两份 RFC 来描述推荐的处理逻辑,最新的是 RFC8305 Happy Eyeballs Version 2: Better Connectivity Using Concurrency,有兴趣的可以去阅读下,这里只摘抄出最重要的一段:

The algorithm proceeds as follows: if a positive AAAA response (a responsewith at least one valid AAAA record) is received first, the first IPv6connection attempt is immediately started. If a positive A response is receivedfirst due to reordering, the client SHOULD wait a short time for the AAAAresponse to ensure that preference is given to IPv6 (it is common for the AAAAresponse to follow the A response by a few milliseconds). This delay will bereferred to as the "Resolution Delay". The recommended value for theResolution Delay is 50 milliseconds. If a positive AAAA response is receivedwithin the Resolution Delay period, the client immediately starts the IPv6connection attempt.

简单来说就是 RFC 建议优选 IPv6,且给 AAAA 记录的返回留50毫秒的容忍期。

由于系统的限制,这样选择逻辑的落地是客户端网络库无关的,基于网络材料来看目前各系统的具体实现:

苹果 iOS:在 v4 和 v6 双协议栈的情况下,从 ios9 开始苹果会发出 A 和 AAAA 记录的 DNS 请求,如果首先收到了 DNS 的 AAAA 记录返回,那么苹果会马上发出v6 的 syn,如果首先收到了 A 记录的返回,会有一个 25ms 的定时器,如果超时了就会发送 v4 syn,如果在这个定时器内收到 AAAA 就会发送 v6 的 syn。这个机制和 Happy Eyeball 基本一致,只是等待时长不同,苹果会不会修改到 RFC 建议值未知。

Android:优选 v6,但等待时长未知。

这样的机制从理论上就会出现由于这个等待时长造成业务体验的下降。

还有NAT吗,会有PAT吗,还有防火墙吗?

在移动网只有 IPv4 的场景下,手机用户访问服务端的整个链路中必不可少的一个中间设备就是运营商的防火墙,一方面为了应对 v4 地址稀缺的问题,设备会做公私网的地址翻译,为了更进一步的复用地址,还会做端口翻译,另一方面为了安全性考虑,设备会做一些类似 ACL 的安全策略,通常只会允许出方向的访问,同时为了降低对于设备的负荷,还会做一些超时的设置,断开那些空闲较长的连接。而这样的限制对于服务端某些过度依赖地址的应用,对于端口转换不友好的应用,对于需要长期保活的应用都会产生一定的影响,那么在 IPv4v6 双栈的场景下如何呢,我做了如下的测试:在一台有公网 IPv6 地址的服务器上简单用 python 写了一个开启80端口的服务端:

Server start at:2400:3200:1000:xx:::80
wait for connection...
在手机上执行busybox telnet 2400:3200:1000:xx:: 80 命令
这是服务端打出的日志
Connected by('2409:8928:84e:995:xxxx:xxxx:xxxx:xxxx', 34102, 0, 0)

其中 Connected by ('2409:8928:84e:995:xxxx:xxxx:xxxx:xxxx 为服务端看到的手机地址,34102为对应的端口号,那么这个地址和端口号是不是手机自身的呢,去手机上看一下:

odin:/ $ busyboxnetstat
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 10.84.66.147:43623 111.13.134.131:443 CLOSE_WAIT
tcp 0 0 10.84.66.147:46051 140.205.34.21:443 ESTABLISHED
tcp 0 0 ::ffff:10.84.66.147:49856 ::ffff:112.13.64.13:5333 ESTABLISHED
tcp 0 0 ::ffff:10.84.66.147:37201 ::ffff:39.106.239.196:443 ESTABLISHED
tcp 0 0 ::ffff:10.84.66.147:55440 ::ffff:118.194.55.183:5223 ESTABLISHED
tcp 0 0 2409:8928:84e:995:3b4a:38ce:8ead:1972:34102 2400:3200:1000:xxxx:::80ESTABLISHED

从上面输出来看,运营商的中间设备没有做 NAT,也没有做 PAT,服务端看到了手机发出的原始的源地址和源端口,当然防火墙还是有的,主动从服务端还是不能访问手机。

MTU问题

从协议头来看 v4 和 v6 有一个比较重要的差异就是 Don’t Fragment bit 这个位一直开的,也就是由于一直是开的所以在 IPv6 的头里面就没有明示这个字段,如果有fragment 就会增加一个 Fragemention Header。由于网络中间支持 v6 的路由器不会对对 IPv6 包进行分片,所以如果一个包过大那么路由器会产生 ICMP6 Type2 数据包,内含 Packet Too Big (PTB) 和 MTU 信息返回给发送方,这样机制看上去比较好,但是由于中间设备可能会过滤掉 PTB 数据包造成这样的通知发送方收不到影响正常传输,因此发送方最好在开始的时候就不要发送过大的数据包,目前一个建议参考值 MTU 是1280字节。

 

android ipv6 网络请求 android app ipv6_运营商_11

未结束的结束语

IPv6 的序幕刚刚拉开,这篇文章也仅是粗浅的初步分析,抛砖引玉。随着时间的推移,文中的一些举例也可能随着网络演进或者策略更改而变化,所以若有不对的地方还请见谅,希望在后面的过程中能够积累沉淀出更多的实践和思考,提升 IPv6 下的业务体验。

IPv6 首部格式

IPv6为了减轻路由器的负担,省略了首部校验和字段。因此路由器不再需要计算校验和,从而提高了包的转发效率。

此外,分片处理所用的识别码成为可选项。为了让64位CPU的计算机处理起来更方便,IPv6的首部及可选项都由8字节构成。

android ipv6 网络请求 android app ipv6_运营商_12

版本:和IPv4 一样,由4比特构成。IPv6其版本号为6,因此在这个字段上的值为“6”。

通信量类:相当于IPv4的TOS(Type Of Service)字段,也由8比特构成。有TOS在IPv4中几乎没有什么建树,未能成为卓有成效的技术,本来计划在IPv6中删掉这个字段,不过出于今后研究的考虑还是保留了该字段。

流标号:由20比特构成,准备用于服务质量(Qos:Quality Of Service)控制。使用这个字段提供怎样的服务已经成为未来研究的课题。不适用Qos时每一位可以全部设置为0。   在进行服务质量控制的时,将流标号设置为一个随机数,然后利用一种可以设置流的协议RSVP(Resource Reservation Protocol )在路由器上进行Qos设置。当某个包在发送途中需要Qos时,需要附上RSVP预想的流标号。路由器接收到这样的IP包后现先将流标号作为查找关键字,迅速从服务质量控制信息中查找并做相应处理。此外,只有流标号、源地址以及目标地址三项完全一致时,才被认为是一个流。

有效荷载长度:有效荷载长度是指包的数据部分。IPv4的TL(Total Length)是指包含首部在内的所有长度。然而IPv6中的这个Playload Length不包括首部,只表示数据部分的长度。由于IPv6的可选项是指连接IPv6首部的数据,只有当有可选项时,此处包含可选项数据的所有长度就是Playload Length。

下一个首部:相当于IPv4中的协议字段。由8比特构成。通常表示IP的上一层协议是TCP或UDP。不过在有IPv6扩展首部的情况下,该字段表示后面第一个扩展首部的协议类。

跳数限制:由8比特构成。与IPv4中的TTL意思相同。为了强调“可通过路由器个数”这个概念,才将名字改为“Hop Limit”。数据每经过一次路由器就减1,减到0则丢弃数据。

源地址:由128比特构成,表示发送端IP地址。

目标地址:由128比特构成,表示接收端IP地址。

IPv6扩展首部:IPv6的首部长度对固定,无法将可选项将入其中,取而代之的是通过扩展首部对功能进行了有效扩展。 扩展首部通常介于IPv6首部与TCP/UDP首部中间。在IPv4中可选项长度固定为40字节,但是在IPv6中没有这样的限制。也就是说,IPv6的扩展首部可以是任意长度。扩展首部当中还可以包含扩展首部协议以及下一个扩展首部字段。