前几天买了个字节的coding plan,送了一台arkClaw服务器。这台服务器没有公网ip,地址也在境内。在上面干啥事都很憋屈。于是想给他弄个透明代理把流量都走代理转发出去。
但是头疼的问题是:访问国内服务(飞书、百度、火山引擎 API 等)也被转发到远端节点绕一大圈,延迟高、速度慢。
本文介绍一套基于 GOST + iptables + dnsmasq + ipset 的方案,彻底解决这个问题——指定域名自动直连,其余流量走代理隧道,零感知切换。
原理简介
很多人第一反应是在 GOST 配置里加 bypass 规则,填上 *.feishu.cn、*.baidu.com 之类的域名。实际上这行不通——GOST 透明代理(red://)收到的是被 iptables REDIRECT 过来的 TCP 连接,它拿到的目标地址是原始目标 IP,不是域名。即使开启了 sniffing=true 能从 TLS ClientHello 里读出 SNI,bypass 规则匹配的仍然是 IP 层面的目标地址,域名规则根本不会命中,配了也等于没配。
正确的做法是把 bypass 逻辑放到 iptables 层面:在流量进入 GOST 之前,就把需要直连的 IP 筛出来放行。
问题来了:域名对应的 IP 是动态的,不能手动维护。这里用 dnsmasq 来解决——它在处理 DNS 查询时,可以把指定域名解析到的 IP 自动写入一个 ipset 集合。iptables 只需要对这个 ipset 里的 IP 做 RETURN,就实现了域名级别的动态直连。
整体流程:
本机/局域网发出 TCP 连接
→ iptables GOST 链
├─ 目标 IP 在 gost-bypass ipset 中?→ RETURN,直连
├─ 目标是私有网段/SSH/远端节点 IP?→ RETURN,直连
└─ 其余流量 → REDIRECT 到 127.0.0.1:12345
→ GOST 透明代理
→ mwss 隧道 → 远端节点转发
环境准备
- Linux 服务器,已安装
iptables - 一个可用的远端代理节点(本文以
ss://Shadowsocks 协议为例) - 安装以下工具:
apt-get install -y dnsmasq ipset ipset-persistent iptables-persistent
第一步:部署 GOST
从 gost 官方 Release 下载对应架构的二进制文件,放到 /root/gost 并赋予执行权限:
chmod +x /root/gost
创建 systemd 服务文件 /etc/systemd/system/gost-proxy.service:
[Unit]
Description=GOST Proxy Service
After=network.target
[Service]
Type=simple
User=root
ExecStart=/root/gost -L "red://:12345?sniffing=true&so_mark=100" -F "ss://chacha20-ietf-poly1305:your-password@your-server.example.com:8388?so_mark=100"
Restart=always
RestartSec=10
StandardOutput=append:/tmp/gost.log
StandardError=append:/tmp/gost.log
[Install]
WantedBy=multi-user.target
把 your-password 和 your-server.example.com 换成你自己的密码和节点地址,端口按实际情况修改。参数说明:
-L "red://:12345?sniffing=true&so_mark=100"— 在 12345 端口开启透明代理,sniffing=true让 GOST 从 TLS ClientHello 中读取 SNI 识别目标域名,so_mark=100给 GOST 自身的出站包打上 fwmark 标记,防止被 iptables 再次拦截造成死循环-F "ss://chacha20-ietf-poly1305:password@host:port?so_mark=100"— 通过 Shadowsocks 协议连接远端节点,加密方式可按需替换(如aes-256-gcm),同样打 mark 防回环
启动服务:
systemctl daemon-reload
systemctl enable --now gost-proxy
第二步:配置 iptables
创建一条专用链 GOST,将 OUTPUT 链的 TCP 流量引入:
iptables -t nat -N GOST
iptables -t nat -A OUTPUT -p tcp -j GOST
在 GOST 链中,依次添加直连规则,最后把剩余流量重定向到 GOST 监听端口:
# gost-bypass ipset 中的 IP 直连(后面配置 dnsmasq 后会动态填充)
iptables -t nat -A GOST -m set --match-set gost-bypass dst -j RETURN
# SSH 不走代理
iptables -t nat -A GOST -p tcp --dport 22 -j RETURN
# 远端代理节点的 IP 直连(换成你自己节点的 IP,防止代理流量再被代理)
iptables -t nat -A GOST -d 1.2.3.4 -j RETURN
# 上游 DNS 服务器直连(dnsmasq 查询上游时不能走代理,否则死循环或解析失败)
# 把这里换成你的 /etc/dnsmasq.d/upstream.conf 里配置的 DNS IP
iptables -t nat -A GOST -d 8.8.8.8 -j RETURN
iptables -t nat -A GOST -d 8.8.4.4 -j RETURN
# 私有网段和本地地址直连
iptables -t nat -A GOST -d 127.0.0.0/8 -j RETURN
iptables -t nat -A GOST -d 192.168.0.0/16 -j RETURN
iptables -t nat -A GOST -d 10.0.0.0/8 -j RETURN
iptables -t nat -A GOST -d 172.16.0.0/12 -j RETURN
# GOST 自身出站包(fwmark=100)直连,防止死循环
iptables -t nat -A GOST -p tcp -m mark --mark 0x64 -j RETURN
# 其余 TCP 流量全部转发给 GOST
iptables -t nat -A GOST -p tcp -j REDIRECT --to-ports 12345
初始化 ipset(用于存放直连域名解析出的 IP,TTL 1小时自动过期):
ipset create gost-bypass hash:ip timeout 3600
然后把 ipset 的 RETURN 规则插到链的最前面(优先级最高):
iptables -t nat -I GOST 1 -m set --match-set gost-bypass dst -j RETURN
持久化规则,重启后不丢失:
ipset save > /etc/iptables/ipsets
iptables-save > /etc/iptables/rules.v4
第三步:配置 dnsmasq 自动填充 ipset
dnsmasq 有一个 ipset 指令,可以在解析指定域名时,把结果 IP 自动写入内核 ipset。这是整套方案的核心。
首先,关闭 systemd-resolved 的 stub listener,否则它会占用 53 端口与 dnsmasq 冲突:
创建 /etc/systemd/resolved.conf.d/no-stub.conf:
[Resolve]
DNSStubListener=no
systemctl restart systemd-resolved
关键前提:把系统 DNS 指向 dnsmasq
整套方案的核心依赖是:本机所有 DNS 查询必须经过 dnsmasq,才能在解析时自动填充 ipset。如果系统 resolver 绕过了 dnsmasq,bypass 域名的 IP 就永远不会进 ipset,流量还是会走代理。
/etc/resolv.conf 通常是 systemd-resolved 管理的软链,需要替换为静态文件:
unlink /etc/resolv.conf
echo "nameserver 127.0.0.1" > /etc/resolv.conf
然后配置 dnsmasq 的上游 DNS,创建 /etc/dnsmasq.d/upstream.conf:
server=8.8.8.8
server=8.8.4.4
如果你的环境有内网 DNS,填内网 DNS 地址。
配置需要直连的域名,创建 /etc/dnsmasq.d/gost-bypass.conf:
ipset=/volces.com/feishu.com/feishu.cn/larksuite.com/baidu.com/minimaxi.com/gost-bypass
格式是 ipset=/域名1/域名2/.../ipset名称,支持通配符(自动匹配所有子域名)。
启动 dnsmasq:
systemctl enable --now dnsmasq
验证是否生效——查询一个 bypass 域名,然后检查 ipset:
dig +short www.baidu.com @127.0.0.1
ipset list gost-bypass
# 应该能看到 baidu 的 IP 已经写进去了
第四步:解决冷启动问题
这里有个坑:ipset 只在 dnsmasq 收到查询时才填入 IP。机器刚重启时 ipset 是空的,如果程序在发出 DNS 查询之前就尝试建立连接(比如使用了 DNS 缓存、连接池复用等),那次连接会被 iptables 重定向进 GOST,然后报错。
解决办法是写一个预热脚本,在服务启动后主动查询一遍所有 bypass 域名,提前把 IP 填入 ipset。
创建 /usr/local/bin/gost-bypass-warmup.sh:
#!/bin/bash
# Pre-warm dnsmasq ipset by querying all bypass domains
domains=(
"www.volces.com"
"ark.cn-beijing.volces.com"
"open.feishu.cn"
"www.feishu.cn"
"www.feishu.com"
"www.larksuite.com"
"www.baidu.com"
)
for domain in "${domains[@]}"; do
dig +short "$domain" @127.0.0.1 > /dev/null
done
chmod +x /usr/local/bin/gost-bypass-warmup.sh
创建对应的 systemd 服务 /etc/systemd/system/gost-bypass-warmup.service:
[Unit]
Description=Pre-warm gost bypass ipset via dnsmasq
After=dnsmasq.service gost-proxy.service
Requires=dnsmasq.service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/gost-bypass-warmup.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
systemctl enable --now gost-bypass-warmup.service
新增直连域名
以后想让某个域名走直连,只需三步:
1. 在 dnsmasq 配置里加上该域名
编辑 /etc/dnsmasq.d/gost-bypass.conf,在 ipset= 行末(/gost-bypass 之前)插入新域名:
ipset=/volces.com/feishu.cn/.../newdomain.com/gost-bypass
2. 在预热脚本里加一条
编辑 /usr/local/bin/gost-bypass-warmup.sh,在 domains 数组里加一行代表性子域名:
"www.newdomain.com"
3. 重启 dnsmasq 并立即预热
systemctl restart dnsmasq
dig +short www.newdomain.com @127.0.0.1
此时 ipset 里已有该域名的 IP,直连立刻生效,无需重启任何其他服务。
排查问题
域名仍然走代理?
手动查询一次触发 ipset 填充,然后确认 IP 已写入:
dig +short open.feishu.cn @127.0.0.1
ipset list gost-bypass
SSL 握手失败(SSL_ERROR_SYSCALL)?
说明 IP 还没进 ipset,流量被 GOST 拦截后关闭了连接。清理 DNS 缓存后重新查询:
systemctl kill -s SIGHUP dnsmasq
resolvectl flush-caches
dig +short 问题域名 @127.0.0.1
检查实时流量走向
tail -f /tmp/gost.log
日志里能看到每条连接的目标域名,确认 bypass 域名是否还出现在其中。
查看 iptables 规则
iptables -t nat -L GOST -n --line-numbers 阅读:13 评论: 0 💬

