Wireguard+iptables实现网络层的转发
2021/9/2更新: 暴露docker中的端口
这几天一个朋友说她要在学校放一台服务器,但是拿不到独立的公网ip,于是我突然有了这么一个想法——能不能将远程服务器的ip分配给本地使用呢?这样就可以让本地用上独立的固定公网ip了,我的想法大概如下(如果远程服务器有两个公网ip):
- 本地通过wireguard主动连接到远程的服务器
- 远程服务器将发送到其中一个公网ip的请求全部转发给虚拟局域网中本地服务器对应的ip
- 本地进行的响应也走wireguard
当然,现在已经有诸如frp等很成熟的端口转发工具了,如果只是单纯的转发一个端口、暴露一个服务之类的需求直接使用frp就好了。但是如果想实现低至网络层、全端口的数据包转发则可以使用这种方法,标题中所说的"分配",指的是实现网络层的转发,而不是真的给本地网卡分配一个公网ip,当然,在表现上来说,和本地分配了一个公网ip没什么区别。
想明白流程之后的实际操作不难,折腾了一会便实现了这个想法,一开始实验的时候用的是双ip的服务器,但是实际上单ip服务器也是可以的,操作见下文即可。
使用的机器:
- 双ip的justhost
- 单ip的腾讯云
使用wireguard组网
首先我们要在两台机器间建立一个虚拟局域网(除了wireguard,zerotier之类的软件也是可以的),先在本地与远程两台机器上安装wireguard,并生成密钥。
|
|
在 /etc/wireguard/ 下创建配置文件 wg.conf:
在具有公网ip的服务器上配置如下:
|
|
另外修改 /etc/sysctl.conf 添加 net.ipv4.ip_forward = 1 执行 sysctl -p
开启转发。
在没有公网ip的服务器上配置如下
|
|
之后启动wireguard
|
|
另外,如果使用的是境外的机器用于转发,普通的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)
|
|
之后使用 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参数中。
|
|
服务端修改成这样后,就可以了,但是还有一个问题需要处理,此时在远程服务器上对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]中添加如下字段
|
|
加上上面的字段后,wireguard会新建一个路由表,而不是直接在主路由表上进行修改,然后我们只需要让来自于wg的请求以及目的ip是虚拟局域网中的地址的请求根据这个表进行路由,而本地主动发出的请求根据主路由表路由,即可实现上面需要的功能。
然后执行 systemctl restart wg-quick@wg
重启wireguard即可。 可以看见延迟发生了变化
单ip服务器的转发
其实单ip服务器的转发也很简单,唯一的一点不同就是SNAT和DNAT的时候,需要忽略几个端口,比如需要忽略目的端口是wireguard监听端口(16000)的请求,如果我们还想要连接到用于中转的服务器,那么还需要忽略22端口。
单ip服务器转发使用腾讯云的机器进行,几家大厂的服务器都有个特点——网卡没有直接绑定公网ip。在建立好了虚拟局域网之后,我们要设置下面的转发规则: (multiport模块不支持 -p all参数)
|
|
之后的在本地服务器上进行的设置和多ip服务器的设置一样,就不赘述了。
Docker中的使用
我在使用这种方式给将发往远程服务器的请求全部转发给本地之后发现了一个问题——docker容器在启动时如果是通过 -p 参数指定的映射端口,访问远程ip的对应端口发现的不到响应(如果使用的是–net host 模式,那么不需要再进行额外设置),在服务器上抓包,发现docker中是收到了对应的数据包的,响应的数据包的目的地址是访问者的IP,而在没有设置路由规则的情况下,这些包是由主网卡eth0发出的,而不会走wg网卡,那么自然无法顺利建立连接了,我的解决方法是用ip route指定源地址为docker中地址的数据包根据table 1进行路由。
|
|
按照我上面的wg配置文件的写法,所有源ip为docker0的网络范围的数据包都会经过table1通过wireguard进行路由,这时再访问公网ip,已经可以访问到我们docker中的服务了。
下面的方法理论上可行…但是我不知道为什么始终没有成功,iptables已经匹配了包并进行了标记,但是貌似数据包并没有经过table 1来路由,希望知道原因的大佬可以告诉我。
不过这样又存在一个新的问题,这时docker容器中所有对外的通信都会经过wireguard由我们的远程服务器进行转发,于是我们的下一个需求就是——仅让特定源端口的数据包通过wg进行传输,这里就需要配合netfilter的标记来实现策略路由了。
我们通过iptables来控制netfilter对特定数据包进行标记,比如我们需要仅仅让源端口为19999的数据包经过wg进行传输。
|
|
通过上述方案,也可以实现仅对于docker中特定端口的流量经过wireguard进行传输。
我使用的配置文件
本地服务器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[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服务器
1 2 3 4 5 6 7 8 9 10 11 12
[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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
[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