-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathwvpn
executable file
·268 lines (243 loc) · 7.94 KB
/
wvpn
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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
#!/bin/sh -eu
# wvpn (part of ossobv/vcutil) // wdoekes/2021,2024 // Public Domain
#
# Helps out when you have a bunch of openvpn clients on your machine.
#
# Requires that you have consistent naming:
# /etc/openvpn/client/myvpn.conf - where you use 'dev vpn-myvpn'
#
# All interfaces on your machine that start with "vpn-" are assumed to be
# openvpn tun/tap devices.
#
# Example usage:
#
# $ wvpn
# vpn-myvpn 192.168.33.129 up
# vpn-system2 10.199.197.1 up-after-reconnect
#
# $ wvpn myvpn
# vpn-myvpn 192.168.33.129 up
#
# $ wvpn :myvpn
# (stops myvpn)
#
# $ wvpn
# vpn-system2 10.199.197.1 up
#
# $ wvpn :
# (stops all vpns)
#
# If you run your openvpn-client@%i.service jobs as --user, you can
# alter ~/.config/wvpn to this:
#
# _service_start() { systemctl --user --quiet start "$1"; }
# _service_stop() { systemctl --user --quiet stop "$1"; }
#
# If you have clusters of VPNs you want started together, you can put
# this in ~/.config/wvpn:
#
# mycluster='myvpn1 myvpn2'
#
# Starting or stopping mycluster will then do both myvpn1 and myvpn2.
_get_gateway() {
local iproute="$(ip route show dev "$1" 2>/dev/null)"
test $? -ne 0 && exit 1
local gwip="$(
echo "$iproute" | sed -e '/ via /!d;s/.* via //' | sort -u)"
local protokernel="$(echo "$iproute" | sed '/ proto kernel /!d;s/ .*//')"
if test -n "$gwip" && echo "$protokernel" | grep -q /; then
echo $gwip
elif echo "$iproute" | sed -e "/^${gwip%.*}.[0-9]* /!d;s/ .*//" |
head -n1 | grep ''; then
true
elif test -n "$protokernel"; then
local net="$(echo "$protokernel" | head -n1)"
echo "${net%.*}.1"
fi
}
_test_gateway() {
local gwip="$1"
test -n "$gwip" && ping -q -c1 -w1 "$gwip" >/dev/null && return
false
}
_list_found_vpn_interfaces() {
ip link | sed -ne 's/^[0-9]*: *\(vpn-[^:]*\):.*/\1/p'
}
_list_expected_vpn_interfaces() {
( _list_found_vpn_interfaces; _service_list | _service_to_interface ) |
LC_ALL=C sort -uV
}
_interface_to_service() {
local ifname="$1"
local startswith=${ifname#vpn-} # drop "vpn-" from ifname
local config=
# Try without find first, in case the user has no read permissions on the
# directories.
for path in /etc/openvpn /etc/openvpn/client /etc/openvpn/server; do
test -f "$path/$startswith.conf" &&
config="$path/$startswith.conf" &&
break
done
# Interface name is limited to 15 chars.. so if filename is longer than
# <11>.conf we must globmatch the rest.
test -z "$config" &&
config=$(
find /etc/openvpn -maxdepth 2 -name "${startswith}*.conf" |
LC_ALL=C sort | head -n1)
config=${config%.conf}
config=${config#/etc/openvpn/}
if test -z "$config"; then
echo skipping-$ifname-has-no-config.service
elif test "${config#client/}" != "$config"; then # startswith client/
echo openvpn-client@${config#client/}.service
elif test "${config#server/}" != "$config"; then # startswith server/
echo openvpn-server@${config#server/}.service
elif test "${config#*/}" = "$config"; then # has no slash
echo openvpn@$config.service
else
echo skipping-$ifname-unknown-config.service
fi
}
_service_to_interface() {
sed -e 's/^[^@]*@\([^.]*\).service/vpn-\1/'
}
_service_list() {
local service_names='openvpn@* openvpn-client@*'
# Looking for "loaded active" or "loaded activating".
systemctl --no-legend list-units $service_names |
sed -e '/loaded act/!d;s/[[:blank:]].*//'
}
_service_start() {
sudo systemctl start "$1"
}
_service_stop() {
sudo systemctl stop "$1"
}
_service_force_stop() {
# Grrrr.. openvpn might still be in 'starting' state, waiting for
# user/pass input. Then a 'stop' doesn't get through.
_service_stop "$1" & pid=$!
while test -d /proc/$pid; do # kill -0 returns EPERM
pgrep systemd-ask-pas >/dev/null && sudo pkill systemd-ask-pas
done
wait
}
_restart_interface() {
local ifname="$1"
local service="$(_interface_to_service "$ifname")"
echo "(restarting $service)" >&2
_service_force_stop "$service" || true
_service_start "$service"
_wait_for_interface "$ifname"
}
_stop_interface() {
local ifname="$1"
local service="$(_interface_to_service "$ifname")"
_service_force_stop "$service"
}
_wait_for_interface() {
local ifname="$1"
local gwip
for i in 1 2 3 4 5; do
gwip=$(_get_gateway "$ifname")
test -n "$gwip" && break
sleep 1
done
if test -z "$gwip"; then
echo "$ifname: no gateway received" >&2
false
elif ! _test_gateway "$gwip"; then
echo "$ifname: no ping on $gwip" >&2
false
else
true
fi
}
_test_and_list() {
local ifnames="$1"
local ifname
local gwip
for ifname in $ifnames; do
gwip=$(_get_gateway "$ifname")
if _test_gateway "$gwip"; then
printf '%-16s %-16s %s\n' "$ifname" "$gwip" "up"
elif _restart_interface "$ifname"; then
gwip=$(_get_gateway "$ifname")
printf '%-16s %-16s %s\n' "$ifname" "$gwip" "up-after-reconnect"
else
printf '%-16s %-16s %s\n' "$ifname" "$gwip" "DOWN"
fi
done
}
_alias_to_interfaces() {
# Convert name/alias to interface name (including "vpn-" prefix).
local alias="$(
echo "$1" | head -n1 | grep '^[A-Za-z0-9_][A-Za-z0-9@_-]*$')"
if test "$1" != "$alias"; then
echo "$0: bad alias/interface name: '$1'" >&2
exit 1
fi
# If the name contains only [A-Za-z0-9_] it might be an alias.
if test "$alias" = "${alias#*-}" -a "$alias" = "${alias#*@}"; then
local value="$(eval echo "\${${alias}}" 2>/dev/null || true)"
local arg
for arg in $(test -n "$value" && echo $value || echo $alias); do
test "$arg" = "${arg#vpn-}" && echo "vpn-$arg" || echo "$arg"
done
else
test "$alias" = "${alias#vpn-}" && echo "vpn-$alias" || echo "$alias"
fi
}
_ensure_started() {
local ifname ifnames
ifname=$(_alias_to_interfaces "$1") || return
# (truncate interface names to 15 chars)
ifname=$(echo "$ifname" | sed -e 's/\(.\{15\}\).*/\1/')
_test_and_list "$ifname"
}
_ensure_stopped() {
local ifname ifnames
ifname=$(_alias_to_interfaces "$1") || return
# (truncate interface names to 15 chars)
ifname=$(echo "$ifname" | sed -e 's/\(.\{15\}\).*/\1/')
# Set ifnames to ifname or all-interfaces if no arg
test -n "$ifname" && ifnames=$ifname ||
ifnames=$(_list_expected_vpn_interfaces)
for ifname in $ifnames; do
_stop_interface "$ifname"
done
}
list() {
_test_and_list "$(_list_expected_vpn_interfaces)"
}
# Use local config to define groups, like:
# bestvpn='vpn-system1 vpn-system2'
local_config=$HOME/.config/wvpn
test -f "$local_config" && . "$local_config"
if test $# -eq 0; then
list
elif test "$1" = "-h" -o "$1" = "--help"; then
cat << EOF
wvpn lists openvpn connectivity and allows for quick restarts
wvpn lists currently connected vpns (reconnects if needed)
wvpn system1 system2 ensures that vpn-system1 and vpn-system2 are up
wvpn mygroup same as previous, if mygroup='vpn-system1 vpn-system2'
wvpn :system1 :system2 ensures that vpn-system1 and vpn-system2 are down
wvpn : stops all
The config in $local_config is read before starting.
There you can define aliases, like mygroup='vpn-system1 vpn-system2' and
refer to both using the 'mygroup' name.
(You might also override _interface_to_service() if you're not starting
your openvpn daemons as openvpn-client@IFNAME_WITHOUT_VPN_PREFIX.)
EOF
else
for arg in "$@"; do
# Does not start with a colon?
if test "$arg" = "${arg#:}"; then
_ensure_started "$arg" || true
else
_ensure_stopped "${arg#:}" || true
fi
done
fi
# vim: set ts=8 sw=4 sts=4 et ai: