概述
前文的提出的基于源IP的策略路由方案存在未解决的问题,即,无法访问docker映射出的端口,因为docker会再次进行NAT操作,导致回包源IP变为docker容器内部ip而非10.0.0.2,不能被策略路由匹配;而基于连接标记(CONNMARK)的策略路由存在同样缺陷
前文还提到了基于arp proxy的二层方案,而随着云服务商大量使用VPC网络,可行性较低
因此,本文提出了基于 动态 IPSet 的有状态策略路由 方案:
- 动态感知:利用
iptables捕获从 WireGuard 入站的请求,将访问者的真实公网 IP 记录至带有 TTL 的ipset集合中。 - 目标打标:当家中主机或 Docker 容器回复数据包时,只要目标 IP 属于
ipset,即打上策略标记并强制送回 WireGuard 管道原路返回。 - 环路防御:脚本自动提取并豁免 VPS Endpoint 节点 IP,防止 WireGuard 建立隧道的流量被误路由至隧道内部而导致断连。
最终实现了无需修改 Docker 网络、完美透传客户端真实 IP、纯内核态高吞吐且支持全端口转发的完美内网穿透方案。
至此,我们获得了同时具有以下优点的完美内网穿透!
- 高性能,wireguard+iptables纯内核态处理 (
私、高性能ですから!) - 完全开源私有化部署,不依赖任何商业软件
- 完美的tcp/udp真实ip透传,无需服务端额外支持
- 端口范围转发,或者全端口转发
- 强兼容性,完美支持docker,经过masquerade等复杂网络环境后仍能正常工作
- 部署要求宽松,配置简单,可以参考前文配置
部署
首先当然要把wireguard弄通了)
配置文件
post-up:
#!/bin/bash
set -e
WG_IFACE="wg0"
ROUTING_TABLE="4242"
MARK_VAL="0x42"
IPSET_NAME="wg_peers"
LAN_NET="192.168.1.0/24"
TTL=3600
WG_CONF="/etc/wireguard/${WG_IFACE}.conf"
echo "[$(date)] Setting up dynamic WG peer routing..."
#########################################
# 0. 自动提取 WG Endpoint 外网 IP (关键防御!)
#########################################
# 从 wg0.conf 中提取 Endpoint 的 IP 地址
ENDPOINT_IP=$(grep -iE '^\s*Endpoint\s*=' "${WG_CONF}" | awk -F'[: ]+' '{print $3}' | head -n 1 || true)
if [ -n "${ENDPOINT_IP}" ]; then
echo "[$(date)] Detected WG Endpoint IP: ${ENDPOINT_IP}, it will be EXCLUDED from ipset."
fi
#########################################
# 1. 创建 ipset
#########################################
ipset create ${IPSET_NAME} hash:ip timeout ${TTL} -exist
# 如果 Endpoint IP 在 ipset 中(可能是残留脏数据),立刻删除它解套!
if [ -n "${ENDPOINT_IP}" ]; then
ipset del ${IPSET_NAME} ${ENDPOINT_IP} 2>/dev/null || true
fi
#########################################
# 2. WireGuard 路由表配置
#########################################
ip route replace default dev ${WG_IFACE} table ${ROUTING_TABLE}
#########################################
# 3. Policy Routing (策略路由)
#########################################
ip rule del pref 10 2>/dev/null || true
ip rule del pref 100 2>/dev/null || true
# LAN 流量永远走 main 表
ip rule add to ${LAN_NET} lookup main pref 10
# 被标记的 peer 流量走 WG 路由表 (4242)
ip rule add fwmark ${MARK_VAL} lookup ${ROUTING_TABLE} pref 100
#########################################
# 4. iptables 规则配置 (自定义链)
#########################################
iptables -t mangle -N WG_PEER_IN 2>/dev/null || true
iptables -t mangle -N WG_PEER_OUT 2>/dev/null || true
iptables -t mangle -F WG_PEER_IN
iptables -t mangle -F WG_PEER_OUT
iptables -t mangle -C PREROUTING -j WG_PEER_IN 2>/dev/null || iptables -t mangle -A PREROUTING -j WG_PEER_IN
iptables -t mangle -C OUTPUT -j WG_PEER_OUT 2>/dev/null || iptables -t mangle -A OUTPUT -j WG_PEER_OUT
#########################################
# 5. 记录 WG Peer (入口记录与 TTL 刷新)
#########################################
# 5.1 忽略 LAN 内部流量
iptables -t mangle -A WG_PEER_IN -i ${WG_IFACE} -s ${LAN_NET} -j RETURN
# 5.2 【核心修复】:绝对禁止将 WG Endpoint 节点的公网 IP 写入 ipset!
if [ -n "${ENDPOINT_IP}" ]; then
iptables -t mangle -A WG_PEER_IN -s ${ENDPOINT_IP} -j RETURN
fi
# 5.3 记录 WireGuard 入站流量 IP 并更新 TTL
iptables -t mangle -A WG_PEER_IN -i ${WG_IFACE} -j SET --add-set ${IPSET_NAME} src --exist --timeout ${TTL}
#########################################
# 6. 对目标 Peer 打 Mark (支持转发与本机流量)
#########################################
# 6.1 本机发出的流量 -> 打 Mark
iptables -t mangle -A WG_PEER_OUT -m set --match-set ${IPSET_NAME} dst -j MARK --set-mark ${MARK_VAL}
# 6.2 Docker 等容器/转发发出的流量 -> 打 Mark
iptables -t mangle -A WG_PEER_IN ! -i ${WG_IFACE} -m set --match-set ${IPSET_NAME} dst -j MARK --set-mark ${MARK_VAL}
#########################################
# 7. 对 WG 出口流量做 MASQUERADE
#########################################
iptables -t nat -C POSTROUTING -o ${WG_IFACE} -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -o ${WG_IFACE} -j MASQUERADE
#########################################
# 8. 关闭 rp_filter (反向路径过滤)
#########################################
sysctl -q -w net.ipv4.conf.all.rp_filter=0
sysctl -q -w net.ipv4.conf.default.rp_filter=0
sysctl -q -w net.ipv4.conf.${WG_IFACE}.rp_filter=0
#########################################
# 9. 刷新路由缓存
#########################################
ip route flush cache
echo "[$(date)] Dynamic WG peer routing successfully loaded with Endpoint immunity!"
post-down:
#!/bin/bash
set -e
WG_IFACE="wg0"
ROUTING_TABLE="4242"
MARK_VAL="0x42"
IPSET_NAME="wg_peers"
LAN_NET="192.168.1.0/24"
echo "[$(date)] Cleaning up dynamic WG peer routing..."
#########################################
# 1. 解挂并删除 iptables 自定义链
#########################################
# 先从主链中解挂(忽略解挂失败的报错)
iptables -t mangle -D PREROUTING -j WG_PEER_IN 2>/dev/null || true
iptables -t mangle -D OUTPUT -j WG_PEER_OUT 2>/dev/null || true
# 清空自定义链中的规则
iptables -t mangle -F WG_PEER_IN 2>/dev/null || true
iptables -t mangle -F WG_PEER_OUT 2>/dev/null || true
# 删除自定义链
iptables -t mangle -X WG_PEER_IN 2>/dev/null || true
iptables -t mangle -X WG_PEER_OUT 2>/dev/null || true
#########################################
# 2. 删除 Policy Routing (策略路由)
#########################################
# 按 pref 彻底删除添加的路由规则
ip rule del pref 10 2>/dev/null || true
ip rule del pref 100 2>/dev/null || true
#########################################
# 3. 清理自定义路由表 (4242)
#########################################
ip route flush table ${ROUTING_TABLE} 2>/dev/null || true
#########################################
# 4. 销毁 ipset
#########################################
# -F 清空,-X 销毁(-exist 避免不存在时报错)
ipset flush ${IPSET_NAME} 2>/dev/null || true
ipset destroy ${IPSET_NAME} 2>/dev/null || true
#########################################
# 5. 恢复 rp_filter (可选)
#########################################
# 恢复为 Linux 默认的严格/松散模式 (通常系统默认为 1 或 2)
sysctl -q -w net.ipv4.conf.all.rp_filter=2 2>/dev/null || true
sysctl -q -w net.ipv4.conf.default.rp_filter=2 2>/dev/null || true
#########################################
# 6. 刷新路由缓存
#########################################
ip route flush cache
echo "[$(date)] Dynamic WG peer routing cleaned up successfully."
(可选)辅助watchdog脚本
#!/bin/bash
# 配置部分:请确保 WG_UP_SCRIPT 指向你最新的 PostUp 配置脚本路径
WG_UP_SCRIPT="/etc/wireguard/ip_post_up.sh"
WG_IFACE="wg0"
ROUTING_TABLE="4242"
MARK_VAL="0x42"
IPSET_NAME="wg_peers"
# 1. 检查网卡是否存在 (防止 WireGuard 处于 Down 状态时误触发)
if ! ip link show "$WG_IFACE" > /dev/null 2>&1; then
# WG 接口未启动,静默退出
exit 0
fi
NEED_RECOVER=0
# 2. 检查策略路由是否存在 (fwmark 0x42 -> lookup 4242)
if ! ip rule list | grep -q "fwmark ${MARK_VAL}.*lookup ${ROUTING_TABLE}"; then
echo "[$(date)] Watchdog: Missing ip rule for fwmark ${MARK_VAL}."
NEED_RECOVER=1
fi
# 3. 检查 ipset 容器是否存在
if ! ipset list "${IPSET_NAME}" > /dev/null 2>&1; then
echo "[$(date)] Watchdog: Missing ipset ${IPSET_NAME}."
NEED_RECOVER=1
fi
# 4. 检查 iptables 自定义链挂载点是否存在
if ! iptables -t mangle -C PREROUTING -j WG_PEER_IN > /dev/null 2>&1 || \
! iptables -t mangle -C OUTPUT -j WG_PEER_OUT > /dev/null 2>&1; then
echo "[$(date)] Watchdog: Missing iptables mangle hooks."
NEED_RECOVER=1
fi
# 6. 如果发现任何一项缺失,重新执行修复
if [ "${NEED_RECOVER}" -eq 1 ]; then
echo "[$(date)] Watchdog: Inconsistency detected! Restoring WG peer routing rules..."
if [ -f "${WG_UP_SCRIPT}" ]; then
bash "${WG_UP_SCRIPT}"
echo "[$(date)] Watchdog: Restored successfully using ${WG_UP_SCRIPT}."
else
echo "[$(date)] Watchdog ERROR: Setup script ${WG_UP_SCRIPT} not found!"
exit 1
fi
fi
# 规则全部完好,静默退出
exit 0
检查运行状态
$ sudo ipset list wg_peers| grep -i "Number of entries"
Number of entries: 3568至此艺术已成
结尾
贴别感谢turbothorsten的回帖的启发:https://superuser.com/questions/1777082/using-vps-to-give-public-ip-to-on-home-network/1777106
文章内容遵循 CC BY 4.0,代码/脚本遵循 MIT License。