2068 字
10 分钟
通过WireGuard+软路由做端口映射(保留客户端源地址)

前言#

本文不构成购买建议,仅仅是分享个人使用心得,体验。

对此您参看本文造成的问题笔者不承担任何责任。

内网穿透是一个老生常谈的问题,如果只是访问的话有很多软件可以解决(如cloudflare的argo tunnel、自建或公共的frp服务等)。但是内网穿透软件大多数都是L4的,模式大概类似于:

客户端 -> 转发服务器(如: 你的frp服务器) -> 转发客户端 -> 本地服务端

这样的话连接建立没问题,但缺点是在本地服务端上,你看到的客户端的地址是127.0.0.1,也就是”转发客户端“的地址,为了解决这个问题有很多种方案,比如HTTP协议的各种声明真实客户端IP的头(最典型就是Cloudflare的X-Real-IP)、ProxyProtocol ( 参见文章: 此处 ),不过个人认为这些方案都有不足,都需要网络协议本身支持。

所以新的方案是:通过一台在公网的服务器,以dstnat的形式将流量转发到本地,效果如下:

image-20250619151953431.png

可以看到,8.138.x.x是云服务器的IP,223.160.x.x是客户端的IP。

方案#

云服务器一台,装好wireguard(先不配置)。

本地端需要一个软路由,文章使用RouterOS,实际上只要能做到连接标记、根据标记走特定路由、支持WireGuard即可。

注:WireGuard可以换成其他的协议,比如SSTP、L2TP,此处不赘述。

实现的原理是通过云服务器直接将流量DSTNAT到本地,大概路径变成了这样:

客户端 -> 云服务器 -(DSTNAT / WIREGUARD) -> 软路由 -> NAS

这个过程之中,客户端发往云服务器的包是三层转发的,并没有被重写IP,于是NAS可以获取到真实的客户端IP地址。

但NAS返回的数据包需要做特殊处理,默认情况下,NAS肯定通过自己的网络返回数据包,进而无法成功建立连接: NAS -> 客户端

所以有一个软路由,软路由会对云服务器通过wireguard来的连接打标记,对于有标记的连接则会被路由到云服务器上,这样一来路径就变成了: NAS -> 软路由 -> 云服务器 -> 客户端。

虽然让wireguard代理本机所有流量更加简单,但是考虑到服务器的带宽比较珍贵,所以还是引入软路由分流。

实操#

Wireguard大内网搭建#

连接到云服务器,输入 apt install wireguard 安装好wireguard。

然后打开系统内核转发功能。

使用编辑器编辑 /etc/sysctl.conf

image-20250704111420775

找到 net.ipv4.ip_forward=1 一行并解除注释,不同的系统模板这个文件并不完全一样,所以你可能要自行变通。

保存后,输入 sysctl -p /etc/sysctl.conf 或者重启服务器生效。

随后生成密钥对,输入以下指令即可:

wg genkey | tee /etc/wireguard/server.private.key | wg pubkey > /etc/wireguard/server.public.key

生成好的私钥就是/etc/wireguard/server.private.key,公钥是/etc/wireguard/server.public.key。

这里先把私钥复制出来:

cat /etc/wireguard/server.private.key

随后仿照下面的配置文件,保存为/etc/wireguard/wg0.conf

[Interface]
Address = 192.168.46.1/24 # 本机在虚拟局域网中的IP
ListenPort = 51820 # 端口号,记得在云服务器中放行该udp端口
PrivateKey = # 这里填入服务端私钥
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip route add 192.168.46.0/24 dev %i # 手动添加192.168.46.0的路由到wireguard接口上
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Table = off # 这个非常重要

这里有个重点是Table设置成off。

RouterOS在7.0+已经原生支持WireGuard,我们可以很轻松的完成配置。打开Winbox,点击菜单中的Wireguard,随后新建一个wireguard接口:

image-20250619152932990.png

点击OK,此时在列表中就显示出了你建好的wireguard接口了,路由器侧wireguard的密钥对会自动生成,双击打开接口详情界面并复制软路由侧公钥

image-20250619153056386.png

复制完成后,回到云服务器,在wireguard配置文件下附加peer信息:

[Interface]
Address = 192.168.46.1/24 # 本机在虚拟局域网中的IP
ListenPort = 51820 # 端口号,记得在云服务器中放行该udp端口
PrivateKey = # 这里填入服务端私钥
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostUp = ip route add 192.168.46.0/24 dev %i # 手动添加192.168.46.0的路由到wireguard接口上
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
Table = off # 这个非常重要
[Peer]
PublicKey = # 软路由侧公钥
AllowedIPs = 192.168.46.2/32, 0.0.0.0/0

