巧用 Linux 网络机制,打通网际孤岛
巧用 Linux 网络机制,打通网际孤岛
问题描述
在日常的服务器管理和网络工作中,我们常常会遇到一个棘手的「网络跳板」问题。这个问题可以被清晰地抽象为一个经典的 A-B-C 模型:
- 场景:我们有 A、B、C 三台位于不同网络的服务器,它们都拥有静态地址。
- 挑战:由于防火墙、安全组或网络隔离策略,节点间的连通性受到了限制:A-B 之间可以互相访问,B-C 之间可以互相访问,但 A-C 之间无法直接建立连接。
- 目标:我们需要建立一条路径,让 A 能够 透明地 访问 C 上的服务,就如同它们之间存在直接路由一样。所有复杂的流量转发和加解密都应在幕后由 B 完成,对 A 上的应用程序完全无感。
这个看似抽象的模型,其实在现实世界中有着非常广泛的应用场景,例如:
- 企业内网穿透:A 是仅能访问公司内网的开发机,B 是一台同时拥有内外网权限的网关 / 堡垒机,而 C 是位于公网的某个云服务。我们需要让开发机 A 能直接调用公网 C 的 API。
- 虚拟化开发环境:A 是你的办公电脑,B 是用于部署虚拟化开发环境的服务器,C 是运行在 B 上的虚拟机(使用 NAT 模式,常见的 Docker、Hyper-V 等虚拟化技术均默认采用 NAT 模式)。你想从远程直接访问虚拟机 C 内部署的服务,同时避免将 C 暴露给公网。
- 骨干网 IPv6 改造与兼容:随着网络技术演进,不少运营商或企业的骨干网已完成或正在推进 IPv6 化,但数据中心内仍存在部分仅支持 IPv4 的老旧设备(如存储、监控设备)。A 所在侧仅具备 IPv4 连通性(A-B 承载网络为 IPv4),但 A 本身仍可部署 WireGuard;C 是 IPv6 网络中的服务。B 处于网络边缘,同时拥有 IPv4 和 IPv6 连接能力,是一台双栈服务器。此时需要使 A 无缝访问 C,从而在不淘汰旧有硬件的前提下,平滑地完成网络架构的现代化改造。
- 跨校网络加速与中转:主机 A 在位于大学 A,无法稳定访问位于大学 C 的服务器 C。但你有服务器 B,它与 A 和 C 之间都有良好的网络连接,此时 B 就可以作为理想的中转节点。
面对这类挑战,利用 Linux 内核自带的 WireGuard、隧道(Tunnel)机制和 TUN 设备机制,以及 网络地址转换(Network Address Translation,NAT)机制,是一种极其优雅、安全且高效的解决方案。本文将详细介绍其实现原理,并提供一份清晰、可复现的配置指南。
解决方案原理
本文所述解决方案对应的网络拓扑如下图所示。
核心概念解析
隧道 Tunnel
隧道(Tunnel)是一种网络通信机制,它将一种网络协议的数据包(负载)封装在另一种网络协议的数据包(载体)内进行传输。在本方案中,从 A 发往 C 的原始 IP 数据包会被「包装」成一个新的 IP 数据包,其目标地址是 B。当 B 收到这个包后,它会「拆开」包装,取出原始的数据包,然后根据其真正的目标地址 C 将其转发出去。这个过程就像在 A 和 B 之间挖了一条虚拟的、点对点的通道,也就是「隧道」。Linux 内核支持多种隧道模式,如 (IPv6-in-IPv6)、(IPv4-in-IPv6)、(IPv6-in-IPv4)和 (IPv4-in-IPv4)等。[1]
WireGuard 隧道
WireGuard 是一种现代的网络层安全隧道技术 [2]。它将 IP 数据包封装在 UDP 数据包中进行传输。在本方案中,从 A 发往 C 的原始 IP 数据包会被 WireGuard 加密并封装,发送给 B。B 收到后解密并提取出原始数据包,再根据路由表转发给 C。相比传统的隧道,WireGuard 提供了强力的加密保障;在现代 Linux 系统上通常以内核模块形式运行,性能极高。
网络地址转换 NAT
网络地址转换(Network Address Translation,NAT)是一种在 IP 数据包通过路由器或防火墙时,重写其源或目标地址的技术。在本方案中,我们主要使用源地址转换(Source NAT,SNAT)。示例里使用了 / 的 目标(适合出口地址可能变化的场景);如果 B 的出口地址固定,也可以使用显式 。
当来自 A 的数据包通过隧道到达 B,并准备被转发给 C 时,B 会将数据包的源地址(A 的隧道地址)替换成 B 自己的出口网卡地址。这样做是为了让 C 能够把响应数据包正确发回给 B;代价是 C 侧看到的源地址将是 B。若你希望 C 看到 A 的真实隧道源地址,可以不使用 SNAT,转而在 C(或其上游路由器)上添加到 A 隧道网段的回程路由,指向 B。
关键角色:路由转发 vs 协议转换网关
在本文提供的解决方案中,服务器 B 扮演的角色是一个 支持加解密 的三层转发节点:它负责路由转发,并且可以按需做 SNAT,但它不做 IPv4/IPv6 的协议转换。理解这一点是判断“隧道内层 IP 版本”应如何选择的关键。
路由器的工作原理是:当它在一个接口(如 WireGuard 接口 )上收到一个数据包时,它会查看该数据包的目标地址,然后根据自己的路由表,将这个数据包 几乎原封不动地 从另一个接口(如连接外部网络的物理网卡)发送出去。它只负责路由,不负责改变数据包的协议类型(例如从 IPv4 转换到 IPv6)。
因此,就会出现以下两种情况:
- 协议一致时:如果从隧道中出来的数据包是 IPv6,且 B 和 C 之间的网络协议也是 IPv6,那么 B 可以毫无障碍地将这个 IPv6 包经过路由直接转发给 C。整个过程是通畅的。
- 协议不一致时:如果从隧道出来的是一个 IPv4 包,但服务器 C 只支持 IPv6,那么此时如果 B 只做三层路由转发,就无能为力了。要实现这种跨协议通信,B 就必须扮演更复杂的 协议转换网关 角色,例如使用 NAT64 / DNS64 这样的技术,但这已超出了本文所讨论的 Linux 内核原生转发功能的范畴。
综上所述,本文之所以只讨论协议一致的场景,是因为它旨在提供一个仅依赖 Linux 内核 WireGuard 功能以及基础路由、转发和(可选)SNAT 实现的简洁方案。
网际协议组合完整分析表
下表完整地列出了 A-B、B-C 和 隧道 三者之间所有理论上的协议组合以及本文提供解决方案的适用性。为避免歧义:A-B 指 WireGuard 外层 UDP 所依赖的承载网络 IP 版本(underlay);“隧道”指 上的内层 IP 版本(inner);B-C 指 B 出口到 C 的网络 IP 版本(egress)。
| 场景 | A-B | B-C | 隧道 | 适用性 | 备注 |
|---|---|---|---|---|---|
| 1 | IPv6 | IPv6 | IPv6 | 是 | 纯 IPv6 环境下的标准场景。 |
| 2 | IPv6 | IPv6 | IPv4 | 否 | 需要 B 做协议转换(NAT46),配置复杂。 |
| 3 | IPv6 | IPv4 | IPv6 | 否 | 需要 B 做协议转换(NAT64),配置复杂。 |
| 4 | IPv6 | IPv4 | IPv4 | 是 | 典型的过渡方案,用 IPv6 网络访问纯 IPv4 服务。 |
| 5 | IPv4 | IPv6 | IPv6 | 是 | 典型的过渡方案,用 IPv4 网络访问纯 IPv6 服务。 |
| 6 | IPv4 | IPv6 | IPv4 | 否 | 需要 B 做协议转换(NAT46),配置复杂。 |
| 7 | IPv4 | IPv4 | IPv6 | 否 | 需要 B 做协议转换(NAT64),配置复杂。 |
| 8 | IPv4 | IPv4 | IPv4 | 是 | 最常见的场景,用 IPv4 网络访问 IPv4 服务。 |
实际配置时,你可以先根据 A-B 与 B-C 的网络现状,用上表确定「隧道内协议」应选择 IPv4 还是 IPv6。然后在下文的选项卡中,选择与该结论一致、且与 A-B 底层连通性(承载协议)匹配的一组命令执行即可。
配置过程
由于需要对网络层的配置进行修改,因此下面所有的配置命令都需要在 用户下执行。同时,为了适配当前越来越多网络环境的骨干网已经完成或正在进行 IPv6 化,但仍然存在大量 IPv4 孤岛的情况,本文对 IPv4 也做了适配,可以在下文给出命令处通过切换不同的选项卡获取所需的命令。
在开始配置之前,推荐先确认以下前置条件是否满足:
- 内核已支持 WireGuard(较新内核已内置,否则需要相应模块或回移植包)。
- 系统已安装 WireGuard 管理工具(如 、)。
- B 能够从外部被访问到 WireGuard 监听端口(默认示例为 UDP 51820),对应云安全组或本地防火墙已放行。
- A、B、C 的基础网络连通性(A-B、B-C)已经建立,且能通过常规 IP/路由验证。
- C 的目标服务端口已允许来自 B 的访问(云安全组或本地防火墙策略)。
准备工作:生成密钥对
WireGuard 基于公钥加密。首先在 A 和 B 上生成各自的密钥。
# 在服务器 A 上执行
wg genkey | tee a_private.key | wg pubkey > a_public.key
# 在服务器 B 上执行
wg genkey | tee b_private.key | wg pubkey > b_public.key私钥文件建议设置为仅 root 可读(分别在 A 与 B 上执行):
chmod 600 a_private.key b_private.key隧道网段分配
为了最小化地址使用并遵循最佳实践,我们为 WireGuard 隧道分配如下的点对点网段。
| 协议版本 | 隧道网段 | A 端隧道地址 | B 端隧道地址 |
|---|---|---|---|
| IPv4 | |||
| IPv6 |
在下面的命令中,我们使用 ${a_ipv4}、${b_wg_ipv6} 等占位符来指代服务器的地址,请根据你的实际环境和上表规划替换它们。
Shell 变量声明
你可以提前声明所需的变量,使得 Shell 能够自行执行变量代换,这样就不需要手动替换配置命令中存在的占位符了。注意:地址和掩码(前缀长度)是分开定义的。
# === 根据你的环境修改下面的地址 ===
# A、B、C 的公网 IPv6 地址
a_ipv6="2001:db8::1"
b_ipv6="2409:db8::1"
c_ipv6="240e:db8::2"
# C 的地址掩码,/128 表示只针对这一个主机
c_ipv6_mask="128"
# 隧道两端的 IPv6 地址和掩码
a_wg_ipv6="fd00::1"
b_wg_ipv6="fd00::2"
tun_ipv6_mask="126"
# A、B、C 的公网 IPv4 地址
a_ipv4="198.51.100.1"
b_ipv4="203.0.113.1"
c_ipv4="203.0.113.2"
# C 的 IPv4 掩码,/32 表示只针对这一个主机
c_ipv4_mask="32"
# 隧道两端的 IPv4 地址和掩码
a_wg_ipv4="192.168.255.1"
b_wg_ipv4="192.168.255.2"
tun_ipv4_mask="30"
# === WireGuard 密钥配置 ===
# 将下面的密钥值替换为你在准备工作中生成的实际密钥
# 例如:a_public_key=$(cat a_public.key)
a_public_key="A_PUBLIC_KEY_HERE"
b_public_key="B_PUBLIC_KEY_HERE"服务器 B 配置
在服务器 B 上,我们需要完成三个关键配置:
- 开启内核转发 [3]。
- 配置 SNAT 规则:为从 WireGuard 隧道到达 B 并转发至 C 的流量配置 SNAT(示例使用 MASQUERADE 目标),确保 C 可以将返回流量路由回 B。为了遵循权限最小原则,示例同时指定了源地址和目标地址(均为精确的单个 IP);如果你的目标是一个网段,请相应调整目标地址与掩码。
- 设置防火墙转发策略:默认情况下 链的策略可能是 ,因此需要显式允许 A 和 C 之间的流量通过 B 转发。
这些配置都在 B 上执行,用于确保 A 和 C 之间的流量能够正确转发。实际环境中,连接 C 的物理网卡名称不一定是文中的 ,建议在动手前通过 ip route get ${c_ipv4} 或 ip -6 route get ${c_ipv6} 确认出口网卡名称并替换。若 B 上存在入站防火墙策略,还需要额外放行 WireGuard 监听端口(示例为 UDP 51820),否则 A 与 B 无法完成握手。
另外,不同发行版的防火墙实现可能基于 nftables, / 往往是兼容层。若你发现规则无法生效或 nat 表不可用,请优先确认系统实际生效的防火墙体系,并用对应方式配置等价规则。
在 服务器 B 上执行:
# 1. 开启 IPv6 转发
sysctl -w net.ipv6.conf.all.forwarding=1
# 2. 配置 SNAT,让来自 A 的 WireGuard 流量(源 ${a_wg_ipv6})去往 C(目标 ${c_ipv6})时,源地址伪装成 B 的出口网卡地址
ip6tables -t nat -A POSTROUTING -s ${a_wg_ipv6} -d ${c_ipv6} -o eth0 -j MASQUERADE
# 3. 配置防火墙转发策略
# 允许从 A 到 C 的 WireGuard 流量转发
ip6tables -A FORWARD -i wg0 -o eth0 -s ${a_wg_ipv6} -d ${c_ipv6} -j ACCEPT
# 允许从 C 到 A 的返回流量转发
ip6tables -A FORWARD -i eth0 -o wg0 -s ${c_ipv6} -d ${a_wg_ipv6} -j ACCEPT在 服务器 B 上执行:
# 1. 开启 IPv4 转发
sysctl -w net.ipv4.ip_forward=1
# 2. 配置 SNAT,让来自 A 的 WireGuard 流量(源 ${a_wg_ipv4})去往 C(目标 ${c_ipv4})时,源地址伪装成 B 的出口网卡地址
iptables -t nat -A POSTROUTING -s ${a_wg_ipv4} -d ${c_ipv4} -o eth0 -j MASQUERADE
# 3. 配置防火墙转发策略
# 允许从 A 到 C 的 WireGuard 流量转发
iptables -A FORWARD -i wg0 -o eth0 -s ${a_wg_ipv4} -d ${c_ipv4} -j ACCEPT
# 允许从 C 到 A 的返回流量转发
iptables -A FORWARD -i eth0 -o wg0 -s ${c_ipv4} -d ${a_wg_ipv4} -j ACCEPT在 服务器 B 上执行:
# 1. 开启 IPv6 转发
sysctl -w net.ipv6.conf.all.forwarding=1
# 2. 配置 SNAT,让来自 A 的 WireGuard 流量(源 ${a_wg_ipv6})去往 C(目标 ${c_ipv6})时,源地址伪装成 B 的出口网卡地址
ip6tables -t nat -A POSTROUTING -s ${a_wg_ipv6} -d ${c_ipv6} -o eth0 -j MASQUERADE
# 3. 配置防火墙转发策略
# 允许从 A 到 C 的 WireGuard 流量转发
ip6tables -A FORWARD -i wg0 -o eth0 -s ${a_wg_ipv6} -d ${c_ipv6} -j ACCEPT
# 允许从 C 到 A 的返回流量转发
ip6tables -A FORWARD -i eth0 -o wg0 -s ${c_ipv6} -d ${a_wg_ipv6} -j ACCEPT在 服务器 B 上执行:
# 1. 开启 IPv4 转发
sysctl -w net.ipv4.ip_forward=1
# 2. 配置 SNAT,让来自 A 的 WireGuard 流量(源 ${a_wg_ipv4})去往 C(目标 ${c_ipv4})时,源地址伪装成 B 的出口网卡地址
iptables -t nat -A POSTROUTING -s ${a_wg_ipv4} -d ${c_ipv4} -o eth0 -j MASQUERADE
# 3. 配置防火墙转发策略
# 允许从 A 到 C 的 WireGuard 流量转发
iptables -A FORWARD -i wg0 -o eth0 -s ${a_wg_ipv4} -d ${c_ipv4} -j ACCEPT
# 允许从 C 到 A 的返回流量转发
iptables -A FORWARD -i eth0 -o wg0 -s ${c_ipv4} -d ${a_wg_ipv4} -j ACCEPT在服务器 A 和 B 上建立 WireGuard 隧道
此时,我们可以在 A 和 B 之间配置 WireGuard 隧道,接口名为 wg0。
下文选项卡中的“承载”指 A 与 B 之间用于建立 WireGuard UDP 连接的底层网络协议版本;“隧道内协议”指分配在 上、并在隧道内转发的数据包协议版本。
同时,AllowedIPs 不仅用于限制某个 Peer 允许接收/发送哪些地址段,也会影响路由选择:只有匹配 AllowedIPs 的目标地址,才会被内核选择从该 Peer 发往隧道。因此配置时需要确保它覆盖你希望经由隧道访问的目标(例如 C 的地址),否则流量不会进入隧道。
使用 WireGuard 隧道,隧道内协议为 IPv6。
在服务器 B 上执行:AllowedIPs 在 B 侧主要用于标识 A 的隧道地址,从而让 B 将发往该地址的回包送回 A 对应的 Peer。
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${b_wg_ipv6}/${tun_ipv6_mask} dev wg0
# 设置私钥与监听端口
wg set wg0 listen-port 51820 private-key ./b_private.key
# 添加 Peer A(服务器 A)
# AllowedIPs 必须包含 A 的 WireGuard 地址
wg set wg0 peer ${a_public_key} allowed-ips ${a_wg_ipv6}/128
# 启动接口
ip link set wg0 up在服务器 A 上执行:
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${a_wg_ipv6}/${tun_ipv6_mask} dev wg0
# 设置私钥
wg set wg0 private-key ./a_private.key
# 添加 Peer B(服务器 B)
# AllowedIPs 关键点!必须包含 C 的地址,否则内核不会将发往 C 的包投入隧道
wg set wg0 peer ${b_public_key} \
endpoint [${b_ipv6}]:51820 \
allowed-ips ${b_wg_ipv6}/128,${c_ipv6}/${c_ipv6_mask} \
persistent-keepalive 25
# 启动接口
ip link set wg0 up使用 WireGuard 隧道,隧道内协议为 IPv4。
在服务器 B 上执行:AllowedIPs 在 B 侧主要用于标识 A 的隧道地址,从而让 B 将发往该地址的回包送回 A 对应的 Peer。
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${b_wg_ipv4}/${tun_ipv4_mask} dev wg0
# 设置私钥与监听端口
wg set wg0 listen-port 51820 private-key ./b_private.key
# 添加 Peer A(服务器 A)
# AllowedIPs 必须包含 A 的 WireGuard 地址
wg set wg0 peer ${a_public_key} allowed-ips ${a_wg_ipv4}/32
# 启动接口
ip link set wg0 up在服务器 A 上执行:
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${a_wg_ipv4}/${tun_ipv4_mask} dev wg0
# 设置私钥
wg set wg0 private-key ./a_private.key
# 添加 Peer B(服务器 B)
# AllowedIPs 关键点!必须包含 C 的地址,否则内核不会将发往 C 的包投入隧道
wg set wg0 peer ${b_public_key} \
endpoint [${b_ipv6}]:51820 \
allowed-ips ${b_wg_ipv4}/32,${c_ipv4}/${c_ipv4_mask} \
persistent-keepalive 25
# 启动接口
ip link set wg0 up使用 WireGuard 隧道,隧道内协议为 IPv6。
在服务器 B 上执行:AllowedIPs 在 B 侧主要用于标识 A 的隧道地址,从而让 B 将发往该地址的回包送回 A 对应的 Peer。
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${b_wg_ipv6}/${tun_ipv6_mask} dev wg0
# 设置私钥与监听端口
wg set wg0 listen-port 51820 private-key ./b_private.key
# 添加 Peer A(服务器 A)
# AllowedIPs 必须包含 A 的 WireGuard 地址
wg set wg0 peer ${a_public_key} allowed-ips ${a_wg_ipv6}/128
# 启动接口
ip link set wg0 up在服务器 A 上执行:
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${a_wg_ipv6}/${tun_ipv6_mask} dev wg0
# 设置私钥
wg set wg0 private-key ./a_private.key
# 添加 Peer B(服务器 B)
# AllowedIPs 关键点!必须包含 C 的地址,否则内核不会将发往 C 的包投入隧道
wg set wg0 peer ${b_public_key} \
endpoint ${b_ipv4}:51820 \
allowed-ips ${b_wg_ipv6}/128,${c_ipv6}/${c_ipv6_mask} \
persistent-keepalive 25
# 启动接口
ip link set wg0 up使用 WireGuard 隧道,隧道内协议为 IPv4。
在服务器 B 上执行:AllowedIPs 在 B 侧主要用于标识 A 的隧道地址,从而让 B 将发往该地址的回包送回 A 对应的 Peer。
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${b_wg_ipv4}/${tun_ipv4_mask} dev wg0
# 设置私钥与监听端口
wg set wg0 listen-port 51820 private-key ./b_private.key
# 添加 Peer A(服务器 A)
# AllowedIPs 必须包含 A 的 WireGuard 地址
wg set wg0 peer ${a_public_key} allowed-ips ${a_wg_ipv4}/32
# 启动接口
ip link set wg0 up在服务器 A 上执行:
# 创建 WireGuard 接口
ip link add dev wg0 type wireguard
# 分配 WireGuard 内网地址
ip addr add ${a_wg_ipv4}/${tun_ipv4_mask} dev wg0
# 设置私钥
wg set wg0 private-key ./a_private.key
# 添加 Peer B(服务器 B)
# AllowedIPs 关键点!必须包含 C 的地址,否则内核不会将发往 C 的包投入隧道
wg set wg0 peer ${b_public_key} \
endpoint ${b_ipv4}:51820 \
allowed-ips ${b_wg_ipv4}/32,${c_ipv4}/${c_ipv4_mask} \
persistent-keepalive 25
# 启动接口
ip link set wg0 up本文示例默认使用 SNAT(MASQUERADE)来确保 C 的回包能够返回 B(因此 C 侧看到的源地址会是 B)。如果你可以控制 C(或其上游路由器),也可以选择不使用 SNAT:在 C 上为 A 的隧道网段添加一条静态路由指向 B,并保留上面的转发放行规则即可。IPv6 场景同理,这也能避免引入 IPv6 NAT(NAT66)。
在服务器 A 上配置路由
使用 启动接口时,通常会根据 AllowedIPs 自动写入系统路由。本文采用 ip + wg 的手动配置方式,为了让发往 C 的流量进入 ,需要在 A 上显式添加到 C 的路由。
在 服务器 A 上执行。
# 将 C 的 IPv6 指向 WireGuard 接口
ip -6 route add ${c_ipv6}/${c_ipv6_mask} dev wg0在 服务器 A 上执行。
# 将 C 的 IPv4 指向 WireGuard 接口
ip route add ${c_ipv4}/${c_ipv4_mask} dev wg0在 服务器 A 上执行。
# 将 C 的 IPv6 指向 WireGuard 接口
ip -6 route add ${c_ipv6}/${c_ipv6_mask} dev wg0在 服务器 A 上执行。
# 将 C 的 IPv4 指向 WireGuard 接口
ip route add ${c_ipv4}/${c_ipv4_mask} dev wg0测试连通性
此时,从服务器 A 应该可以直接访问服务器 C 上的服务了。可以通过 命令进行测试。
在 服务器 A 上执行。
ping -6 ${c_ipv6}在 服务器 A 上执行。
ping ${c_ipv4}在 服务器 A 上执行。
ping -6 ${c_ipv6}在 服务器 A 上执行。
ping ${c_ipv4}常见故障排查
如果握手或连通性不符合预期,可以按下面顺序快速定位:
- 在 A、B 上执行
wg show,确认是否出现latest handshake,以及 A 与 B 的端点地址与端口是否正确。 - 在 A 上执行
ip route get ${c_ipv4}或ip -6 route get ${c_ipv6},确认发往 C 的路由是否走 。 - 在 B 上确认转发已开启:IPv4 对应
net.ipv4.ip_forward=1,IPv6 对应net.ipv6.conf.all.forwarding=1。 - 在 B 上检查防火墙链:确认
INPUT放行 UDP 51820,FORWARD放行 A 与 C 之间的转发流量,且规则命中了正确的入/出接口。 - 在 C 上检查入站策略:确认目标服务端口允许来自 B 的访问(或按你的策略允许来自 A 的隧道网段)。
- 在 IPv4 场景下,如果你发现回包被丢弃或转发不稳定,排查是否存在
rp_filter等反向路径过滤策略导致的丢包。 - 如果出现
ping正常但 TCP 访问卡顿/超时、或只在传输大报文时失败,优先检查 MTU / PMTU 是否被破坏(例如中间网络丢弃了 ICMP 报文导致 PMTUD 失效)。可尝试适当调低wg0的 MTU,或在路径上确保相关 ICMP 报文可达。
总结
本文通过一个经典的 A-B-C 网络模型,详细阐述了如何利用 Linux 内核原生的 隧道 和 网络地址转换 功能,来巧妙地打通网络孤岛。通过在中间节点 B 上建立隧道,并结合精细化的 IP 转发、防火墙策略和 SNAT 规则,我们成功实现了让服务器 A 透明地访问服务器 C 的目标,整个过程对 A 端的应用程序完全无感。
该方案的核心优势在于:
- 原生高效:转发与加解密主要依赖 Linux 内核完成,无需额外部署用户态转发代理,性能卓越、资源占用极低。
- 透明无感:对于服务器 A 上的应用程序而言,访问 C 的体验与直接连接无异,无需修改任何代码或应用配置。
- 安全可控:遵循权限最小原则配置 / 规则,可以精确控制允许转发的流量,有效保障了系统的安全性。
- 灵活通用:方案全面覆盖了 IPv6 和 IPv4 网络的多种组合场景,无论是纯粹的 IPv6 或者 IPv4 环境还是两者共存的过渡网络,都能轻松应对。
最后需要指出,文中的所有基于 、、 和 / 的配置通常都是临时配置,在系统重启后会失效。若要实现永久生效,建议将转发相关的 sysctl 固化到系统配置(例如写入 sysctl.d),将 WireGuard 配置整理为 wg0.conf 并交由系统服务管理,同时使用发行版提供的防火墙规则持久化机制固化转发与 NAT 规则。[4]
总而言之,深入理解并善用 Linux 强大的网络堆栈,能为我们解决复杂的网络连接问题提供意想不到的简洁与高效。