DNS 解析中的搜索引擎线路那些事儿

本文不会贴出任何搜索引擎蜘蛛实际的测试数据,仅作理论分析,感兴趣的同学可以根据一些公开的第三方工具和文章中提到的一些技术方案自行分析。

首先简单介绍下 DNS 解析中搜索引擎线路的作用,当代权威 DNS 的基本功能都包括分线路解析(也有智能解析、view、geo 等等名字) ,其中很多权威 DNS 服务商会提供针对搜索引擎蜘蛛抓取的线路(包括搜索引擎线路、SEO 优化、搜索引擎回源等等名字),主要目的是对普通用户返回 CDN 或其他通用的地址,而准对搜索引擎蜘蛛的抓取则直接返回源站的地址,可以提高权重、收录等目的。

搜索引擎蜘蛛在抓取网页前,首先要进行域名解析,获取目标域名的 IP 地址之后再进行抓取,此处需要涉及到蜘蛛使用的 LocalDNS 和目标域名使用的权威 DNS 两个部分的解析过程,首先介绍权威 DNS 解析过程。

权威 DNS 解析过程

权威 DNS 主要是依靠 LocalDNS 发送的 DNS 请求中的 IP 地址来进行分线路的解析的,而携带 IP 地址主要有两个位置,分别进行说明。

DNS 请求源地址

一个标准的 DNS 请求不管是 UDP 还是 TCP 协议,都必然要有其源 IP 地址,权威 DNS 服务器可以通过获取该地址来查询 IP 库和线路设置情况来进行分线路解析。

edns_client_subnet(ECS) 选项中携带 IP 地址

DNS 扩展协议中的 ECS 段可以在 DNS 请求包额外携带 IP 地址来供权威 DNS 获取并依此进行分线路解析,主要用于提升节点分布于全球的公共 DNS 来提升解析准确度,但是 ECS 的现状是大量权威 DNS 已经实现了对 ECS 的支持,但是 LocalDNS 对 ECS 的支持情况则很不乐观,除了 Google Public DNS 和 OpenDNS 有相对比较完整的支持外,其他包括运营商 DNS 和大部分其他公共 DNS 并没有很完整的支持 ECS 协议(ECS 协议支持情况可以参考之前的一篇文章《国内主要公共 DNS 支持 ECS 情况测试 – 20210315》)。

当然可知目前绝大部分的搜索引擎蜘蛛使用的 LocalDNS 也都是不支持 ECS 协议的,而权威 DNS 要正确解析蜘蛛访问的域名的线路主要就依赖蜘蛛使用的 LocalDNS 本身的外网出口 IP 来判断。下面再讨论蜘蛛使用的 LocalDNS 几种不同的场景。

蜘蛛使用的 LocalDNS

蜘蛛使用的 DNS 可能分为几种情况:

1. 使用蜘蛛独占的网段搭建 LocalDNS 进行解析

这种情况是对权威 DNS 的搜索引擎线路最友好的方式,该网段为蜘蛛独占,不与其他业务混用,尤其是公共DNS,目标域名的权威 DNS 只需要定期收集更新其蜘蛛使用的 LocalDNS 的外网出口 IP 即可达到很好的优化效果。

如果蜘蛛 IP 和 LocalDNS 的外网出口 IP 地址位于相同网段,通过搜索引擎官网公布或第三方收集的蜘蛛 IP、蜘蛛的 UA、IP 段的反解析结果等收集、验证、更新蜘蛛 IP 段即可。

如果 LocalDNS 的外网出口 IP 地址与蜘蛛 IP 位于不同网段,但是只要该网段依然是蜘蛛独占,可以通过一些技术手段来收集这些 LocalDNS 的外网出口 IP,如向 LocalDNS 请求 whoami.ip.dnspod.net,则返回结果即为该 LocalDNS 的外网出口 IP,如果 LocalDNS 支持 ECS,则会同时返回 LocalDNS 请求的源 IP 和携带的 ECS IP。

类似可以检测 LocalDNS 的外网出口 IP 的域名现网有多家大厂都有提供,仅返回结果的展示方式不一致。

但是想想就知道,蜘蛛全部独占相关的网段的可能性有多低,现网的相关测试数据也完全证明蜘蛛使用的网段大部分是也有其他业务在使用, 其实大部分其他业务复用蜘蛛 IP 段不会对搜索引擎线路优化有什么影响 ,除了公共 DNS。

2. 蜘蛛 LocalDNS 的外网出口 IP 网段和公共 DNS 网段重合

这种场景对同时提供搜索引擎服务和公共 DNS 服务的厂商是非常常见的,此处也不再进行举例说明,仅说明存在的问题。

目前从各种渠道获取到的蜘蛛 IP(包括其使用的 LocalDNS 外网出口 IP)在 IPv4(IPv6 本文不额外讨论) 中一般精度可达到 C 段 IP(/24),非常难达到非常准确的 /32 精度,这就导致权威 DNS 无法区分来访问的 IP 到底是蜘蛛来访问的还是普通用户使用公共 DNS 来访问的,从而无法给双方都解析到准确的结果,只能折衷选取一种策略。

举例说明,目前腾讯云 DNSPod 的解析策略是对此类 IP 不添加到蜘蛛 IP 库中,结果是蜘蛛很大概率无法获取搜索引擎线路设置的特定结果,但是也不会造成普通用户错误的获取到蜘蛛线路的解析结果,尤其是在其公共 DNS 服务的普通用户体量巨大的时候。

3. 直接使用公共 DNS 进行解析

