Envoy DNS Resolver 扩展详解:c-ares、Apple、getaddrinfo 与 Hickory DNS 的配置与原理
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
Envoy 通过可插拔的 DNS resolver 扩展机制为集群服务发现(strict DNS / logical DNS)、动态正向代理(Dynamic Forward Proxy)和 UDP DNS Filter 等组件提供域名解析能力,默认使用 c-ares 库,同时内置 Apple(iOS/macOS)、getaddrinfo 与 Hickory DNS 三套可选实现。本文以docs/root/api-v3/config/dns_resolver/dns_resolver.rst索引所覆盖的四个 DNS resolver 扩展为骨架,结合api/envoy/extensions/network/dns_resolver/下的 proto 定义、source/extensions/network/dns_resolver/下的实现源码与动态正向代理的完整配置示例,系统讲解各解析器的配置字段、默认值、适用场景与运行统计,帮助读者掌握在集群、动态正向代理缓存等场景中定制 DNS 解析行为的完整方法。
一、Envoy 的 DNS 解析扩展机制
Envoy 中多个组件都会触发 DNS 解析:不同集群类型(strict DNS、logical DNS)、动态正向代理系统(由集群与 HTTP Filter 组合而成)、UDP DNS Filter 等。为了统一管理这些场景的解析行为,Envoy 将 DNS 解析抽象为可插拔扩展,每个扩展通过工厂注册表(Registry)注册,配置时使用TypedExtensionConfig按名字加载对应的 typed config。相关架构说明见 DNS Resolution 架构文档。
从源码source/common/network/dns_resolver/dns_factory_util.cc可以确认默认行为:makeDefaultDnsResolverConfig会优先尝试 Apple API(受 runtime 特性envoy.restart_features.use_apple_api_for_dns_lookups控制),否则回退到 c-ares,即c-ares 是 Envoy 的默认 DNS 解析库;在 Apple 系操作系统上,可通过 runtime 特性启用 Apple 原生 API。
1.1 内置的四个 DNS resolver 扩展
Envoy 内置了四个 DNS resolver 扩展,其 proto 定义分别位于:
扩展名(typed_dns_resolver_config.name) | proto 定义文件 | 核心特性 |
|---|---|---|
envoy.network.dns_resolver.cares | cares_dns_resolver.proto | 默认实现,基于 c-ares,功能最丰富 |
envoy.network.dns_resolver.apple | apple_dns_resolver.proto | 仅 iOS/macOS,基于 Apple 系统 API |
envoy.network.dns_resolver.getaddrinfo | getaddrinfo_dns_resolver.proto | 调用系统getaddrinfo(),独立线程执行 |
envoy.network.dns_resolver.hickory | hickory_dns_resolver.proto | 纯 Rust 实现,支持 DoT / DoH / DNSSEC |
其中 Hickory DNS 是基于 Hickory DNS 的纯 Rust 解析器,通过动态模块框架(dynamic modules framework)集成,运行在独立的 Tokio runtime 线程上,与 Envoy 的事件循环线程隔离,因此 DNS 解析不会阻塞 dispatcher 线程。
二、DNS 解析器的配置入口
DNS resolver 的 typed config 通过typed_dns_resolver_config字段注入,主要出现在三个位置:
- Cluster 级别:
Cluster.typed_dns_resolver_config(替换旧的Cluster.dns_resolution_config),见 dns_cluster.proto 中的说明; - DNS 集群扩展:
DnsCluster.typed_dns_resolver_config,当集群通过cluster_type使用envoy.clusters.dns扩展时,该字段优先级最高——若它与 Cluster 上的 resolver 配置同时存在,Envoy 会采用此处的配置并忽略 Cluster 中的相关字段; - 动态正向代理 DNS 缓存:
DnsCacheConfig.typed_dns_resolver_config,Filter 与 Cluster 必须配置相同的 DNS 缓存参数才能协同工作。
2.1 通用解析选项:DnsResolverOptions 与 DnsResolutionConfig
在api/envoy/config/core/v3/resolver.proto中定义了两个通用消息:
DnsResolverOptions控制解析器的基础行为,包含两个布尔开关:use_tcp_for_dns_lookups:所有 DNS 查询改用 TCP 而非默认的 UDP;no_default_search_domain:不使用系统默认搜索域,仅按主机名原样或别名查询。
DnsResolutionConfig是较早期的配置形态,包含resolvers(DNS 解析器地址列表,至少 1 项)与dns_resolver_options。若未指定resolvers,则使用系统默认解析器(如 Unix 下的/etc/resolv.conf)。该字段现已逐渐被typed_dns_resolver_config取代。
三、c-ares 解析器(默认):CaresDnsResolverConfig
c-ares 是 Envoy 的默认解析器,其配置消息CaresDnsResolverConfig定义于 cares_dns_resolver.proto,字段编号已用到 12,属于[#next-free-field: 13]。各字段说明如下:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
resolvers | 重复的config.core.v3.Address | 系统默认 | DNS 解析器地址列表;是否覆盖系统默认值取决于use_resolvers_as_fallback |
use_resolvers_as_fallback | bool | false | 为true时,仅在 c-ares 无法从系统(如/etc/resolv.conf)获取 nameserver 时才使用resolvers;否则resolvers将覆盖系统默认解析器 |
filter_unroutable_families | bool | false | 查询可用网络接口,若某个 IP 族(IPv4/IPv6)没有任何可用接口,则从结果中过滤该族的地址 |
dns_resolver_options | config.core.v3.DnsResolverOptions | — | 复用通用选项(TCP 查询、禁用搜索域) |
udp_max_queries | UInt32Value | — | 限制基于 UDP 的 DNS 查询数量上限(当前仅 c-ares 适用) |
query_timeout_seconds | UInt64Value | Envoy 默认5秒 | 每个 nameserver 首次应答的超时秒数;注意 c-ares 库默认 2 秒,Envoy 未设置时默认 5 秒,这是为了保持旧行为、避免用户反馈的解析耗时上升。校验规则要求>= 1 |
query_tries | UInt32Value | Envoy 默认4次 | 放弃前的最大查询尝试次数,每次尝试可能使用不同 nameserver;c-ares 库默认 3 次,Envoy 未设置时默认 4 次。校验规则要求>= 1 |
rotate_nameservers | bool | false | 开启后按轮询方式选择 nameserver,分散查询负载;关闭(默认)时按配置顺序依次尝试。该设置会覆盖系统对 nameserver 轮转的配置 |
edns0_max_payload_size | UInt32Value | c-ares 内部默认(通常 1232) | EDNS0 UDP 负载上限(字节)。设置后 c-ares 会在查询中携带 EDNS0,并用该值作为最大 UDP 响应大小。推荐值:1232(安全默认,避免分片)、4096(最大值)。校验规则要求512 <= x <= 4096 |
max_udp_channel_duration | Duration | 不设置则不刷新 | 若设置,解析器会周期性重新初始化 c-ares channel,避免陈旧 socket 状态、改善 UDP 端口负载分布 |
reinit_channel_on_timeout | bool | false | 当 DNS 查询以ARES_ETIMEOUT失败时重新初始化 c-ares channel,帮助从 UDP socket 不可用等罕见故障中恢复。若超时源于间歇性网络问题,开启可能增加 channel 重建频率,可考虑改用max_udp_channel_duration做周期刷新 |
qcache_max_ttl | UInt32Value | Envoy 默认0(关闭查询缓存) | c-ares 查询缓存的最大 TTL(秒)。设为非 0 时启用缓存,并遵守 DNS 响应中的 TTL(不超过该上限)。c-ares 库默认缓存 1 小时,而 Envoy 默认关闭 |
3.1 实现细节与统计指标
c-ares 解析器的实现位于 source/extensions/network/dns_resolver/cares/dns_impl.h 与dns_impl.cc。其统计宏ALL_CARES_DNS_RESOLVER_STATS定义了 6 项指标,挂在dns.cares统计树(stats tree)下:
| 名称 | 类型 | 说明 |
|---|---|---|
resolve_total | Counter | DNS 查询总数 |
pending_resolutions | Gauge | 进行中的 DNS 查询数 |
not_found | Counter | 返回NXDOMAIN或NODATA的查询数 |
get_addr_failure | Counter | 查询期间的一般性失败次数 |
timeouts | Counter | 超时查询数 |
reinits | Counter | c-ares channel 重新初始化次数 |
实现上,DnsResolverImpl的所有调用与回调都发生在创建它的 dispatcher 线程上;c-ares 仅支持 channel 级取消,因此PendingResolution::cancel只是标记cancelled_并跳过回调,网络事件仍会继续(见dns_impl.h中PendingResolution的实现注释)。
四、Apple 解析器:AppleDnsResolverConfig
Apple 解析器仅适用于 iOS/macOS 平台,配置消息定义于 apple_dns_resolver.proto,仅有一个字段:
include_unroutable_families(bool,默认false):开启后绕过系统"仅返回可路由的 IPv4/IPv6 地址"的启发式逻辑,返回所有可能的地址。该设置在 DNS 查询族被限定为 v4-only 或 v6-only 时会被忽略。绝大多数场景应保持false,但在自行过滤地址(例如实现 Happy Eyeballs)时可能有用。
其统计指标挂在dns.apple统计树下(见 DNS Resolution 架构文档):
| 名称 | 类型 | 说明 |
|---|---|---|
connection_failure | Counter | 连接 DNS 服务器失败的次数 |
get_addr_failure | Counter | 调用 GetAddrInfo API 时的一般性失败次数 |
network_failure | Counter | 网络连通性导致的失败次数 |
processing_failure | Counter | 处理 DNS 服务器返回数据时的失败次数 |
socket_failure | Counter | 获取连接 DNS 服务器的 socket 文件描述符失败的次数 |
timeout | Counter | 超时查询数 |
在启用 Apple 解析器时需要借助 runtime 特性envoy.restart_features.use_apple_api_for_dns_lookups(参见 dns_factory_util.cc 中tryUseAppleApiForDnsLookups的逻辑)。
五、getaddrinfo 解析器:GetAddrInfoDnsResolverConfig
getaddrinfo 解析器直接调用系统getaddrinfo()函数解析主机名,配置消息定义于 getaddrinfo_dns_resolver.proto:
num_retries(UInt32Value):放弃前的重试次数;未指定时解析器会无限重试,直到成功或 DNS 查询超时。num_resolver_threads(UInt32Value):用于解析待处理 DNS 查询的线程数;未指定时使用 1 个线程。
该消息的文档中有两处重要提示:
- 解析结果使用硬编码 60 秒 TTL:因为
getaddrinfo()API 不提供真实 TTL,目前固定为 60 秒,未来如需可再增加配置; - 实现层面,从 getaddrinfo.h 的注释可见,该解析器在专用解析线程上调用
getaddrinfo(),目前只适合相对低频率的解析场景(未来可扩展为线程池)。
此外,getaddrinfo 解析器当前不产生任何解析器专属统计指标。
六、Hickory DNS 解析器:HickoryDnsResolverConfig
Hickory DNS 是 Envoy 中较新的纯 Rust 解析器,支持标准 DNS(UDP/TCP)、DNS-over-TLS(DoT)、DNS-over-HTTPS(DoH)以及 DNSSEC 校验。配置消息定义于 hickory_dns_resolver.proto:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
resolvers | 重复的config.core.v3.Address | 系统配置 | 标准 UDP/TCP 解析的 DNS 地址列表;未指定且use_system_config未显式设为false时,使用系统配置(Unix 下/etc/resolv.conf) |
dns_over_tls | DnsOverTlsConfig | — | DoT 配置,指定后查询将经 TLS 发送至配置的服务器 |
dns_over_https | DnsOverHttpsConfig | — | DoH 配置,指定后查询将经 HTTPS 发送至配置的端点 |
enable_dnssec | bool | false | 启用 DNSSEC 校验,验证签名并拒绝校验失败的响应 |
cache_size | UInt32Value | 1024 | DNS 响应缓存条目上限,LRU 淘汰策略,支持负缓存(缓存NXDOMAIN/NODATA响应) |
num_resolver_threads | UInt32Value | 2 | 异步解析所用 Tokio runtime 线程数,最大16,每个解析器实例运行自己的 Tokio runtime |
use_system_config | BoolValue | 未配置resolvers/dns_over_tls/dns_over_https时为true | 是否读取系统 DNS 配置(nameserver 与搜索域);同时指定resolvers时以resolvers优先 |
query_timeout | Duration | 5秒 | 单次查询尝试的超时时间,校验要求>= 1ms |
query_tries | UInt32Value | 3 | 放弃前的最大查询尝试次数,每次可能使用不同 nameserver,校验要求>= 1 |
其中两个子消息:
DnsOverTlsConfig:servers(DoT 服务器地址列表,端口通常为 853)+tls_server_name(TLS 校验使用的 SNI 主机名,指定servers时必填,至少 1 个字符);DnsOverHttpsConfig:server_urls(DoH 端点 URL 列表,如https://dns.google/dns-query,每个 URL 至少 1 个字符)。
Hickory 解析器的统计指标挂在dns.hickory统计树下,包括resolve_total(完成的查询数)、pending_resolutions(进行中查询数)、not_found、get_addr_failure、timeouts。其实现位于 source/extensions/network/dns_resolver/hickory/hickory_dns_impl.cc。
七、完整配置示例:动态正向代理中的 typed_dns_resolver_config
官方文档 dynamic_forward_proxy_filter.rst 给出了将 c-ares 解析器配置到动态正向代理 DNS 缓存的完整示例(源文件为 dns-cache-circuit-breaker.yaml)。其中 Filter 与 Cluster 通过同名的dns_cache_config(dynamic_forward_proxy_cache_config)关联,并在typed_dns_resolver_config中指定envoy.network.dns_resolver.cares及其参数:
admin: address: socket_address: protocol: TCP address: 127.0.0.1 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/force-host-rewrite" route: cluster: dynamic_forward_proxy_cluster typed_per_filter_config: envoy.filters.http.dynamic_forward_proxy: "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.PerRouteConfig host_rewrite_literal: www.example.org - match: prefix: "/" route: cluster: dynamic_forward_proxy_cluster http_filters: - name: envoy.filters.http.dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.dynamic_forward_proxy.v3.FilterConfig dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY dns_cache_circuit_breaker: max_pending_requests: 1024 typed_dns_resolver_config: name: envoy.network.dns_resolver.cares typed_config: "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig resolvers: - socket_address: address: "8.8.8.8" port_value: 53 dns_resolver_options: use_tcp_for_dns_lookups: true no_default_search_domain: true - name: envoy.filters.http.router typed_config: "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router clusters: - name: dynamic_forward_proxy_cluster lb_policy: CLUSTER_PROVIDED cluster_type: name: envoy.clusters.dynamic_forward_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.clusters.dynamic_forward_proxy.v3.ClusterConfig dns_cache_config: name: dynamic_forward_proxy_cache_config dns_lookup_family: V4_ONLY dns_cache_circuit_breaker: max_pending_requests: 1024 typed_dns_resolver_config: name: envoy.network.dns_resolver.cares typed_config: "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.cares.v3.CaresDnsResolverConfig resolvers: - socket_address: address: "8.8.8.8" port_value: 53 dns_resolver_options: use_tcp_for_dns_lookups: true no_default_search_domain: true transport_socket: name: envoy.transport_sockets.tls typed_config: "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext common_tls_context: validation_context: trusted_ca: {filename: /etc/ssl/certs/ca-certificates.crt}该示例同时展示了两个层面的要点:
- Filter 与 Cluster 必须成对配置,并指向同一套 DNS 缓存参数;示例中还包含 DNS 缓存级熔断(
dns_cache_circuit_breaker.max_pending_requests: 1024)与标准集群熔断(circuit_breakers),前者限制待处理 DNS 请求数、防止压垮解析器,后者限制到上游主机的连接/请求/重试等; - 地址族策略:
dns_lookup_family: V4_ONLY限定仅解析 IPv4;该字段的完整取值定义于 dns_cluster.proto 引用的common.dns.v3.DnsLookupFamily(AUTO为默认值)。
如需在 iOS/macOS 上改用 Apple 解析器,只需将typed_dns_resolver_config替换为以下形态(参见 dns-cache-circuit-breaker-apple.yaml):
typed_dns_resolver_config: name: envoy.network.dns_resolver.apple typed_config: "@type": type.googleapis.com/envoy.extensions.network.dns_resolver.apple.v3.AppleDnsResolverConfig八、DNS 集群(DnsCluster)中的解析器配置与刷新语义
除了动态正向代理,DNS 发现型集群(envoy.clusters.dns扩展)同样通过typed_dns_resolver_config接入自定义解析器。结合 dns_cluster.proto 的字段说明,其关键刷新参数与解析器配置协同工作:
dns_refresh_rate:集群 DNS 刷新间隔,未设置时默认5000ms,最小 1ms;dns_failure_refresh_rate:查询失败时的刷新间隔(含base_interval与可选的max_interval,后者默认是 base 的 10 倍);respect_dns_ttl:为true时按 DNS 资源记录 TTL 设置刷新速率;配合dns_min_refresh_rate可设置 TTL 下限(至少 1 秒,TTL 更短的记录按该下限刷新);dns_jitter:为刷新加入随机延迟(上限为配置值),避免大量请求同时触发 DNS 的"惊群"效应;dns_lookup_family:解析地址族,默认AUTO;all_addresses_in_single_endpoint:所有返回地址视为单个端点(logical DNS 语义),否则每个地址视为独立端点(strict DNS 语义)。
该消息的注释还明确说明了配置优先级:当DnsCluster.typed_dns_resolver_config、Cluster.typed_dns_resolver_config、Cluster.dns_resolution_config同时存在时,若集群通过cluster_type使用DnsCluster扩展,Envoy 采用DnsCluster.typed_dns_resolver_config并忽略 Cluster 中的解析器相关字段;否则回退到Cluster.typed_dns_resolver_config。
九、实践建议与注意事项
- 默认行为无需配置:绝大多数场景直接使用默认的 c-ares 解析器即可,系统 nameserver 会自动从
/etc/resolv.conf读取; - 自定义 nameserver 时注意 fallback 语义:
CaresDnsResolverConfig的resolvers默认会覆盖系统配置,如需保留系统配置作为兜底,应显式设置use_resolvers_as_fallback: true; - 超时与重试的默认值差异:c-ares 解析器的
query_timeout_seconds(Envoy 默认 5s vs 库默认 2s)与query_tries(Envoy 默认 4 vs 库默认 3)都做了调整,以保持 Envoy 旧版行为,避免解析耗时上升;配置时需知悉这一默认值差异; - EDNS0 负载建议:
edns0_max_payload_size推荐使用 1232 字节的安全默认值以避免 UDP 分片,最大允许 4096; - 缓存策略差异:c-ares 的
qcache_max_ttl在 Envoy 中默认关闭(0),而库默认 1 小时;Hickory 默认开启 1024 条目 LRU 缓存并支持负缓存,且自带 DoT/DoH/DNSSEC 能力,适合对解析安全性与协议有更高要求的场景; - getaddrinfo 的适用限制:固定 60 秒 TTL、专用线程执行、无解析器专属统计,仅适合低频率解析场景;
- 统计观测:c-ares、Apple、Hickory 分别挂载于
dns.cares、dns.apple、dns.hickory统计树,可用于监控解析量、超时率、失败率与 channel 重建次数(reinits)。
如需深入了解 DNS 解析器在各类集群与过滤器中的完整接入方式,可继续阅读 dns_resolution.rst、dynamic_forward_proxy_filter.rst 以及四个解析器扩展的源码实现(cares、apple、getaddrinfo、hickory)。
【免费下载链接】envoyCloud-native high-performance edge/middle/service proxy项目地址: https://gitcode.com/GitHub_Trending/en/envoy
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考