前言
本文不构成购买建议,仅仅是分享个人使用心得,体验。
对此您参看本文造成的问题笔者不承担任何责任。
内网穿透是一个老生常谈的问题,如果只是访问的话有很多软件可以解决(如cloudflare的argo tunnel、自建或公共的frp服务等)。但是内网穿透软件大多数都是L4的,模式大概类似于:
客户端 -> 转发服务器(如: 你的frp服务器) -> 转发客户端 -> 本地服务端
这样的话连接建立没问题,但缺点是在本地服务端上,你看到的客户端的地址是127.0.0.1,也就是”转发客户端“的地址,为了解决这个问题有很多种方案,比如HTTP协议的各种声明真实客户端IP的头(最典型就是Cloudflare的X-Real-IP)、ProxyProtocol ( 参见文章: 此处 ),不过个人认为这些方案都有不足,都需要网络协议本身支持。
所以新的方案是:通过一台在公网的服务器,以dstnat的形式将流量转发到本地,效果如下:

可以看到,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。
随后生成密钥对,输入以下指令即可:
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接口:

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

复制完成后,回到云服务器,在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信息,记得提前复制云服务器的公钥

此处AllowedAddress也要改成0.0.0.0/0,允许所有流量经过。和linux服务器不同,routeros并不会自动添加路由表,所以不需要做额外的操作。
点击OK保存,随后跑到IP去添加虚拟局域网的IP,然后就可以开始ping测试了。
IP - IP Address

Address写虚拟局域网的软路由侧的IP,Network写网段,Interface选择你建的wireguard接口。
然后你就可以开启termial测试互通情况


到这里互通没问题了,接下来进入下一步。
路由表
接着跑到云服务器去创建路由表,这里假设你的本地局域网IP端是10.0.16.0/24
ip route add 10.0.16.0/24 via 192.168.46.2
随后尝试在云服务器直接ping本地局域网的IP

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

连接处理
上面只是完成了互通,加个ip route可以将云服务器收到的流量路由到本地了。但是本地端在默认情况下会通过本地的网络回包,然后GG,所以我们要针对云服务器来的连接打标记,让这部分从哪里来回哪里去。
打开winbox,找到IP - Firewall,在Mangle中新建两条规则:
第一条规则:Chain选Prerouting,In.Interface 选择wireguard接口,然后Action选择mark-connection,大概如下:


这样一来,我们就给wireguard来的流量打了标记。不过这个标记覆盖面很大,你也可以结合in interface + dst address list更加精细的筛选流量。
第二条规则则让带有标记的流量通过wireguard远端发回,chain还是prerouting,In Interface选择非wireguard接口,connection-mark选择你第一条规则创建的mark,在Action中,选择“route”,随后IP写你云服务器端的IP。


端口转发
跑到云服务器上,这里假设你服务器使用nftables:
nft 'add rule nat prerouting iif eth0 tcp dport 41601 dnat to 10.0.16.100:3389'
如果是iptabes,那么就是:
iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 41601 -j DNAT --to 10.0.16.100:3389
然后使用mstsc连接到你服务器的41601端口,应该就ok了。