0%

使用WireGuard技术实现外部节点与公司内网服务互访

1 使用WireGuard技术实现外部节点与公司内网服务互访

1.1 场景介绍

对于大企业而言,在过去几十年总部和分支机构的组网需求中,MPLS-VPN一直以稳定性作为大型组网的首选之作

近些年以软件定义网络的SD-WAN组网解决方案为企业带来了弹性和经济性的优势

而对于个人移动办公节点,还存在远程用户(出差或家里)、公司的分支机构、商业合作伙伴、供应商等公司和自己的公司内部网络之间建立可信的安全连接或是局域网连接的需求,一些大企业往往采用专用VPN硬件设备,通过建立在公网基础上建立VPN隧道访问内网服务

VPN技术的基本原理本质是隧道技术,隧道技术其实就是对传输的报文进行封装,利用公网建立专用的数据传输通道

常见的VPN技术包括L2TP、IPSec、SSL VPN,由于IPSec设计过于复杂,人们有设计出了OpenVPN

随着近些年科学上网的一些需求,运营商对基于TCP的VPN技术进行阻断,而采用UDP的VPN技术也不失为一种办法

相较于当下IPSec,OpenVPN等VPN技术,WireGuard被誉为下一代VPN技术,而且Linux的管理者Linus Torvalds已经将WireGuard合并至内核5.6中

WireGuard可以实现更少的代码(意味更加安全)、更快的部署以及更安全的加密算法

WireGuard的速度完全超越其他VPN协议,与其他VPN协议性能比较详见1.2.6章节

在家庭部署一台软路由,在公司内网部署一台轻量级的Linux虚拟机,通过WireGuard技术完全可以实现两个内网互访的应用,满足在公司访问家庭NAS或者在家访问公司服务等各类sao操作

更多VPN的场景对于高性能、高效部署等需求更加密切

1、使用Wireguard技术实现多个异地k8s集群之间访问通讯

2、一个家庭下多个物理地址环境下智能家居、视频监控等组网需求,实现内网穿透

实际上目前运营商对UDP流量几乎都进行了限速和限制,在实际使用过程中会出现莫名的卡顿、包括TLS应用的访问失败

本文通过家庭群晖内的一台虚拟机作为外部节点A、阿里云ECS公网作为中间VPN网关B、公司内部一台虚拟机作为公司内部网节点C通过Wireguard进行组网

理论上家庭内的各终端只要能与外部节点A正常通讯,即IP可达(针对三层以上的防火墙应用另行讨论),且将网关指向外部节点A

同样公司内网的各终端只要能与公司内网节点C正常通讯,即IP可达(针对三层以上的防火墙应用另行讨论),且将网关指向外部节点C

两边的内部网络就可以通过现有公网,所有流量通过中间VPN网关B(本实验环境下阿里云ECS主机)实现互访

整个环境全部采用真实环境模拟,实施步骤简单

1、首先在中间VPN网关(阿里云ECS主机上,实际上可以在任何安装了wireguard-tools软件包的机器上)生成个节点的私钥和公钥,私钥由各节点自己保存,公钥提供给与自身通讯的节点

2、在剩余各个节点上安装wireguard-tools软件包,并在各节点上使用wg-quick服务配置WireGuard服务器配置文件

3、启动wg-quick服务配置启动Wireguard服务,确保三个节点成功组建Wireguard网络

本环境是采用了外部节点A、公司内部网节点C分别与作为中间VPN网关的中间节点B形成了WireGuar网络,但这样中间节点会转发所有流量,且容易形成流量瓶颈

实际环境是可以full-mesh组网架构,具体可以看1.2.4 Wireguard组网架构

4、最后我们通过一个故障的分析定位及排查去针对性说明两个网络实现互访的关键操作

本文对Ubuntu22.04、iptables、最新的RHEL9以及nftables的NAT转换等操作均有详细说明

1.2 WireGuard技术介绍

Wikipedia上关于wireguard的说明

WireGuard是由Jason A. Donenfeld开发的开放源代码VPN,由于它可以运行在内核空间,因此可以高速提供安全的网络

除了可以跨平台之外,WireGuard 的最大优点之一就是易于部署

总的来说,部署非常简单,基本就是包括两个步骤,一是创建密钥对,二是创建配置文件,最后启动即可

1.2.1 客户端和服务端

实际上参与WireGuard VPN的所有主机都是同级的

使用客户端只是用来来描述建立连接的主机

使用服务器来描述固定主机名或客户端连接的 IP 地址的主机,并可选通过这个服务器路由所有流量

