IPv6 使用及注意事项

By | 2018-07-19

本文介绍常用工具及软件使用 ipv6 时候的注意事项, 以及应用程序支持 ipv6 需要进行的检查列表. 下文中提到的混合服务表示工具可以同时服务 ipv4 和 ipv6, 很多软件在代码层做了 AF_INETAF_INET6 标识的判断, 数据可以复用同一个 socket, 比如下面提到的 nginx, tomcat, resin 这三个软件是混合服务的, 即便 ipv6 请求连接了 nginx, nginx 到 tomcat, resin 也可以用 ipv4 处理, 对已有的架构影响不大, 不过一些获取 ipv6 地址, 存储访问地址的应用需要额外修改下; 独立服务表示需要开启单独的选项指定以区别 ipv4 和 ipv6, 这种情况下就只能 ipv4, ipv6 各自单独服务, 互不影响, 这样对一些较老的工具软件冲击较大, 需要做额外的开发支持. 另外下文并没有深入介绍 linux 系统对 ipv6 的具体支持, 更多信息可参考: linux ipv6 howto .

通用 IPv6 介绍

IPv6 地址结构

IPv6地址由被划分为8个16位块的128位组成,然后将每个块转换为由冒号符号分隔的4位十六进制数字, 下面给出的是以二进制格式表示并被划分为八个16位块的128位IPv6地址:

 0010000000000001 0000000000000000 0011001000111000 1101111111100001 ‭0000000001100011‬ 0000000000000000 0000000000000000 ‭1111111011111011‬

每个块被转换为十六进制并由 : 符号分隔:

 2001:0000:3238:DFE1:0063:0000:0000:FEFB

IPv6地址缩减规则1 — 丢弃前导0:(在块5,0063中,可以省略前导的两个0,例如(第五块):)

 2001:0000:3238:DFE1:63:0000:0000:FEFB

IPv6地址缩减规则2 — 如果两个或多个块包含连续零,则省略它们并用双冒号 ::替换,例如(第6和第7块):

 2001:0000:3238:DFE1:63::FEFB

IPv6地址缩减规则3 — 连续的零块只能被 :: 替换一次。如果地址中仍有零块,它们可以缩小到一个零,例如(第二块):

 2001:0::3238:DFE1:63::FEFB

IPv6 地址分类

IPv6 接口ID(Interface ID)

IPv6有三种不同类型的单播地址方案,地址的后半部分(最后64位)始终用于接口ID, 系统的MAC地址由48位组成并以十六进制表示,MAC地址被认为是在世界范围内唯一分配的,接口ID利用MAC地址的这种唯一性生成, 主机可以使用IEEE的扩展唯一标识符(EUI-64)格式自动配置其接口ID

主机将其自己的MAC地址划分为两个24位的半部分,然后16位十六进制值 FFFE 被夹在这两个MAC地址的两半之间产生EUI-64接口ID, 如主机MAC地址为: 52:54:00:6a:c1:8f

OUI NIC Specific
52 54 00 6a c1 8f

EUI-64 ID转换为IPv6接口标识符
在前24位 52 54 00 和 后24位 6A C1 8F 之间插入 FF FE :

52 54 00 FF FE 6A C1 8F

EUI-64 ID的最高有效的第7位(二进制位)做反转:

52(HEX) ->  0101 0010(BIN) -> 0101 0000(BIN第7位反转) -> 50(HEX)

EUI-64 ID转换后的接口ID为:

5054:00FF:FE6A:C18F
IPv6 全局单播地址 (Global Unicast Address)

此地址类型等同于IPv4的公共地址, IPv6中的全球单播地址是全局可识别的和唯一可寻址的:

Blobal Routing Prerfix Subnet ID Interface ID
48 Bits 16 Bits 64 Bits

全局路由前缀最高有效48位指定为全局路由前缀,分配给特定的自治系统。 全局路由前缀的三个最高有效位始终设置为 001 .

 2xxx:xxxx:xxxx:xxxx:InterfaceID
IPv6 链路本地地址(link local address)

自动配置的IPv6地址称为链路本地地址, 链路本地地址始终以 FE80 开头,前16位总是设置为 1111 1110 1000 0000(FE80),接下来的48位设置为0,后64位设置为 Interface ID:

16 Bits 48 Bits 64 Bits
1111 1110 1000 0000 0000……0000 Interface ID
FE80 :: Interface ID
1111 1110 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 InterfaceID
FE80:0000:0000:0000:5054:00FF:FE6A:C18F (Interface ID)
FE80::5054:00FF:FE6A:C18F (Interface ID)

设计链路本地地址的目的是为了用于自动地址配置及邻居发现或无路由器存在的单链路寻址等,这些地址不可路由,因此路由器不会将这些地址转发到链路之外.

