Skip to content

09 - 转发服务 ​

概述 ​

转发模式(forwarder)下,TDNS 将所有未命中缓存的查询转发到配置的上游 DNS 服务器,自身不执行递归解析。转发器支持全局转发和条件转发两种机制,并提供负载均衡和故障转移能力。

适用场景:安全网关 DNS、内网 DNS 出口、条件转发(分域名路由)、网络受限无法直连根服务器的环境。


快速上手:三步跑通转发服务 ​

1. 配置 ​

toml
# /etc/tdns/tdns.toml
[server]
mode = "forwarder"

[recursion]
enabled = false          # 转发模式不需要递归

# 全局转发到公共 DNS
[[forwarders]]
addr = "8.8.8.8"
port = 53
priority = 1
weight = 1

[[forwarders]]
addr = "1.1.1.1"
port = 53
priority = 1
weight = 1

[access-control]
allow-query = ["trusted"]
allow-recursion = ["trusted"]

[acl.trusted]
addresses = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]

2. 验证配置 + 启动 ​

bash
tdns --validate --config /etc/tdns/tdns.toml
sudo systemctl start tdns

3. 测试转发 ​

bash
# 测试全局转发
dig @127.0.0.1 www.google.com
dig @127.0.0.1 www.baidu.com

# 查看上游连通性
dig @8.8.8.8 example.com +time=5

全局转发 ​

全局转发器将所有查询转发到配置的上游服务器。支持多上游、优先级和加权轮询。

toml
# 主用上游(priority=1,高权重优先)
[[forwarders]]
addr = "8.8.8.8"
port = 53
protocol = "udp"              # udp | tcp
priority = 1
weight = 2

# 主用上游(priority=1,低权重次优先)
[[forwarders]]
addr = "1.1.1.1"
port = 53
protocol = "udp"
priority = 1
weight = 1

# 备用上游(priority=2,仅在主用全部失败后使用)
[[forwarders]]
addr = "8.8.4.4"
port = 53
protocol = "udp"
priority = 2
weight = 1

负载均衡算法 ​

上游服务器按 priority 升序 + weight 降序 排序后使用:

  1. priority:数值越小优先级越高。同 priority 的上游组成一个组,组内按 weight 分配。
  2. weight:同 priority 组内,weight 越大越优先被轮询到。
  3. 轮询(Round-Robin):同组内通过原子计数器轮询,起始位置每次请求递增,实现均匀分配。
  4. 故障转移:当前上游超时或失败时,自动尝试排序中的下一个上游,直到成功或全部失败。

查询超时 ​

toml
[recursion]
query-timeout = "5s"     # 单个上游查询超时时间

超时后标记该上游失败,自动切换到下一个。支持 5s、500ms、2m 等格式。


条件转发 ​

条件转发允许根据查询域名的后缀,将查询路由到不同的上游服务器。典型用于内网域名转发到内网 DNS。

toml
# 内网 corp 域名转发到内网 DNS
[[conditional-forwards]]
zone = "internal.corp."
forwarders = ["10.0.0.10", "10.0.0.11"]

# Active Directory 域转发到域控 DNS
[[conditional-forwards]]
zone = "ad.example.com."
forwarders = ["10.0.0.20", "10.0.0.21"]

# 反向解析转发
[[conditional-forwards]]
zone = "168.192.in-addr.arpa."
forwarders = ["10.0.0.10"]

最长前缀匹配 ​

条件转发采用最长前缀匹配(Longest Prefix Match)策略:

  • 查询 host.internal.corp 同时匹配 internal.corp. 和 corp. 规则
  • 选择标签数更多(更具体)的 internal.corp. 规则
  • 确保最精确的转发规则生效

匹配规则 ​

  • 域名不区分大小写
  • 查询名和规则名均按 FQDN 比较(自动补全尾部 .)
  • 子域名匹配:查询 a.b.internal.corp 匹配规则 internal.corp.
  • 精确匹配:查询 internal.corp 也匹配规则 internal.corp.
  • 未匹配任何条件转发规则的查询,回退到全局转发上游

