引言
Across the Great Wall we can reach every corner in the world
网络拓扑
在我的家庭网络环境中,主要设备均接入 2.5G 交换机。主路由负责拨号并作为 DHCP 服务器,旁路由(运行 OpenWRT 的斐讯 N1)与交换机一同接入主路由的 LAN 口。
核心思路:将需要代理的网络设备的 DNS 指向旁路由;同时在主路由上配置静态路由表,将特定 IP 段(如 Fake-IP 段)的流量统统转发给旁路由处理。

主路由配置
参考文章:AX6000 静态路由配置
如果你的路由器官方固件支持(例如 H3C NX54),直接在后台管理页面中添加静态路由表即可,无需通过 SSH 进行以下修改。
对于后台页面不支持设置静态路由、但能解锁 SSH 的路由器,可参考以下配置进行手动修改:
注:如果路由器既不支持静态路由也无法解锁 SSH,可采用手动指定客户端 DHCP 网关为旁路由 IP 的方式。但需注意,这种方式在访问国内网络时也会受限于旁路由的转发性能。
1. 增加 Fake-IP 段路由表
通过 SSH 登录主路由并编辑网络配置文件:
在网络配置中添加以下路由信息,将 198.18.0.0/16(任意 FakeIP 网段)的流量指向旁路由:
1 2 3 4 5
| config route option interface 'lan' option target '198.18.0.0' option netmask '255.255.0.0' option gateway '192.168.x.x' # 此处替换为旁路由的实际 IP 地址
|
2. 修改防火墙转发策略
为了允许 Fake-IP 流量的正常转发,需要修改防火墙配置:
1
| vim /etc/config/firewall
|
找到 defaults 和对应的 zone(lan)配置块,修改相应的策略:
1 2 3 4 5 6 7 8 9 10 11 12 13
| config defaults option syn_flood '0' option input 'ACCEPT' option output 'ACCEPT' option forward 'ACCEPT' # 将 REJECT 修改为 ACCEPT option drop_invalid '0' # 将 1 修改为 0
config zone option name 'lan' option network 'lan' option input 'ACCEPT' option output 'ACCEPT' option forward 'ACCEPT' # 将 REJECT 修改为 ACCEPT
|
3. 应用配置
重启网络与防火墙服务使得配置生效:
1 2
| /etc/init.d/network restart /etc/init.d/firewall restart
|
旁路由配置
在 OpenWRT 旁路由上安装并配置 OpenClash(或其他代理核心)。需要将本机 53 端口的请求劫持并转发到代理核心。核心运行在 Fake-IP + Tun 模式下,且 Fake-IP 的网段需要与上文中静态路由设置的 FakeIP 网段保持一致(通常为 198.18.0.0/16)。
采用 Fake-IP 的主要优势如下:
- 无感分流:配合主路由的静态路由表,客户端只需修改 DNS 服务器即可实现高速上网冲浪;国内流量直接不经过旁路由,即使旁路由断网,也不影响国内网站的正常访问。
- 连接加速:与 Real-IP 模式相比,域名解析在远端完成,省去了等待 DNS 返回结果再建立连接的过程,减少了一次 RTT。
在 Tun 模式下,OpenClash 会自动向系统注入如下路由规则:
1
| 198.18.0.0/16 dev utun scope link
|
这使得来自主路由转发的 Fake-IP 流量,能够顺利进入旁路由的 Tun 网卡,并被前端代理内核接管,完成后续的流量代理过程。
注意:如果不使用 OpenClash,可能需要手动配置防火墙(iptables/nftables 等)以允许 Fake-IP 流量进入核心,并开启 IP 转发(IP Forwarding)。
内网服务与外网访问
出于安全考虑,建议内网的所有服务(如软路由后台、NAS 等)仅监听 LAN 地址,避免将服务直接暴露到公网。
如果有从外网远程访问家庭局域网的需求,建议搭建虚拟局域网(VPN)或安全的代理隧道。以下以 Mihomo 为例分别介绍 IPv6 和 IPv4 环境下的解决方案:
建议:在复杂的跨 ISP 访问环境下,使用 TCP 连接以增强稳定性。
配置一:IPv6 直连
在具备 IPv6 的网络环境下,通过主路由直接获取 IPv6 地址,并在旁路由上配置具备鉴权能力的代理协议:
1 2 3 4 5
| listeners: - name: AAA-in-ipv6 type: aaaaaa port: bbbbb listen: "::"
|
随后配合 DDNS-GO 服务,将旁路由获取到的公网 IPv6 地址动态解析至个人域名。在外部具备 IPv6 的环境下,即可通过域名和特定端口安全地访问内网。
配置二:IPv4 NAT打洞
在没有 IPv6 或公网 IPv4 的环境下,主要有以下两种内网穿透方案:
- Cloudflare Tunnel (cloudflared):借助 Cloudflare 节点网络进行反向代理,无需公网 IP 和端口映射,适合作为备用兜底方案,但国内访问延迟较高且速率一般。
- NATMap (P2P 打洞):利用运营商 NAT 策略进行直连打洞。打通后延迟极低,体验近乎直连公网。
前置要求:NATMap 打洞要求家宽的 NAT 类型为 NAT1 (Full Cone),且通常需要在主路由中将旁路由设置为 DMZ 主机。若网络架构不支持,请考虑其它中转方案(如 cloudflared)。
以下分享基于 NATMap 配合 Cloudflare Worker 与客户端 Sub-Store,实现外网节点随 NATMap 端口变化动态更新的自动打洞方案(此思路利用 Gemini 协助,实测可行)。
1. 配置 Cloudflare Worker
在 Cloudflare 面板创建 Worker,并绑定一个 KV 命名空间(如 NATMAP_KV),在 Settings -> Variables 中添加 SECRET_TOKEN 环境变量,设置一个强密码。
将以下代码发布至 Worker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| export default { async fetch(request, env, ctx) { const url = new URL(request.url); const userAgent = request.headers.get("User-Agent") || "";
if (!userAgent || (userAgent.includes("curl") && request.method === "GET")) { return new Response("Access Denied", { status: 403 }); }
if (request.method === "GET" && url.pathname === "/sub") { const token = url.searchParams.get("token"); if (token !== env.SECRET_TOKEN) { return new Response("Unauthorized", { status: 401 }); }
const address = await env.NATMAP_KV.get("natmap_address"); if (!address) { return new Response("No node available yet.", { status: 404 }); } return new Response(address, { headers: { "Content-Type": "text/plain; charset=utf-8" } }); }
if (request.method === "POST" && url.pathname === "/update") { const authHeader = request.headers.get("Authorization"); if (authHeader !== `Bearer ${env.SECRET_TOKEN}`) { return new Response("Unauthorized", { status: 401 }); }
const newAddress = await request.text(); if (!newAddress || !newAddress.includes(":")) { return new Response("Bad Request: Invalid format", { status: 400 }); }
await env.NATMAP_KV.put("natmap_address", newAddress); return new Response("Update Success: " + newAddress, { status: 200 }); }
return new Response("Not Found", { status: 404 }); }, };
|
2. 旁路由 NATMap 触发脚本
在旁路由配置 NATMap 任务。当 NAT 端口发生变化时,触发执行该脚本并将最新的 IP:Port 自动推送给 CF Worker:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #!/bin/sh
PUBLIC_IP=$1 PUBLIC_PORT=$2
if [ -z "$PUBLIC_IP" ] || [ -z "$PUBLIC_PORT" ]; then echo "Error: Missing IP or Port" exit 1 fi
WORKER_URL="https://你的worker前缀.你的用户名.workers.dev" SECRET_TOKEN="你的自定义密码"
PAYLOAD="${PUBLIC_IP}:${PUBLIC_PORT}"
curl -s -X POST "${WORKER_URL}/update" \ -H "Content-Type: text/plain" \ -H "Authorization: Bearer ${SECRET_TOKEN}" \ -d "${PAYLOAD}"
echo "$(date): Updated natmap address to ${PAYLOAD}" >> /var/log/natmap_update.log
|
3. 客户端 Sub-Store 节点动态更新
在客户端的 Sub-Store 中编写脚本操作,每次更新订阅时自动拉取最新的打洞端口并覆写至指定的家宽入口节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| async function operator(proxies) { const apiUrl = "https://你的worker前缀.你的用户名.workers.dev/sub?token=你的自定义密码"; const targetNodeName = "Home_Intranet";
try { const response = await fetch(apiUrl); if (!response.ok) { throw new Error(`HTTP Error: ${response.status}`); } const data = await response.text(); const parts = data.trim().split(":"); if (parts.length === 2) { const newIp = parts[0]; const newPort = parseInt(parts[1], 10); proxies.forEach(proxy => { if (proxy.name === targetNodeName) { proxy.server = newIp; proxy.port = newPort; proxy.name = `${targetNodeName} [${newPort}]`; } }); } } catch (e) { console.log("Fetch natmap port failed: " + e.message); } return proxies; }
|