公开的 LocalDNS 包括运营商 DNS 和其他公共 DNS,因为运营商 DNS 大多有源 IP 段必须为本运营商的限制,对于可能分布于各地的蜘蛛来说不是很好的选择,公共 DNS 则可以作为主要选择。

蜘蛛可以直接使用这些公共 DNS 进行解析或者自建的缓存 LocalDNS 本身不进行递归查询,只是将解析请求转发至公共 DNS 并缓存解析结果。

此类场景在蜘蛛中也是比较常见的,因为可以节省大量的 LocalDNS 的部署维护成本,导致的结果和策略选择也与第二种场景一致。

解决方案

如果想让权威 DNS 的搜索引擎线路更有效,解决目前存在的问题,可以考虑 2 种方案,但是都存在相同的问题,虽然理论上是有一定的可操作性的,但是实际中确较难达成,主要维护成本或资源成本,且依赖搜索引擎的部署策略,与权威 DNS 不是一家的^_^。

  1. 蜘蛛及其 LocalDNS 使用 IP 段不要与公共 DNS 复用
  2. 蜘蛛使用支持完整 ECS 的递归 DNS

方案 1 主要针对蜘蛛自建 LocalDNS ,实施难度本身不大,主要是在部署自己的 LocalDNS 时能够和公共 DNS 隔离即可,需要更多的IP段资源,且在后续变更时需要注意一直维护该策略不变才可。

方案 2 主要针对直接使用或者转发到公共 DNS 的蜘蛛,因为完整的 ECS 在 LocalDNS 中对域名的解析结果会按照网段进行缓存,即使蜘蛛与普通用户复用相同 DNS 也不会造成很大的影响,仅对蜘蛛 IP 同网段的其他业务可能产生一定的影响,范围非常有限,不会影响到大量的普通用户的访问。

此方案存在的问题主要是公共 DNS 支持完整 ECS 的少之又少,尤其是在国内短期内不一定会有长期持续而稳定支持完整 ECS 的公共 DNS,而 Google Public DNS 和 OpenDNS 则对国内使用者非常不友好,可操作性不如方案 1。


DNS 控制面异常处理思考与实践

这个国庆假期互联网最大的新闻就是某不存在的公司 Facebook 全线业务宕机了 7 个小时,这其中有一个不起眼但是很关键的原因是其权威 DNS 节点在检测到部分网络异常(可以理解为控制面异常)后进行自我剔除操作,所有 DNS 节点“集体自杀”,从而导致 Facebook 自身及其他使用其权威 DNS 服务的业务全线异常。

这里会简单聊聊 DNSPod权威 DNS 的控制面异常时是如何处理的,包括曾经的思考与当前的实践经验,如何保障在出现类似问题的情况下尽量保障 DNS 服务的连续性,最终方案其实很简单,一点都不高大上,但好在简单实用。

权威 DNS 的控制面最主要的工作是同步用户记录的修改,不管是通过私有协议还是域传送,一旦控制面异常,边缘的 DNS 节点在故障期间无法同步用户最新的记录修改数据,终端用户就可能解析到旧的 IP 上,对业务造成一定影响,所以控制面故障时主要要解决的问题就是如何防止故障节点继续响应过期记录。

当前权威 DNS 大多使用多个 IP 同时提供服务,可能是 Anycast 集群,或者单地域集群,或者混合部署,都可以提供一定的容灾和负载均衡能力。而递归 DNS 对多个不同的权威 DNS 服务 IP 一般是通过 SRTT 方式来选择使用哪一个 IP 进行请求,可以自动剔除(减少请求)故障的权威 IP,一般只要权威的 IP 列表中有可用的 IP 且容量足够就可以正常进行递归解析。

所以最简单的处理方式就是直接踢掉控制面异常的权威 DNS 节点,即可保证递归不会解析到过期的数据,但是就是这最简单的踢掉故障节点的操作,其实也会涉及到很多的思考和实践经验,下面从头开始介绍。

控制面故障影响范围分类

按照影响 DNS 节点范围和处理方式来区分控制面故障的类型,主要就是两种类型,部分节点受影响和全部节点受影响。

  1. 部分节点受影响

这里可能有多种原因,最常见的如 DNS 节点与控制中心之间的网络异常,或者部分控制中心从节点故障,只影响少部分边缘的 DNS 节点的控制面数据同步,那么这时候故障 DNS 节点自救做自我剔除,将自己从服务集群中摘除貌似完全没有影响,然后自动/手动切换到其他正常的控制节点或者等待故障的控制节点恢复,再恢复 DNS 节点的对外服务即可。

当然这里没有影响是有一个前提的,那就是剩余节点必须有足够的服务能力,而故障节点本身因为控制面故障形成了一个孤岛节点,是没有足够的信息进行相关判断的,可见即使只有部分节点受影响,故障节点也是不能轻易进行自我剔除操作的。

  1. 全部节点受影响

这里最大的可能原因是控制中心节点故障,如控制中心主节点宕机,或者网络故障导致所有从节点数据同步落后,此时如果故障 DNS 节点还进行自我剔除,所有 DNS 节点“集体自杀”了,后果严重,就是这次 Facebook 事件中 DNS 节点服务器的操作了。

如何解决

从上面的分析可以得出一个结论:权威 DNS 节点在控制面故障时一定不能做自我剔除的操作,因为孤岛节点是没有足够的信息,无法得出正确的结论,是否应该进行自我剔除 ,下面是前两年时讨论这个问题的一个截图,当然这个结论在很多年前其实就已经分析得出了。

