iptables本身不支持实时域名解析,所有“基于域名”的规则实为脚本定期解析域名并更新静态ip规则;其核心是预解析+定时刷新,而非动态dns查询。

iptables 本身不支持实时域名解析,也不能在规则运行时动态查 DNS。所谓“基于域名的 iptables 规则”,本质是用外部脚本定期将域名解析为 IP 地址,并更新 iptables 的 -d(目标 IP)或 --to-destination 参数。整个流程是“静态规则 + 定期刷新”,不是 iptables 自动做 DNS 查询。
下面分三部分说明:怎么实现、关键注意事项、一个可用的最小化脚本示例。
一、核心原理:iptables 只认 IP,域名需预解析
iptables 的 nat 表中 DNAT/REDIRECT/PREROUTING 规则不支持写域名(如 -d www.example.com 在多数内核版本中会报错或静默失败)。即使某些旧版内核允许,它也只在规则加载时做一次 DNS 解析(非动态),后续 IP 变更不会自动同步。
所以必须靠外部脚本完成:
- 定时执行
dig +short www.example.com A或nslookup -type=A www.example.com | tail -1 - 提取返回的 IPv4 地址(注意过滤 AAAA、CNAME、空行)
- 用新 IP 替换旧 iptables 规则(先删再加,或用
-I插入+-D删除旧条目) - 保存规则并确保不干扰其他策略(如避免重复添加、漏删)
二、实用配置要点
以把访问 www.bad-site.com 的流量重定向到本地拦截页(127.0.0.1:8080)为例:
- 必须用 PREROUTING 链:匹配进入本机的数据包(网关模式下对内网请求生效;本机直连需配合 OUTPUT 链)
-
协议和端口要明确:HTTP 用
--dport 80,HTTPS 用--dport 443(但注意:HTTPS 目标 IP 是服务器 IP,不是域名;SNI 域名在 TLS 层,iptables 看不到) -
避免循环和误杀:加
-s 127.0.0.1 -j RETURN和-d 127.0.0.1 -j RETURN防代理自环 - 推荐用 DNAT 而非 REDIRECT:DNAT 可指定完整目标 IP+端口,语义更清晰;REDIRECT 仅限本机端口重定向
-
规则要带注释(--comment):方便脚本识别和清理,例如:
--comment "domain-dnat-www.bad-site.com"
三、最小可行脚本示例(每5分钟更新)
保存为 /usr/local/bin/update-domain-dnat.sh,赋予可执行权限:
#!/bin/bash
DOMAIN="www.bad-site.com"
TARGET_IP=$(dig +short "$DOMAIN" A | head -n1 | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$')
if [ -z "$TARGET_IP" ]; then
echo "[$(date)] DNS lookup failed for $DOMAIN" >&2
exit 1
fi
<h1>先删除旧规则(按 comment 匹配)</h1><p>iptables -t nat -D PREROUTING -d "$TARGET_IP" -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080 --comment "domain-dnat-$DOMAIN" 2>/dev/null</p><h1>添加新规则</h1><p>iptables -t nat -I PREROUTING -d "$TARGET_IP" -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:8080 --comment "domain-dnat-$DOMAIN"</p><h1>保存(适配 Debian/Ubuntu)</h1><p>iptables-save > /etc/iptables/rules.v4 2>/dev/null || true
</p>
加入定时任务(crontab -e):
*/5 * * * * /usr/local/bin/update-domain-dnat.sh >> /var/log/dnat-update.log 2>&1
注意:该脚本只处理 A 记录且仅 IPv4;若目标站使用 CDN 或多 IP 轮询,需扩展逻辑(如取全部 A 记录并逐条加规则)。
不复杂但容易忽略:DNS 解析结果可能有 TTL 缓存、CDN 返回不同地区 IP、域名无 A 记录只有 CNAME——这些都得在脚本里加判断和 fallback。











