WRITING / 2021.09.23

工程日志:如何证明一次请求真的走了 IPv6

从 DNS 解析、客户端选址到抓包验证,整理 IPv6 链路探测中容易出现的误判与一套可重复的验证方法。

给一条 IPv6 链路补充可用性探测时,最容易犯的错误是:请求成功了,日志里也出现了 IPv6 地址,于是认为探测已经覆盖 IPv6。

但日志字段可能来自代理转发的请求头,客户端也可能在双栈环境中自动选择 IPv4。要证明链路真实经过 IPv6,需要同时控制解析结果、保留 HTTPS 主机名校验,并在网络层验证数据包。

先把验证目标说清楚

一次完整的链路探测至少应验证:

  1. DNS 能够得到目标 IPv6 地址;
  2. 客户端实际向 IPv6 地址建立连接;
  3. TLS 仍按原始域名完成证书校验;
  4. 应用请求和响应符合预期;
  5. 监控机器本身具备正确的 IPv6 路由。

如果直接请求字面量 IPv6 地址并关闭证书校验,只验证了“某个地址和端口可达”,没有覆盖域名与证书这一段真实链路。

在客户端显式选择 IPv6

双栈域名可能同时返回 A 和 AAAA 记录,客户端最后选择哪一个地址受运行库、系统配置和连接策略影响。探测程序不应依赖这种隐式选择。

Java 客户端可以为探测请求提供受控的 DNS 解析器,只返回选定的 IPv6 地址:

final class Ipv6OnlyResolver implements DnsResolver {
    @Override
    public InetAddress[] resolve(String host) throws UnknownHostException {
        return Arrays.stream(InetAddress.getAllByName(host))
            .filter(Inet6Address.class::isInstance)
            .toArray(InetAddress[]::new);
    }
}

生产实现还要处理没有 AAAA 记录、存在多个 IPv6 地址和连接失败时的错误分类,不能返回空地址后继续执行。

命令行验证时,可以使用 curl --resolve 把域名和端口固定到测试地址:

curl --resolve 'media.example.com:443:[2001:db8::10]' \
  'https://media.example.com/health'

请求 URL 仍然使用域名,因此 TLS 可以校验证书;--resolve 只覆盖本次请求的地址解析。

为什么访问日志不能作为最终证据

反向代理通常会把 X-Forwarded-For 等字段写入访问日志。这个值描述的是代理链路携带的信息,不一定等于当前连接使用的网络协议。

更可靠的证据是抓包:

tcpdump -nni any 'ip6 and tcp port 443' -w ipv6-check.pcap

打开抓包文件后,应确认源地址和目标地址都是 IPv6,并且连接对应探测请求的时间窗口。

抓包时还要注意网卡选择。管理网、业务网和容器网可能分别拥有不同的 IPv6 地址;只监听默认网卡,有可能得到“没有流量”的错误结论。首次验证时使用 any,确认实际出口后再收窄范围更稳妥。

常见误区

机器有 IPv6 地址,不代表有可用路由

链路本地地址、管理地址或自动生成地址只能说明网卡配置存在。还需要确认默认路由、出口策略和目标网络可达。

直接请求 IPv6 字面量并跳过证书

这样会绕过证书域名验证,探测结果不能代表真实 HTTPS 访问。除非目标就是验证裸地址端口,否则不应把 --insecure 当作正式方案。

只检查应用日志

应用层日志适合确认业务结果,网络层抓包适合确认路径。两者需要互相印证,不能互相替代。

一套可重复的检查顺序

确认监控机 IPv6 地址与路由
        ↓
查询目标 AAAA 记录
        ↓
显式绑定域名到 IPv6 地址
        ↓
发起保留 TLS 校验的 HTTPS 请求
        ↓
抓包确认真实连接
        ↓
核对应用响应与监控结果

链路探测的价值不只是“定时请求成功”,而是失败时能明确区分 DNS、路由、TCP、TLS 和业务响应问题。验证路径越清楚,报警才越接近可执行的诊断信息。