AllowedIPs一定要有0.0.0.0/0,允许所有流量经过Wireguard,如果不在interface手动设置Table = off的话,启动wireguard将会自动添加一个 0.0.0.0/0 via 192.168.46.2的路由,进而发生悲剧。

Table = off的目的是不允许Wireguard自动添加路由,由我们手动管理。

随后保存,用wg-quick把wireguard接口启用:

wg-quick up wg0

回到软路由,添加上云服务器的peer信息,记得提前复制云服务器的公钥

image-20250619153423191.png

此处AllowedAddress也要改成0.0.0.0/0,允许所有流量经过。和linux服务器不同,routeros并不会自动添加路由表,所以不需要做额外的操作。

点击OK保存,随后跑到IP去添加虚拟局域网的IP,然后就可以开始ping测试了。

IP - IP Address

image-20250619153832904.png

Address写虚拟局域网的软路由侧的IP,Network写网段,Interface选择你建的wireguard接口。

然后你就可以开启termial测试互通情况

image-20250619153939439.png

image-20250619153956680.png

到这里互通没问题了,接下来进入下一步。

路由表#

接着跑到云服务器去创建路由表,这里假设你的本地局域网IP端是10.0.16.0/24

ip route add 10.0.16.0/24 via 192.168.46.2

随后尝试在云服务器直接ping本地局域网的IP

image-20250619155453083.png

此时你在本地端用wireshark抓包也能抓到对应的icmp包

image-20250619155636489.png

连接处理#

上面只是完成了互通,加个ip route可以将云服务器收到的流量路由到本地了。但是本地端在默认情况下会通过本地的网络回包,然后GG,所以我们要针对云服务器来的连接打标记,让这部分从哪里来回哪里去。

打开winbox,找到IP - Firewall,在Mangle中新建两条规则:

第一条规则:Chain选Prerouting,In.Interface 选择wireguard接口,然后Action选择mark-connection,大概如下:

image-20250619155055610.png

image-20250619155105854.png

这样一来,我们就给wireguard来的流量打了标记。不过这个标记覆盖面很大,你也可以结合in interface + dst address list更加精细的筛选流量。

第二条规则则让带有标记的流量通过wireguard远端发回,chain还是prerouting,In Interface选择非wireguard接口,connection-mark选择你第一条规则创建的mark,在Action中,选择“route”,随后IP写你云服务器端的IP。

image-20250619155334142image-20250619155340729

端口转发#

单端口规则添加#

如果是iptabes,那么就是:这里的eth0请换成你外网的网卡

iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 41601 -j DNAT --to 10.0.16.100:3389

这样会将TCP 41601端口转发到10.0.16.100:3389,然后使用mstsc连接到你服务器的41601端口,应该就ok了。

如果是udp,就把-p修改成udp

多端口规则#

iptables -t nat -A PREROUTING -i eth0 -p udp --dport 30001:30010 -j DNAT --to 10.0.32.200

这样一来30001 ~ 30010的端口都会转发到10.0.32.200上

查询#

首先使用 iptables --list -t nat -n --line-number 列出规则

Chain PREROUTING (policy ACCEPT)
num target prot opt source destination
1 DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:41601 to:10.0.16.100:3389
2 DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:30000 to:10.0.32.200:3389
3 DNAT 6 -- 0.0.0.0/0 0.0.0.0/0 tcp dpts:30001:30009 to:10.0.32.200
Chain INPUT (policy ACCEPT)
num target prot opt source destination
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
num target prot opt source destination
1 MASQUERADE 0 -- 0.0.0.0/0 0.0.0.0/0

删除#

查看PREROUTING链前面的编号,以删除第三条指令为例,执行以下命令删除:

iptables -t nat -D PREROUTING 4

Iptables持久化#

直接使用指令添加的Iptables规则很容易因为重启掉规则,建议持久化保存。

安装 iptables-persistent 工具 apt-get install iptables-persistent

随后执行 netfilter-persistent save 即可保存iptables规则

通过WireGuard+软路由做端口映射(保留客户端源地址)
https://naturesuki.net/posts/2025_06_19_portforward_wireguard/
作者
Matsuzaka Yuki
发布于
2025-06-19
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时