故障 DNS 节点能做的是告警、尝试切换寻找正常的控制节点等操作,很多时候故障节点已经可以自动恢复,比如单独某个控制从节点故障自动切换即可恢复。但是也有时候无法自动恢复,如 DNS 节点本身内网故障的时候,而且这时候节点本身的告警也是无法正常发出来的^_^。

那就只能通过权威 DNS 节点及控制面之外的外围监控来进行处理,目前 DNSPod 的实践经验是这样的:

控制面故障监控

  1. 在多个地区多个运营商部署了监控节点,对所有的权威 DNS 的 LB VIP 以及 RS 的物理 IP 持续进行拨测;
  2. 每一轮拨测前通过 API 对拨测域名添加一条 TXT 记录,记录值为当前拨测点当前时间戳;
  3. 正常情况下 DNSPod 所有的权威 DNS 节点的数据同步都是秒级的,所以一旦拨测结果与最新设置的记录值不一致,则可以判断该节点数据同步落后,并可以计算出落后的时长,汇总统计所有数据同步落后节点后发出告警,实现对 DNS 节点控制面是否正常的监控。
  4. 该监控可以同时对 DNS 节点的数据面是否能够正常解析进行监控告警。

控制面故障处理

接下来就需要对控制面故障的 DNS 节点进行处理,此处可以有多种处理方式,自动或手动,DNSPod 目前主要还是手动处理,主要由以下几个原因:

  1. 目前外部的监控节点对 DNS 服务器做的是完全黑盒拨测,很难自动判断 DNS 节点控制面故障的真实原因,从而无法确定处理方式。
  2. 监控节点无法直接控制 DNS 服务器的行为,而通过控制中心处理的话,但是此时控制面已经异常了,可能会陷入死锁无法处理,还是比如故障节点内网中断的这个场景。
  3. 大部分异常节点已经可以自动恢复,无法自动恢复的告警发生频率很低。

不同场景处理方式

本节列出几个常见的控制面故障场景,以及对应的处理方式

节点本身内网故障

此时故障节点处于一个网络孤岛,内网中断,外网因为安全原因禁止登陆,根据不同情况可以由以下几种处理方式:

  1. 如果只是 LB + RS 集群中单独某台 RS 故障,优先通过 LB 剔除;
  2. 如果LB异常或没有LB(如直接的 OSPF 集群),有带外,可以通过带外方式登陆服务器,将该服务器剔除,剔除方式优先选择停止 OSPF,如果没有 OSPF,则可以考虑封禁数据面 53 端口的请求以及停止服务程序等方式;
  3. 没有带外或者带外故障,如果该故障节点是 Anycast 节点,可以选择在交换机等处取消 BGP 广播下掉整个节点,需要能够快速联系到网络交换机相关负责人,且不会影响其他业务。
  4. 直接通过防护系统(宙斯盾)或运营商防护接口在某地域或全地域封禁该IP,此时虽然对应节点的 IP 不可用,但是 LocalDNS 可以通过文章开头提到的 SRTT 机制去其他正常节点请求,不影响总体解析。
  5. 前面的处理方式都有一个前提,就是其他节点容量足够,但是如果剩余节点容量不足怎么办,此时处理方案是不对 DNS 节点进行下线处理,虽然可能导致部分 DNS 请求会解析到过时的记录,但是有变化的记录毕竟是少数,需要优先保证整个大盘的解析正常。此场景在某些极端情况下有一定的出现的可能,比如整个平台遭受持续超大量的 DDoS 攻击时,同时这台服务器出现了控制面故障,根据具体情况评估影响后可能采取此不处理下线的方式。
  6. 在权威解析控制台及注册局(商)出删除该 IP,因为 TTL 很长,一般不考虑。

控制中心完全故障导致所有节点控制面故障

类似此次的 Facebook 的最初始故障,如判断确实是控制中心就是无法连接,无法同步数据,那么也只能降级服务,不对 DNS 节点进行下线处理,等待控制中心恢复。

参考阅读

https://www.thousandeyes.com/blog/facebook-outage-analysis

基于 F-Stack 的权威 DNS 单机 1 亿 QPS 性能优化实践

腾讯云 DNSPod 的权威 DNS 解析目前包含两个版本,基于 F-Stack 的 FTDNS 和基于内核协议栈的 DPDNS,其中 FTDNS 目前性能远高于 DPDNS,是腾讯云对外提供权威 DNS 服务的主力,而 DPDNS 目前主要用作异构容灾及部分特殊场景的部署,如部分只能使用 CVM 或私有化部署的场景。

本文主要介绍 FTDNS 在对 100G 机型进行适配优化的过程,UDP DNS 如何达到单机 1 亿 QPS 的性能,主要涉及网络 I/O 的性能优化和 DNS 解析计算资源的平衡分配,对于 TCP DNS 和内核版的 DPDNS 的性能优化后续将专门进行介绍。

测试平台

测试程序:FTDNS,权威DNS解析程序
F-Stack: 1.21(DPDK 19.11)
OS: Tencent tlinux release 2.2 (Final)
Kernel: 4.14.105-1-tlinux3-0020
CPU: AMD EPYC 7K62 48-Core Processor * 2
NIC: Mellanox Technologies MT28800 Family [ConnectX-5 Ex]
Device type: ConnectX5
Name: MCX516A-CDA_Ax_Bx
Description: ConnectX-5 Ex EN network interface card; 100GbE dual-port QSFP28; PCIe4.0 x16; tall bracket; ROHS R6

Run to completion 架构

因为 DNS 解析的总体逻辑相对比较简单,除了网络 I/O 外只有查找本地缓存查找,所以 FTDNS 在常规架构下完全使用 RTC(Run to completion) 架构以达到更好的性能,网卡收发队列、协议栈、应用与 CPU 核心一一绑定,如下图所示

