我正在尝试将 Linux 机器上的所有传出 DNS 查询重定向到本地缓存存根解析器(未绑定)。
iptables -t nat -A OUTPUT -p tcp --dport 53 -j DNAT --to 1.1.1.1:53
iptables -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to 1.1.1.1:53
iptables -t nat -A POSTROUTING -j MASQUERADE
当我使用上述规则时,所有传出的 DNS 查询都会被拦截并重定向到位于 1.1.1.1 的 DNS 服务器
但是,如果我将“1.1.1.1”替换为“127.0.0.1”,则所有 DNS 查询都会失败,并且不会定向到我的本地存根解析器。
我确实传递了下面的 sysctl 参数
sysctl -w net.ipv4.conf.eth0.route_localnet=1
但我的问题仍然一样。有什么指点吗?
答案1
如果使用strace
, 和nc
/进行调试socat
,就会清楚的是,这nat/POSTROUTING
并MASQUERADE
没有改变最初选择的用于出去的地址。可能是因为它仍然被认为是要“路由”到的本地地址,所以lo
不需要更改:该MASQUERADE
规则在这里不起作用。
无论如何,事情就是这样发生的。所以在回复时UDP协议查询时,服务器实际上连接回发送数据的源,现在用作目的地。自然地,选择最好的源用于该目的地,相同的本地地址,即不是127.0.0.1。因此,如果使用conntrack -E
,示例本地 IP 为 192.0.2.2,目标为 198.51.100.1 UDP 端口 53,则会发生以下情况:
[NEW] udp 17 30 src=192.0.2.2 dst=198.51.100.1 sport=40037 dport=53 [UNREPLIED] src=127.0.0.1 dst=192.0.2.2 sport=53 dport=40037
[NEW] udp 17 30 src=192.0.2.2 dst=192.0.2.2 sport=53 dport=40037 [UNREPLIED] src=172.16.0.22 dst=172.16.0.22 sport=40037 dport=53
回复与初始查询不相关(因为源 IP 不是 127.0.0.1),因此 conntrack 将此作为第二个流处理。同时,客户端将其 UDP 套接字置于连接模式,这意味着从错误的源 IP(即使是正确的端口)接收到的 UDP 数据包将被拒绝,并且服务器会收到 ICMP 错误(这可以通过 见证tcpdump -i lo
)。
更正非常简单:不要使用MASQUERADE
but SNAT
。当然,它现在必须专门针对此特定流(您不希望将SNAT
所有内容都转换为 127.0.0.1),因此请MASQUERADE
将此行替换为:
iptables -t nat -A POSTROUTING -p udp --dport 53 -j SNAT --to-source 127.0.0.1
通过更正后的流程,本地服务器现在使用 conntrack 的预期地址进行回复,该地址现在将其关联到之前的流程中并正确地对其进行 de-SNAT:
[NEW] udp 17 30 src=192.0.2.2 dst=198.51.100.1 sport=38871 dport=53 [UNREPLIED] src=127.0.0.1 dst=127.0.0.1 sport=53 dport=38871
[UPDATE] udp 17 30 src=192.0.2.2 dst=198.51.100.1 sport=38871 dport=53 src=127.0.0.1 dst=127.0.0.1 sport=53 dport=38871
客户端收到预期的源 198.51.100.1 并且一切按预期工作。
传输控制协议不会遭受相同的结果,因为一旦在 192.0.2.2 和 127.0.0.1 之间建立连接,回复就会在同一个已建立的连接内,它不是像 UDP 那样的新连接,因此已经有了预期的源,并且是由 conntrack 正确处理。为了一致性,最好添加这个:
iptables -t nat -A POSTROUTING -p tcp --dport 53 -j SNAT --to-source 127.0.0.1
两个注意事项:
对于您的具体情况,
route_localnet
不需要,因为所有数据包都是本地的并保留在lo
.相反:转发发送到 127.0.0.1 的数据包将需要它(以及其他技巧)。如果您的 DNS 服务器也是向外部发送查询的 DNS 客户端(递归 DNS 服务器就是这种情况),或者它自己的查询将被重新路由到自身,从而形成循环,您可能需要额外的例外规则。通常通过让服务器以特定用户运行并使用 iptables 来解决
-m owner
匹配。就像在每组规则之前插入这样的内容(在nat/OUTPUT
和中nat/POSTROUTING
):iptables -t nat -I .... -m owner --uid-owner unbound -j RETURN