IPv6 站点本地地址(site local address

站点本地地址终以 FEC0 开头, 前16位总是设置为 1111 1110 1100 0000(FEC0),接下来的32位设置为0,再下来的16位设置为 Subnet ID,最后64位设置为 Interface ID:

16 Bits 32 Bits 16 Bits 64 Bits
1111 1110 1100 0000 0000…..0000 Subnet ID Interface ID
FEC0 :: Subnet ID Interface ID
1111 1110 1100 0000 0000 0000 0000 0000 0000 0000 0000 0000 SubnetID InterfaceID
FEC0:0000:0000:XXXX(Subnet ID):5054:00FF:FE6A:C18F (Interface ID)
FEC0::XXXX(Subnet ID):5054:00FF:FE6A:C18F (Interface ID)

站点本地地址的设计目的是为了用于无需全球前缀的站点内部寻址,这些地址不可路由,因此路由器不会将这些地址转发到链路之外, 类似 ipv4 中的私网地址, 目前已弃用.

IPv6 唯一本地地址 (Unique Local Address)

唯一本地地址在全球 Internet 中不可路由, 仅在站点内部可路由, 用于站点内部通信, 其独立于ISP,任何人都可以随意使用, rfc4139 中定义了 ULA 地址, 其在构建 IPv6 VPN 的应用中使用较多.

7 bits 1 40 bits 16 bits 64 bits HEX
Prefix L Global ID Subnet ID Interface ID
1111 110 0 x40 bits Subnet ID Interface ID FC00/8 L位为0未定义
1111 110 1 x40 bits Subnet ID Interface ID FD00/8 L位为1已定义

Prefix 前缀固定为 FC00::/7, 指明地址为唯一本地地址; L:指明全球 ID 的产生方式, 置 1 表示本地生成全球 ID; 置 0 未定义, 留作将来用; Gloabl ID: 占 40 bit, 由伪随机算法生成, 保证很高的全球唯一性概率(以减少当服务器合并或数据包误传到网络时碰撞的风险); Subnet ID: 划分子网时使用; Interface Id: 一般由上述的 EUI-64 构成;

此地址只可在一群服务器中转发,定义在RFC 4193中,用来取代站点本地地址。
这些地址除了只能用于本地区域外,还具备全域性的范畴,这点违反了唯一本地地址所取代的站点本地地址的定义。

IPv6 特殊地址

一般专用地址

IPv6 地址 IPv4 地址 描述
::/128 未指定地址
::/0 0.0.0.0/0 默认路由地址
::1/128 127.0.0.1 本地环回地址

路由协议的保留组播地址 (按照与IPv4相同的规则保留地址)

IPv6 地址 路由协议
FF02::5 OSPFv3
FF02::6 OSPVv3 Designated Routers
FF02::9 RIPng
FF02::A EIGRP

保留路由器/节点的多播地址

IPv6 地址 Scope 范围
FF01::1 All Nodes in interface-local
FF01::2 All Routers in interface local
FF02::1 All Nodes in link-local
FF02::2 All Routers in link-local
FF05::2 All routers in site-local

IPv4嵌入IPv6地址
IPv6地址可以将一个IPv4地址内嵌进去,并且写成IPv6形式和平常习惯的IPv4形式的混合体, IPv6有两种内嵌IPv4的方式:IPv4映像地址和IPv4兼容地址.

IPv4映像地址, 用于 ipv4 与 ipv6 的互通:

80 Bits 16 Bits 32 Bits
0000……0000 FFFF IPv4 Address

IPv4兼容地址, 用于 ipv4 兼容 ipv6 的自动隧道:

80 Bits 16 Bits 32 Bits
0000……0000 0000 IPv4 Address

无状态 NAT64 地址
stateless NAT64 应用地址, 用于 ipv6 访问 ipv4, 运营商可以修改 NAT64 的前缀:

64:ff9b::/32

ipv6 header 表示

详见: rfc2460 , ipv6 的头信息比 ipv4 的简单了很多, 条目较少, 从下面的 header 结构中可以看到整个 ipv6 头部共占 40 字节. 各字段表示如下:

    Version: 协议版本号, 占 4 bit, ipv4 为 4, ipv6 为 6;

    Traffic Class: 流量类别字段, 占 8 bit. 类似 ipv4 中的 Type of Service 字段. 节点或路由转发时通过该
字段区分不同的类别而提供不同的服务;

    Flow Label: 流标签, 占 20 bit. 数据包发送的时候源端可以通过该字段标记报文的序列, 路由处理该报文的时
候会做特殊处理, 该字段适用于对服务质量和实时传输比较高的场景;

    Payload Length: ipv6 数据包中净数据的大小, 即整个 ipv6 报文中除过 ipv6 header 长度的大小, 占 16 bit, 
如果没有扩展字段的话, 在 tcp 协议中, payload length 即为后续 tcp 报文的大小, payload length 减去 tcp 头
长度即为数据长度;

    Next Header: 用来识别跟随在 ipv6 之后的报文头部格式, tcp 即为 tcp, udp 为 udp, icmp 为 icmpv6; 如果
包含私有扩展则包含私有扩展的信息, 私有扩展头部紧跟在 ipv6 header 后面. Next header 共占 8 bit;

    Hop Limit: 节点每转发一次该报文该字段就做减一操作, 为 0 时则丢弃该报文;

    Source Address: 源 ipv6 地址, 占 128 bit 即 4 个字节;

    Destination Address: 目的 ipv6 地址, 占 128 bit 即 4 个字节;

header 表示:

   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |Version| Traffic Class |           Flow Label                  |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |         Payload Length        |  Next Header  |   Hop Limit   |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                         Source Address                        +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   +                                                               +
   |                                                               |
   +                      Destination Address                      +
   |                                                               |
   +                                                               +
   |                                                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

ipv6 前缀表示

以下格式等同:

   the following four prefix specifications are equivalent:
      2001:db8:dead:beef:0000:00f1:0000:0000/96
      2001:db8:dead:beef:0:f1:0:0/96
      2001:db8:dead:beef::f1:0:0/96
      2001:db8:dead:beef:0:f1::/96

端口号使用:
详见: rfc5952

IPv6 Addresses with Port Numbers:

   o  [2001:db8::1]:80      √ (推荐格式)
   o  2001:db8::1:80
   o  2001:db8::1.80
   o  2001:db8::1 port 80
   o  2001:db8::1p80
   o  2001:db8::1#80

ipv6 大小写问题

用小写字母表示 ipv6 地址:

   RFC 5925 recommends that IPv6 addresses be entered entirely in lowercase.

ipv6 子网划分计算

ipv6 和 ipv4 一样支持 VLSM(变长子网掩码), 不过计划分配的时候最好以 4 bit 为一组, 子网掩码为 4 的整数倍, 比如 /64, /68, /96, /100 等, 这样掩码表看起来简洁清晰, 也方便路由表收敛.

如果不是 4 bit 的整数倍, 则显示比较混乱, 也不方便计算, 如下所示 2001:db8:abcd::/48 被分成 2^(53 – 48) = 32 个 /53 的子网:

2001:db8:abcd:0000::/53
2001:db8:abcd:0800::/53
2001:db8:abcd:1000::/53
2001:db8:abcd:1800::/53
......

4 bit 的整数倍则简单清晰, 如下所示第 49 bit ~ 56 bit 组成 00 ~ ff 共 256 个子网:

2001:db8:abcd:000::/56
2001:db8:abcd:100::/56
2001:db8:abcd:200::/56
2001:db8:abcd:300::/56
...
2001:db8:abcd:fe00::/56
2001:db8:abcd:ff00::/56

在 4 bit 的方式下也方便计算各掩码之间的数量关系, 比如:

1 个 /48  ==>  2^4 个 /52
1 个 /52  ==>  2^4 个 /56
1 个 /56  ==>  2^4 个 /60

计算有多少个可用 ip 地址也很方便, 可以使用公式 2^(128 – P) 进行计算, P 为子网前缀长度, 比如以下地址:

# 2^(128 - 108) = 2^16 = 65535
2001:db8:abcd:f100::1234:/108

# 2^(128 - 116) = 2^8 = 256
2001:db8:abcd:f100::1234:5600/116

在上述 /116 子网中, 最后八字节表示如下, “0000 0000” ~ “1111 1111” 即为 2^8 = 256:

0101 0110 0000 0000

ipv6 源地址选择

在 ipv4 的环境中, 客户端发起一个请求默认以网卡的实际 ip 地址连接, 虚 ip 只被动接收数据. 在 ipv6 环境中, 多个 ipv6 地址则按照一系列规则选择源 ipv6 地址, 更多信息见 rfc6274rfc2462 , 源端地址选择规则大致如下:

1. Prefer same address.
2. Prefer appropriate scope.
3. Avoid deprecated addresses.
4. Prefer home addresses.
5. Prefer outgoing interface.
6. Prefer matching label.
7. Prefer privacy addresses.
8. Use longest matching prefix.

以一台主机上的两个 ipv6 地址 SA 和 SB 访问 D 为例说明:

 规则一很好理解, 目的地址和源地址如果一样, 就优先选择, 比如 ping6 SA 就优先选择 SA 作为源地址; 
 规则二则是选择适当的范围, 比如访问链路本地地址就优先选择本地地址, 访问全局地址就优先选择全局地址;
 规则三则尽量避免已丢弃的地址(比如 lifetime 过期等), 丢弃的地址详见 rfc2462;
 规则四一般用于 Mobile ipv6 中;
 规则五优先选择出口网卡上的地址;
 规则六优先选择相匹配的 label 值, 类似 /etc/gai.conf 中的 label 值, 不过 gai.conf 控制目的源;
 规则七优先选择私有的地址;
 规则八优先选择匹配的最长前缀的地址;

如果是同样的 ipv6 前缀地址, 比如单网卡上多个 ipv6 地址则按照最新添加的优先选择, 比如以下示例:

2: em1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2001:db8::8/96 scope global 
       valid_lft forever preferred_lft forever
    inet6 fe80::baca:3aff:fe62:f98/64 scope link 
       valid_lft forever preferred_lft forever

如果要 ping6 2001:db8::1 地址按照规则三选用 2001:db8::8 作为源地址:

# ping6 -I em1 2001:db8::1
PING 2001:db8::1(2001:db8::1) from 2001:db8::8 em1: 56 data bytes
64 bytes from 2001:db8::1: icmp_seq=1 ttl=64 time=0.380 ms
64 bytes from 2001:db8::1: icmp_seq=2 ttl=64 time=0.242 ms

增加虚 ip 2001:db8::3/96, 同样的前缀信息选择新加的作为源地址:

# ip -6 addr add 2001:db8::3/96 dev em1          
# ping6 -I em1 2001:db8::1
PING 2001:db8::1(2001:db8::1) from 2001:db8::3 em1: 56 data bytes
64 bytes from 2001:db8::1: icmp_seq=1 ttl=64 time=0.224 ms
64 bytes from 2001:db8::1: icmp_seq=2 ttl=64 time=0.244 ms

如果增加 ip 2001:db8::3/48, 按照规则八选择前缀最长的 2001:db8::8/96 作为源地址:

# ip -6 addr add 2001:db8::3/48 dev em1  
# ping6 -I em1 2001:db8::1                    
PING 2001:db8::1(2001:db8::1) from 2001:db8::8 em1: 56 data bytes
64 bytes from 2001:db8::1: icmp_seq=1 ttl=64 time=0.215 ms
64 bytes from 2001:db8::1: icmp_seq=2 ttl=64 time=0.230 ms

更多源地址选择信息见 IPv6 Source Address Selection , 控制源地址选择见 Controlling IPv6 source address selection

常用软件说明

iptables

需要开始 ip6tables 服务(iptables-ipv6-xxx.x86_64), 发行版默认都有安装, 部分编译的内核未启用该功能, 使用说明:

ip6tables -I INPUT 3 -p tcp -s fe80::/64 --dport 80 -j ACCEPT

tcpdump

使用:

tcpdump 高级规则使用 中介绍的规则, tcpdump 默认支持 ipv6 报文的抓取, 可以通过 wireshark 分析抓取后的结果:

# 抓取所有目的端口 6380 的报文, 包含 ipv4 和 ipv6
tcpdump -nn -s 0 -S port 6380

# 抓取指定网卡上所有ipv6 的 tcp 报文
tcpdump -nn -s 0 -S -i em2 ip6 proto 6

# 仅抓取 ipv6 报文
tcpdump -nn -s 0 -S ip6 and not ip proto 41

# 抓取指定目的 ipv6 和端口的报文
tcpdump -S -s 0 -nn -i any dst fe80::1a03:73ff:fef5:f0 and port 6380 -w icmp.pcap -U 

高级规则使用需要单独计算报文头部的信息, 不过使用具体协议则可以忽略这些计算, ipv4 下使用 ip, tcp, udp 规则进行计算, ipv6 下使用 ip6 进行计算, 不能使用 tcp, udp 规则, 比如下面

# 抓取所有 Next Header 为 tcp 的 ipv6 报文
tcpdump -nn -s 0 -i any '(ip6[6]&0x0f = 6)'

# 下面的条件永远为假, tcp 规则仅用于 ipv4
tcpdump -nn -s 0 -i any '((ip6[6]&0x0f = 6) and (tcp[2:2] = 6380))'

ip (arp in IPv4)

ipv6 中使用邻居发现协议发现同一网段内的设备信息, 没有 arp 等命令, 需要使用 ip 命令操作:

# 增加 ipv6 地址(邻居主机可以自动发现, 不需要单独宣告(比如 ipv4 中需要 arping .. 操作))
ip -6 addr add fe80::5054:ff:feab:187/64 dev eth0

# 查看信息
ip -6 neigh show

# 手动增加条目
ip -6 neigh add fec0::1 lladdr 02:01:02:03:04:05 dev eth0

# 删除条目
ip -6 neigh del fec0::1 lladdr 02:01:02:03:04:05 dev eth0

tcp port proxy

大多数编程语言提供的较新的网络框架都支持 ipv6 协议, 很多也同时支持 ipv4 和 ipv6 协议. 如果端口转发工具使用较新的网络包或框架, 那么其也可能支持 ipv6 的端口转发, 比如我们自己的 portproxy 工具:

# 监听 ipv6 地址, 收到数据转发到后端的ipv4 地址 127.0.0.1
portproxy -bind "[::]:13306" -backend 127.0.0.1:3306

比较成熟的工具包含 [3proxy]:(https://github.com/z3APA3A/3proxy) , 支持的功能较多, 0.8.12 支持了很多 ipv6 特性, 以编译后的 tcppm 小工具为例:

./tcppm -h
./tcppm of 3proxy-0.8.12 (180418205022)
Usage: ./tcppm options [-e<external_ip>] <port_to_bind> <target_hostname> <target_port>
Available options are:
......
......
 -iIP ip address or internal interface (clients are expected to connect)
 -eIP ip address or external interface (outgoing connection will have this)
 -rHOST:PORT Use IP:port for connect back proxy instead of listen port
 -RHOST:PORT Use PORT to listen connect back proxy connection to pass data to
 -4 Use IPv4 for outgoing connections
 -6 Use IPv6 for outgoing connections
 -46 Prefer IPv4 for outgoing connections, use both IPv4 and IPv6
 -64 Prefer IPv6 for outgoing connections, use both IPv4 and IPv6

-i 选项指定监听的地址, 只能为 ipv4 或 ipv6, 如果要同时支持可以启动两个进程分别指定 ipv4 和 ipv6 地址, -46-64 选项则指定优先使用哪个连接后端的地址和端口, 比如以下监听 ipv6 地址, 收到数据后优先使用 ipv4 连接后端的地址和端口:

tcppm -i:: -46 8080 arstercz.com 80

在底层代码中, 很多工具支持 ipv6 的方式如下所示, 在 bind 之前区分 ipv4 或 ipv6 的 socket 信息, accept 接收收到的数据, 要将数据转发到后端的 ipv4 还是 ipv6 地址则使用 connect 指定:

#if (HAVE_INET6)
    struct in6_addr       inaddr6;
    struct sockaddr_in6  *sin6;
......

#if (HAVE_INET6)
   if (ngx_inet6_addr(..., inaddr6.s6_addr) == OK) {
        family = AF_INET6;
......
#if (HAVE_INET6)
    if (ls.sockaddr->sa_family == AF_INET6) {
......
......
    bind(.....)
    accept(......)
......
// proxy tcp port based on ipv4 or ipv6
    connect(remote_socket, remote_addr, ...)

nginx

配置:

   resolver 8.8.8.8 ipv6=off;
   listen [::]:443 ipv6only=on;

混合服务:

   ipv4 && ipv6 mixed.

httpd

配置:

     Listen 80;
     Listen [2001:db8:1234::100]:80

混合服务:

   ipv4 && ipv6 mixed.

tomcat

默认监听所有地址:

   listend on all interface with ipv4, ipv6;

示例:

curl -6 -vv -g -H "Host:arstercz.com" fe80::eef4:bbff:fed3:f3c5%eth0:8080

混合服务:

   ipv4 && ipv6 mixed.

resin

默认监听所有地址:

   listend on all interface with ipv4, ipv6;

示例:

curl -6 -vv -g -H "Host:arstercz.com" fe80::eef4:bbff:fed3:f3c5%eth0:8080

混合服务:

   ipv4 && ipv6 mixed.

MySQL

使用:

   privilege:
      grant all on *.* to v6t@`fe80::%em1`;
   connect:
      mysql -h fe80::baca:3aff:fe61:dba4%em1 -P 3301 -u v6t -p
   column:
      variables column (8*4 + 7) for ipv6, unsigned bigint for 2^64 - 1

混合服务:

   ipv4 && ipv6 mixed.

Redis

配置:

      redis.conf:
         bind 127.0.0.1 fe80::1a03:73ff:fef5:f0%em2

      redis-cli -h fe80::1a03:73ff:fef5:f0%em2 -p 6380

混合服务:

   ipv4 && ipv6 mixed.

Memcached

问题:
issue-98 1.4.23 版本修复;

配置:

      memcached -d -l:: -p 11229 -m 1024 ...
      memcached -d -lfe80::222:19ff:fe64:63c2%em1 -p 11230 -m 1024

使用 sys-memcached-check 或 telnet 检查:

      perl sys-memcached-check -6 -h fe80::222:19ff:fe64:63c2%em1 -p 11229
      telnet fe80::222:19ff:fe64:63c2%em1 11229

混合服务:

   ipv4 && ipv6 mixed

MongoDB

配置:

config.yaml 配置中增加
   ipv6: true

或以 mongod --ipv6 启动

使用:

ipv6 地址需要以 "[]" 包围, 同时使用 ipv6 选项 
#  mongo --host [fe80::266e:96ff:fe63:f7d2%eth2] --port 5702 --ipv6

认证:

用户密码以用户名和角色决定, 同一用户可被 ipv4, ipv6 同时使用;

独立服务:

ipv4 && ipv6 separate

curl

连接:

   LocalLink: 
     (curl 7.61.0)
        curl -g -6 http://[fe80::5054:ff:feab:185%eth0]:8080/
        curl -g -6 http://[fe80::5054:ff:feab:185%25eth0]:8080/

     (curl 7.29.0 && low)
        curl -6 -g  http://fe80::baca:3aff:fe5c:4b7e%eth2:8080/

   PublicLink:
      curl -g -6 http://[2001:db8:1234::100]:8080/

问题:
* bugzilla-680996
* curl-ipv6

高版本(7.61.0) curl 无论本地是否启用 ipv6, 都会先尝试 ipv6 连接:

# curl -I -v arstercz.com
* Rebuilt URL to: arstercz.com/
*   Trying 139.162.108.99...
* TCP_NODELAY set
*   Trying 2400:8902::f03c:91ff:fe73:5c05...
* TCP_NODELAY set
* Immediate connect fail for 2400:8902::f03c:91ff:fe73:5c05: Network is unreachable
* Connected to arstercz.com (139.162.108.99) port 80 (#0)

strace 调用查看:

[pid 26321] 16:46:33.823240 connect(3, {sa_family=AF_INET6, sin6_port=htons(80), inet_pton(AF_INET6, "2400:8902::f03c:91ff:fe73:5c05", &sin6_addr), sin6_flowinfo=0, si
n6_scope_id=0}, 28) = -1 ENETUNREACH (Network is unreachable)
[pid 26321] 16:46:33.823302 close(3)    = 0

独立服务:

   ipv4 && ipv6 separate.

ping6

ping6 测试链路本地地址需要指定出站接口或者在IPv6地址后增加接口标识符, 有默认路由则可以直接操作:

    ping6 -I eth1 fe80::5054:ff:fe7b:3fc8  (指定出站接口)
    ping6 fe80::5054:ff:fe7b:3fc8%eth1 (指定接口标识符)
    ping6 2001:db8:abcd::100
    ping6 ipv6.google.com

独立服务:

   ipv4(ping) && ipv6(ping6) separate.

ssh

存在路由出口可以直接连接 ipv6, 连接本地链路需指定相应网卡, 连接域名则由本地规则决定使用 ipv6 还是 ipv4:

    ssh -6 fe80::5054:ff:feab:185%eth0
    ssh 2001:db8::100
    ssh domainname

混合服务:

   ipv4(-4) && ipv6(-6) mixed.

telnet

使用:

telnet fe80::266e:96ff:fe63:f7d2%eth2 5702

混合服务:

   ipv4(-4) && ipv6(-6) separate.

keepalived

问题: issue497 (1.3.2 版本解决)

dns

dns server 中需要配置 AAAA 记录, 配置中不能忽略 ipv6 地址中开头的 0, 如果要配置 NAT64, 至少升级到 9.8.0:

   unlike the format of ipv6 address in AAAA records, omitting leading zeros is not allowed in ip6.arpa.

   add AAAA record, ptr record.
   prioritization of AAAA and A record.

   bind > 9.7.0 support filtering mechanism for broken resolvers(compile with -enable-filter-aaaa).

   DNS64(> bind 9.8.0), NAT64

dns server 中为同一个域名分别配置 A 记录和 AAAA 记录后, 客户端无论是否拥有 ipv6 地址, 请求 dns server 后即返回 A 和 AAAA 两条记录:

15:01:19.273180 IP6 2001:db8:1234::100.36866 > 2001:db8:1234::100.53: 18411+ A? arstercz.com. (27)
15:01:19.273226 IP6 2001:db8:1234::100.36866 > 2001:db8:1234::100.53: 27310+ AAAA? arstercz.com. (27)

15:04:18.430595 IP 192.168.0.90.45644 > 8.8.8.8.53: 64366+ A? arstercz.com. (27)
15:04:18.430627 IP 192.168.0.90.45644 > 8.8.8.8.53: 49618+ AAAA? arstercz.com. (27)

frc3484 规定默认情况下要优先选择 ipv6, 绝大多数客户端获取到两条记录后默认优先使用 AAAA 记录请求, 失败后再使用 A 记录. java 工程可以通过 java.net.preferIPv6Addresses=<true|false> 等配置选择优先使用 ipv4 还是 ipv6, 详见: java_ipv6_guide , 其它编程语言可以参考各自对应的相关文档说明.

如果请求到多个 ipv6 地址, linux 下的系统可以在 /etc/gai.conf 配置中设置期望哪个 ipv6 优先连接的优先值, 详见: resolver

对于单个域名的ipv4 和 ipv6 地址, 同样可以在 /etc/gai.conf 中设置优先选择 ipv4, xxx.xxx.xxx.xxx 为解析出来的 ipv4 地址, 在优先级较高的情况下, 系统访问指定域名的时候优先选选择 xxx.xxx.xxx.xxx, 如下所示:

# cat /etc/gai.conf 
#For sites which prefer IPv4 connections change the last line to
precedence ::ffff:xxx.xxx.xxx.xxx/32 90
precedence 2001:db8:1234::100/128 50

应用程序通用检查

api接口

应用程序端需要支持 ipv6:

   api 接口是否能处理 ipv4/ipv6?
   接口使用的协议(http, https, ftp ...)是否支持 ipv4/ipv6?
   ipv6 访问连接: https://[2001:db8::98]:8000/

内存分配

ipv6 消耗更多的内存空间:

   32-bit for ipv4, 3 ~ 128-bit for ipv6

配置文件

工程或者应用的配置需要处理 ipv6 格式的条目:

   是否有硬编码 ip 写在配置里, 如果有需要单独处理以支持 ipv6 的格式:

     listen 10.1.1.2:8000
     listen [2001:db8:1234::100]:8000

另外 ipv6 地址中开头的 0 是否省略由开发者决定:

   does need suppression zero?

超时处理

详见 dns 部分, 程序端确保设置好 ipv6 的连接超时时间, 避免 ipv6 问题引起的过长等待.

数据库

存储 ipv6 地址等表结构需要进行修改, 单个 ipv6 地址最长 39 个字符, ipv4 最长 15 个字符:

variables column (8*4 + 7) for ipv6, unsigned bigint for 2^64 - 1

客户端连接

浏览器

rfc6555 协议指定了在双栈系统(ipv4 和 ipv6 同时支持)环境中, 客户端需要使用 happy eyeballs 算法在指定的毫秒时间内优先选择 ipv6 进行连接, 如果在指定时间内还没连接成功则开始并行的连接 ipv4 地址, 在此之后两者中哪个第一个连接成功则选用哪个进行通信. 协议中建议的指定时间为 150 ~ 250 毫秒:

It is RECOMMENDED that connection attempts be paced 150-250 ms apart to balance human factors against network load.

手机端

iphone 手机

从 2016 年开始所有提交到苹果的项目都需要支持 ipv6, 所以目前包括 mac, ipad, iphone 等在内的设备都支持 ipv6 的连接. 不过由于苹果手机设备默认不开启防火墙策略, ipv6 直接对外, 所以会暴露 62078 端口:

Nmap scan report for 2001:db8:a480:1::1001
Host is up (0.51s latency).
Not shown: 999 closed ports
PORT      STATE SERVICE
62078/tcp open  iphone-sync

Nmap done: 1 IP address (1 host up) scanned in 91.24 seconds

关闭 62078 端口见: https://gist.github.com/pwnsdx/cc82feb97f451f26c24b

android 手机

官方的安卓版本 5.0 之后开始对 ipv6 有了较全的支持, 不过对 dhcpv6 的支持还有所欠缺. 国内部分安卓厂商额外增加了 dhcpv6 功能(比如魅族5, 魅族6 pro 等), 支持 dhcpv6 的手机可以正常通过 dhcpv6 服务获取 ipv6 地址. 如果没有支持 dhcpv6, 可以通过 SLAAC 无状态方式试着获取 ipv6 地址.

浏览器

具体实现中, 新版的 libcurl 默认为 200ms (详见 –happy-eyeballs-timeout-ms 选项), Firefox 和 Chrome 默认为 300ms, 见 chrome and firefox – happy eyeballs

较老的浏览器版本实现各不一样, 老版本的 Chrome 默认先连接 ipv6, 超过 300ms 后则使用 ipv4 进行连接通信, 见 old chrome ipv6 , 这两个老版本浏览器 ipv6 超时时间也有相关的测试介绍: irefox and chrome ipv6 timeout

ipv6 访问 ipv4

目前已知的 ipv6 访问 ipv4 方式主要包含 6over4, NAT-PT, NAT64, SIIT 等方式, 6over4 通过端到端的隧道方式实现, 一些专用的 vpn 设备和较新的 linux 系统都支持此方式, NAT-PT 方式在路由设备中使用较多, 比如 cisco NAT-PT 的实现方式, 一些网络设备也支持 NAT64SIIT(Stateless IP/ICMP Translation), 也有专门的软件支持 NAT64 和 SIIT 方式, 在测试或小规模使用的环境中, 可以使用 Jool 工具实现 NAT64SIIT 的方式.

Jool 提供了两种方式用来实现 ipv6 访问 ipv4 的环境, 分别包含 SIITNAT64 , 两种方式分别提供了应用程序和内核模块两种使用方式. 出于效率考虑, 建议使用内核模块 方式安装.

编译环境需要内核 3.5 版本及以上, gcc 6.1 或更高版本. 模块编译完成后加载模块可以使用 RFC 协议规定的 64:ff9b::/96 作为 ipv4 映射的前缀, 也可以按需指定前缀:

modprobe --first-time jool pool6=64:ff9b::/96

纯 ipv6 的主机如果需要访问 ipv4 地址, 需要将上述安装了 jool 模块的主机作为默认的网关, 如下示例:
|_. hostname |_. ipv4 |_. ipv6 |_. role |
| host3 | | 2001:db8::8 | client |
| host7 | 10.0.21.7 | 2001:db8::1 | jool(NAT64) |

在 host3 主机中增加默认路由即可访问 ipv4 环境:

# 设置网关为 host7 地址
ip -6 route add default via 2001:db8::1 dev em1

访问 ipv4 地址:

# ping6 -I em1 64:ff9b::114.114.114.114
PING 64:ff9b::114.114.114.114(64:ff9b::7272:7272) from 2001:db8::8 em1: 56 data bytes
64 bytes from 64:ff9b::7272:7272: icmp_seq=1 ttl=65 time=29.8 ms
64 bytes from 64:ff9b::7272:7272: icmp_seq=2 ttl=73 time=31.9 ms
....

# dig @64:ff9b::114.114.114.114 arstercz.com A
......
......
;; ANSWER SECTION:
arstercz.com.           1       IN      A       139.162.108.99
;; Query time: 8 msec
;; SERVER: 64:ff9b::7272:7272#53(64:ff9b::7272:7272)

配合 DNS64 使用则更为方便, dns64 配置的时候需要做一些规则过滤, 比如下面的配置:

# named.conf

acl "rfc1918" { 10.0.0.0/8; 192.168.0.0/16; 172.16.0.0/12; };

options {
....
        dns64       64:ff9b::/96 {
            clients { any; };
            mapped { !rfc1918; any; };
            exclude { 64:ff9b::/96; ::ffff:0000:0000/96; };
            suffix ::;
        };
}

上述配置中, 只有 ipv4 记录的域名, 在客户端请求的时候返回 “64:ff9b::xxx.xxx.xxx.xxx” 作为域名的 AAAA 记录, 客户端连接此 ip, 通过 jool(NAT64) 网关进行代理处理, jool 主机处理后再将数据返回给客户端. 比如以下 host3 访问 arstercz.com, 实际上为 host7 的 ipv4 地址访问 arstercz.com:

# ping6 -I em1 arstercz.com
PING arstercz.com(arstercz.com (64:ff9b::8ba2:6c63)) from 2001:db8::8 em1: 56 data bytes
64 bytes from arstercz.com (64:ff9b::8ba2:6c63): icmp_seq=2 ttl=46 time=225 ms
64 bytes from arstercz.com (64:ff9b::8ba2:6c63): icmp_seq=3 ttl=46 time=235 ms
......

传输层注意事项

路由交换设备需要支持 ipv6, 较新的路由器支持 OSPFv3 协议, 可以同时支持 ipv4 和 ipv6, 俗称双栈路由, 操作系统层面也需要支持 ipv6, 有 vip 切换需求的需要升级 keepalived; 应用程序如果要连接外部域名, 需要做好超时切换的策略, 默认连接 ipv6 域名, 失败后连接 ipv4 域名. MTU 则自动发现, 另外 ipv6 中不存在多播广播的机制, 有 icmpv6 等其他机制作为支持:

   Dual-stack versus ipv6

   os support ipv6

   multiple ipv6 address (virtual ip address 
issue: https://github.com/acassen/keepalived/issues/497)

   ipv6 address may change over time if "privacy extension" 
are enabled(RFC 4941).
       +- autoconfiguration ipv6(DHCP and router advertisement)

   path MTU discovery(fragmentation in ipv6 network path is not allowed), 
RFC 1981: find smallest MTU in the path between the source and the 
destination. all packets are sent from the source with the path MTU which 
is found.

   multicast and broadcast(no longer any concept of broadcast packet in ipv6)

   NAT and IPv6: no longer need for NAT(RFC 4864), However reality of IPv6 
is that we still see NAT being deployed with IPv6(maybe because security, 
network architects..), RFC 4193 Unique Local Addresses (ULAs) for  IPv6 and f
irewalls with application-layer gateways (ALGs)

书写

2001:db8::/32 被专门用来作为文档, 博客等书写的目的, 这些地址在互联网中不会被访问到:

   RFC 3849: 2001:db8::/32 just as a documentation-only prefix, nonroutable.

应用程序切换到 ipv6 的检查列表

整体上程序处理的较多, 开发人员需要注意配置文件, api 接口, db 表结构的变更:

      When displaying IP addresses, can your application correctly display 
the longer IPv6 addresses?

      When receiving IP addresses as input, do the entry boxes in your 
app allow for the entry of IPv6 addresses?

      In the display or input of IPv6 addresses, can your application handle 
the variable length of IPv6 addresses?

      Does your app correctly use or accept the “square bracket” notation to 
allow portnumbers to be displayed after an IPv6 address?

      Does your app correctly handle the input and display of subnet masks 
using   CIDR notation?

      Is there a need to be concerned about case-sensitivity? Do you need to 
normalize IPv6 addresses on input to be entirely lowercase?

      If you perform validity checking on an input field for an IP address, 
have you updated that validity checking for IPv6?

  Can your application handle both A and AAAA records from DNS?

      Does your app implement any kind of “happy eyeballs” mechanism to 
try to connect over both IPv4 and IPv6?

     Does your app expose any APIs where there is an IP address format 
dependency?

     Does your app consume any APIs where there is an IP address format 
dependency?

      Can the components of your application that work with your API 
correctly handle IPv6 addresses?

      If you store IP addresses in either memory or a database, is the location 
or field large enough for an IPv6 address? Or could there be a buffer overflow 
or database error?

      For dual-stack systems, can you store two IP addresses in memory, 
database, or a configuration file for both IPv4 and IPv6?

      In storing addresses, are you compensating for the variable length, 
case-insensitivity, and zero compression in IPv6 addresses?

      Are there hardcoded IP addresses lurking in your configuration files?

      Can your application work in a dual-stack environment? Or will it only 
work with   either IPv4 or IPv6?

      If you app runs in a dual-stack environment, does it in fact bind to 
both interfaces?

      Does your app need to get down into network layer issues? Does it use 
multicast   or broadcast or have to worry about MTU discovery?

      Is NAT traversal a concern for your application?

      Will your documentation materials need to be updated to reflect any 
changes to the  user interface?

      Will your training materials need any updates?

      Do your test plans now incorporate IPv6?

Leave a Reply

Your email address will not be published. Required fields are marked *