名字解析成地址,而这些地址可以落进防火墙集合,进而影响路由,这让无代理的 按名字引流 成为可能。问题在于四个组件必须正确交接:解析器、集合、为新流打标记的分类器,以及承载它的对端。每次交接的失败方式不同,各自也守着不同的时间。

把设计看成一条链,而不是一份配置。每一步要问的是:哪个组件拥有决策,输入缺失时会发生什么。

DNS 应答能做什么、不能做什么

dnsmasq 可以把应答中的地址复制进 nftables 集合,这只是喂料,不是路由决策。该选项记录在 dnsmasq 手册 中,它要指明地址族、表和集合,其开头的 4# 只选取 A 记录,因此 v6 目的地需要另作安排。集合必须事先存在,因为解析器只是往防火墙主人建好的东西里添加元素。这一步完全不选接口、表或对端。

客户端也必须用这个解析器来解析名字。自带加密 DNS 的应用,或者策略生效前缓存的应答,会在集合里一条记录都不创建的情况下直接连出去。

集合是输入,不是路由

集合里的元素是一个匹配条件。必须有东西去看它,判定这个包要开启新流,并施加把流送进隧道的策略。在 Linux 上,这通常意味着一条能打标记的路由型输出路径、一条把带标记的包送进专用表的规则,以及该表中指向 WireGuard 设备的路由。仅有成员身份,这些一个都不会装上。

nft add set inet vpn_select office_v4 { type ipv4_addr\; }
ip rule add fwmark 0x40 table 200
ip route add default dev wg0 table 200
wg show wg0 allowed-ips

对端还会再做一次覆盖检查,而这一点常被忘记。WireGuard 只会为允许地址包含目的地的对端加密,所以当对端列表排除了该前缀时,把包路由进接口还不够。请把列表放在路由归属方的控制之下,而不是依赖管理器碰巧做了哪些路由创建。

标记、表与对端覆盖

标记是共享的命名空间,所以值和掩码都要与其他使用方约定一致。把带标记流量送进专用表的规则,必须在表里没有匹配路由时有一个明确结果,因为无人应答的查找会继续走到下一条规则,把包从物理接口放出去。专用表里的一条 blackhole 默认路由,会把这种悄悄的逃逸变成看得见的丢弃。设计仍然做不到的,是对从未进入集合的目的地做失败关闭。

隧道自身的外层包必须躲开分类器。WireGuard 工具正好为此提供 socket 标记,用了它,封装就不会被路由回自己正在构建的接口。

分别测试每一种生存期

三个存储在三种时间表上老化:解析器缓存、集合元素和受跟踪的连接。按这个顺序测试,观察每个是否在该动的时候动。下面的命令只读取解析器、集合、连接表和政策规则,不改动任何东西。

dig +short office.example
nft list set inet vpn_select office_v4
conntrack -L -d 10.60.8.15
ip rule show

一次新增元素的新鲜查询证明喂料在工作,而一条活得比应答更久的旧元素则是值得记录下来的生存期错配。连接表里出现带预期标记的新连接,说明分类器跑过了;旧连接保留原有标记是正确行为,不是缺陷。规则列表回答的是政策查找是否存在,这与它是否匹配是两回事。按这个顺序读这四份输出,出问题的那次交接会自己现形。

如果要求是某个名字永远走同一条路,DNS 驱动的集合无法证明这一点,因为共享地址也承载着别的名字。代理或应用层机制才是这种保证的工具。

相关文章