Homelab 网络解决方案 Part I

背景

家里的服务器上跑了一些Web服务,而家宽的80/443被ISP ban掉了,很长一段时间内都使用frp类似的工具作内网穿透。

但frp这类工具都存在着一个问题:服务无法在不做改动的情况下,正确获取到用户的IP地址。因为内网穿透的原理可理解为是在VPS上跑一个listener,然后将所有在指定端口收到的请求内容转发到家里服务器上的receiver,这个receiver再在本地向对应服务发送请求内容,而此时包的source ip则变成了本地的IP,因此服务就只能读取到这个本地的source ip了。

要解决这个问题,对于Web服务来说,还可以在VPS上跑一个HTTP反向代理的服务,将真实的IP通过X-Forwarded-IP Header传到家里服务器,服务再从Header中读取真实的IP。但这个方法并不能适用于别的普通TCP/UDP服务,且可能需要做两次TLS加解密,引入了额外的开销。

因此就想到,能否利用VPN技术将VPS的IP直接“透传”给家里服务器,让家里服务器直接“拥有”一张具有VPS IP的网卡呢?

思路

假设VPS的外网IP是1.1.1.1,ifconfig中读取到的IP是172.18.0.2(多数VPS厂商会进行一次NAT),访问服务的用户的IP为3.3.3.3

可以使用WireGuard VPN,让VPS和家里服务器建立一个L3的隧道,然后把家里服务器端的VPN接口上的IP设置为VPS外网IP。之后在VPS上添加一条路由,让所有dst ip为1.1.1.1的包都从VPN接口发送出去。这样我们便可以在VPS上通过1.1.1.1访问到家里的服务器了。

那如何使用户也能通过1.1.1.1访问到家里的服务器呢?

当用户访问1.1.1.1时,VPS会收到一个src ip为3.3.3.3、dst ip为172.18.0.2的包,我们需要将这个dst ip修改成1.1.1.1,这样包便会继续转发至家里的服务器。同理,当家里服务器respond的时候,我们也需要把VPS上收到的从家里服务器发出的src ip为1.1.1.1、dst ip为3.3.3.3的包中的src ip修改为172.18.0.2,这样包便能正确地发回给用户。

可以发现,其实这就是一个1:1 NAT,因此只需要再在VPS上配置好SNAT、DNAT即可实现这个功能。

实现

VPS上启用包转发

/etc/sysctl.conf中加入:

net.ipv4.ip_forward=1

然后执行

sudo sysctl -p /etc/sysctl.conf

配置WireGuard VPN

首先在VPS上安装WireGuard,Debian 11可使用以下命令

sudo apt update && sudo apt install wireguard

然后在VPS上建立并配置VPN接口

ip link add dev catmeownet type wireguard # 建立VPN接口,名称为catmeownet
ip address add dev catmeownet 10.0.0.1/24 # 用于测试,可不用添加IP
wg genkey > serverpk # 生成私钥
wg set catmeownet private-key ./serverpk

再在客户端上生成私钥,并通过wg pubkey < privatekey获取公钥,然后复制。在VPS上继续执行

wg set catmeownet listen-port 3 peer <客户端公钥> allowed-ips 1.1.1.1/32 # 让WireGuard监听在UDP 3端口,并允许客户端使用1.1.1.1 IP
ip link set up dev catmeownet

客户端上用同样的方式设置VPN接口,但将接口IP设置为1.1.1.1/32,peer的公钥设置为服务端公钥,并把peer的endpoint设置为1.1.1.1:3,把peer的allowed-ips设置为0.0.0.0/0。

VPS配置路由

ip route add 8.218.86.187/32 dev catmeownet

VPS配置nftables

nft add table nat
nft 'add chain nat postrouting { type nat hook postrouting priority 100 ; } '
nft add rule nat postrouting ip saddr 1.1.1.1/32 oif eth0 snat to 172.18.0.2
nft 'add chain nat prerouting { type nat hook prerouting priority -100 ; }'
nft add rule nat prerouting iif eth0 tcp dport != 22 dnat to 1.1.1.1 # 不转发SSH端口
nft add rule nat prerouting iif eth0 udp dport != 3 dnat to 1.1.1.1 # 不转发WireGuard端口

效果