FTDNS(RTC)
【注】简化结构,仅简单示意 UDP DNS

对于 UDP DNS 请求,直接通过网卡的 RSS 功能将请求包负载收包至已经绑定到各个队列和 CPU 的 FTDNS 业务进程中,并且使用 F-Stack lib 提供的 ff_regist_packet_dispatcher() 函数直接注册回调函数对 UDP 的 DNS 请求包直接进行应用层的解析处理,并直接回包,旁路掉同为用户态的 FreeBSD 的网络协议栈。

性能测试

在拿到 100G 的机器后,使用原有程序直接进行了性能测试,最优性能表现如下图所示

32 进程 RTC QPS
32 进程 RTC CPU 使用率

该测试机型单个 NUMA 节点的 CPU 为 48 核 96 线程 ,对应一张 100G 网卡,为了达到最优性能,正常只使用 48 个物理核进行测试,通过对不同核数分别进行测试,发现在使用 32 核左右时才能达到最优性能,但是也只有 76M 的 QPS,离一亿的小目标差距有点大,接下来进行瓶颈分析。

瓶颈分析

因为直接启动 48 个业务进程测得的性能很低,只有 35M QPS 左右,而此时 CPU 并没跑满,空闲都在一半以上,如下图所示

48 进程 RTC QPS
48 进程 RTC CPU 使用率

所以分别启动不同数量的业务进程分别测得性能数据,且为了排除 FTDNS 业务应用本身性能的影响,同时做了部分关闭 DNS 实际解析逻辑(其他处理逻辑保留)的 echo server 的对比测试,测试结果大致如下

进程数 空载性能(QPS) DNS性能(QPS)
4 80 M /
8 107 M 19 M
12 113 M 28 M
16 113 M 38 M
20 84 M 47 M
24 80 M 56 M
32 77 M 76 M
40 40 M 40 M
48 37 M /

很明显,从以上数据可以得出几个结论:

  1. 收发包性能受队列数影响很大:当队列数较少时,收发包性能主要受 CPU 性能本身影响;当接近 16 个队列时,收发包性能最优,可以达到 113M QPS(非单纯收发的最高性能,因为还包括其他处理逻辑),之后性能急剧下降,分析主要是受 PCIE 通道数量为 16 个的影响,当队列数超过 16 后造成性能下降(此条以后如拿到其他配置机型后可做对比测试进行验证)。
  2. DNS 解析查询性能可线性扩展:当进程数较少时,FTDNS 的性能主要受限于 CPU 的计算性能,在达到收发包极限前,性能可以随进程数增长而线性增长(纯内存缓存、无共享、无锁等),超过 32 进程之后则受收发包性能影响,CPU 出现大量空闲。

优化方向

1. 调整网卡相关参数

通过查看 MLX5 网卡的相关参数文档,对部分参数进行组合调整并压测验证,如 mprq_en, rxqs_min_mprq, mprq_log_stride_size, mprq_max_memcpy_len, txqs_min_inline, txq_mpw_en 等,期望可以达到在网卡开启更多接收队列(如 48 个)也能达到更高的收发包性能,最终通过大量的测试表明,在48队列时网卡的收发包性能依然很低,辅证是由 PCIE 通道数引起的性能下降,此处不再详细展开。

2. 架构优化

2.1 Pipeline

既然网卡开启 16 个接收队列时可以达到最好的接收性能,那么很自然的一个想法,将业务程序的架构由 Run to completion 改为 Pipeline 架构,只开启 16 个接收队列专门用于接收 DNS 请求,再通过 rte ring 将请求报转发到其他业务进程进行实际的 DNS 解析后再直接发送出去,虽然单进程性能会因为 CPU cache miss 的问题急剧下降,但是可以使用更多的 CPU 运行业务进程来弥补,架构简图如下所示。

Pipeline 架构
【注】红色箭头为与 RTC 架构的数据包流向的区别

F-Stack 本身已经提供了函数 ff_regist_packet_dispatcher() 用于对接收到的数据包进行重新分发,且 FTDNS 中已经使用来直接进行 UDP DNS 的解析,稍作修改前 16 个进程仅分发,不再处理 DNS 解析即可,但是实际测试发现转发性能太差,perf 分析主要是软分发回调函数是对数据包一个个进行处理的,性能较差。

所以不得不侵入式修改 F-Stack 的转发逻辑,在 F-Stack 的收发包主循环中,
前 16 个进程 在接收到数据包后直接批量根据配置转发到业务进程中,转发到 ring 的性能得到提升,但业务进程单核性能也下降明显,实测结果如下图所示,

48 进程 Pipelie QPS
【注】该统计将 F-Stack 对流量信息统计进行了修改,软分发也统计到了 worker 进程上,更直观

48 进程 Pipelie CPU

此时 48 核为同一个 NUMA 节点的物理核心,全部使用后性能可以达到 68M QPS,单核性能下降约 12%,符合前期的心里预期,查看 perf 信息,热点也符合预期(如下图所示)。

跨 CPU 访问数据热点

如果有更多业务进程,是否就可以达到小目标了呢?因为机器架构限制的原因,本物理核的另外 48 个超线程核心很难使用到,且根据历史经验,即使使用性能提升也非常小,所以直接继续使用另一个 NUMA 节点的 CPU 的物理核心进行进一步测试,可以用到 80 个 CPU 核心,对应的收包分发线程调整为 20 进程,测试得到 Pipeline 的最优性能如下图所示。