如果所有的节点都是公网可达的,则不需要考虑中间VPN网关(中继服务器),只有当有对等节点没有公网IP即位于NAT后面时才需要考虑使用VPN网关的方式进行组网

在 WireGuard 里,客户端和服务端基本是平等的,差别只是谁主动连接谁而已

如果双方都没有公网IP地址即都在NAT后,则需要使用类似信令服务器STUN的技术

1.2.2 密钥对的使用

当发送端WireGuard收到需要发送的数据时,当目标IP在配置文件[Peer]AllowedIPs中时,会使用PrivateKey进行加密

当接收端WireGuard收到数据时,首先会使用发送端的PublicKey进行解密

由于非对称加密原理,只有私钥对应的公钥才能正确对加密数据进行解密

1.2.3 WireGuard使用协议

WireGuard的代码虽然短小【关于WireGuard精美之处,各位可以自行搜索Linus Torvalds对于WireGuard评价原文】,但是支持的加密算法非常完善

ChaCha20 用于通过 Poly1305 进行身份验证,使用带有关联数据(AEAD)的 Authenticated Encryption,如 RFC7539 所述

Curve25519 用于 Elliptic-curve Diffie-Hellman(ECDH)密钥交换

用于哈希和密钥哈希的 BLAKE2s,如 RFC7693所述

用于哈希表键的 SipHash24

用于密钥派生的 HKDF,如 RFC5869所述

1.2.4 Wireguard组网架构

正如前所述,实际上参与WireGuard VPN 的所有主机都是同级的

组网架构存在中心化和去中心化的组网架构

我们一般采用一台中间节点作为网关,其他WireGuard节点与本中间节点进行连接

这种中心化的组网架构缺点相当明显:

1、当Peer越来越多时,VPN网关就会变成垂直扩展的瓶颈

2、通过VPN网关转发流量的成本很高

3、通过VPN网关转发流量会带来很高的延迟

还有一种全互联模式(full mesh),任意一个Peer节点和其他所有Peer都是直连,但是又来带一个新问题,就是那些没有公网IP地址的peer节点,这里就可以采用Wireguard 自身NAT穿透解决问题,不在本次话题讨论中

1.2.5 关于Allowed IPs地址列表

这点非常重要,对于理解Wireguard的工作原理非常重要

发送数据时,可以把Allowed IPs地址列表看成路由表,将需要访问的VPN对端的地址添加到Allowed IPs地址列表中。如果是全局流量,可以将Allowed IPs设置为0.0.0.0/0,通过策略路由完成对路由表的不破坏目的

接收数据时,可以把Allowed IPs地址列表看成ACL

特别注意,实际上要注意在隧道的各节点通过iptables或者nftables进行NAT转换

[Interface]中的Address只是wireguard虚拟网卡的接口地址,需要与Allowed IPs进行区别对待

1.2.6 与其他VPN协议性能比较

WireGuard 与其他 VPN 协议的性能测试对比

测试环境【使用iperf3软件进行测试】

  • ntel Core i7-3820QM and Intel Core i7-5200U
  • Intel 82579LM and Intel I218LM gigabit ethernet cards
  • Linux 4.6.1
  • WireGuard configuration: 256-bit ChaCha20 with Poly1305 for MAC
  • IPsec configuration 1: 256-bit ChaCha20 with Poly1305 for MAC
  • IPsec configuration 2: AES-256-GCM-128 (with AES-NI)
  • OpenVPN configuration: equivalently secure cipher suite of 256-bit AES with HMAC-SHA2-256, UDP mode
  • iperf3 was used and the results were averaged over 30 minutes

https://www.wireguard.com/performance/

image-20231005183115133

1.2.7 WireGuard报文格式

WireGuard使用了UDP对需要进入VPN流量的报文进行了二次封装,被封装的数据通过TCP去确保数据的有效传输

例如中间VPN节点(VPN地址192.168.200.254)ping访问外部网络节点(VPN地址192.168.200.2)

image-20231005211650446

18和19号的报文,分别是阿里云ESC的私网地址发往家庭公网出口地址(114.84.236.199,电信出口IP)以及对方发回的Wireguard报文

1.3 试验组网环境

1.3.1 组网示意图

本次实验组网环境采用中间网关的方式,即将阿里云ECS主机作为中间网关,家庭网络节点(后续统称外部网络节点)和公司内网节点的通讯都通过该节点进行数据转发

注意

因为需要中间网关的转发,因此必须开启该节点Linux的内核转发功能

因为各节点实际上都作为网关进行转发,因此实验环境下的各节点都需要开启Linux的内核转发功能

