Wireguard+iptables实现网络层的转发
使用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),这样会导致无法正常响应
服务端修改成这样后,就可以了,但是还有一个问题需要处理,此时在远程服务器上对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服务器的设置一样,就不赘述了。
Docker中的使用
我在使用这种方式给将发往远程服务器的请求全部转发给本地之后发现了一个问题——docker容器在启动时如果是通过 -p 参数指定的映射端口,访问远程ip的对应端口发现的不到响应(如果使用的是–net host 模式,那么不需要再进行额外设置),在服务器上抓包,发现docker中是收到了对应的数据包的,响应的数据包的目的地址是访问者的IP,而在没有设置路由规则的情况下,这些包是由主网卡eth0发出的,而不会走wg网卡,那么自然无法顺利建立连接了,我的解决方法是用ip route指定源地址为docker中地址的数据包根据table 1进行路由。
PostUp = ip rule add from 10.0.1.2 table 1; ip rule add to 10.0.1.1/32 table 1; ip rule add from 172.18.0.2/32 table 1; # 可以指定为某一确定容器的ip
PostDown = ip rule del from 10.0.1.2 table 1; ip rule del to 10.0.1.1/32 table 1; ip rule del from 172.18.0.2/32 table 1;
按照我上面的wg配置文件的写法,所有源ip为docker0的网络范围的数据包都会经过table1通过wireguard进行路由,这时再访问公网ip,已经可以访问到我们docker中的服务了。
我使用的配置文件
- 本地服务器
[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
本文转载自https://aoyouer.com/posts/wireguard-l3-forward/
过程写的很清楚,忘记dports参数了,找到这篇文章又想起来了.
本文的wireguard是一种方案,n2n也是一种方案,开头的zerotier也是。不过没办法在n2n内配置开启关闭时执行的脚本。