80 进程 Pipeline QPS
【注】:此处 PPS 统计使用的是 F-Stack 原始统计方式,不够直观

80 进程 Pipeline CPU

性能有很大进展,已经达到了 95M QPS,离小目标不远了,但是也可以看出来进程 48 – 79 跨 NUMA 访问的 32 个进程 CPU 使用率是高于本 NUMA 节点的 CPU 的,查看 perf 信息同样证明了跨 NUMA 访问存在的问题。

此架构下继续调整尝试多次,包括继续增加 CPU 等,也无法达到更好的性能,只好尝试其他方向。

2.2 Run to completion + Pipeline

分析 48 进程 Pipeline 架构的 CPU 图可以发现,dispatcher 进程的 CPU 尚有一半空闲,是否也可以利用起来呢?

继续对分发部分代码进行稍微的修改, dispatcher 进程可以支持按照配置的分发进程数自动将收包分别交由本进程的应用层进行解析处理或者通过 Ring 转发到其他 worker 进程,架构如下图所示

RTC + Pipleline 架构
【注】:红色箭头表示与 Pipeline 架构不同的数据包流向

经过多次尝试调整不同进程的数量以及数据包分发比例,最终还是每个 dispatcher 与 worker 进程处理相同量的数据包,但是一个 dispatcher 进程可以根据配置自动对应多个 worker 进程,并测试得到了此时的最优性能,如下图所示

48 进程 RTC + Pipeline PPS
48 进程 RTC + Pipeline CPU

此处达到了 92M的性能,但是总性能还是略差于 Pipleline 性能,优势就是只使用了 48 个 CPU,使用 24 dispatcher + 24 worker 与 16 dispatcher + 32 worker 总体性能相当,此时虽然 dispatcher 进程依然有部分 CPU 空闲,但是增加其 DNS 处理比列并不能提升整体性能了,主要是因为如果分配更多的 CPU 时间片到 DNS 业务处理,则整体轮询次数减少,导致收包能力下降。

此时但是小目标依然没能实现,还需要考虑其他优化点。

2.3 单进程性能提升

在 RTC 测试中,单核 DNS 的性能虽然达到 2.38M QPS,已经很高了,但是通过 perf 分析在 DNS 业务处理逻辑中查询缓存时的 Hash 和 Key 的字符串比较函数占用 CPU 较高,依然有一定的优化空间,先看下 FTDNS 内存缓存结构,如下图所示

FTDNS 内存缓存结构

内存缓存是常规的 Hash 表结构,解决 Hash 冲突使用的是拉链法,存储的是写入时已经组好 DNS 应答包格式的数据,且无共享(无任何多写的数据结构)、无锁(类似 RCU 锁思想,但是并不加锁)等。

对 Hash 的 Key 进行优化,去除了部分不必包含和字符串比较函数进行优化,去除了部分非必要的字段,减少了需要进行 Hash 计算的 Key 长度。

因为大部分 Key 较短,暂未使用 memcmp() 进行比较, 因为仅需要判断 Key 是否相等即可,所以自行实现字符串比较函数,去除标准比较函数中多余的操作。

优化完成后,Pipeline 和 RTC + Pipeline 架构的性能都达到了 1 亿(100M) QPS 的小目标,性能测试结果如下所示

80 进程 Pipeline QPS100M
80 进程 Pipeline CPU 100M
48 进程 RTC + Pipeline QPS 100M
48 进程 RTC + Pipeline CPU100M

虽然两种架构的性能都达到了目标,但是综合考虑 RTC + Pipeline 只需要使用一个 NUMA 节点的 48 个物理核心即可,可以节省大量的 CPU 计算资源,且性能只是略低,所以最终在 FTDNS 中采用 RTC + Pipeline 的架构,常规配置 dispatcher 进程数量为 0 表示使用 RTC 模式,如有需要可以随时修改配置切换为 RTC + Pipeline 模式。

其他问题

  1. 本次测试使用的是 AMD CPU + Mellanox 的网卡,对于其他组合后续拿到其他组合的机型(如 Intel 的 CPU,Intel 或 Broadcom 的网卡等)也需要进行对应的测试,测试性能及验证队列数多于 PCIE 通道数时性能下降的问题,该问题也与 Intel 工程师有过交流。
  2. 跨 CPU 核心访问数据性能下降问题 Intel 工程师表示后续会有新的指令集可能对性能提升会有帮助。
  3. 后续还需要对 TCP DNS,或者说 F-Stack 在 100GE 机型上的短/长链接 TCP 进行测试和优化。

国内主要公共 DNS 支持 ECS 情况测试 – 20210315

本次测试仅通过黑盒测试分析不同公共 DNS 当前(20210315)是否支持 ECS,及方案上的一些差异,相关方案的优劣不进行额外说明。

结论

厂商IP是否支持ECS
腾讯云/DNSPod119.29.29.29、119.28.28.28
阿里223.5.5.5、223.6.6.6
DNS派101.226.4.6、123.125.81.6等
OneDNS117.50.11.11、52.80.66.66
114114.114.114.114、114.114.115.115
CNNIC1.2.4.8、210.2.4.8
百度180.76.76.76

细节差异简析

腾讯云/DNSPod

  1. 绝大部分后端递归节点是支持 ECS 的节点,少部分递归节点为不支持 ECS 的节点;其中不支持 ECS 的节点仅会对本省本运营商的请求进行递归, 支持 ECS 的递归节点可为所有线路进行递归。
  2. 支持 ECS 的递归节点携带用户的实际 IP 作为 ECS IP 向权威 DNS 进行请求,为防源 IP 泄漏,统一格式化为 x.x.x.1/32。
  3. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。

