Wireguard+iptables实现网络层的转发
这几天一个朋友说她要在学校放一台服务器,但是拿不到独立的公网ip,于是我突然有了这么一个想法——能不能将远程服务器的ip分配给本地使用呢?这样就可以让本地用上独立的固定公网ip了,我的想法大概如下(如果远程服务器有两个公网ip):
- 本地通过wireguard主动连接到远程的服务器
- 远程服务器将发送到其中一个公网ip的请求全部转发给虚拟局域网中本地服务器对应的ip
- 本地进行的响应也走wireguard
当然,现在已经有诸如frp等很成熟的端口转发工具了,如果只是单纯的转发一个端口、暴露一个服务之类的需求直接使用frp就好了。但是如果想实现低至网络层、全端口的数据包转发则可以使用这种方法,标题中所说的"分配",指的是实现网络层的转发,而不是真的给本地网卡分配一个公网ip,当然,在表现上来说,和本地分配了一个公网ip没什么区别。
想明白流程之后的实际操作不难,折腾了一会便实现了这个想法,一开始实验的时候用的是双ip的服务器,但是实际上单ip服务器也是可以的,操作见下文即可。
使用的机器:
- 双ip的justhost
- 单ip的腾讯云
使用wireguard组网
首先我们要在两台机器间建立一个虚拟局域网(除了wireguard,zerotier之类的软件也是可以的),先在本地与远程两台机器上安装wireguard,并生成密钥。
apt install wireguard
wg genkey | tee privatekey | wg pubkey > publickey
在 /etc/wireguard/ 下创建配置文件 wg.conf:
在具有公网ip的服务器上配置如下:
[Interface]
# 客户端连接的端口
ListenPort = 16000
# 虚拟局域网中的ip
Address = 10.0.0.1/24
# 填上之前生成的私钥
PrivateKey = $PRIVATEKEY
# 下面两条是放行的iptables和MASQUERADE
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE
# 无公网ip的服务器设置
[Peer]
# 无公网ip的服务器公钥
PublicKey = $PUBLICKEY
AllowedIPs = 10.0.0.2/32
另外修改 /etc/sysctl.conf 添加 net.ipv4.ip_forward = 1 执行 sysctl -p
开启转发。
在没有公网ip的服务器上配置如下
[Interface]
# 虚拟局域网中的ip
Address = 10.0.0.2/24
# 客户端私钥
PrivateKey = CLIENTA_PRIVATE_KEY
# MTU(可选)
# MTU = 1420
# 监听端口(可选)
# ListenPort =
[Peer]
# Server
# Server 公钥
PublicKey = SERVER_PUBLICKEY
# Server IP:Port
Endpoint = 1.2.3.4:16000
# 通过 wireguard传输的 IP 列表
AllowedIPs = 10.0.0.0/24
# 强烈建议开启定期握手,不然远程服务器可能没办法主动连接上本地机器
PersistentKeepalive = 20
之后启动wireguard
wg-quick up wg
# 或者
systemctl start wg-quick@wg
另外,如果使用的是境外的机器用于转发,普通的udp跨境速度感人,Hysteria可以加速连接,并且隐藏wireguard的特征,但是relay_udp模式貌似存在一些问题,在我的测试中都没有速度,感兴趣的可以查看一下这篇文章使用Hysteria进行双边加速…国内的机器则不用太担心这点(除非机房限制了udp)。
实现流量转发
在组建了虚拟局域网之后我们就可以对流量进行转发了。下面两种情况要分开考虑:
- 远程服务器有多个公网ip(双栈也可以)
- 远程服务器只有一个公网ip
多ip服务器的转发
假设网络环境如下
远程服务器有双ip(双栈也可以) 1.1.1.1和2.2.2.2,虚拟局域网中的ip为10.0.0.1,本地机器只有局域网ip,虚拟局域网中的ip为10.0.0.2。我们需要将2.2.2.2给本地的机器使用,
本地的服务器使用1.1.1.1和远程服务器建立wg连接,之后的流程如下:
- 外部流量到 2.2.2.2
- iptables修改目的ip为 10.0.0.2
- 路由到wg虚拟网卡进行处理
- 经过wireguard到达本地
- 响应请求
- 经过wg虚拟网卡到达远程服务器
- iptables 修改源ip为2.2.2.2
我使用justhost来测试,他家创建服务器的时候就算选择了多个ip,但是创建出来只绑定了一个ip,所以我们还需要进行配置。
两个公网ip为:
- 45.89.228.209 用于建立wireguard连接 (双栈服务器可以使用ipv6来建立连接)
- 45.130.146.230 分配给本地使用
修改 /etc/netplan/50-cloud-init.yaml
(Ubuntu18之后的配置文件,之前的版本为network/interfaces)
# network: {config: disabled}
network:
version: 2
ethernets:
eth0:
addresses:
- 45.89.228.209/23
- 45.130.146.230/23
- 2a00:b700:2::7:156/64
gateway4: 45.89.228.1
gateway6: 2a00:b700:2::1
match:
macaddress: 5e:e7:6c:db:9f:83
nameservers:
addresses:
- 77.88.8.8
- 77.88.8.1
- 2a00:b700::220
- 2a00:b700:1::220
search:
- justhost.ru
set-name: eth0
之后使用 netplan apply
应用配置,应用之后尝试 ping 45.130.146.230
已经可以获得回应了,但是有一个很奇怪的问题,我用 ifconfig
还是无法看见新分配的ip,但是使用 ip addr
是可以看见的。
分配好ip后,我们尝试将新分配到的45.130.146.230分配给本地的服务器使用。为了使连接不受影响,我们需要修改本地wireguard配置文件中对应peer的endpoint,使用45.89.228.209:16000来建立连接。
连接已建立
在虚拟局域网中ip如下:
10.0.0.2: 本地的服务器ip
10.0.0.10: 远程的服务器ip
之后我们需要在远程服务器上使用iptables进行一些修改 这两条命令也可以写到wireguard的postup和postdown参数中。
# 首先需要实现将目的ip为45.130.146.230的数据包修改目的地址为10.0.0.2
iptables -t nat -I PREROUTING -d 45.130.146.230 -j DNAT --to-destination 10.0.0.2
# 因为wireguard修改了路由表,所以之后目的地址为10.0.0.2的数据包就会转发至10.0.0.2
# 另外需要将10.0.0.2的响应请求,源ip修改为 45.130.146.230
iptables -t nat -I POSTROUTING -s 10.0.0.2 -j SNAT --to-source 45.130.146.230
# 另外,数据包经过wg的时候被改写了源ip(变成10.0.010),这样会导致无法正常响应
Copy
服务端修改成这样后,就可以了,但是还有一个问题需要处理,此时在远程服务器上对wg接口抓包,这时是可以看见 xxx.xxx.xxx.xxx(来自公网的ip) -> 10.0.0.2 的icmp数据包的,但是在本地的服务器上对wg接口抓包,什么都看不见。
原因是本地的wg配置文件中只设置了 AllowedIPs = 10.0.0.0/24,也就是说只允许源ip和目的ip为10.0.0.0/24在wg上进行传输。
虽然设置成0.0.0.0/0,可以允许所有的包经过wg进行传输,但是本地的所有请求也会经过wireguard进行传输,但如果我们只希望本地服务器的被动响应走wg,而主动连接不经过wg,那么还需要再客户端上进行一些设置。
修改本地机器的 wg.conf,在[Interface]中添加如下字段
Table = 1
PostUp = ip rule add from 10.0.0.2 table 1; ip rule add to 10.0.0.0/24 table 1
PostDown = ip rule del from 10.0.0.2 table 1; ip rule add to 10.0.0.0/24 table 1
加上上面的字段后,wireguard会新建一个路由表,而不是直接在主路由表上进行修改,然后我们只需要让来自于wg的请求以及目的ip是虚拟局域网中的地址的请求根据这个表进行路由,而本地主动发出的请求根据主路由表路由,即可实现上面需要的功能。
然后执行 systemctl restart wg-quick@wg
重启wireguard即可。 可以看见延迟发生了变化
启动前
启动后
单ip服务器的转发
其实单ip服务器的转发也很简单,唯一的一点不同就是SNAT和DNAT的时候,需要忽略几个端口,比如需要忽略目的端口是wireguard监听端口(16000)的请求,如果我们还想要连接到用于中转的服务器,那么还需要忽略22端口。
单ip服务器转发使用腾讯云的机器进行,几家大厂的服务器都有个特点——网卡没有直接绑定公网ip。在建立好了虚拟局域网之后,我们要设置下面的转发规则: (multiport模块不支持 -p all参数)
iptables -t nat -I PREROUTING -d 127.0.0.1 -p tcp -m multiport ! --dports 22,16000 -j DNAT --to-destination 10.0.0.2
iptables -t nat -I PREROUTING -d 127.0.0.1 -p udp -m multiport ! --dports 22,16000 -j DNAT --to-destination 10.0.0.2
iptables -t nat -I POSTROUTING -s 10.0.0.2 -j SNAT --to-source 127.0.0.1
之后的在本地服务器上进行的设置和多ip服务器的设置一样,就不赘述了。
我使用的配置文件
-
本地服务器
[Interface] # 虚拟局域网内的 Address = 10.0.0.2/24 # 客户端私钥 PrivateKey = privatekey Table = 1 PostUp = ip rule add from 10.0.0.2 table 1; ip rule add to 10.0.0.0/24 table 1 PostDown = ip rule del from 10.0.0.2 table 1; ip rule del to 10.0.0.0/24 table 1 [Peer] PublicKey = publickey1 Endpoint = x.x.x.x:16000 AllowedIPs = 10.0.0.1/32, 0.0.0.0/0 PersistentKeepalive = 20 [Peer] PublicKey = publickey2 Endpoint = x.x.x.x:16000 AllowedIPs = 10.0.0.10/32 PersistentKeepalive = 20
-
多ip服务器
[Interface] # 客户端连接的端口 ListenPort = 16000 Address = 10.0.0.10/24 # 填上之前生成的私钥 PrivateKey = privatekey # 下面两条是放行的iptables和MASQUERADE PostUp = iptables -t nat -I PREROUTING -d 45.130.146.230 -j DNAT --to-destination 10.0.0.2; iptables -t nat -I POSTROUTING -s 10.0.0.2 -j SNAT --to-source 45.130.146.230; PostDown = iptables -t nat -D PREROUTING -d 45.130.146.230 -j DNAT --to-destination 10.0.0.2; iptables -t nat -D POSTROUTING -s 10.0.0.2 -j SNAT --to-source 45.130.146.230 [Peer] PublicKey = publickey AllowedIPs = 10.0.0.2/32
-
单ip服务器
腾讯云等大厂的服务器绑定的一般都是内网ip,而非公网ip,这里是10.0.4.5
[Interface] # 客户端连接的端口 ListenPort = 16000 Address = 10.0.0.1/24 # 填上之前生成的私钥 PrivateKey = privatekey PostUp = iptables -t nat -A PREROUTING -d 10.0.4.5 -p tcp -m multiport --dports 22,16000 -j ACCEPT; iptables -t nat -A PREROUTING -d 10.0.4.5 -p udp -m multiport --dports 22,16000 -j ACCEPT; iptables -t nat -A PREROUTING -d 10.0.4.5 -j DNAT --to-destination 10.0.0.2; iptables -t nat -A POSTROUTING -s 10.0.0.2 -j SNAT --to-source 10.0.4.5; PostDown = iptables -t nat -D PREROUTING -d 10.0.4.5 -p tcp -m multiport --dports 22,16000 -j ACCEPT; iptables -t nat -D PREROUTING -d 10.0.4.5 -p udp -m multiport --dports 22,16000 -j ACCEPT; iptables -t nat -D PREROUTING -d 10.0.4.5 -j DNAT --to-destination 10.0.0.2; iptables -t nat -D POSTROUTING -s 10.0.0.2 -j SNAT --to-source 10.0.4.5; # 无公网ip的服务器设置 [Peer] # 无公网ip的服务器公钥 PublicKey = publickey AllowedIPs = 10.0.0.2/32
参考
https://fuckcloudnative.io/posts/wireguard-docs-practice/#-dns
https://www.cnblogs.com/wanstack/p/7728785.html
https://www.liuvv.com/p/a8480986.html