1
2
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

image-20231005213718120

1.3.2 各节点说明

外部网络节点我们这里采用位于家庭网络的群晖上的一台虚拟机作为试验环境,硬件配置为2核(vCPU) 内存为2GiB

image-20231003174016857

操作系统为Ubuntu 22.04.3 LTS

1
2
3
4
root@synology:~# cat /etc/os-release 
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"

中间公网节点为阿里云一台云服务器ECS,硬件配置为1核(vCPU) 内存为1GiB,操作系统为Rocky Linux9.1 64位

image-20231003174200992

公司内网的节点是运行在公司虚拟化平台vSphere8上的一台Linux虚拟机,硬件配置为8核(vCPU) 内存为8GiB,操作系统Red Hat Enterprise Linux release 9.0 (Plow)

image-20231003174227623

外部网络节点通过内核Wireguard客户端与位于公网阿里云ECS虚拟机建立VPN连接

公司内网的节点通过内核Wireguard与位于公网阿里云ECS虚拟机建立VPN连接

外部网络节点和公司内网节点都通过Iptables或者Nftables通过开启NAT转换

最终实现位于家庭的移动pad或者PC可以访问位于公司内网的相关服务,以及位于公司内网环境下访问家庭NAS服务

试验环境成功的几个重要注意点

1、需要访问公司内网服务的终端,例如家庭的移动pad或者PC需要将网关指向实验环境下群晖下Ubuntu虚拟机

2、Ubuntu虚拟机节点、中间VPN网关(阿里云ECS节点)、公司内网节点都需要开启Linux内核转发功能

3、Ubuntu虚拟机节点需要通过iptables创建SNAT转换规则

4、公司内网节点因为采用RHEL9版本,因此通过nftables创建NAT转换规则

各节点IP地址

1、三个节点之间私网地址我们这里配置为192.168.200.x/24,实际上可以选择任何与其他当前网络以及公司网络节点不冲突的网段接口

VPN组网wireguard虚拟网卡接口地址 内网地址
中间公网节点 192.168.200.254 /
公司内网节点 192.168.200.1 192.168.3.36
外部网络节点 192.168.200.2 172.18.3.16
外部网络终端测试节点 172.18.3.50
公司内网测试用服务节点 172.17.50.1

2、外部网络节点,通过电信路由器实现上网,公网地址为动态分配

3、中间公网节点阿里云ECS公网地址是47.100.39.165

4、公司内网节点的内网地址是172.18.3.16,通过企业联通链路实现互联网访问

可以看出,实现外部节点对公司内网服务的访问[公司内网服务的地址为172.17.50.1],需要满足以下条件

1、外部移动节点和内网节点能够正常访问互联网,与中间节点实现正常的IP通讯

2、中间节点需要一个固定的公网地址,满足其他两个节点主动连接

2 实施步骤

2.1 中间公网节点操作

RHEL9内核为5.14.0,已经内置WireGuard技术

2.1.1 安装wireguard-tools软件包

查看wg命令是由wireguard-tools软件包提供,采用dnf软件包安装

1
2
3
4
5
6
7
8
[root@ethan ~]# dnf provides wg
Last metadata expiration check: 4:16:15 ago on Sun 01 Oct 2023 08:49:03 AM CST.
wireguard-tools-1.0.20210914-2.el9.x86_64 : Fast, modern, secure VPN tunnel
Repo : appstream
Matched from:
Filename : /usr/bin/wg

[root@ethan ~]# dnf install -y wireguard-tools

验证

1
2
[root@ethan ~]# wg --version
wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

2.1.2 配置各节点所需要的私钥及对应公钥

发送端通过私钥加密,接收端通过对应的公钥解密,因此我们创建两对密钥,分别用于外部网络节点与中间公网节点,以及公司内网节点与中间公网节点的通讯加密用途

节点名称 私钥名称 公钥名称
外部网络节点 ubuntu.key.key ubuntu.key.key.pub
中间公网节点 server1.key server1.key.pub
公司内网节点 client1.key client1.key.pub

创建中间公网节点的私钥

1
2
3
[root@ethan ~]# wg genkey > server1.key
Warning: writing to world accessible file.
Consider setting the umask to 077 and trying again.

创建中间公网节点的公钥

1
[root@ethan ~]# wg pubkey < server1.key > server1.key.pub

注意

1、后续Wireguard是需要私钥和对应公钥的内容,而不是文件本身,实际上可以删除

2、但仍建议保留文件,并对上述文件进行权限设置,保证数据安全性

设置上述文件权限