阿里

  1. 部分后端递归节点是支持 ECS 的节点,部分递归节点为不支持 ECS 的节点;;其中不支持 ECS 的节点仅会对本省本运营商的请求进行递归, 支持 ECS 的递归节点为未部署后端递归节点的线路进行递归。
  2. 支持 ECS 的递归节点不会携带实际用户的 IP 向权威 DNS 请求,而是携带相关线路固定的 IP 作为 ECS IP 向权威 DNS 进行请求,并格式化为 x.x.x.0/24,目的可能是为了减少递归 DNS 节点的缓存量。
  3. 中国移动线路在未选择到后端递归节点的省份中,似乎都选择了相同的一个移动 IP 作为 ECS IP,没有为不同省份选择不同的 ECS IP。
  4. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。

DNS 派

  1. 绝大部分后端递归节点是支持 ECS 的节点,少部分递归节点为不支持 ECS 的节点。
  2. 支持 ECS 的递归节点携带用户的实际 IP 作为 ECS IP 向权威 DNS 进行请求,为防源 IP 泄漏,统一格式化为 x.x.x.0/24。
  3. 无缓存的应答结果可能返回多个 ECS 段。
  4. 缓存层按照省份运营商线路(如广东电信)进行缓存,减少缓存量。
  5. 海外转发至其他公共 DNS,如 CloudFlare 的 1.1.1.1。

OneDNS

  1. 本身的后端递归节点不支持 ECS,部分省份运营商会转发至腾讯云/DNSPod 的公共 DNS 进行解析。
  2. 海外请求全部转发至腾讯云/DNSPod 的公共 DNS 进行解析。

其他

  1. 测试的114、CNNIC、百度等公共 DNS 暂不支持 ECS。
  2. 支持 ECS 的公共 DNS 部分不支持 ECS 的递归节点不一定是真的不支持,不排除 DNS 请求被重定向的可能。

Nginx TCP 多证书透明代理及 Linux/F-Stack(FreeBSD) 路由相关设置

某个 TCP 服务对外有多个域名提供相同的服务,且每个域名都是基于 TLS 的,需要通过 Nginx 对 TLS 进行卸载后转发到实际的上游服务,且上游服务必须使用客户端的源 IP,所以 Nginx 需要使用透明代理。分别需要对Nginx 和系统路由进行配置。

Nginx 配置

需要 Nginx 1.15.9 以上版本,简化配置如下所示,

stream {       
  upstream up_server {
      server 192.168.1.3:8081;
  }
   
  # 通过 map 配置不同域名(SNI)使用不同的证书文件
  # 证书为泛解析证书, 匹配泛解析域名
  # 会降低性能
  map $ssl_server_name $targetCert {
      ~*domain1.com$ /usr/local/cert/domain1.crt;
      ~*domain2.com$ /usr/local/cert/domain2.crt;
      ~*domain2.com$ /usr/local/cert/domain3.crt;
      default /usr/local/cert/domain1.crt;
  }
   
  map $ssl_server_name $targetCertKey {
      ~*domain1.com$ /usr/local/cert/domain1.key;
      ~*domain2.com$ /usr/local/cert/domain2.key;
      ~*domain2.com$ /usr/local/cert/domain3.key;
      default /usr/local/cert/domain1.key;
  }
   
  server {
      listen 8080 ssl reuseport;
   
      ssl_certificate $targetCert;
      ssl_certificate_key $targetCertKey;
      ssl_protocols TLSv1.2 TLSv1.3;
      ssl_session_tickets off;

      proxy_pass up_server;
      proxy_bind $remote_addr transparent; # 透明代理
  }
}

Linux 系统路由配置

因为透明代理的源 IP 是实际客户的 IP,在实际服务接受处理完响应包返回时会返回给实际的客户 IP,所以需要配置将回包发到 Nginx 进行处理,这里的上游服务为本机服务,需进行如下配置。如上游在其他服务器上,可以查看参考资料中文章并进行对应配置。

# 新建一个 DIVERT 给包打标签
iptables -t mangle -N DIVERT;
iptables -t mangle -A DIVERT -j MARK --set-mark 1;
iptables -t mangle -A DIVERT -j ACCEPT;

# 把本机 TCP 服务的回包给 DIVERT 处理
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 8081 -j DIVERT

# 有标签的包去查名为 100 的路由表
ip rule add fwmark 1 lookup 100

# 100的路由表里就一条默认路由,把所有包都扔到lo网卡上去
ip route add local 0.0.0.0/0 dev lo table 100

F-Stack(FreeBSD) 路由配置

F-Stack(FreeBSD) 上游回包路由配置,

# upstream 为本机时
# 假设 f-stack-0 的 IP 为 192.168.1.3,将 upstream 往外发的所有出包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3,8080 tcp from 192.168.1.2 8081 to any out

# upstream 为其他机器时
# 将 upstream 通过设置网关或者 IP 隧道(需额外进行隧道配置)等方式发过来的所有入包都转发到 F-Stack Nginx 监听的 IP 和端口即可
# 因为转发到本机地址时目的端口会被忽略,可不设置端口
ff_ipfw add 100 fwd 192.168.1.3 tcp from 192.168.1.2 8081 to any in via f-stack-0

参考资料

  1. nginx TLS SNI routing, based on subdomain pattern
  2. 使用nginx的proxy_bind选项配置透明的反向代理
  3. [sslh] Using sslh transparent proxy on FreeBSD?

过去的2020

