用 UID 规则让一个 Linux 账户走 OpenVPN
uidrange 规则无需 namespace,即可把一个账户的 IPv4 套接字放进现有隧道。DNS、IPv6 与回退路径要分别决定,然后再验证。
当某个 Linux 用户拥有的每个 IPv4 套接字都应当通过现有隧道出去时,按账户路由才合适;当你需要程序级隔离时,它就不合适。它按 UID 选择,因此以该账户运行的一切都在范围内,包括你忘掉的那个批处理任务。碰任何一条 ip rule 之前,先把这个问题定下来。
下面的例子假设有一个 UID 1500 的专用浏览器账户、一个持有本地地址 10.80.0.11 的隧道设备 tun-office,以及一个允许相关目的地的 VPN。守护进程本身在该 UID 之外运行。
选择机制的三个条件
当干活的身份就是这个账户、当可以接受把它全部流量搬走、当 namespace 比这项任务更重时,选 UID 规则。当工作负载需要自己的接口、解析器和路由表时,选网络 namespace。当恰好一个应用需要移动、而会话其余部分留在本地路径时,选代理或按进程绑定 只针对这一个应用。把这三点写下来,可以避免规则悄悄覆盖了比预想更多的东西。
要避免的错误,是以为启动器决定了归属。由另一个用户会话递来 URL 的浏览器,并没有在 UID 1500 下创建套接字,任何规则都不会搬走它产生的流量。
表、规则与守卫
在检查现有规则并挑好未用的表号和优先级之后,安排很短:专用表里承载隧道源地址的默认路由、把该账户的查询送进这张表的规则,以及阻断回退的终止规则。写出来就是一次幂等的路由替换 ip -4 route replace default dev tun-office src 10.80.0.11 table 51820,随后是 ip -4 rule add priority 12100 uidrange 1500-1500 lookup 51820。
守卫是 ip -4 rule add priority 12101 uidrange 1500-1500 unreachable,它比长度看起来更重要。没有它,51820 表里缺失的路由会落到普通上行链路,账户就悄悄泄漏了策略本该拦住的流量。有了它,同样的失败会变成超时,也就是阻断而不是回退。
把常规的本地地址规则放在这些之前,并有意识地决定 LAN 或管理例外该不该放在所选表之前。在那里找到的默认路由,之后不会为更具体的 LAN 路由再查 main,这让期待最长前缀匹配跨表生效的人感到意外。趁推理还新鲜,把打算保留的例外列表写进变更记录。
UID 路由没有覆盖的部分:DNS
解析器守护进程以自己的 UID 打开上游套接字,所以选 1500 并不会把这些查询搬进 51820 表。DNS 集成是另一个决定:把该账户指向可通过隧道抵达的解析器,或者接受名称解析走主机路径、而流量不走。
发往本地 stub 的 loopback 查询,不能证明上游查询用了浏览器的路由;从缓存成功加载页面同样不能。
IPv6 在设计里需要单独一句话
上面的规则只覆盖 IPv4。如果要求是这条账户没有直连 IPv6 的 IPv4 隧道,就加一条经过评审的终止规则,例如 ip -6 rule add priority 12101 uidrange 1500-1500 unreachable,然后检查更早的 IPv6 规则以及你想保留的 loopback 例外。
阻断直连 IPv6 不是双栈隧道,而是一条边界。把它叫作隧道,会让下一位读策略的人困惑,而这种困惑最终会变成一张关于 IPv6 站点打不开的服务台工单。
用真实账户验证,然后接管生命周期
比较查询:把 ip -4 route get 198.51.100.25 uid 1500 与用普通 UID 查同一目的地对比。然后在该专用账户下发起一个被允许的请求,检查远端看到的源地址。首选源地址不是 source NAT,它也不会覆盖绑定了别的地址的应用。如果两次查询返回同一条路径,说明规则没在做你以为的事。
把表、规则和隧道路由的生命周期交给同一个责任人,测试重连与隧道丢失,退役策略时只删除它精确的条目。如果你真正需要的是独立接口和独立状态,namespace 依然可用。