1
[root@ethan ~]# chmod 600 server1.key server1.key.pub

其余两个节点的私钥和公钥操作一致,这里省略

验证

1
2
3
4
5
6
7
8
[root@ethan wireguard]# ll
total 24
-rw------- 1 root root 45 Oct 2 22:53 client1.key
-rw------- 1 root root 45 Oct 2 22:54 client1.key.pub
-rw------- 1 root root 45 Oct 1 20:53 ubuntu.key.key
-rw------- 1 root root 45 Oct 1 20:53 ubuntu.key.key.pub
-rw------- 1 root root 45 Oct 1 17:53 server1.key
-rw------- 1 root root 45 Oct 1 17:54 server1.key.pub

2.1.3 使用wg-quick服务配置WireGuard服务器配置文件

使用wg-quick服务可以将wireguard作为systemd服务进行统一管理

注意

/etc/wireguard目录下创建*.conf文件的文件名将作为后续systemd服务管理名

进入/etc/wireguard目录下创建wireguard0.conf文件

1
2
cd /etc/wireguard
vim wireguard0.conf

具体配置文件参考如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Interface]
Address = 192.168.200.254/24
ListenPort = 51820
PrivateKey = 【中间公网节点的私钥】

[Peer]
PublicKey = 【公司内网节点的公钥】
AllowedIPs = 192.168.200.1/32, 172.18.3.0/24, 172.17.50.0/24
PersistentKeepalive = 20

[Peer]
PublicKey = 【外部网络节点公钥】
AllowedIPs = 192.168.200.2/32
PersistentKeepalive = 20

配置参数说明

1、Address是中间公网VPN节点的Wireguard虚拟网卡的接口地址

2、PrivateKey是中间公网VPN节点的私钥

实际操作过程中注意替换其中内容

3、ListenPort是中间公网VPN节点的监听端口,默认是51820

4、PersistentKeepalive是保持keepalive心跳时间间隔

5、特别注意就是这里有两个[Peer]字段

解释如下:从中间公网节点来看,它是与外部网络节点和公司内网节点两个节点进行VPN互联,因此需要配置两个[Peer]字段

注意

1、当家庭网络访问公司内网时候,对于外部网络节点而言属于发送数据方向,可以把Allowed IPs地址列表看成路由表

2、因此将需要的访问公司内网的网段添加到AllowedIPs列表中

WireGuard会自动添加两条路由条目【详见第二条和第三条】

1
2
3
4
5
6
[root@ethan wireguard]# ip route
default via 172.19.143.253 dev eth0 proto dhcp src 172.19.128.148 metric 100
172.17.50.0/24 dev wireguard0 scope link
172.18.3.0/24 dev wireguard0 scope link
172.19.128.0/20 dev eth0 proto kernel scope link src 172.19.128.148 metric 100
192.168.200.0/24 dev wireguard0 proto kernel scope link src 192.168.200.254

分别是172.17.50.0/24 dev wireguard0 scope link172.18.3.0/24 dev wireguard0 scope link

image-20231005213556389

2.1.4 启动wg-quick服务

启用wg-quick服务

1
systemctl enable wg-quick@wireguard0 --now

注意

wg-quick@后面的名称需要和/etc/wireguard中配置的.conf文件名一致

验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@ethan wireguard]# systemctl status wg-quick@wireguard0.service 
● wg-quick@wireguard0.service - WireGuard via wg-quick(8) for wireguard0
Loaded: loaded (/usr/lib/systemd/system/wg-quick@.service; enabled; preset: disabled)
Active: active (exited) since Tue 2023-10-03 16:10:36 CST; 4h 38min ago
Docs: man:wg-quick(8)
man:wg(8)
https://www.wireguard.com/
https://www.wireguard.com/quickstart/
https://git.zx2c4.com/wireguard-tools/about/src/man/wg-quick.8
https://git.zx2c4.com/wireguard-tools/about/src/man/wg.8
Main PID: 268715 (code=exited, status=0/SUCCESS)
CPU: 59ms