又到了本命年,过去的一年大事发生了很多事,不管是疫情、水灾、航天、矿难,虽一直在默默关注,但个人 能做的有限,还是只简单总结下自己过去的一年吧。

家庭关系好多了,前两年陪孩子太少,现在陪的多了,明显亲密多了,当然想达到孩他妈的程度还是差远了。另外发生了一件比较伤心伤身的事情,孩他妈辛苦了。 身体急剧变差,上半年疫情严重时期长时间在家呆着一直不出门,吃得多,运动少,工作也太拼,造成整个人持续失眠、焦虑、压力大、暴躁易怒、体重剧增20斤,基本每天都是忙到凌晨,躺下睡不着,经常要差不多天亮才能睡着。下半年对工作和生活作息做了很大的调整,精神状态算是慢慢好了不少,身体又出毛病了,经常性的头疼、颈椎疼、腰疼,尤其是腰,弯一会能疼一天,还好各种检查虽然有点小异常,但没啥大问题,经过多休息、调整坐姿站姿少做剧烈运动,换了个有泳池的健身房,大幅减少了跑步,游泳占了运动的一多半,到年底算是有好转,没那么严重了。当然也有好消息,下半年把20斤的体重又减下去了,回到了去年底的水平。

工作上两个主要产品权威DNS和公共DNS/HTTPDNS各开发发布了一个大版本,以及N个小功能小版本,遗憾的是F-Stack未能抽出时间去做一直计划要做的一些大改动,只有一些小改动,发布了一个小版本。21年还会有各自的大版本开发发布,也会在F-Stack上投入更多一点精力。另外因为整个人的精神状态太差,在工作中明显会出现多次急躁不耐烦,语气有时候比较冲的情况,向所有人说句对不起了,会努力调整自己的状态。

IOS14对使用8.8.8.8进行DNS(CDN)调度的影响

2023-12-21更新:虽然8.8.8.8对MX、CAA以及其他类型等依然不支持ecs(符合RFC标准),但是在cname链中已经不会对A/AAAA等其他记录类型造成影响,看上去像是单独处理了,已经修复了该问题。

但是依然存在ecs失效的可能,比如8.8.8.8的递归到权威的请求偶发超时、请求权威跨国且请求了同权威IP被污染的域名等场景,依然可能造成递归判断权威的IP不支持ecs,导致后续一段时间内的ecs失效,暂无太好的办法。


2021-04-02 更新: Google 公共 DNS 已经对 HTTPS 和 SVCB 支持了 ECS,其他类型正在评估,测试正常。


2021-03-26 更新:标准化组织正在对该问题进行讨论,大体方案可能为:

  1. 如果递归 DNS 准备解析 HTTPS 记录指向的域名,并且在返回给客户端的应答附加段中添加 A/AAAA 记录,则递归在向权威请求HTTPS 记录时应携带 /0,表示递归支持 ECS,但对 HTTPS 记录类型禁用,此时权威在响应时不能在附加段中添加相关 A/AAAA 记录,以便递归 DNS 携带正常的 ECS 进行 A/AAAA 记录的请求。
  2. 如果递归 DNS 不准备执行上面的方案,则递归在向权威进行 HTTPS 类型的请求时应携带与请求 A/AAAA 类型相同的 ECS。

方案1实现较复杂,递归 DNS 需要对 HTTPS 记录类型进行单独处理,此处也要注意 HTTPS 记录类型也包括对应的 CNAME,但是可以减轻缓存压力;方案2简单粗暴,对应增加的就是递归 DNS 的缓存压力。

具体信息可以参考: https://issuetracker.google.com/issues/181593657https://github.com/MikeBishop/dns-alt-svc/pull/308


我们一直的观点是在国内不要使用 Google 的公共 DNS(8.8.8.8),这里再从技术层面讨论下 IOS 14 更新后使用 8.8.8.8 进行 DNS 解析对 CDN 调度的影响。

结论

  1. 在国内任意地区都不应该系统上直接设置或转发到 8.8.8.8 进行 DNS 解析,这个结论依然有效。
  2. IOS 14 更新后会对域名自动发出 TYPE HTTPS (TYPE65) 类型的请求,这会导致 8.8.8.8 对于包含 CNAME 记录的解析结果 ECS 判断失效的情况极大增多,调度不准确的情况会远多于 IOS 14 更新之前,CDN 调度受影响比较严重。
  3. 仅限于当前时间(2021.03.04)有效,如果后续相关 DNS 请求行为有变化,则结论可能不再准确。

解析过程

公共 DNS 的解析准确度一般是通过在尽量多的地区和运营商部署递归节点,以及是通过 ECS (EDNS Client Subnet,常简称为 EDNS) 技术来提示解析准确度,最主要的使用场景就是 CDN 的调度,所以一旦 ECS 失效,对 CDN 调度的影响也是最大的。

虽然任何 DNS 都不能保证完全解析准确,但是此前 8.8.8.8 做的还是不错的,但是从 20 年 IOS 14 更新后,解析准确度每况愈下,尤其是对部分使用 CDN 进行调度的域名影响较大,经常发生国内解析到国外、不同地区使用多家 CDN 时解析结果不符合预期等问题,下面分析下 IOS 14 对该现象的具体影响过程。

假设在权威 DNS 上对typehttps.example.com配置以下记录, 并且权威 DNS 对 ECS 的支持正确,8.8.8.8 的自动检测也可以正确检测到权威 DNS 是支持 ECS 的。

typehttps   默认  600 CNAME   test1.example.com.
typehttps   境外 600 CNAME   test2.example.com.
test1       默认 600 A       8.8.8.8
test2       默认 600 A       8.8.4.4