工作原理 ​

客户端查询 www.example.com A (RD=1)
    │
    ▼
1. ACL 检查 → 通过
    │
    ▼
2. 缓存查找 → 未命中
    │
    ▼
3. 条件转发匹配?
    ├─ 是 → 选择匹配的转发上游
    └─ 否 → 选择全局转发上游
    │
    ▼
4. 按优先级+权重排序,轮询发送查询
    │
    ▼
5. 验证响应(TXID + 源 IP + 源端口)
    │
    ├─ 成功 → 缓存结果,返回客户端
    ├─ 超时/失败 → 尝试下一个上游
    └─ 全部失败 → Serving Stale 或 SERVFAIL

安全特性 ​

防护项机制如何调整
缓存投毒防护验证上游响应 TXID、源 IP 和源端口,三者不匹配则丢弃自动生效
欺骗响应检测响应来源地址与上游地址不一致时标记为 SpoofedResponse自动生效
故障自动切换单个上游失败后自动尝试下一个配多个 [[forwarders]]
查询超时控制每个上游独立超时,防等待放大攻击recursion.query-timeout
缓存复用转发成功的结果按 TTL 缓存,减少重复转发[cache]
Serving Stale全部上游不可用时返回过期缓存cache.stale-ttl

转发器统计 ​

TDNS 维护转发器运行时统计,可通过 Prometheus 指标监控:

bash
# 查看上游查询统计
curl -s http://localhost:9090/metrics | grep upstream

# 关键指标
# tdns_upstream_queries_total{upstream="8.8.8.8:53",rcode="NOERROR"}  12000
# tdns_upstream_queries_total{upstream="8.8.8.8:53",rcode="FAILED"}    23
# tdns_upstream_duration_seconds_bucket{upstream="8.8.8.8:53",le="0.05"} 11800

rcode="FAILED" 表示上游超时或错误,可用于告警。


与递归模式的对比 ​

特性转发模式递归模式
解析路径客户端 → TDNS → 上游 DNS客户端 → TDNS → 根 → TLD → 权威
网络要求仅需访问上游 DNS需直连公网根服务器
解析延迟低(上游已有缓存)较高(完整迭代链路)
依赖性依赖上游可用性依赖根服务器可达性
缓存投毒面上游可信则风险低需验证多级响应
适用场景内网出口、安全网关企业出口、独立解析

转发模式是网络受限环境下的首选方案,只需确保上游 DNS 可达即可。


适用场景配置 ​

安全网关 DNS ​

将所有 DNS 查询集中转发到受控上游,便于审计和过滤:

toml
[server]
mode = "forwarder"

[recursion]
enabled = false

[[forwarders]]
addr = "10.0.0.1"        # 安全 DNS 网关
port = 53
priority = 1
weight = 1

[access-control]
allow-query = ["trusted"]   # 仅允许内网客户端
allow-recursion = ["none"]  # 不提供递归
allow-transfer = ["none"]   # 禁止传输

内网 DNS 出口 ​

内网客户端通过 TDNS 转发到公网 DNS,内网域名条件转发到内网 DNS:

toml
[server]
mode = "forwarder"

[[forwarders]]
addr = "8.8.8.8"
port = 53
priority = 1
weight = 1

# 条件转发:内网域名走内网 DNS
[[conditional-forwards]]
zone = "internal.corp."
forwarders = ["10.0.0.10"]

[[conditional-forwards]]
zone = "ad.corp.internal."
forwarders = ["10.0.0.20"]  # 更具体,最长前缀匹配优先

网络受限环境 ​

无法直连根服务器时,使用转发模式代替递归:

toml
[server]
mode = "forwarder"

[[forwarders]]
addr = "10.0.0.1"        # 可达的上游 DNS
port = 53
priority = 1
weight = 1