外观
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 tdns3. 测试转发
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 降序 排序后使用:
- priority:数值越小优先级越高。同 priority 的上游组成一个组,组内按 weight 分配。
- weight:同 priority 组内,weight 越大越优先被轮询到。
- 轮询(Round-Robin):同组内通过原子计数器轮询,起始位置每次请求递增,实现均匀分配。
- 故障转移:当前上游超时或失败时,自动尝试排序中的下一个上游,直到成功或全部失败。
查询超时
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