Oct 03 16:10:35 ethan systemd[1]: wg-quick@wireguard0.service: Deactivated successfully.
Oct 03 16:10:35 ethan systemd[1]: Stopped WireGuard via wg-quick(8) for wireguard0.
Oct 03 16:10:35 ethan systemd[1]: Starting WireGuard via wg-quick(8) for wireguard0...
Oct 03 16:10:36 ethan wg-quick[268715]: [#] ip link add wireguard0 type wireguard
Oct 03 16:10:36 ethan wg-quick[268715]: [#] wg setconf wireguard0 /dev/fd/63
Oct 03 16:10:36 ethan wg-quick[268715]: [#] ip -4 address add 192.168.200.254/24 dev wireguard0
Oct 03 16:10:36 ethan wg-quick[268715]: [#] ip link set mtu 1420 up dev wireguard0
Oct 03 16:10:36 ethan wg-quick[268715]: [#] ip -4 route add 172.18.3.0/24 dev wireguard0
Oct 03 16:10:36 ethan wg-quick[268715]: [#] ip -4 route add 172.17.50.0/24 dev wireguard0
Oct 03 16:10:36 ethan systemd[1]: Finished WireGuard via wg-quick(8) for wireguard0.

实际上/usr/bin/wg-quick本质是一个shell文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#!/usr/bin/bash                                                                                                                                                                                         
# SPDX-License-Identifier: GPL-2.0
#
# Copyright (C) 2015-2020 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
#

set -e -o pipefail
shopt -s extglob
export LC_ALL=C

SELF="$(readlink -f "${BASH_SOURCE[0]}")"
export PATH="${SELF%/*}:$PATH"

WG_CONFIG=""
INTERFACE=""
ADDRESSES=( )
MTU=""
DNS=( )
DNS_SEARCH=( )
TABLE=""
PRE_UP=( )
POST_UP=( )
PRE_DOWN=( )
POST_DOWN=( )
SAVE_CONFIG=0
CONFIG_FILE=""
PROGRAM="${0##*/}"
ARGS=( "$@" )

cmd() {
echo "[#] $*" >&2
"$@"
}

die() {
echo "$PROGRAM: $*" >&2
exit 1
}

parse_options() {
local interface_section=0 line key value stripped v
CONFIG_FILE="$1"
[[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf"
[[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist"
[[ $CONFIG_FILE =~ (^|/)([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] || die "The config file must be a valid interface name, followed by .conf"
CONFIG_FILE="$(readlink -f "$CONFIG_FILE")"
((($(stat -c '0%#a' "$CONFIG_FILE") & $(stat -c '0%#a' "${CONFIG_FILE%/*}") & 0007) == 0)) || echo "Warning: \`$CONFIG_FILE' is world accessible" >&2
INTERFACE="${BASH_REMATCH[2]}"
shopt -s nocasematch
while read -r line || [[ -n $line ]]; do
stripped="${line%%\#*}"
key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}"
value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}"
[[ $key == "["* ]] && interface_section=0
[[ $key == "[Interface]" ]] && interface_section=1
if [[ $interface_section -eq 1 ]]; then
case "$key" in
Address) ADDRESSES+=( ${value//,/ } ); continue ;;
MTU) MTU="$value"; continue ;;

2.1.5 防火墙设置

因为中间公网地址需要作为中间网关,接收其他两个节点的UDP连接

因此需要对配置文件中ListenPort定义的端口进行放行,这里ListenPort我们定义为默认的51820

对于阿里云ECS服务器,需要在控制台的安全组进行设置具体如下

image-20231001193132048

如果操作系统开启了防火墙,需要通过firewall-cmd添加监听端口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@ethan wireguard]# firewall-cmd --add-port=51820/udp --permanent
success
[root@ethan wireguard]# firewall-cmd --add-port=51820/udp
success
[root@ethan wireguard]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: eth0
sources:
services: cockpit dhcpv6-client ssh
ports: 51820/udp
protocols:
forward: yes
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:

注意

1、需要开启firewalld的区内转发,RHEL8需要手动开启,RHEL9默认自动开启

2、需要确认物理网卡和Wireguard虚拟网卡需要在同一个firewall区域内

验证eth0wireguard0属于同一个区域内,例如public

1
2
3
[root@ethan wireguard]# firewall-cmd --get-active-zones
public
interfaces: wireguard0 eth0

如果wireguard0虚拟网卡不属于eth0同一个区域内,进行区域变更操作

1
2
3
4
[root@ethan wireguard]# firewall-cmd --zone=public --change-interface=wireguard0 --permanent
success
[root@ethan wireguard]# firewall-cmd --zone=public --change-interface=wireguard0
success

2.1.5 重要操作【开启内核转发】

开启Linux内核转发

1
2
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

中间VPN节点并不需要开启SNAT转换服务,源地址不需要改变

2.2 外部网络节点操作

由于本人没有专用软路由设备,因此从试验环境考虑,直接采用了家庭网络内群晖NAS的虚拟机作为外部网络节点

但从性能出发,在实际操作过程中可以考虑部署一台例如爱快或者ROS的专用软路由作为外部网络节点

2.2.1 群晖安装Ubuntu虚拟机

套件中心安装Virtual Machine Manager

image-20231003180559335

安装完成后打开Virtual Machine Manager

image-20231003180659807

剩下很多操作就是按向导一步一步操作,具体安装步骤省略

其中ens3接口地址是192.168.3.36

1
2
3
4
5
6
7
8
9
10
11
12
13
14
root@synology:~# ip ad
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: ens3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 02:11:32:24:e7:dd brd ff:ff:ff:ff:ff:ff
altname enp0s3
inet 192.168.3.36/24 brd 192.168.3.255 scope global ens3
valid_lft forever preferred_lft forever
inet6 fe80::11:32ff:fe24:e7dd/64 scope link
valid_lft forever preferred_lft forever

确保能够正常访问中间节点阿里云的ECS公网地址

1
2
3
4
5
6
7
8
9
root@synology:~# ping -c 3 47.100.39.165
PING 47.100.39.165 (47.100.39.165) 56(84) bytes of data.
64 bytes from 47.100.39.165: icmp_seq=1 ttl=52 time=14.6 ms
64 bytes from 47.100.39.165: icmp_seq=2 ttl=52 time=12.2 ms
64 bytes from 47.100.39.165: icmp_seq=3 ttl=52 time=12.0 ms

--- 47.100.39.165 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 12.021/12.934/14.565/1.155 ms

2.2.2 Ubuntu虚拟机安装wireguard软件

通过apt命令安装

1
apt install wireguard

验证

1
2
root@synology:~# wg --version
wireguard-tools v1.0.20210914 - https://git.zx2c4.com/wireguard-tools/

2.2.3 使用wg-quick服务配置WireGuard服务器配置文件

由于私钥和公钥已经在中间节点上统一进行了配置

因此直接配置wireguard的配置文件

使用wg-quick服务可以将wireguard作为systemd服务进行统一管理

注意

/etc/wireguard目录下创建*.conf文件的文件名将作为后续systemd服务管理名

进入/etc/wireguard目录下创建wireguard0.conf文件

1
2
cd /etc/wireguard
vim wireguard0.conf

具体配置文件参考如下

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey = 【外部网络节点的私钥】
Address = 192.168.200.2/24

[Peer]
PublicKey = 【中间公网节点的公钥】
AllowedIPs = 192.168.200.0/24, 172.18.3.0/24, 172.17.50.0/24
Endpoint = 47.100.39.165:51820
PersistentKeepalive = 20

配置参数说明

1、Address是外部网络节点的Wireguard虚拟网卡的接口地址

2、PrivateKey是外部网络节点的私钥

3、PublicKey是中间VPN网关的公钥

4、Endpoint是连接至中间VPN网关的公网地址和端口

5、PersistentKeepalive是保持keepalive心跳时间间隔

注意

1、当家庭网络访问公司内网时候,对于外部网络节点而言属于发送数据方向,可以把Allowed IPs地址列表看成路由表

2、因此将需要访问公司内网的网段添加到AllowedIPs列表中

3、因此作为外部网络节点的Ubuntu虚拟机作为请求发起方,Peer中AllowedIPs允许172.18.3.0/24, 172.17.50.0/24流量包会进入VPN

隧道流入公网服务器。

image-20231005213621080

2.2.4 启动wg-quick服务

启用wg-quick服务

1
systemctl enable wg-quick@wireguard0 --now

注意

wg-quick@后面的名称需要和/etc/wireguard中配置的.conf文件名一致

验证,这里采用wg show命令

1
2
3
4
5
6
7
8
9
10
11
12
root@synology:~# wg show
interface: wireguard0
public key: iq6ug0A7gvKEJueBouk+pppglYmpd73cFSRsfXLMRQA=
private key: (hidden)
listening port: 59240

peer: zo1vyzdA9TLXJiXeJcreNkuwCEVjZuWBk92CSNk7Z14=
endpoint: 47.100.39.165:51820
allowed ips: 192.168.200.0/24, 172.18.3.0/24, 172.17.50.0/24
latest handshake: 1 minute, 11 seconds ago
transfer: 6.10 KiB received, 20.57 KiB sent
persistent keepalive: every 20 seconds

可以看出此时外部网络节点已经和中间公网节点建立了VPN连接

2.3 公司内网节点操作

2.3.1 安装wireguard-tools软件包

同样对于RHEL9系统,直接采用dnf软件包安装

查看wg命令是由wireguard-tools软件包提供,采用dnf软件包安装

1
[root@RHEL9 ~]# dnf install -y wireguard-tools

验证

1
2
[root@RHEL9 ~]# wg --version
wireguard-tools v1.0.20210424 - https://git.zx2c4.com/wireguard-tools/

由于私钥和公钥已经在中间节点上统一进行了配置

因此直接配置wireguard的配置文件

2.3.2 使用wg-quick服务配置WireGuard服务器配置文件

使用wg-quick服务可以将wireguard作为systemd服务进行统一管理

1
2
3
4
5
6
7
8
9
[Interface]
PrivateKey = 【外部网络节点的私钥】
Address = 192.168.200.2/24

[Peer]
PublicKey = 【中间公网节点的公钥】
AllowedIPs = 192.168.200.0/24
Endpoint = 47.100.39.165:51820
PersistentKeepalive = 20

配置参数说明

1、Address是公司内网网络节点的Wireguard虚拟网卡的接口地址

2、PrivateKey是公司外部网络节点的私钥

3、PublicKey是中间VPN网关的公钥

4、Endpoint是连接至中间VPN网关的公网地址和端口

5、PersistentKeepalive是保持keepalive心跳时间间隔

2.3.3 启用wg-quick服务

1
systemctl enable wg-quick@wireguard0 --now

注意

wg-quick@后面的名称需要和/etc/wireguard中配置的.conf文件名一致

验证,这里采用wg show命令

1
2
3
4
5
6
7
8
9
10
11
12
[root@RHEL9 ~]# wg show
interface: wireguard0
public key: +B1iiX+EF8yNc5wFqRiJwqMBThgFkI9TPB2bvGiRxmo=
private key: (hidden)
listening port: 56412

peer: zo1vyzdA9TLXJiXeJcreNkuwCEVjZuWBk92CSNk7Z14=
endpoint: 47.100.39.165:51820
allowed ips: 192.168.200.0/24
latest handshake: 24 seconds ago
transfer: 51.21 KiB received, 41.28 KiB sent
persistent keepalive: every 20 seconds

至此,外部网络节点和公司内网节点均与中间公网节点建立了VPN连接

再在中间公网节进行验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[root@ethan wireguard]# wg show
interface: wireguard0
public key: zo1vyzdA9TLXJiXeJcreNkuwCEVjZuWBk92CSNk7Z14=
private key: (hidden)
listening port: 51820

peer: +B1iiX+EF8yNc5wFqRiJwqMBThgFkI9TPB2bvGiRxmo=
endpoint: 58.33.40.242:2063
allowed ips: 192.168.200.1/32, 172.18.3.0/24, 172.17.50.0/24
latest handshake: 1 minute, 36 seconds ago
transfer: 54.87 KiB received, 90.01 KiB sent
persistent keepalive: every 20 seconds

peer: iq6ug0A7gvKEJueBouk+pppglYmpd73cFSRsfXLMRQA=
endpoint: 114.84.236.199:33337
allowed ips: 192.168.200.2/32
latest handshake: 1 minute, 41 seconds ago
transfer: 111.84 KiB received, 146.06 KiB sent
persistent keepalive: every 20 seconds

可以看出,两个节点均已成功完成连接

至此,WireGuard的部署工作全部完成

3 外部网络与公司内网服务互访关键操作

最后我们通过一个故障的分析定位及排查去针对性说明两个网络实现互访的关键操作

上述操作步骤完成后,实际上外部网络节点和公司内网节点可以实现IP可达

分别在两节点上使用ping命令进行验证

外部网络节点验证

1
2
3
4
5
6
7
8
9
root@synology:~# ping -c 3 192.168.200.1
PING 192.168.200.1 (192.168.200.1) 56(84) bytes of data.
64 bytes from 192.168.200.1: icmp_seq=1 ttl=63 time=18.0 ms
64 bytes from 192.168.200.1: icmp_seq=2 ttl=63 time=18.1 ms
64 bytes from 192.168.200.1: icmp_seq=3 ttl=63 time=18.5 ms

--- 192.168.200.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 17.951/18.186/18.480/0.219 ms

公司内网节点验证

1
2
3
4
5
6
7
8
9
[root@RHEL9 ~]# ping -c 3 192.168.200.2
PING 192.168.200.2 (192.168.200.2) 56(84) bytes of data.
64 bytes from 192.168.200.2: icmp_seq=1 ttl=63 time=18.6 ms
64 bytes from 192.168.200.2: icmp_seq=2 ttl=63 time=39.6 ms
64 bytes from 192.168.200.2: icmp_seq=3 ttl=63 time=17.9 ms

--- 192.168.200.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 17.870/25.347/39.579/10.067 ms

同时由于我在公司内网节点上部署了本次环境的使用nginx服务

外部网络节点验证也可以直接访问该nginx节点

1
2
3
4
5
6
7
8
9
10
root@synology:~# curl 172.18.3.16
<html>
<head><title>Index of /</title></head>
<body>
<h1>Index of /</h1><hr><pre><a href="../">../</a>
<a href="RHEL8.7/">RHEL8.7/</a> 04-Sep-2023 07:53 -
<a href="RHEL9/">RHEL9/</a> 29-Sep-2023 13:05 -
<a href="ansible-navigator.log">ansible-navigator.log</a> 01-Oct-2023 15:13 455
</pre><hr></body>
</html>

1、如果启用了防火墙,需要开启80/tcp端口

2、由于172.18.3.16属于公司内网节点本机的ens33的接口地址,因此不需要开启内核转发以及其他NAT转换规则

3.1 问题:外部网络节点无法访问公司内网其他服务器

首先公司内网节点可以访问某一服务,服务测试地址为172.17.50.1,服务为http

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@RHEL9 ~]# curl 172.17.50.1
<!DOCTYPE html>
<html>
<head>
<title>Welcome to sundray!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to sundray!</h1>
</body>
</html>

而外部网络节点无法正常访问,使用curl命令无法正常返回

3.1.1 故障定位

首先判断从外部网络访问172.17.50.1的网络包是否发送至中间公网节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@ethan wireguard]# tcpdump -i any host 172.17.50.1 -nn
tcpdump: data link type LINUX_SLL2
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
22:07:57.665956 wireguard0 In IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491050467 ecr 0,nop,wscale 7], length 0
22:07:57.665993 wireguard0 Out IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491050467 ecr 0,nop,wscale 7], length 0
22:07:58.676105 wireguard0 In IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491051478 ecr 0,nop,wscale 7], length 0
22:07:58.676126 wireguard0 Out IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491051478 ecr 0,nop,wscale 7], length 0
22:08:00.691879 wireguard0 In IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491053494 ecr 0,nop,wscale 7], length 0
22:08:00.691929 wireguard0 Out IP 192.168.200.2.52560 > 172.17.50.1.80: Flags [S], seq 1778852611, win 64860, options [mss 1380,sackOK,TS val 2491053494 ecr 0,nop,wscale 7], length 0
^C
6 packets captured
10 packets received by filter
0 packets dropped by kernel

从tcpdump结果看出,中间公网节点已经从wireguard0虚拟网卡收到并发出来自源地址是192.168.200.2,目的地址是172.17.50.1的网络包请求

然后判断公司内部网络节点是否将来自192.168.200.2的网络包从ens33网卡发出

1
2
3
4
5
6
7
8
9
10
11
[root@RHEL9 ~]# tcpdump -i ens33 host 172.17.50.1 -nn -v
dropped privs to tcpdump
tcpdump: listening on ens33, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:50:08.578908 IP (tos 0x0, ttl 62, id 58173, offset 0, flags [DF], proto TCP (6), length 60)
192.168.200.2.45510 > 172.17.50.1.80: Flags [S], cksum 0x506b (correct), seq 1940371022, win 64860, options [mss 1380,sackOK,TS val 2490442572 ecr 0,nop,wscale 7], length 0
21:50:09.580885 IP (tos 0x0, ttl 62, id 58174, offset 0, flags [DF], proto TCP (6), length 60)
192.168.200.2.45510 > 172.17.50.1.80: Flags [S], cksum 0x4c81 (correct), seq 1940371022, win 64860, options [mss 1380,sackOK,TS val 2490443574 ecr 0,nop,wscale 7], length 0
^C
2 packets captured
2 packets received by filter
0 packets dropped by kernel

可以看出当公司内网网络节点收到源地址是192.168.200.2的网络包时,的确从ens33网卡发出,但是由于网络包的源地址为Wireguard私网地址192.168.200.2/32,导致目的节点172.17.50.1在收到网络包时,由于路由表没有相关条目,导致无法回包

3.1.2 故障处理

因此需要在公司内网节点开启内核转发及SNAT转换

配置内核转发

1
2
echo "net.ipv4.ip_forward = 1" >> /etc/sysctl.conf
sysctl -p

配置DNAT转换

1
iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE

实际上,如果采用nftables命令,效果一样

1
nft add rule ip nat POSTROUTING oifname "ens33" counter masquerade

此时,再在外部网络节点进行验证,故障排除

image-20231005213649724