DNS隧道流量路由问题:如何让系统流量走tun0,程序生成的DNS包直接走物理网卡
看起来你遇到了典型的路由循环陷阱——因为你把所有流量都默认导向了tun0,包括你的Python程序生成的DNS包,结果这些包又被送回了你的监听程序,根本发不出去。其实VPN程序解决这类问题的核心思路都是策略路由,也就是根据流量的特征(比如来源、标记)选择不同的路由规则,而不是只用一张全局默认路由表。下面给你具体的实现方案:
核心解决方案:给DNS包打标记+策略路由
1. 给你的Python程序的DNS数据包打标记
最精准的方式是在程序内部给发送DNS请求的socket设置标记,让内核识别这些包是特殊的。在Python里可以这样做:
import socket # 创建发送DNS请求的UDP socket dns_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 给这个socket打上标记(这里选0x10作为标记值,只要是未被占用的数值都可以) dns_socket.setsockopt(socket.SOL_SOCKET, socket.SO_MARK, 0x10) # 之后用这个socket发送DNS查询到你的服务器即可
这样只有这个socket发出的DNS包会被标记,其他系统流量不受影响。
2. 创建自定义路由表,专供标记过的DNS包使用
首先在路由表配置文件里添加一个自定义表:
echo "100 dns_route_table" >> /etc/iproute2/rt_tables
然后给这个表添加默认路由,指向你的物理网卡ens33的网关(替换成你实际的网关地址,比如192.168.1.1):
sudo ip route add default via 192.168.1.1 dev ens33 table dns_route_table
3. 添加策略路由规则,让带标记的包走自定义表
运行这条命令,告诉内核:凡是标记为0x10的数据包,都使用我们刚创建的dns_route_table路由表:
sudo ip rule add fwmark 0x10 table dns_route_table priority 100
这里的priority数值要比默认规则(通常是32766)小,确保这条规则会被优先匹配。
4. 保持默认路由,让普通流量继续走tun0
你原来设置的默认路由可以保留,确保除了DNS包之外的所有系统流量都被导向tun0:
sudo ip route add default via 10.8.0.1 dev tun0
原理说明
这样配置后,整个流程就通了:
- 普通系统流量 → 匹配默认路由 → 进入tun0被你的Python程序捕获
- 程序生成的DNS包(带0x10标记) → 匹配策略路由规则 → 走ens33直接发往互联网,不会再绕回tun0
备选方案:用iptables根据进程打标记
如果不想修改Python代码,也可以用iptables根据进程ID给DNS包打标记(假设你的Python程序PID是1234):
sudo iptables -t mangle -A OUTPUT -p udp --dport 53 -m owner --pid-owner 1234 -j MARK --set-mark 0x10
不过这种方法需要每次重启程序都重新设置,不如在代码里设置socket标记来得稳定。
VPN程序本质上也是用类似的思路:通过标记自身的控制流量,让这些流量不走VPN隧道,避免出现路由循环,同时把用户的普通流量导向隧道接口。
备注:内容来源于stack exchange,提问作者YMeC