当我们从国内向 8.8.8.8 请求 typehttps.example.com或携带国内IP(如 119.29.0.0/24)的 ECS IP 时,正常过程是这样的,

dig @8.8.8.8 typehttps.example.com +subnet=119.29.0.0/24

正常的结果应该是类似这样的,因为权威 DNS 是根据 8.8.8.8 请求时携带的119.29.0.0/24进行解析的,

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
; CLIENT-SUBNET: 119.29.0.0/24/24
;; QUESTION SECTION:
;typehttps.example.com.           IN     A

;; ANSWER SECTION:
typehttps.example.com. 550 IN CNAME   test1.example.com.
test1.example.com       600 IN A       8.8.8.8

但是现在你有很大概率拿到会是以下的错误结果,

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
; CLIENT-SUBNET: 119.29.0.0/24/24
;; QUESTION SECTION:
;typehttps.example.com.           IN     A

;; ANSWER SECTION:
typehttps.example.com. 590 IN CNAME   test2.example.com.
test1.example.com       600 IN A       8.8.4.4

解析到了境外的 CNAME 及其 IP,以前虽然也可能出现这种解析错误的情况,但是相对少很多,那是什么原因呢,此时如果我们指定请求 CNAME 类型再来看下有什么区别,

dig @8.8.8.8 typehttps.example.com +subnet=119.29.0.0/24 CNAME
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
; CLIENT-SUBNET: 119.29.0.0/24/0
;; QUESTION SECTION:
;typehttps.example.com.           IN     CNAME

;; ANSWER SECTION:
typehttps.example.com. 590 IN CNAME   test2.example.com.

此时可以看到 ECS 段中的 scope 为 0,这就表示 8.8.8.8 的缓存中将境外线路的 CNAME 记录作为全局结果缓存了,那为什么会出现这种错误的解析和缓存结果呢?

通过抓包分析可以发现,8.8.8.8 对权威 DNS 发起的查询请求中,即使权威是支持 ECS 的,8.8.8.8 也并不会对所有类型的请求记录携带 ECS 段,大部分常见的请求类型,如 A 记录(IPv4)、AAAA 记录(IPv6)、CNAME 记录等并不会出现问题,但是有部分请求类型则不会携带 ECS 进行请求,而这其中有部分类型可能会返回 CNAME 记录,比如 MX 、CAA 以及 HTTPS 类型,权威 DNS 就会按照 8.8.8.8 的递归 DNS 的 IP 所在地返回境外线路的 CNAME 记录,而后 8.8.8.8 就会按照全局 /0 的 scope 缓存该 CNAME。

所以这里就破案了,具体过程是如下两图所示,

A 记录请求过程
HTTPS 记录请求过程
  1. IOS14 升级后会支持 TYPE HTTPS 的记录类型,解析一个域名时应同时请求 A/AAAA/HTTPS 记录类型。
  2. 则 A 和 AAAA 记录可以正常获得境内的 CNAME 和 IP 并正常按照 119.29.0.0/24 的 IP 段缓存,但该缓存仅对该 IP 段有效.
  3. HTTPS 类型的返回的错误 CNAME 结果就会因为上述原因作为全局 /0 的缓存。
  4. 后续所有非119.29.0.0/24网段 请求都会命中全局缓存的错误 CNAME 记录,然后再对 CNAME 记录进行正常的递归查询,客户端最后拿到的就是错误的境外结果。
  5. 虽然 MX 、CAA、HTTPS 类型都会造成此问题,但是在实际的网络环境中,普通域名的 MX 和 CAA 记录类型都是很少被请求到的,所以 8.8.8.8 虽然一直存在类似的问题,但是并不严重。直到 IOS14 更新后, HTTPS 记录类型默认被请求,该问题就变得比较严重了。

影响范围

对不同地区设置了不同 CNAME 记录的域名会有较大影响,一般常见于 CDN,所以对 CDN 影响比较大,具体分析话大概是这样的,

  1. 如果境内和境外使用了不同的 CNAME 记录,如国内使用国内的 CDN,国外使用国外的 CDN,则国内使用 8.8.8.8 会大概率解析到国外线路的 CNAME,很可能解析到国外节点影响访问体验。
  2. 如果未设置其他国家、大洲、境外等线路,只是不同省份运营商使用不同的 CNAME 记录进行负载均衡,则国内使用 8.8.8.8 会大概率会解析到默认线路上,虽然会造成负载均衡效果不符合预期,但在客户端的访问体验上总体影响不大。
  3. 对境外用户影响有限,因为虽然也存在一样的 ECS 失效问题,但根据递归 DNS IP 获取到的 CNAME 结果范围不会差距很大。

规避方法

  1. 在国内避免使用 8.8.8.8,当然这里有部分运营商已经帮忙搞定这一步了,移动端则可以考虑使用 HTTPDNS 等技术手段规避。
  2. 如果条件允许,不同地区应使用相同的 CNAME,如果必须使用不同的CNAME,则需要关注默认和境外相关线路的容量等问题,如境外线路的 CDN 能提供境内 CDN 节点则可以很大的缓解该问题。

其他公共DNS

  1. OpenDNS 208.67.222.222 仅 CAA 记录可能存在此问题;腾讯云/DNSPod 119.29.29.29,各请求类型均不受影响;阿里云 223.5.5.5 不受影响。
  2. 1.1.1.1、9.9.9.9、180.76.76.76、114.114.114.114,默认未支持 ECS ,仅能通过后端递归节点的部署进行调度,不涉及该问题。

参考资料

draft-ietf-dnsop-svcb-httpssvc-03.