前言
我写这个题目,首先是假定看这个文章的人是有基本的wireguard配置基础的,不是一个入门的教程,我会尝试分析一下wireguard使用过程中一些问题和提供一些研究的解决方案,算是自己的一个备忘,也希望对读者有帮助,如下为了简要起见,wireguard缩写为wg.
wg的接入点可以当网关来用吗
所有的教程中,关于wg服务端Peer配置AllowedIPs中的IP都是用32位子网掩码来限制IP的:
举例 192.168.28.2/32
先从AllowedIPs说起,AllowedIPs的这个配置项名还是有一定迷惑性的,其真实意义在于: 这个 Peer 后面,能出现哪些源 IP
也就是说:
如果我将AllowedIPs改成如下配置
把接入点当作网关,子网掩码允许一个网段IP,只要配置一个Peer,就可以接入一堆设备,如果wg有DHCP的能力,似乎看起来是更好的方案,那这样配置现实情况可行吗?
答案是:不可行
首先服务端这样的配置子网是没有问题的,但是wg只给最后一个发包的客户端保持链接,在服务端使用wg show可以看到类似:
1
2
3
4
|
peer: QOwrJESVWC...
endpoint: 1.2.3.4:54321 ← 一直在变
allowed ips: 192.168.28.0/24
latest handshake: xx seconds ago
|
说明endpoint 会在不同peer之间不断切换,一个peer上线会挤掉其他peer,导致不同peer端严重的丢包和超时的问题.
如果一定要配置/24的子网,那么客户端应该统一接入到一个网关,网关再以客户端的方式接入服务端,只适用于打通两个子网的场景
1
2
3
4
5
|
PC / 手机 / 平板
↓
一个“客户端网关”(OpenWrt / Linux / Docker)
↓(一个 peer)
服务器 peer
|
wg接入点的管理
考虑无法将wg作为网关,那么对于多个设备的连接,wg接入点管理就是一个现实的问题,这里推荐一个wg-access-server的开源工程作为接入点的管理,具体怎么配置操作github上都有较详细的说明,就不在这里啰嗦了.
我对wg的真实需求点
在真实家宽环境中使用wg,就是从外部接入家庭内网,还面临着很多现实的网络问题:
-
家宽的ipv4是无法拿到公网ip的,只有ipv6地址可以直连
-
为了节省流量,我有时候需要只访问内网资源,而有时候为了利用家中路由来访问外网则需要转发全局流量
-
udp可能限流或者封锁导致根本无法连上udp端口
以我自身情况为前提(在AIO设备上虚拟化了openwrt路由器),对于第一个问题,ipv4环境下可以打通一条udp的frp隧道,连接上frp隧道即可,ipv6可在openwrt做ddns的AAAA解析,直接连接ipv6的域名(frp和域名解析都有插件可使用)
第二第三个问题则需要在配置中修改endpoint做不同接入点,绕过udp还需要用udp2raw做更复杂的处理,这个后面再说
针对以上需求,对于每个私钥,我用脚本自动按名称规则生成如下配置文件,以匹配不同网络接入情况:
命名规则: (tags)_(accesspoint)_(ipv4/v6)(local/global)(s)
1
2
3
4
5
6
7
8
|
我的接入配置名
aio_p1_globals.conf
aio_p1_locals.conf
aio_p1_v4global.conf
aio_p1_v4local.conf
aio_p1_v6global.conf
aio_p1_v6local.conf
|
详解配置和说明如下:
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
#==> ./aio_p1_globals.conf udp2raw全局接入wg <==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 119.29.29.29, 2402:4e00::
MTU = 1300
[Peer]
PublicKey = my-server-key
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = 127.0.0.1:20828
PersistentKeepalive = 25
#==> ./aio_p1_locals.conf udp2raw内网接入wg <==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 192.168.12.1
MTU = 1200
[Peer]
PublicKey = my-server-key
AllowedIPs = 192.168.12.0/24, 192.168.28.0/24
Endpoint = 127.0.0.1:20828
PersistentKeepalive = 25
#==> ./aio_p1_v4global.conf ipv4 frp全局接入wg<==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 192.168.12.1
MTU = 1300
[Peer]
PublicKey = my-server-key
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = frp-domain:frp-port
PersistentKeepalive = 25
#==> ./aio_p1_v4local.conf ipv4 frp内网接入wg<==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 192.168.12.1
MTU = 1200
[Peer]
PublicKey = my-server-key
AllowedIPs = 192.168.12.0/24, 192.168.28.0/24
Endpoint = frp-domain:frp-port
PersistentKeepalive = 25
#==> ./aio_p1_v6global.conf ipv6 直连全局接入wg<==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 119.29.29.29, 2402:4e00::
MTU = 1300
[Peer]
PublicKey = my-server-key
AllowedIPs = 0.0.0.0/0, ::/0
Endpoint = ipv6_ddns_domain:ipv6-port
PersistentKeepalive = 25
#==> ./aio_p1_v6local.conf ipv6 直连内网接入wg<==
[Interface]
PrivateKey = my-client-key
Address = 192.168.28.2/32, fd08:28::2/128
DNS = 119.29.29.29, 2402:4e00::
MTU = 1200
[Peer]
PublicKey = my-server-key
AllowedIPs = 192.168.12.0/24, 192.168.28.0/24
Endpoint = ipv6_ddns_domain:ipv6-port
PersistentKeepalive = 25
|
udp2raw的faketcp封装
针对udp的封锁,可以使用udp2raw将udp模拟成tcp发送,目前只能做到ipv6直连,原理是这样:
1
2
3
|
[本地WG客户端] → 原生WG-UDP包 → [本地udp2raw] → 伪装流量 → 公网 → [远端udp2raw] → 还原WG-UDP包 → [远端WG服务端]
[远端WG服务端] → 原生WG-UDP包 → [远端udp2raw] → 伪装流量 → 公网 → [本地udp2raw] → 还原WG-UDP包 → [本地WG客户端]
|
在openwrt配置的服务如下路径(/etc/init.d/udp2raw_service):
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
#!/bin/sh /etc/rc.common
# Service startup priority (recommended 90 for network-related services to ensure network initialization is complete)
START=90
# Service stop priority
STOP=10
# Core configuration: program path and startup arguments
# Note: The argument ----conf-file starts with two hyphens, keep consistent with your actual usage
UDP2RAW_BIN="/root/bin/udp2raw_amd64_hw_aes"
UDP2RAW_ARGS="----conf-file u2raw_server.conf"
# PID file path (used for process management)
PID_FILE="/var/run/udp2raw_service.pid"
# Enable procd framework (required)
USE_PROCD=1
# Core function to start the service
start_service() {
# Check if the program file exists to avoid startup failure
if [ ! -x "$UDP2RAW_BIN" ]; then
echo "Error: udp2raw program file does not exist or has no execute permission → $UDP2RAW_BIN"
return 1
fi
# Initialize procd instance
procd_open_instance
# Specify the program and arguments to run (key: pass all arguments completely)
procd_set_param command "$UDP2RAW_BIN" $UDP2RAW_ARGS
# Configure automatic restart on process crash (core requirement)
# respawn parameter explanation:
# 3600 = Stop restarting if crashed more than retry times within 1 hour
# 5 = 5 seconds interval between each restart
# 5 = Maximum retry times (0 means infinite retries)
procd_set_param respawn 3600 5 5
# Specify PID file (for precise process management)
procd_set_param pidfile "$PID_FILE"
# Running user (default root, no need to modify)
procd_set_param user root
# Output logs to system log (view via logread)
procd_set_param stdout 1
procd_set_param stderr 1
# Close procd instance configuration
procd_close_instance
echo "udp2raw_service started successfully"
}
# Core function to stop the service (send normal exit signal instead of forced kill)
stop_service() {
# Check if PID file exists
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
# Send normal exit signal SIGTERM (instead of kill -9) to let the program exit gracefully
if kill -TERM "$pid" 2>/dev/null; then
# Wait for process to exit (max 5 seconds)
sleep 1
# Delete PID file after confirming process exit
if ! ps | grep -q "$pid"; then
rm -f "$PID_FILE"
echo "udp2raw_service stopped normally (PID: $pid)"
else
# Force kill if still not exited (fallback)
kill -9 "$pid" 2>/dev/null
rm -f "$PID_FILE"
echo "udp2raw_service stopped forcefully (PID: $pid)"
fi
else
rm -f "$PID_FILE"
echo "udp2raw_service process does not exist, PID file cleaned up"
fi
else
echo "udp2raw_service is not running (no PID file)"
fi
}
|
u2raw_server.conf内容如下,可以根据自己端口情况修改:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
-s
# Or use -s if you use it on server side
# Define local address
-l [::]:1667
# Define remote address
-r 127.0.0.1:11111
# Password
# -k my_awesome_password
# Mode
--raw-mode faketcp
-a
# Log Level
--log-level 4
|
当然我们在配置启动用得先启动客户端的udp2raw,配置参数需要对应上服务端,有很多资料可查,就不详细说明了
通用接入脚本
根据我这些需求,我写了一个开源的脚本自动连接wg,可以实现通过参数运行连接global或者local以及不同的peer点,支持自动拉起udp2raw做全局或者内网接入wg.
开源地址如下(有详细的md说明):
https://github.com/oserz/auto_wg.git
这个脚本最复杂的点在于global全局的的udp2raw接入,理论上当接入wg时,全局模式下 WireGuard 的AllowedIPs=0.0.0.0/0劫持了自身发往 udp2raw 的本地 UDP 流量,形成路由环路,全导致流量发送出错.
解决这个问题的思路是,修改特定目的ip走原始路由表中的网关,并且在wg开启后恢复原先网关优先级放在wg网关前.
对这个点有兴趣的朋友们可以自行分析脚本代码