1 ## @defgroup openvpn OpenVPN (allgemein)
2 ## @brief Vorbereitung, Konfiguration und Prüfung von VPN-Verbindungen (z.B. für Nutzertunnel oder UGW).
3 # Beginn der openvpn-Doku-Gruppe
7 OPENVPN_CONFIG_BASEDIR=/var/etc/openvpn
10 ## @fn enable_openvpn_service()
11 ## @brief Erzeuge eine funktionierende openvpn-Konfiguration (Datei + UCI).
12 ## @param service_name Name eines Dienstes
13 ## @details Die Konfigurationsdatei wird erzeugt und eine openvpn-uci-Konfiguration wird angelegt.
14 ## Falls zu diesem openvpn-Dienst kein Zertifikat oder kein Schlüssel gefunden wird, dann passiert nichts.
16 trap
"error_trap enable_openvpn_service '$*'" $GUARD_TRAPS
17 local service_name=
"$1"
19 msg_info "Refuse to enable openvpn server ('$service_name'): missing key or certificate"
20 trap
"" $GUARD_TRAPS &&
return 1
22 # ermittle die openvpn-config-Vorlagedatei
24 local uci_prefix=
"openvpn.$service_name"
27 # zukuenftige config-Datei referenzieren
29 # zuvor ankuendigen, dass zukuenftig diese uci-Konfiguration an dem Dienst haengt
30 service_add_uci_dependency
"$service_name" "$uci_prefix"
31 # lege die uci-Konfiguration an und aktiviere sie
32 uci set
"${uci_prefix}=openvpn"
33 uci set
"${uci_prefix}.enabled=1"
34 uci set
"${uci_prefix}.config=$config_file"
40 ## @brief Schreibe eine openvpn-Konfigurationsdatei.
41 ## @param service_name Name eines Dienstes
43 trap
"error_trap update_vpn_config '$*'" $GUARD_TRAPS
44 local service_name=
"$1"
47 service_add_file_dependency
"$service_name" "$config_file"
48 # Konfigurationsdatei neu schreiben
49 mkdir -p
"$(dirname "$config_file
")"
55 ## @brief Löschung einer openvpn-Verbindung
56 ## @param service_name Name eines Dienstes
57 ## @details Die UCI-Konfiguration, sowie alle anderen mit der Verbindung verbundenen Elemente werden entfernt.
58 ## Die openvpn-Verbindung bleibt bestehen, bis zum nächsten Aufruf von 'apply_changes openvpn'.
60 trap
"error_trap disable_openvpn_service '$*'" $GUARD_TRAPS
61 local service_name=
"$1"
62 # Abbruch, falls es keine openvpn-Instanz gibt
63 [ -z
"$(uci_get "openvpn.$service_name
")" ] &&
return 0
64 # openvpn wird automatisch neugestartet
65 cleanup_service_dependencies
"$service_name"
66 # nach einem reboot sind eventuell die dependencies verlorengegangen - also loeschen wir manuell
71 ## @fn get_openvpn_service_state()
72 ## @brief Prüfe ob eine openvpn-Verbindung besteht bzw. im Aufbau ist.
73 ## @param service_name Name eines Dienstes
74 ## @details Die Prüfung wird anhand der PID-Datei und der Gültigkeit der enthaltenen PID vorgenommen.
75 ## @returns "active", "connecting" oder einen leeren String (unbekannt, bzw. keine Verbindung).
77 trap
"error_trap get_openvpn_service_state '$*'" $GUARD_TRAPS
78 local service_name=
"$1"
79 # existiert ein VPN-Eintrag?
80 [ -z
"$(uci_get "openvpn.$service_name
")" ] &&
return
81 # gibt es einen Verweis auf eine passende PID-Datei?
82 if check_pid_file
"$(get_service_value "$service_name
" "pid_file
")" "openvpn"; then
83 # Die "openvpn_established_indicator_file"-Variable wird vom up/down-Skript erzeugt.
84 # Die Variable verweist ebenfalls auf eine Datei mit der PID. Dies erlaubt die Unterscheidung
85 # einer Verbindung im Aufbau (bzw. in der Phase einer wiederholten Ablehnung) von einer
86 # beiderseits akzeptierten Datenverbindung. Dies ist insbesondere fuer die mesh-VPN-Verbindungen
87 # sinnvoll, da hier mehr Toleranz beim Verbindungsaufbau sinnvoll ist.
88 if check_pid_file
"$(get_service_value "$service_name
" "openvpn_established_indicator_file
")" "openvpn"; then
99 ## @fn _change_openvpn_config_setting()
100 ## @brief Ändere eine Einstellung in einer openvpn-Konfigurationsdatei.
101 ## @param config_file Name der Konfigurationsdatei.
102 ## @param config_key Name der openvpn-Einstellung.
103 ## @param config_value Neuer Inhalt der Einstellung - die Einstellung wird gelöscht, falls dieser Parameter fehlt oder leer ist.
104 ## @attention OpenVPN-Optionen ohne Parameter (z.B. --mtu-test) können nicht mittels dieser Funktion gesetzt werden.
106 local config_file=
"$1"
107 local config_key=
"$2"
108 local config_value=
"${3:-}"
109 sed -i
"/^$config_key[\t ]/d" "$config_file"
110 [ -n
"$config_value" ] && echo
"$config_key $config_value" >>
"$config_file"
115 ## @fn get_openvpn_config()
116 ## @brief liefere openvpn-Konfiguration eines Dienstes zurück
117 ## @param service_name Name eines Dienstes
119 trap
"error_trap get_openvpn_config '$*'" $GUARD_TRAPS
120 local service_name=
"$1"
128 # Falls es sich um einen relay-Dienst handelt, koennen wir uns leider nicht mit uns selbst verbinden,
129 # da die firewall-redirect-Regeln keine "device"-Quelle kennen (anstelle des ueblichen "on_mesh").
130 # Also ermitteln wir den lokal bekannten proxy-Dienst und verwenden dessen Daten, sofern on-usergw installiert ist.
131 if [
"$remote" =
"$(get_main_ip)" -a -n
"${RELAYABLE_SERVICE_PREFIX:-}" ]; then
132 local proxy_service_type=
"$RELAYABLE_SERVICE_PREFIX$(get_service_value "$service_name
" "service
")"
134 if [ -n
"$relayed_service" ]; then
135 # Hostname und Port ersetzen
139 msg_info "Failed to use locally relayed service for openvpn - trying to continue, anyway."
143 [
"$protocol" =
"tcp" ] && protocol=tcp-client
146 # schreibe die Konfigurationsdatei
147 echo
"# automatically generated by $0"
148 echo
"remote $remote $port"
149 echo
"proto $protocol"
150 echo
"writepid $pid_file"
152 # sicherstellen, dass die Konfigurationsdatei mit einem Zeilenumbruch endet (fuer
"echo >> ...")
157 ## @fn verify_vpn_connection()
158 ## @brief Prüfe einen VPN-Verbindungsaufbau
159 ## @param service_name Name eines Dienstes
160 ## @param key [optional] Schluesseldatei: z.B. $VPN_DIR/on_aps.key
161 ## @param cert [optional] Zertifikatsdatei: z.B. $VPN_DIR/on_aps.crt
162 ## @returns Exitcode=0 falls die Verbindung aufgebaut werden konnte
164 trap
"error_trap verify_vpn_connection '$*'" $GUARD_TRAPS
165 local service_name=
"$1"
166 local key_file=
"${2:-}"
167 local cert_file=
"${3:-}"
174 config_file=$(mktemp -t
"VERIFY-${service_name}-XXXXXXX")
176 # wir benoetigen die template-Datei fuer das Erzeugen der Basis-Konfiguration
178 msg_debug "start vpn test of $service_name"
179 # erstelle die config-Datei
181 # filtere Einstellungen heraus, die wir ueberschreiben wollen
182 # nie die echte PID-Datei ueberschreiben (falls ein Prozess laeuft)
185 # some openvpn options:
186 # ifconfig-noexec: we do not want to configure a device (and mess up routing tables)
187 # route-noexec: keinerlei Routen hinzufuegen
188 echo
"ifconfig-noexec"
191 # some timing options:
192 # inactive: close connection after 15s without traffic
193 # ping-exit: close connection after 15s without a ping from the other side (which is probably disabled)
194 echo
"inactive 15 1000000"
198 # verb: verbose level 3 is required for the TLS messages
199 # nice: testing is not too important
200 # resolv-retry: fuer ipv4/ipv6-Tests sollten wir mehrere Versuche zulassen
203 echo
"resolv-retry 3"
205 # prevent a real connection (otherwise we may break our current vpn tunnel):
206 # tls-exit: stop immediately after tls handshake failure
207 # ns-cert-type: enforce a connection against a server certificate (instead of peer-to-peer)
209 echo
"ns-cert-type server"
213 # kein Netzwerkinterface erzeugen
215 # keine PID-Datei anlegen
217 # keine Netzwerkkonfiguration via up/down
220 # TLS-Pruefung immer fehlschlagen lassen
225 # nur fuer tcp-Verbindungen (ipv4/ipv6)
226 # connect-retry: Sekunden Wartezeit zwischen Versuchen
227 # connect-timeout: Dauer eines Versuchs
228 # connect-retry-max: Anzahl moeglicher Wiederholungen
229 if grep -q
"^proto[ \t]\+tcp" "$config_file"; then
230 echo
"connect-retry 1"
231 echo
"connect-timeout 15"
232 echo
"connect-retry-max 1"
235 # Schluessel und Zertifikate bei Bedarf austauschen
236 [ -n
"$key_file" ] &&
\
238 [ -n
"$cert_file" ] &&
\
241 # Aufbau der VPN-Verbindung bis zum Timeout oder bis zum Verbindungsabbruch via "tls-exit" (/bin/false)
242 openvpn --config
"$config_file" ||
true
243 # read the additional options from the config file (for debug purposes)
244 file_opts=$(grep -v
"^$" "$config_file" | grep -v
"^#" | sed
's/^/--/' | tr
'\n' ' ')
246 grep -q
"Initial packet" "$log_file" &&
return 0
247 msg_debug "openvpn test failed: openvpn $file_opts"
248 trap
"" $GUARD_TRAPS &&
return 1
253 ## @brief Prüfe ob das Zertifikat eines openvpn-basierten Diensts existiert.
254 ## @returns exitcode=0 falls das Zertifikat existiert
255 ## @details Falls der Ort der Zertifikatsdatei nicht zweifelsfrei ermittelt
256 ## werden kann, dann liefert die Funktion "wahr" zurück.
258 local service_name=
"$1"
261 local config_template
263 # im Zweifelsfall (kein Template gefunden) liefern wir
"wahr"
264 [ -z
"$config_template" ] &&
return 0
265 # Verweis auf lokale config-Datei (keine uci-basierte Konfiguration)
266 if [ -e
"$config_template" ]; then
270 # im Zweifelsfall: liefere "wahr"
273 # das Zertifikat scheint irgendwie anders konfiguriert zu sein - im Zeifelsfall: OK
274 [ -z
"$cert_file" -o -z
"$key_file" ] &&
return 0
275 # existiert die Datei?
276 [ -e
"$cert_file" -a -e
"$key_file" ] &&
return 0
277 trap
"" $GUARD_TRAPS &&
return 1
281 ## @fn has_openvpn_credentials_by_template()
282 ## @brief Prüft, ob der Nutzer bereits einen Schlüssel und ein Zertifikat angelegt hat.
283 ## @param template_file Name einer openvpn-Konfigurationsdatei (oder einer Vorlage). Aus dieser Datei werden "cert"- und "key"-Werte entnommen.
284 ## @returns Liefert "wahr", falls Schlüssel und Zertifikat vorhanden sind oder falls in irgendeiner Form Unklarheit besteht.
286 trap
"error_trap has_openvpn_credentials_by_template '$*'" $GUARD_TRAPS
287 local template_file=
"$1"
293 # Pruefe, ob eine "cd"-Direktive enthalten ist - vervollständige damit relative Pfade
295 [ -n
"$base_dir" -a
"${cert_file:0:1}" !=
"/" ] && cert_file=
"$base_dir/$cert_file"
296 [ -n
"$base_dir" -a
"${key_file:0:1}" !=
"/" ] && key_file=
"$base_dir/$key_file"
297 # im Zweifel: liefere
"wahr"
298 [ -z
"$key_file" -o -z
"$cert_file" ] &&
return 0
299 # beide Dateien existieren
300 [ -e
"$key_file" -a -e
"$cert_file" ] &&
return 0
301 trap
"" $GUARD_TRAPS &&
return 1
306 ## @brief Allgemeines Ereignisbehandlung fuer openvpn-Verbindungen: Logging und eventuell Dienst-Bereinigung (nur für "down").
307 ## @details Alle Informationen (bis auf das Log-Ziel) werden aus den Umgebungsvariablen gezogen, die openvpn in
308 ## seinen Ereignisskripten setzt.
310 local log_target=
"$1"
311 # die config-Datei enthaelt den Dienst-Namen
314 local established_indicator_file
315 service_name=$(basename
"${config%.conf}")
317 established_indicator_file=$(
get_service_value "$service_name" "openvpn_established_indicator_file")
318 if [ -z
"$established_indicator_file" ]; then
319 established_indicator_file=
"${pid_file}.established"
320 set_service_value
"$service_name" "openvpn_established_indicator_file" "$established_indicator_file"
322 case "$script_type" in
325 [ -n
"$pid_file" ] && cat
"$pid_file" >
"$established_indicator_file"
329 # der openwrt-Build von openvpn setzt wohl leider nicht die
"time_duration"-Umgebungsvariable
330 [ -z
"${time_duration:-}" ] && time_duration=$(($(date +%s) - $daemon_start_time))
331 # Verbindungsverlust durch fehlende openvpn-Pings?
332 if [
"${signal:-}" =
"ping-restart" ]; then
334 "Lost connection with ${remote_1}:${remote_port_1} after ${time_duration}s"
335 # Verbindung als unklar definieren
336 set_service_value
"$service_name" "status" ""
337 set_service_value
"$service_name" "status_timestamp" "$(get_uptime_minutes)"
339 [ -n
"$pid_file" ] && rm -f
"$pid_file"
340 [ -n
"$established_indicator_file" ] && rm -f
"$established_indicator_file"
344 "Closing connection with ${remote_1}:${remote_port_1} after ${time_duration}s"
354 ## @fn prepare_openvpn_service()
355 ## @param Name eines Diensts
356 ## @brief Erzeuge oder aktualisiere einen OpenVPN-Dienst
358 trap
"error_trap prepare_openvpn_service '$*'" $GUARD_TRAPS
359 local service_name=
"$1"
360 local pid_file=
"/var/run/${service_name}.pid"
361 local config_file=
"$OPENVPN_CONFIG_BASEDIR/${service_name}.conf"
365 # Diese Stelle ist hier eigentlich falsch, da sie Kenntnisse und Variablen
366 # vorraussetzt, die nicht in
"on-core" definiert sind.
367 # Aufgrund der Wiederverwendung der generischen
368 #
"run_cyclic_service_tests"-Funktion ist eine Separierung dieser Auswahl
369 # jedoch leider nur mit großem Aufwand möglich.
370 if [
"$service_type" =
"gw" ]; then
371 template_file=
"$MIG_OPENVPN_CONFIG_TEMPLATE_FILE"
372 elif [
"$service_type" =
"mesh" ]; then
373 template_file=
"$MESH_OPENVPN_CONFIG_TEMPLATE_FILE"
375 msg_error "unknown service type for openvpn config preparation: $service_type"
378 set_service_value
"$service_name" "template_file" "$template_file"
379 set_service_value
"$service_name" "config_file" "$config_file"
380 set_service_value
"$service_name" "pid_file" "$pid_file"
384 ## @fn openvpn_get_mtu()
385 ## @brief Ermittle die MTU auf dem Weg zum Anbieter des Diensts.
386 ## @details The output can be easily parsed via 'cut'. Even the full status output of openvpn is safe for parsing since potential tabulator characters are removed.
387 ## @returns One line consisting of five fields separated by tab characters is returned (tried_to_remote real_to_remote tried_from_remote real_from_remote full_status_output). Failed tests are indicated by an empty result.
389 trap
"error_trap openvpn_get_mtu '$*'" $GUARD_TRAPS
390 local service_name=
"$1"
395 config_file=$(mktemp -t
"MTU-${service_name}-XXXXXXX")
397 log_file=
"$(get_service_log_filename "$service_name
" "openvpn
" "mtu
")"
402 # kein Netzwerk konfigurieren
403 echo
"ifconfig-noexec"
407 # kein Netzwerkinterface, keine pid-Datei
419 openvpn --mtu-test --config
"$config_file" 2>&1 &
420 # warte auf den Startvorgang
422 local pid=
"$(cat "$pid_file
")"
425 local mtu_out_filtered
426 while [
"$wait_loops" -gt 0 ];
do
427 # keine Fehlermeldungen (-s) falls die Log-Datei noch nicht existiert
428 mtu_out=$(grep -s
"MTU test completed" "$log_file" ||
true)
430 # Thu Jul 3 22:23:01 2014 NOTE: Empirical MTU test completed [Tried,Actual] local->remote=[1573,1573] remote->local=[1573,1573]
431 if [ -n
"$mtu_out" ]; then
432 # Ausgabe der vier Zahlen getrennt durch Tabulatoren
433 mtu_out_filtered=
"$(echo "$mtu_out
" | tr '[' ',' | tr ']' ',')"
434 # Leider koennen wir nicht alle Felder auf einmal ausgeben (tab-getrennt),
435 # da das busybox-cut nicht den --output-delimiter unterstützt.
436 echo
"$mtu_out_filtered" | cut -
d , -f 5 | tr
'\n' '\t'
437 echo
"$mtu_out_filtered" | cut -
d , -f 6 | tr
'\n' '\t'
438 echo
"$mtu_out_filtered" | cut -
d , -f 8 | tr
'\n' '\t'
439 echo
"$mtu_out_filtered" | cut -
d , -f 9 | tr
'\n' '\t'
440 # wir ersetzen alle eventuell vorhandenen Tabulatoren in der Statusausgabe - zur Vereinfachung des Parsers
441 echo -n
"$mtu_out" | tr
'\t' ' '
443 elif [ -z
"$pid" -o ! -d
"/proc/$pid" ]; then
444 msg_info "Failed to verify MTU resctrictions for '$host'"
450 # sicherheitshalber brechen wir den Prozess ab und loeschen alle Dateien
451 kill
"$pid" >/dev/
null 2>&1 ||
true
452 rm -f
"$config_file" "$pid_file"
453 # ist der Zaehler abgelaufen?
454 [
"$wait_loops" -eq 0 ] &&
msg_info "Timeout for openvpn_get_mtu '$host' - aborting."
459 ## @fn cleanup_stale_openvpn_services()
460 ## @brief Beräumung liegengebliebener openvpn-Konfigurationen, sowie Deaktivierung funktionsunfähiger Verbindungen.
461 ## @details Verwaiste openvpn-Konfigurationen können aus zwei Grunden auftreten:
462 ## 1) nach einem reboot wurde nicht du zuletzt aktive openvpn-Verbindung ausgewählt - somit bleibt der vorher aktive uci-Konfigurationseintrag erhalten
463 ## 2) ein VPN-Verbindungsaufbau scheitert und hinterlässt einen uci-Eintrag, eine PID-Datei, jedoch keinen laufenden Prozess
465 trap
"error_trap cleanup_stale_openvpn_services '$*'" $GUARD_TRAPS
470 find_all_uci_sections openvpn openvpn |
while read uci_prefix;
do
471 config_file=$(uci_get
"${uci_prefix}.config")
472 # Keine config-Datei? Keine von uns verwaltete Konfiguration ...
473 [ -z
"$config_file" ] &&
continue
474 service_name=
"${uci_prefix#openvpn.}"
475 # Es scheint sich um eine von uns verwaltete Verbindung zu handeln.
476 # Das
"pid_file"-Attribut ist nicht persistent - nach einem Neustart kann es also leer sein.
478 # Falls die config-Datei oder die pid-Datei fehlt, dann ist es ein reboot-Fragment. Wir löschen die Überreste.
479 if [ ! -e
"$config_file" -o -z
"$pid_file" -o ! -e
"$pid_file" ]; then
480 msg_info "Removing a reboot-fragment of a previously used openvpn connection: $service_name"
482 elif check_pid_file
"$pid_file" "openvpn"; then
483 # Prozess läuft - alles gut
486 # Falls die PID-Datei existiert, jedoch veraltet ist (kein dazugehöriger Prozess läuft), dann
487 # schlug der Verbindungsaufbau fehlt (siehe "tls-exit" und "single-session").
488 # Wir markieren die Verbindung als kaputt.
489 msg_info "Marking a possibly interrupted openvpn connection as broken: $service_name"
490 set_service_value
"$service_name" "status" "n"
494 apply_changes openvpn
497 # Ende der openvpn-Doku-Gruppe
uci_delete(uci_path)
Lösche ein UCI-Element.
get_service_log_filename()
Ermittle den Namen der Log-Datei für diesen Dienst. Zusätzliche Details (z.B. "openvpn mtu"...
get_services(service_type)
Liefere alle Dienste zurueck, die dem angegebenen Typ zugeordnet sind. Falls kein Typ angegben wird...
_change_openvpn_config_setting(config_file, config_key, config_value)
Ändere eine Einstellung in einer openvpn-Konfigurationsdatei.
openvpn_get_mtu()
Ermittle die MTU auf dem Weg zum Anbieter des Diensts.
append_to_custom_log(log_name, event)
Hänge eine neue Nachricht an ein spezfisches Protokoll an.
cleanup_stale_openvpn_services()
Beräumung liegengebliebener openvpn-Konfigurationen, sowie Deaktivierung funktionsunfähiger Verbindun...
get_openvpn_config(service_name)
liefere openvpn-Konfiguration eines Dienstes zurück
update_vpn_config(service_name)
Schreibe eine openvpn-Konfigurationsdatei.
_get_file_dict_value(key)
Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom.
log_openvpn_events_and_disconnect_if_requested()
Allgemeines Ereignisbehandlung fuer openvpn-Verbindungen: Logging und eventuell Dienst-Bereinigung (n...
prepare_openvpn_service(Name)
Erzeuge oder aktualisiere einen OpenVPN-Dienst.
filter_services_by_value(key, value)
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
openvpn_service_has_certificate_and_key()
Prüfe ob das Zertifikat eines openvpn-basierten Diensts existiert.
get_openvpn_service_state(service_name)
Prüfe ob eine openvpn-Verbindung besteht bzw. im Aufbau ist.
enable_openvpn_service()
Erzeuge eine funktionierende openvpn-Konfiguration (Datei + UCI, service_name).
msg_debug(message)
Debug-Meldungen ins syslog schreiben.
set eu on function print_services services log for dir in etc on services d var on services volatile d
get_service_value(key, default)
Auslesen eines Werts aus der Service-Datenbank.
verify_vpn_connection(service_name, key, cert)
Prüfe einen VPN-Verbindungsaufbau.
msg_error(message)
Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben.
set eu grep root::etc shadow exit if which chpasswd dev null
has_openvpn_credentials_by_template(template_file)
Prüft, ob der Nutzer bereits einen Schlüssel und ein Zertifikat angelegt hat.
disable_openvpn_service(service_name)
Löschung einer openvpn-Verbindung.