1 ## @defgroup on-openvpn Nutzer-Tunnel
2 ## @brief Alles rund um die Nutzertunnel-Verbindung: Tests, Auswahl, Aufbau, Abbau, Portweiterleitungen und Logs.
3 # Beginn der Doku-Gruppe
6 MIG_OPENVPN_DIR=/etc/openvpn/opennet_user
7 MIG_OPENVPN_CONFIG_TEMPLATE_FILE=/usr/share/opennet/openvpn-mig.template
11 ## @fn get_on_openvpn_default()
12 ## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-openvpn).
13 ## @param key Name des Schlüssels
14 ## @sa get_on_core_default
20 ## @fn has_mig_openvpn_credentials()
21 ## @brief Prüft, ob der Nutzer bereits einen Schlüssel und ein Zertifikat angelegt hat.
22 ## @returns Liefert "wahr", falls Schlüssel und Zertifikat vorhanden sind oder
23 ## falls in irgendeiner Form Unklarheit besteht.
26 trap
"" $GUARD_TRAPS &&
return 1
30 ## @fn test_mig_connection()
31 ## @brief Prüfe, ob ein Verbindungsaufbau mit einem openvpn-Dienst möglich ist.
32 ## @param Name eines Diensts
33 ## @returns exitcode=0 falls der Test erfolgreich war
34 ## @attention Seiteneffekt: die Zustandsinformationen des Diensts (Status, Test-Zeitstempel) werden verändert.
36 trap
"error_trap test_mig_connection '$*'" $GUARD_TRAPS
37 local service_name=
"$1"
38 # sicherstellen, dass alle vpn-relevanten Einstellungen gesetzt wurden
41 local now=$(get_time_minute)
44 if [ -n
"$timestamp" ] && is_timestamp_older_minutes
"$timestamp" "$nonworking_timeout"; then
45 # if there was no vpn-availability for a while (nonworking_timeout minutes), declare vpn-status as not working
46 set_service_value
"$service_name" "status" "n"
47 # In den naechsten 'vpn_recheck_age' Minuten wollen wir keine Pruefungen durchfuehren.
48 set_service_value
"$service_name" "timestamp_connection_test" "$now"
49 trap
"" $GUARD_TRAPS &&
return 1
53 if [ -z
"$timestamp" ] || [ -z
"$status" ] || is_timestamp_older_minutes
"$timestamp" "$recheck_age"; then
54 # Neue Pruefung, falls:
55 # 1) noch nie eine Pruefung stattfand
57 # 2) die "recheck"-Zeit abgelaufen ist
59 # 3) falls bisher noch kein definitives Ergebnis feststand (dies ist nur innerhalb
60 # der ersten "recheck" Minuten nach dem Booten moeglich).
61 # In jedem Fall kann der Zeitstempel gesetzt werden - egal welches Ergebnis die Pruefung hat.
63 "$VPN_DIR_TEST/on_aps.key" \
64 "$VPN_DIR_TEST/on_aps.crt" \
65 "$VPN_DIR_TEST/opennet-ca.crt"; then
66 msg_debug "vpn-availability of gw $host successfully tested"
67 set_service_value
"$service_name" "status" "y"
68 set_service_value
"$service_name" "timestamp_connection_test" "$now"
71 # kein Zeitstempel? Dann muessen wir beginnen zu zaehlen, damit der
72 # Test irgendwann in den "broken"-Zustand uebergehen kann.
73 [ -z
"$timestamp" ] && set_service_value
"$service_name" "timestamp_connection_test" "$now"
74 # Solange wir keinen "status" setzen, wird der Test bei jedem Lauf wiederholt, bis "nonworking_timeout"
77 trap
"" $GUARD_TRAPS &&
return 1
79 elif uci_is_true
"$status"; then
80 msg_debug "vpn-availability of gw $host still valid"
83 # gateway is currently known to be broken
84 trap
"" $GUARD_TRAPS &&
return 1
89 ## @fn select_mig_connection()
90 ## @brief Aktiviere den angegebenen VPN-Gateway
91 ## @param Name eines Diensts
92 ## @attention Seiteneffekt: Beräumung aller herumliegenden Konfigurationen von alten Verbindungen.
94 trap
"error_trap select_mig_connection '$*'" $GUARD_TRAPS
98 # loesche Flags fuer die Vorselektion
99 set_service_value
"$one_service" "switch_candidate_timestamp" ""
100 # erst nach der Abschaltung der alten Dienste wollen wir den/die neuen Dienste anschalten (also nur Ausgabe)
101 [
"$one_service" =
"$wanted" ] && echo
"$one_service" &&
continue
103 done |
while read one_service;
do
109 ## @fn find_and_select_best_gateway
110 ## @brief Ermittle den besten Gateway und prüfe, ob ein Wechsel sinnvoll ist.
111 ## @param force [optional] erzwinge den Wechsel auf den besten Gateway unabhängig von Wartezeiten (true/false)
113 find_and_select_best_gateway() {
114 trap
"error_trap find_and_select_best_gateway '$*'" $GUARD_TRAPS
115 local force_switch_now=${1:-
false}
119 local current_gateway=
121 local current_priority
123 local switch_candidate_timestamp
124 local now=$(get_time_minute)
126 msg_debug "Trying to find a better gateway"
127 # suche nach dem besten und dem bisher verwendeten Gateway
128 # Ignoriere dabei alle nicht-verwendbaren Gateways.
132 | sort_services_by_priority \
133 |
while read service_name;
do
134 # Ist der beste und der aktive Gateway bereits gefunden? Dann einfach weiterspringen ...
135 # (kein Abbruch der Schleife - siehe weiter unten - Stichwort SIGPIPE)
136 [ -n
"$current_gateway" ] &&
continue
138 uci_is_false
"$(get_service_value "$service_name
" "status
" "false")" && \
139 msg_debug "$host did not pass the last test" && \
141 # der Gateway ist ein valider Kandidat
142 # Achtung: Variablen innerhalb einer
"while"-Sub-Shell wirken sich nicht auf den Elternprozess aus
143 # Daher wollen wir nur ein bis zwei Zeilen:
145 # [den aktiven] (falls vorhanden)
146 # Wir brechen die Ausgabe jedoch nicht nach den ersten beiden Zeilen ab. Andernfalls muessten wir
147 # uns um das SIGPIPE-Signal kuemmern (vor allem in cron-Jobs).
148 [ -z
"$best_gateway" ] && best_gateway=
"$service_name" && echo
"$best_gateway"
151 best_gateway=$(echo
"$result" | sed -n 1
p)
152 current_gateway=$(echo
"$result" | sed -n 2
p)
153 if [
"$current_gateway" =
"$best_gateway" ]; then
154 if [ -z
"$current_gateway" ]; then
155 msg_debug "There is still no usable gateway around"
157 msg_debug "Current gateway ($current_gateway) is still the best choice"
158 # Wechselzaehler zuruecksetzen (falls er hochgezaehlt wurde)
159 set_service_value
"$current_gateway" "switch_candidate_timestamp" ""
163 msg_debug "Current ($current_gateway) / best ($best_gateway)"
164 # eventuell wollen wir den aktuellen Host beibehalten (sofern er funktioniert und wir nicht zwangsweise wechseln)
165 if [ -n
"$current_gateway" ] \
166 && uci_is_false
"$force_switch_now" \
167 && uci_is_true
"$(get_service_value "$current_gateway
" "status
" "false")"; then
168 # falls der beste und der aktive Gateway gleich weit entfernt sind, bleiben wir beim bisher aktiven
169 current_priority=$(get_service_priority
"$current_gateway")
170 best_priority=$(get_service_priority
"$best_gateway")
171 [
"$current_priority" -eq
"$best_priority" ] \
172 &&
msg_debug "Keeping current gateway since the best gateway has the same priority" \
174 # falls der beste und der aktive Gateway gleich weit entfernt sind, bleiben wir beim bisher aktiven
175 # Haben wir einen besseren Kandidaten? Muessen wir den Wechselzaehler aktivieren?
176 # Zaehle hoch bis der switch_candidate_timestamp alt genug ist
177 switch_candidate_timestamp=$(
get_service_value "$current_gateway" "switch_candidate_timestamp")
178 if [ -z
"$switch_candidate_timestamp" ]; then
179 # wir bleiben beim aktuellen Gateway - wir merken uns allerdings den Switch-Zeitstempel
180 set_service_value
"$current_gateway" "switch_candidate_timestamp" "$now"
181 msg_debug "Starting to count down until the switching timer reaches $bettergateway_timeout minutes"
184 # noch nicht alt genug fuer den Wechsel?
185 is_timestamp_older_minutes
"$switch_candidate_timestamp" "$bettergateway_timeout" \
186 || {
msg_debug "Counting down further until we reach $bettergateway_timeout minutes";
return 0; }
189 # eventuell kann hier auch ein leerer String uebergeben werden - dann wird kein Gateway aktiviert (korrekt)
190 [ -n
"$best_gateway" ] \
191 &&
msg_debug "Switching gateway from $current_gateway to $best_gateway" \
192 ||
msg_debug "Disabling $current_gateway without a viable alternative"
197 ## @fn get_active_mig_connections()
198 ## @brief Liefere die aktiven VPN-Verbindungen (mit Mesh-Internet-Gateways) zurück.
199 ## @returns Liste der Namen aller Dienste, die aktuell eine aktive VPN-Verbindung halten.
200 ## @attention Diese Funktion braucht recht viel Zeit.
201 get_active_mig_connections() {
202 trap
"error_trap get_active_mig_connections '$*'" $GUARD_TRAPS
210 ## @fn reset_mig_connection_test_timestamp()
211 ## @brief Löse eine erneute Prüfung dieses Gateways beim nächsten Prüflauf aus.
212 ## @param Name eines Diensts
213 ## @details Das Löschen des *timestamp_connection_test* Werts führt zu einer
214 ## erneuten Prüfung zum nächstmöglichen Zeitpunkt.
215 reset_mig_connection_test_timestamp() {
216 local service_name=
"$1"
217 set_service_value
"$service_name" "timestamp_connection_test" ""
221 ## @fn reset_all_mig_connection_test_timestamps()
222 ## @brief Löse eine erneute Prüfung aller Gateways zum nächstmöglichen Zeitpunkt aus.
223 ## @sa reset_mig_connection_test_timestamp
224 reset_all_mig_connection_test_timestamps() {
227 reset_mig_connection_test_timestamp
"$service_name"
232 ## @fn get_mig_connection_test_age()
233 ## @brief Ermittle das Test des letzten Verbindungstests in Minuten.
234 ## @returns Das Alter des letzten Verbindungstests in Minuten oder nichts (falls noch kein Test durchgeführt wurde).
235 ## @details Anhand des Test-Alters lässt sich der Zeitpunkt der nächsten Prüfung abschätzen.
236 get_mig_connection_test_age() {
237 local service_name=
"$1"
239 # noch keine Tests durchgefuehrt?
240 [ -z
"$timestamp" ] &&
return 0
241 local now=$(get_time_minute)
242 echo
"$timestamp" "$now" | awk
'{ print $2 - $1 }'
246 ## @fn get_client_cn()
247 ## @brief Ermittle den Common-Name des Nutzer-Zertifikats.
248 ## @details Liefere eine leere Zeichenkette zurück, falls kein Zertifikat vorhanden ist.
250 [ -e
"$MIG_OPENVPN_DIR/on_aps.crt" ] ||
return 0
251 openssl x509 -in
"$MIG_OPENVPN_DIR/on_aps.crt" \
252 -subject -nameopt multiline -noout 2>/dev/
null | awk
'/commonName/ {print $3}'
256 ## @fn get_mig_port_forward_range()
257 ## @brief Liefere den ersten und letzten Port der Nutzertunnel-Portweiterleitung zurück.
258 ## @param [optional] common name des Nutzer-Zertifikats
259 ## @returns zwei Zahlen durch Tabulatoren getrennt / keine Ausgabe, falls keine Main-ID gefunden wurde
260 ## @details Jeder AP bekommt einen Bereich von zehn Ports fuer die Port-Weiterleitung zugeteilt.
261 get_mig_port_forward_range() {
262 trap
"error_trap get_mig_port_forward_range '$*'" $GUARD_TRAPS
263 local client_cn=${1:-}
264 [ -z
"$client_cn" ] && client_cn=$(get_client_cn)
271 [ -z
"$client_cn" ] &&
msg_debug "get_mig_port_forward_range: failed to get Common Name - maybe there is no certificate?" &&
return 0
273 if echo
"$client_cn" | grep -q
'^\(\(1\.\)\?[0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
275 cn_address=${client_cn%.aps.on}
276 cn_address=${cn_address#*.}
277 elif echo
"$client_cn" | grep -q
'^\([0-9][0-9]\?[0-9]\?\.mobile\.on\)$'; then
279 cn_address=${client_cn%.mobile.on}
280 elif echo
"$client_cn" | grep -q
'^\(2[\._-][0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
282 cn_address=${client_cn%.aps.on}
283 cn_address=${cn_address#*.}
284 elif echo
"$client_cn" | grep -q
'^\(3[\._-][0-9][0-9]\?[0-9]\?\.aps\.on\)$'; then
286 cn_address=${client_cn%.aps.on}
287 cn_address=${cn_address#*.}
290 if [ -z
"$cn_address" ] || [
"$cn_address" -lt 1 ] || [
"$cn_address" -gt 255 ]; then
291 msg_info "$(basename "$0
"): invalidate certificate Common Name ($client_cn)"
293 first_port=$((portbase + (cn_address-1) * port_count))
294 last_port=$((first_port + port_count - 1))
295 echo -e
"$first_port\t$last_port"
299 # Ende der Doku-Gruppe