2 ## @brief Logging, Datei-Operationen, DNS- und NTP-Dienste, Dictionary-Dateien, PID- und Lock-Behandlung, Berichte
3 # Beginn der Doku-Gruppe
7 # Quelldatei für Standardwerte des Kern-Pakets
8 ON_CORE_DEFAULTS_FILE=
"${IPKG_INSTROOT:-}/usr/share/opennet/core.defaults"
9 # Pfad zur dnsmasq-Server-Datei zur dynamischen Aktualisierung durch Dienste-Erkennung
10 DNSMASQ_SERVERS_FILE_DEFAULT=
"${IPKG_INSTROOT:-}/var/run/dnsmasq.servers"
11 # DNS-Suffix, das vorrangig von den via olsrd publizierten Nameservern ausgeliefert werden soll
13 # Dateiname für erstellte Zusammenfassungen
14 REPORTS_FILE=
"${IPKG_INSTROOT:-}/tmp/on_report.tar.gz"
15 # Basis-Verzeichnis für Log-Dateien
16 LOG_BASE_DIR=
"${IPKG_INSTROOT:-}/var/log"
17 # maximum length of message lines (logger seems to resctrict lines incl. timestamp to 512 characters)
18 LOG_MESSAGE_LENGTH=420
19 # Verzeichnis für auszuführende Aktionen
20 SCHEDULING_DIR=
"${IPKG_INSTROOT:-}/var/run/on-scheduling.d"
21 # beim ersten Pruefen wird der Debug-Modus ermittelt
25 # Aufteilung ueberlanger Zeilen
27 local line_length=
"$1"
28 # ersetze alle whitespace-Zeichen durch Nul
29 # Gib anschliessend soviele Token wie moeglich aus, bis die Zeilenlaenge erreicht ist.
30 tr
'\n\t ' '\0' | xargs -0 -s
"$line_length" echo
35 ## @param message Debug-Nachricht
36 ## @brief Debug-Meldungen ins syslog schreiben
37 ## @details Die Debug-Nachrichten landen im syslog (siehe ``logread``).
38 ## Falls das aktuelle Log-Level bei ``info`` oder niedriger liegt, wird keine Nachricht ausgegeben.
40 # bei der ersten Ausfuehrung dauerhaft speichern
41 [ -z
"$DEBUG_ENABLED" ] && \
42 DEBUG_ENABLED=$(uci_is_true
"$(uci_get on-core.settings.debug false)" && echo 1 || echo 0)
43 [
"$DEBUG_ENABLED" =
"0" ] || echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -t
"$(basename "$0
")[$$]"
48 ## @param message Log-Nachricht
49 ## @brief Informationen und Fehlermeldungen ins syslog schreiben
50 ## @details Die Nachrichten landen im syslog (siehe ``logread``).
51 ## Die info-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt.
53 echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -t
"$(basename "$0
")[$$]"
58 ## @param message Fehlermeldung
59 ## @brief Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben
60 ## @details Jede Meldung wird mit "ERROR" versehen, damit diese Meldungen von
61 ## "get_potential_error_messages" erkannt werden.
62 ## Die error-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt.
64 echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -s -t
"$(basename "$0
")[$$]" "[ERROR] $1"
68 ## @fn append_to_custom_log()
69 ## @param log_name Name des Log-Ziels
70 ## @param event die Kategorie der Meldung (up/down/???)
71 ## @param msg die textuelle Beschreibung des Ereignis (z.B. "connection with ... closed")
72 ## @brief Hänge eine neue Nachricht an ein spezfisches Protokoll an.
73 ## @details Die Meldungen werden beispielsweise von den konfigurierten openvpn-up/down-Skripten gesendet.
79 echo
"$(date) openvpn [$event]: $msg" >>
"$logfile"
80 # Datei kuerzen, falls sie zu gross sein sollte
82 filesize=$(get_filesize
"$logfile")
83 [
"$filesize" -gt 10000 ] && sed -i
"1,30d" "$logfile"
88 ## @fn get_custom_log_filename()
89 ## @param log_name Name des Log-Ziels
90 ## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
91 ## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert).
94 # der Aufrufer darf sich darauf verlassen, dass er in die Datei schreiben kann
95 mkdir -p
"$LOG_BASE_DIR"
96 echo
"$LOG_BASE_DIR/${log_name}.log"
100 ## @fn get_custom_log_content()
101 ## @param log_name Name des Log-Ziels
102 ## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
103 ## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert).
107 [ -e
"$logfile" ] && cat
"$logfile" ||
true
112 ## @param target_filename Name der Zieldatei
113 ## @brief Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
114 ## @details Der neue Inhalt der Datei wird auf der Standardeingabe erwartet.
115 ## Im Falle der Gleichheit von aktuellem Inhalt und zukünftigem Inhalt wird
116 ## keine Schreiboperation ausgeführt. Der Exitcode gibt an, ob eine Schreiboperation
117 ## durchgeführt wurde.
118 ## @return exitcode=0 (Erfolg) falls die Datei geändert werden musste
119 ## @return exitcode=1 (Fehler) falls es keine Änderung gab
121 local target_filename=
"$1"
122 local content=
"$(cat -)"
123 if [ -e
"$target_filename" ] && echo
"$content" | cmp -s -
"$target_filename"; then
124 # the content did not change
125 trap
"" $GUARD_TRAPS &&
return 1
129 dirname=$(dirname
"$target_filename")
130 [ -
d "$dirname" ] || mkdir -p
"$dirname"
131 echo
"$content" >
"$target_filename"
138 ## @brief Übertrage die Liste der als DNS-Dienst announcierten Server in die dnsmasq-Konfiguration.
139 ## @details Die Liste der DNS-Server wird in die separate dnsmasq-Servers-Datei geschrieben (siehe @sa DNSMASQ_SERVERS_FILE_DEFAULT).
140 ## Die Server-Datei wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts.
141 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden.
143 trap
"error_trap update_dns_servers '$*'" $GUARD_TRAPS
147 # wenn wir eine VPN-Tunnel-Verbindung aufgebaut haben, sollten wir DNS-Anfragen über diese Crypto-Verbindung lenken
148 local preferred_servers
149 local use_dns=
"$(uci_get on-core.settings.use_olsrd_dns)"
150 # return if we should not use DNS servers provided via olsrd
151 uci_is_false
"$use_dns" &&
return 0
153 servers_file=$(uci_get
"dhcp.@dnsmasq[0].serversfile")
154 # aktiviere die
"dnsmasq-serversfile"-Direktive, falls noch nicht vorhanden
155 if [ -z
"$servers_file" ]; then
156 servers_file=
"$DNSMASQ_SERVERS_FILE_DEFAULT"
157 uci set
"dhcp.@dnsmasq[0].serversfile=$servers_file"
158 uci commit
"dhcp.@dnsmasq[0]"
161 preferred_servers=$(is_function_available
"get_mig_tunnel_servers" &&
get_mig_tunnel_servers "DNS" ||
true)
162 # wir sortieren alphabetisch - Naehe ist uns egal
167 [ -n
"$port" -a
"$port" !=
"53" ] && host=
"$host#$port"
168 # Host nur schreiben, falls kein bevorzugter Host gefunden wurde
169 [ -z
"$preferred_servers" ] && echo
"server=$host"
170 # Die interne Domain soll vorranging von den via olsrd verbreiteten DNS-Servern bedient werden.
171 # Dies ist vor allem fuer UGW-Hosts wichtig, die über eine zweite DNS-Quelle (lokaler uplink)
173 echo
"server=/$INTERN_DNS_DOMAIN/$host"
175 # eventuell bevorzugte Hosts einfuegen
176 for host in $preferred_servers;
do
180 # es gab eine Aenderung
182 # Konfiguration neu einlesen
183 killall -s HUP dnsmasq 2>/dev/
null ||
true
188 ## @brief Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
189 ## @details Die Liste der NTP-Server wird in die uci-Konfiguration geschrieben.
190 ## Die uci-Konfiguration wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts.
191 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden.
192 ## @sa http://wiki.openwrt.org/doc/uci/system#remote_time_ntp
194 trap
"error_trap update_ntp_servers '$*'" $GUARD_TRAPS
198 local preferred_servers
199 local use_ntp=
"$(uci_get on-core.settings.use_olsrd_ntp)"
200 # return if we should not use NTP servers provided via olsrd
201 uci_is_false
"$use_ntp" &&
return
202 preferred_servers=$(is_function_available
"get_mig_tunnel_servers" &&
get_mig_tunnel_servers "NTP" ||
true)
203 # schreibe die Liste der NTP-Server neu
205 # wir sortieren alphabetisch - Naehe ist uns egal
206 if [ -n
"$preferred_servers" ]; then
207 for host in $preferred_servers;
do
214 [ -n
"$port" -a
"$port" !=
"123" ] && host=
"$host:$port"
223 ## @param event Ereignistext
224 ## @param timestamp [optional] Der Zeitstempel-Text kann bei Bedarf vorgegeben werden.
225 ## @brief Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
226 ## @details Ein Zeitstempel, sowie hübsche Formatierung wird automatisch hinzugefügt.
228 trap
"error_trap add_banner_event '$*'" $GUARD_TRAPS
230 # verwende den optionalen zweiten Parameter oder den aktuellen Zeitstempel
231 local timestamp=
"${2:-$(date)}"
232 local line=
" - $timestamp - $event -"
234 # Steht unser Text schon im Banner? Ansonsten hinzufuegen ...
235 # bis einschliesslich Version v0.5.0 war "clean_restart_log" das Schluesselwort
236 # ab v0.5.1 verwenden wir "system events"
237 if ! grep -qE
'(clean_restart_log|system events)' /etc/banner; then
238 echo
" ------------------- system events -------------------"
240 # die Zeile auffuellen
241 while [
"${#line}" -lt 54 ];
do line=
"$line-";
done
249 ## @brief Alle Log-Einträge aus der banner-Datei entfernen.
251 awk
'{if ($1 != "-") print}' /etc/banner >/tmp/banner
252 mv /tmp/banner /etc/banner
257 ## @fn _get_file_dict_value()
258 ## @param key das Schlüsselwort
259 ## @brief Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom
260 ## @returns Den zum gegebenen Schlüssel gehörenden Wert aus dem Schlüssel/Wert-Eingabestrom
261 ## Falls kein passender Schlüssel gefunden wurde, dann ist die Ausgabe leer.
262 ## @details Jede Zeile der Standardeingabe enthält einen Feldnamen und einen Wert - beide sind durch
263 ## ein beliebiges whitespace-Zeichen getrennt.
264 ## Dieses Dateiformat wird beispielsweise für die Dienst-Zustandsdaten verwendet.
265 ## Zusätzlich ist diese Funktion auch zum Parsen von openvpn-Konfigurationsdateien geeignet.
269 ## @fn _get_file_dict_keys()
270 ## @brief Liefere alle Schlüssel aus einem Schlüssel/Wert-Eingabestrom.
271 ## @returns Liste aller Schlüssel aus dem Schlüssel/Wert-Eingabestrom.
272 ## @sa _get_file_dict_value
273 _get_file_dict_keys() { sed
's/[ \t].*//' "$@" 2>/dev/
null ||
true; }
276 ## @fn _set_file_dict_value()
277 ## @param field das Schlüsselwort
278 ## @param value der neue Wert
279 ## @brief Ersetzen oder Einfügen eines Werts in einen Schlüssel/Wert-Eingabestrom.
280 ## @sa _get_file_dict_value
281 _set_file_dict_value() {
282 local status_file=
"$1"
285 [ -z
"$field" ] &&
msg_error "Ignoring empty key for _set_file_dict_value" &&
return
286 # Filtere bisherige Zeilen mit dem key heraus.
287 # Fuege anschliessend die Zeile mit dem neuen Wert an.
288 # Die Sortierung sorgt fuer gute Vergleichbarkeit, um die Anzahl der
289 # Schreibvorgaenge (=Wahrscheinlichkeit von gleichzeitigem Zugriff) zu reduzieren.
291 grep -v -w -s
"$field" "$status_file"
292 echo
"$field $new_value"
297 ## @fn get_on_core_default()
298 ## @param key Name des Schlüssels
299 ## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-core).
300 ## @details Die default-Werte werden nicht von der Konfigurationsverwaltung uci verwaltet.
301 ## Somit sind nach jedem Upgrade imer die neuesten Standard-Werte verfügbar.
302 get_on_core_default() {
308 ## @fn get_on_firmware_version()
309 ## @brief Liefere die aktuelle Firmware-Version zurück.
310 ## @returns Die zurückgelieferte Zeichenkette beinhaltet den Versionsstring (z.B. "0.5.0").
311 ## @details Per Konvention entspricht die Version jedes Firmware-Pakets der Firmware-Version.
312 ## Um locking-Probleme zu vermeiden, lesen wir den Wert direkt aus der control-Datei des Pakets.
313 ## Das ist nicht schoen - aber leider ist die lock-Datei nicht konfigurierbar.
314 get_on_firmware_version() {
315 trap
"error_trap get_on_firmware_version '$*'" $GUARD_TRAPS
316 local status_file=
"${IPKG_INSTROOT:-}/usr/lib/opkg/info/on-core.control"
317 [ -e
"$status_file" ] ||
return 0
318 awk
'{if (/^Version:/) print $2;}' <
"$status_file"
323 ## @param on_id die ID des AP - z.B. "1.96" oder "2.54"
324 ## @param on_ipschema siehe "get_on_core_default on_ipschema"
325 ## @param interface_number 0..X (das WLAN-Interface ist typischerweise Interface #0)
326 ## @attention Manche Aufrufende verlassen sich darauf, dass *on_id_1* und
327 ## *on_id_2* nach dem Aufruf verfügbar sind (also _nicht_ als "local"
328 ## Variablen deklariert wurden).
331 local on_ipschema=
"$2"
332 local interface_number=
"$3"
333 # das "on_ipschema" erwartet die Variable "no"
334 local no=
"$interface_number"
335 echo
"$on_id" | grep -q
"\." || on_id=1.$on_id
336 on_id_1=$(echo
"$on_id" | cut -
d . -f 1)
337 on_id_2=$(echo
"$on_id" | cut -
d . -f 2)
338 echo $(eval echo $on_ipschema)
343 ## @brief Liefere die aktuell konfigurierte Main-IP zurück.
344 ## @returns Die aktuell konfigurierte Main-IP des AP oder die voreingestellte IP.
345 ## @attention Seiteneffekt: die Variablen "on_id_1" und "on_id_2" sind anschließend verfügbar.
350 on_id=$(uci_get on-core.settings.on_id
"$(get_on_core_default on_id_preset)")
351 ipschema=$(get_on_core_default on_ipschema)
352 get_on_ip
"$on_id" "$ipschema" 0
356 # Pruefe ob eine PID-Datei existiert und ob die enthaltene PID zu einem Prozess
357 # mit dem angegebenen Namen (nur Dateiname - ohne Pfad) verweist.
358 # Parameter PID-Datei: vollstaendiger Pfad
359 # Parameter Prozess-Name: Dateiname ohne Pfad
361 trap
"error_trap check_pid_file '$*'" $GUARD_TRAPS
363 local process_name=
"$2"
365 local current_process
366 [ -z
"$pid_file" -o ! -e
"$pid_file" ] && trap
"" $GUARD_TRAPS &&
return 1
367 pid=$(cat
"$pid_file" | sed
's/[^0-9]//g')
368 # leere/kaputte PID-Datei
369 [ -z
"$pid" ] && trap
"" $GUARD_TRAPS &&
return 1
370 # Prozess-Datei ist kein symbolischer Link?
371 [ ! -L
"/proc/$pid/exe" ] && trap
"" $GUARD_TRAPS &&
return 1
372 current_process=$(readlink
"/proc/$pid/exe")
373 [
"$process_name" !=
"$(basename "$current_process
")" ] && trap
"" $GUARD_TRAPS &&
return 1
378 ## @fn apply_changes()
379 ## @param configs Einer oder mehrere uci-Sektionsnamen.
380 ## @brief Kombination von uci-commit und anschliessender Inkraftsetzung fuer verschiedene uci-Sektionen.
381 ## @details Dienst-, Netzwerk- und Firewall-Konfigurationen werden bei Bedarf angewandt.
382 ## Zuerst werden alle uci-Sektionen commited und anschliessend werden die Trigger ausgefuehrt.
385 for config in
"$@";
do
386 # Opennet-Module achten auch auf nicht-uci-Aenderungen
387 if echo
"$config" | grep -q
"^on-"; then
388 uci -q commit
"$config" ||
true
390 elif [ -z
"$(uci changes "$config
")" ]; then
397 done | grep -v
"^$" | sort | uniq |
while read config;
do
398 # wir wollen die Aktionen erst nach allen commits ausfuehren
401 reload_config ||
true
403 network|wireless|firewall)
404 reload_config ||
true
405 # Zonen- und IP-Aenderungen koennen Policy-Routing-Aenderungen erfordern
407 # eventuelle Zonen-Zuordnungen zu olsr uebertragen
408 update_olsr_interfaces
411 /etc/init.d/olsrd reload ||
true
414 # eventuell ist das openvpn-Paket nicht installiert (siehe on-migrations)
415 [ -e /etc/init.d/openvpn ] && /etc/init.d/openvpn reload || true
429 update_service_relay_status
432 update_captive_portal_status
433 apply_changes nodogsplash
436 update_monitoring_state
439 # es gibt nichts zu tun
442 msg_error "no handler defined for applying config changes for '$config'"
450 # Setzen einer Opennet-ID.
451 # 1) Hostnamen setzen
452 # 2) IPs fuer alle Opennet-Interfaces setzen
453 # 3) Main-IP in der olsr-Konfiguration setzen
454 # 4) IP des Interface "free" setzen
456 trap
"error_trap set_opennet_id '$*'" $GUARD_TRAPS
466 # ID normalisieren (AP7 -> AP1.7)
467 echo
"$new_id" | grep -q
"\." || new_id=1.$new_id
468 # ON_ID in on-core-Settings setzen
469 prepare_on_uci_settings
470 uci set
"on-core.settings.on_id=$new_id"
471 apply_changes on-core
472 # Hostnamen konfigurieren
473 find_all_uci_sections system system |
while read uci_prefix;
do
474 uci set
"${uci_prefix}.hostname=AP-$(echo "$new_id
" | tr . -)"
477 # IP-Adressen konfigurieren
478 ipschema=$(get_on_core_default on_ipschema)
479 netmask=$(get_on_core_default on_netmask)
480 main_ipaddr=$(get_on_ip
"$new_id" "$ipschema" 0)
481 for network in $(get_sorted_opennet_interfaces);
do
482 uci_prefix=network.$network
483 [
"$(uci_get "${uci_prefix}.proto
")" !=
"static" ] &&
continue
484 ipaddr=$(get_on_ip
"$new_id" "$ipschema" "$if_counter")
485 uci set
"${uci_prefix}.ipaddr=$ipaddr"
486 uci set
"${uci_prefix}.netmask=$netmask"
489 # OLSR-MainIP konfigurieren
490 olsr_set_main_ip
"$main_ipaddr"
491 apply_changes olsrd network
495 # Durchsuche eine Schluessel-Wert-Liste nach einem Schluessel und liefere den dazugehoerigen Wert zurueck.
498 # Der Separator ist konfigurierbar.
499 # Die Liste wird auf der Standardeingabe erwartet.
500 # Der erste und einzige Parameter ist der gewuenschte Schluessel.
501 get_from_key_value_list() {
502 local search_key=
"$1"
506 { sed
's/[ \t]\+/\n/g'; echo; } |
while read key_value;
do
507 key=$(echo
"$key_value" | cut -f 1 -
d "$separator")
508 [
"$key" =
"$search_key" ] && echo
"$key_value" | cut -f 2- -
d "$separator" &&
break ||
true
514 ## @fn replace_in_key_value_list()
515 ## @param search_key der Name des Schlüsselworts
516 ## @param separator der Name des Trennzeichens zwischen Wert und Schlüssel
517 ## @brief Ermittle aus einer mit Tabulatoren oder Leerzeichen getrennten Liste von Schlüssel-Wert-Paaren den Inhalt des Werts zu einem Schlüssel.
518 ## @returns die korrigierte Schlüssel-Wert-Liste wird ausgegeben (eventuell mit veränderten Leerzeichen oder Tabulatoren)
519 replace_in_key_value_list() {
520 local search_key=
"$1"
524 sed
's/[ \t]\+/\n/g' |
while read key_value;
do
525 key=$(echo
"$key_value" | cut -f 1 -
d "$separator")
526 if [
"$key" =
"$search_key" ]; then
527 # nicht ausgeben, falls der Wert leer ist
528 [ -n
"$value" ] && echo -n
" ${key_value}${separator}${value}" ||
true
530 echo -n
" $key_value"
537 # Wandle einen uebergebenene Parameter in eine Zeichenkette um, die sicher als Dateiname verwendet werden kann
538 get_safe_filename() {
539 echo
"$1" | sed
's/[^a-zA-Z0-9._\-]/_/g'
543 ## @fn get_uptime_minutes()
544 ## @brief Ermittle die seit dem Systemstart vergangene Zeit in Minuten
545 ## @details Diese Zeit ist naturgemäß nicht für die Speicherung an Orten geeignet, die einen reboot überleben.
546 get_uptime_minutes() {
547 awk
'{print int($1/60)}' /proc/uptime
551 get_file_modification_timestamp_minutes() {
553 date --reference
"$filename" +%s | awk
'{ print int($1/60) }'
557 ## @fn is_timestamp_older_minutes()
558 ## @param timestamp_minute der zu prüfende Zeitstempel (in Minuten seit dem Systemstart)
559 ## @param difference zulässige Zeitdifferenz zwischen jetzt und dem Zeitstempel
560 ## @brief Prüfe, ob ein gegebener Zeitstempel älter ist, als die vorgegebene Zeitdifferenz.
561 ## @returns Exitcode Null (Erfolg), falls der gegebene Zeitstempel mindestens 'difference' Minuten zurückliegt.
562 # Achtung: Zeitstempel aus der Zukunft oder leere Zeitstempel gelten immer als veraltet.
563 is_timestamp_older_minutes() {
564 local timestamp_minute=
"$1"
565 local difference=
"$2"
566 [ -z
"$timestamp_minute" ] &&
return 0
568 now=
"$(get_uptime_minutes)"
570 [
"$now" -ge
"$((timestamp_minute + difference))" ] &&
return 0
571 # timestamp in future -> invalid -> let's claim it is too old
572 [
"$now" -lt
"$timestamp_minute" ] &&
\
573 msg_info "WARNING: Timestamp from future found: $timestamp_minute (minutes since epoch)" && \
575 trap
"" $GUARD_TRAPS &&
return 1
579 ## @fn get_uptime_seconds()
580 ## @brief Ermittle die Anzahl der Sekunden seit dem letzten Bootvorgang.
581 get_uptime_seconds() {
582 cut -f 1 -
d . /proc/uptime
586 ## @fn run_delayed_in_background()
587 ## @param delay Verzögerung in Sekunden
588 ## @param command alle weiteren Token werden als Kommando und Parameter interpretiert und mit Verzögerung ausgeführt.
589 ## @brief Führe eine Aktion verzögert im Hintergrund aus.
590 run_delayed_in_background() {
593 (sleep
"$delay" &&
"$@") </dev/
null >/dev/
null 2>&1 &
597 ## @fn get_filesize()
598 ## @brief Ermittle die Größe einer Datei in Bytes.
599 ## @params filename Name der zu untersuchenden Datei.
602 wc -c
"$filename" | awk
'{ print $1 }'
607 # Der Name der erzeugten tar-Datei wird als Ergebnis ausgegeben.
609 trap
"error_trap generate_report '$*'" $GUARD_TRAPS
615 temp_dir=$(mktemp -
d)
616 reports_dir=
"$temp_dir/report"
619 # die Skripte duerfen davon ausgehen, dass wir uns im Zielverzeichnis befinden
620 mkdir -p
"$reports_dir"
622 find /usr/lib/opennet/reports -type f |
while read fname;
do
623 [ ! -x
"$fname" ] &&
msg_info "skipping non-executable report script: $fname" &&
continue
624 "$fname" ||
msg_error "reports script failed: $fname"
627 tar czf
"$tar_file" "report"
629 mv
"$tar_file" "$REPORTS_FILE"
633 ## @fn get_potential_error_messages()
634 ## @param max_lines die Angabe einer maximalen Anzahl von Zeilen ist optional - andernfalls werden alle Meldungen ausgegeben
635 ## @brief Filtere aus allen zugänglichen Quellen mögliche Fehlermeldungen.
636 ## @details Falls diese Funktion ein nicht-leeres Ergebnis zurückliefert, kann dies als Hinweis für den
637 ## Nutzer verwendet werden, auf dass er einen Fehlerbericht einreicht.
638 get_potential_error_messages() {
639 local max_lines=${1:-}
641 # 1) get_service_as_csv
642 # Wir ignorieren "get_service_as_csv"-Meldungen - diese werden durch asynchrone Anfragen des
643 # Web-Interface ausgeloest, die beim vorzeitigen Abbruch des Seiten-Lade-Vorgangs mit
644 # einem Fehler enden.
645 filters=
"${filters}|trapped.*get_service_as_csv"
646 # 2) openvpn.*Error opening configuration file
647 # Beim Booten des Systems wurde die openvpn-Config-Datei, die via uci referenziert ist, noch
648 # nicht erzeugt. Beim naechsten cron-Lauf wird dieses Problem behoben.
649 filters=
"${filters}|openvpn.*Error opening configuration file"
650 # 3) openvpn(...)[...]: Exiting due to fatal error
651 # Das Verzeichnis /var/etc/openvpn/ existiert beim Booten noch nicht.
652 filters=
"${filters}|openvpn.*Exiting due to fatal error"
653 # 4) openvpn(...)[...]: SIGUSR1[soft,tls-error] received, process restarting
654 # Diese Meldung taucht bei einem Verbindungsabbruch auf. Dieses Ereignis ist nicht
655 # ungewoehnlich und wird mittels des Verbindungsprotokolls bereits hinreichend gewuerdigt
656 filters=
"${filters}|openvpn.*soft,tls-error"
657 # 5) openvpn(...)[...]: TLS Error: TLS handshake failed
658 # Diese Meldung deutet einen fehlgeschlagenen Verbindungsversuch an. Dies ist nicht
659 # ungewoehnlich (beispielsweise auch fuer Verbindungstests).
660 filters=
"${filters}|openvpn.*TLS Error"
661 # 6) olsrd: /etc/rc.d/S65olsrd: startup-error: check via: '/usr/sbin/olsrd -f "/var/etc/olsrd.conf" -nofork'
662 # Falls noch kein Interface vorhanden ist (z.B. als wifi-Client), dann taucht diese Meldung
664 filters=
"${filters}|olsrd.*startup-error"
666 # Beim Booten tauchen Fehlermeldungen aufgrund nicht konfigurierter Netzwerk-Interfaces auf.
667 # TODO: ucarp nur noch als nachinstallierbares Paket markieren (erfordert Aenderung der Makefile-Erzeugung)
668 filters=
"${filters}|ucarp"
669 # 8) olsrd: /etc/rc.d/S65olsrd: ERROR: there is already an IPv4 instance of olsrd running (pid: '1099'), not starting.
670 # Dieser Fehler tritt auf, wenn der olsrd_check einen olsrd-Neustart ausloest, obwohl er schon laeuft.
671 filters=
"${filters}|olsrd: ERROR: there is already an IPv4 instance of olsrd running"
672 # 9) openvpn(...)[...]: Authenticate/Decrypt packet error
673 # Paketverschiebungen nach dem Verbindungsaufbau - anscheinend unproblematisch.
674 filters=
"${filters}|openvpn.*Authenticate/Decrypt packet error"
675 # 10) olsrd: ... olsrd_setup_smartgw_rules() Warning: kmod-ipip is missing.
676 # olsrd gibt beim Starten generell diese Warnung aus. Wir koennen sie ignorieren.
677 filters=
"${filters}|olsrd.*olsrd_setup_smartgw_rules"
678 # 11) olsrd: ... olsrd_write_interface() Warning: Interface '...' not found, skipped
679 # Falls das wlan-Interface beim Bootvorgang noch nicht aktiv ist, wenn olsrd startet, dann erscheint diese
681 filters=
"${filters}|olsrd.*Interface.*not found"
682 # 12) dropbear[...]: Exit (root): Error reading: Connection reset by peer
683 # Verbindungsverlust einer ssh-Verbindung. Dies darf passieren.
684 filters=
"${filters}|dropbear.*Connection reset by peer"
685 # 13) cron-error: nc.*: short write
686 # Falls die Routen via nc während eines olsrd-Neustarts ausgelesen werden, reisst eventuell die Socket-
687 # Verbindung ab - dies ist akzeptabel.
688 filters=
"${filters}|nc: short write"
689 # 14) openvpn(___service_name___)[...]: write UDPv4: Network is unreachable
690 # Beispielsweise bei einem olsrd-Neustart reisst die Verbindung zum UGW-Server kurz ab.
691 filters=
"${filters}|openvpn.*Network is unreachable"
692 # 15) wget: can't connect to remote host
693 # Eine frühe Geschwindigkeitsmessung (kurz nach dem Booten) darf fehlschlagen.
694 filters=
"${filters}|wget: can.t connect to remote host"
695 # 16) openvpn(...)[...]: Options error: Unrecognized option or missing parameter(s) in [PUSH-OPTIONS]:11: explicit-exit-notify (2.3.6)
696 # OpenVPN-Versionen, die ohne die "--extras"-Option gebaut wurden, unterstuetzen keine exit-Notification.
697 # Dies ist unproblematisch - es ist eher eine Sache der Höflichkeit..
698 filters=
"${filters}|openvpn.*Options error.*explicit-exit-notify"
699 # 17) ddns-scripts[...]: myddns_ipv4: ...
700 # ddns meldet leidet beim Starten einen Fehler, solange es unkonfiguriert ist.
701 filters=
"${filters}|ddns-scripts.*myddns_ipv[46]"
702 # 18) Collected errors:
703 # opkg-Paketinstallationen via Web-Interface erzeugen gelegentlich Fehlermeldungen (z.B. Entfernung
704 # abhängiger Pakete), die dem Nutzer im Web-Interface angezeigt werden. Diese Fehlermeldungen landen
705 # zusätzlich auch im log-Buffer. Da der Nutzer sie bereits gesehen haben dürfte, können wir sie ignorieren
706 # (zumal die konkreten Fehlermeldungen erst in den folgenden Zeilen zu finden und somit schlecht zu filtern
708 filters=
"${filters}|Collected errors:"
709 # 19) uhttpd[...]: sh: write error: Broken pipe
710 # http-Requests die von seiten des Browser abgebrochen wurden
711 filters=
"${filters}|uhttpd.*: sh: write error: Broken pipe"
712 # 20) __main__ get_variable ...
713 # Der obige "Broken pipe"-Fehler unterbricht dabei auch die akuell laufende Funktion - dies ist
714 # sehr häufig die Variablen-Auslesung (seltsamerweise).
715 filters=
"${filters}|__main__ get_variable "
716 # System-Fehlermeldungen (inkl. "trapped")
717 logread | grep -i error | grep -vE
"(${filters#|})" |
if [ -z
"$max_lines" ]; then
718 # alle Einträge ausgeben
721 # nur die letzten Einträge ausliefern
727 # Ersetze eine Zeile durch einen neuen Inhalt. Falls das Zeilenmuster nicht vorhanden ist, wird eine neue Zeile eingefuegt.
728 # Dies entspricht der Funktionalitaet des "lineinfile"-Moduls von ansible.
729 # Parameter filename: der Dateiname
730 # Parameter pattern: Suchmuster der zu ersetzenden Zeile
731 # Parameter new_line: neue Zeile
733 trap
"error_trap line_in_file '$*'" $GUARD_TRAPS
738 # Datei existiert nicht? Einfach mit dieser Zeile erzeugen.
739 [ ! -e
"$filename" ] && echo
"$new_line" >
"$filename" &&
return 0
740 # Datei einlesen - zum Muster passende Zeilen austauschen - notfalls neue Zeile anfuegen
743 echo
"$line" | grep -q
"$pattern" && echo
"$new_line" || echo
"$line"
745 # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war
746 grep -q
"$pattern" "$filename" || echo
"$new_line"
751 # Pruefe, ob eine Liste ein bestimmtes Element enthaelt
752 # Die Listenelemente sind durch beliebigen Whitespace getrennt.
757 for token in $list;
do
758 [
"$token" =
"$target" ] &&
return 0 ||
true
760 # kein passendes Token gefunden
761 trap
"" $GUARD_TRAPS &&
return 1
765 # Liefere den Inhalt einer Variable zurueck.
766 # Dies ist beispielsweise fuer lua-Skripte nuetzlich, da diese nicht den shell-Namensraum teilen.
767 # Paramter: Name der Variable
770 eval
"echo \"\$$var_name\""
774 # Pruefe, ob die angegebene Funktion definiert ist.
775 # Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete.
776 is_function_available() {
778 # "ash" liefert leider nicht den korrekten Wert "function" nach einem Aufruf von "type -t".
779 # Also verwenden wir die Textausgabe von "type".
780 # Die Fehlerausgabe von type wird ignoriert - im Falle der bash gibt es sonst unnoetige Ausgaben.
781 type
"$func_name" 2>/dev/
null | grep -q
"function$" &&
return 0
782 trap
"" $GUARD_TRAPS &&
return 1
787 ## @brief Liefere eine Zufallszahl innerhalb des gegebenen Bereichs.
788 ## @returns Eine zufällige Ganzzahl.
791 echo
"$range" | awk
'{srand(systime()); print int(rand() * $1); }'
795 ## @fn get_local_bias_numer()
796 ## @brief Ermittle eine lokale einzigartige Zahl, die als dauerhaft unveränderlich angenommen werden kann.
797 ## @returns Eine (initial zufällig ermittelte) Zahl zwischen 0 und 10^8-1, die unveränderlich zu diesem AP gehört.
798 ## @details Für ein paar gleichrangige Sortierungen (z.B. verwendete
799 ## UGW-Gegenstellen) benötigen wir ein lokales Salz, um strukturelle
800 ## Bevorzugungen zu vermeiden.
801 get_local_bias_number() {
802 trap
"error_trap get_local_bias_number '$*'" $GUARD_TRAPS
804 bias=$(uci_get on-core.settings.local_bias_number)
805 # der Bias-Wert ist schon vorhanden - wir liefern ihn aus
806 if [ -z
"$bias" ]; then
807 # wir müssen einen Bias-Wert erzeugen: beliebige gehashte Inhalte ergeben eine akzeptable Zufallszahl
808 bias=$(get_random 100000000)
809 uci set
"on-core.settings.local_bias_number=$bias"
812 echo -n
"$bias" &&
return 0
816 ## @fn system_service_check()
817 ## @brief Prüfe ob ein Dienst läuft und ob seine PID-Datei aktuell ist.
818 ## @param executable Der vollständige Pfad zu dem auszuführenden Programm.
819 ## @param pid_file Der Name einer PID-Datei, die von diesem Prozess verwaltet wird.
820 ## @deteils Dabei wird die 'service_check'-Funktion aus der openwrt-Shell-Bibliothek genutzt.
821 system_service_check() {
822 local executable=
"$1"
824 local result=$(set +eu; . /lib/functions/service.sh; SERVICE_PID_FILE=
"$pid_file"; service_check
"$executable" && echo
"ok"; set -eu)
825 [ -n
"$result" ] &&
return 0
826 trap
"" $GUARD_TRAPS &&
return 1
830 ## @fn get_memory_size()
831 ## @brief Ermittle die Größe des Arbeitsspeichers in Megabyte.
832 ## @returns Der Rückgabewert (in Megabyte) ist etwas kleiner als der physische Arbeitsspeicher (z.B. 126 statt 128 MB).
835 memsize_kb=$(grep
"^MemTotal:" /proc/meminfo | sed
's/[^0-9]//g')
836 echo $((memsize_kb / 1024))
840 # Liefere alle Dateien in einem Verzeichnis zurück, die entsprechend der
"run-parts"-Funktionalität
841 # beachtet werden sollten.
842 _get_parts_dir_files() {
845 # Abbruch, falls es das Verzeichnis nicht gibt
846 [ -e
"$parts_dir" ] ||
return 0
847 # ignoriere Dateinamen mit ungueltigen Zeichen (siehe 'man run-parts')
848 find
"$parts_dir" -maxdepth 1 | grep
"/[a-zA-Z0-9_-]\+$" |
while read fname;
do
849 # ignoriere verwaiste symlinks
850 [ -f
"$fname" ] ||
continue
851 # ignoriere Dateien ohne Ausführungsrechte
852 [ -x
"$fname" ] ||
continue
859 ## @brief Führe alle Skripte aus, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen.
860 ## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug.
861 ## Die Dateien müssen ausführbar sein.
863 trap
"error_trap run_parts '$*'" $GUARD_TRAPS
866 _get_parts_dir_files
"$rundir" |
while read fname;
do
867 msg_debug "on-run-parts: executing $fname"
868 # ignoriere Fehler bei der Ausfuehrung
874 ## @fn run_scheduled_tasks()
875 ## @brief Führe die zwischenzeitlich für die spätere Ausführung vorgemerkten Aufgaben aus.
876 ## @details Unabhängig vom Ausführungsergebnis wird das Skript anschließend gelöscht.
877 run_scheduled_tasks() {
878 trap
"error_trap run_scheduled_tasks '$*'" $GUARD_TRAPS
881 [ -
d "$SCHEDULING_DIR" ] ||
return 0
882 find
"$SCHEDULING_DIR" -type f | grep -v
"\.running$" |
while read fname;
do
883 temp_fname=
"${fname}.running"
884 # zuerst schnell wegbewegen, damit wir keine Ereignisse verpassen
885 # Im Fehlerfall (eine race condition) einfach beim naechsten Eintrag weitermachen.
886 mv
"$fname" "$temp_fname" 2>/dev/
null ||
continue
887 (/bin/sh
"$temp_fname" | logger -t
"on-scheduled") 2>&1 | logger -t
"on-scheduled-error"
893 ## @fn schedule_task()
894 ## @brief Erzeuge ein Start-Skript für die baldige Ausführung einer Aktion.
895 ## @details Diese Methode sollte für Aufgaben verwendet werden, die nicht unmittelbar ausgeführt
896 ## werden müssen und im Zweifelsfall nicht parallel ablaufen sollen (ressourcenschonend).
898 trap
"error_trap schedule_task '$*'" $GUARD_TRAPS
901 script_content=$(cat -)
902 # wir sorgen fuer die Wiederverwendung des Dateinamens, um doppelte Ausführungen zu verhindern
903 unique_key=$(echo
"$script_content" | md5sum | awk
'{ print $1 }')
904 mkdir -p
"$SCHEDULING_DIR"
905 local target_file=
"$SCHEDULING_DIR/$unique_key"
906 # das Skript existiert? Nichts zu tun ...
907 [ -e
"$target_file" ] &&
return 0
908 echo
"$script_content" >
"$target_file"
912 ## @fn schedule_parts()
913 ## @brief Merke alle Skripte in einem Verzeichnis für die spätere Ausführung via 'run_scheduled_tasks' vor.
914 ## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug.
915 ## Die Dateien müssen ausführbar sein.
917 trap
"error_trap schedule_parts '$*'" $GUARD_TRAPS
918 local schedule_dir=
"$1"
920 _get_parts_dir_files
"$schedule_dir" |
while read fname;
do
921 msg_debug "on-schedule-parts: scheduling $fname"
922 # ignoriere Fehler bei der Ausfuehrung
923 echo
"$fname" | schedule_task
928 ## @fn read_data_bytes()
929 ## @brief Bytes von einem Blockdevice lesen
930 ## @param source das Quell-Blockdevice (oder die Datei)
931 ## @param size die Anzahl der zu uebertragenden Bytes
932 ## @param transfer_blocksize die Blockgroesse bei der Uebertragung (Standard: 65536)
933 ## @details Die verwendete Uebertragung in grossen Bloecken ist wesentlich schneller als das byteweise EinlesenaKopie.sh_backup
934 ## Der abschliessende unvollstaendige Block wird byteweise eingelesen.
937 local transfer_blocksize=
"${2:-65536}"
938 # "conv=sync" ist fuer die "yes"-Quelle erforderlich - sonst fehlt gelegentlich der letzte Block.
939 # Es scheint sich dazu bei um eine race-condition zu handeln.
940 dd
"bs=$transfer_blocksize" "count=$((size / transfer_blocksize))" conv=sync 2>/dev/
null
941 [
"$((size % transfer_blocksize))" -ne 0 ] && dd bs=1
"count=$((size % transfer_blocksize))" 2>/dev/
null
946 ## @fn get_flash_backup()
947 ## @brief Erzeuge einen rohen Dump des Flash-Speichers. Dieser ermöglicht den Austausch des Flash-Speichers.
948 ## @param include_private Kopiere neben den nur-Lese-Bereichen auch die aktuelle Konfiguration inkl. eventueller privater Daten.
949 ## @details Alle mtd-Partition bis auf den Kernel und die Firmware werden einzeln kopiert und dann komprimiert.
950 ## Beispiel-Layout einer Ubiquiti Nanostation:
951 ## dev: size erasesize name
952 ## mtd0: 00040000 00010000 "u-boot"
953 ## mtd1: 00010000 00010000 "u-boot-env"
954 ## mtd2: 00760000 00010000 "firmware"
955 ## mtd3: 00102625 00010000 "kernel"
956 ## mtd4: 0065d9db 00010000 "rootfs"
957 ## mtd5: 00230000 00010000 "rootfs_data"
958 ## mtd6: 00040000 00010000 "cfg"
959 ## mtd7: 00010000 00010000 "EEPROM"
960 ## Dabei ignorieren wir bei Bedarf "rootfs_data" (beschreibbarer Bereich der Firmware).
962 trap
"error_trap get_flash_backup '$*'" $GUARD_TRAPS
963 local include_private=
"${1:-}"
968 grep
"^mtd[0-9]\+:" /proc/mtd |
while read name size blocksize label;
do
969 # abschliessenden Doppelpunkt entfernen
971 # hexadezimal-Zahl umrechnen
972 size=$(echo | awk
"{print 0x$size }")
973 # Anfuehrungszeichen entfernen
974 label=$(echo
"$label" | cut -f 2 -
d '"')
975 # Firmware-Partitionen ueberspringen
976 if [
"$label" =
"rootfs" ]; then
977 local rootfs_device=
"/dev/$name"
978 local rootfs_full_size=
"$size"
979 elif [
"$label" =
"rootfs_data" ]; then
980 # schreibe das komplette rootfs _ohne_ das aktuelle rootfs_data
981 echo >&2
"Read: root-RO $((rootfs_full_size - size))"
982 # Transfer blockweise vornehmen - byteweise dauert es zu lang
983 read_data_bytes
"($((rootfs_full_size - size)))" <
"$rootfs_device"
984 if [ -z
"$include_private" ]; then
985 echo >&2
"Read: root-zero ($size)"
986 # erzeuge 0xFF auf obskure Weise (fuer maximale Flash-Schreibgeschwindigkeit)
987 # siehe http://stackoverflow.com/a/10905109
988 yes $
'\xFF' | tr -
d '\n' | read_data_bytes
"$size"
989 #yes $'\xFF' | tr -d '\n' | dd bs=1 "count=$size"
991 #while [ "$count" -lt "$size" ]; do
996 echo >&2
"Read: root-RW ($size)"
997 # auch das private rootfs-Dateisystem (inkl. Schluessel, Passworte, usw.) auslesen
998 read_data_bytes
"$size" <
"/dev/$name"
1000 elif [
"$label" =
"firmware" ]; then
1001 echo >&2
"Skip: $label ($size)"
1002 # ignoriere die meta-Partition (kernel + rootfs)
1005 echo >&2
"Read: $label ($size)"
1012 ## @fn has_flash_or_filesystem_error_indicators()
1013 ## @brief Prüfe ob typische Indikatoren (vor allem im Kernel-Log) vorliegen, die auf einen Flash-Defekt hinweisen.
1014 has_flash_or_filesystem_error_indicators() {
1015 trap
"error_trap get_flash_backup '$*'" $GUARD_TRAPS
1016 dmesg | grep -q
"jffs2.*CRC" &&
return 0
1017 dmesg | grep -q
"SQUASHFS error" &&
return 0
1018 # keine Hinweise gefunden -> wir liefern "nein"
1019 trap
"" $GUARD_TRAPS &&
return 1
1022 # Ende der Doku-Gruppe
uci_delete(uci_path)
Lösche ein UCI-Element.
get_services(service_type)
Liefere alle Dienste zurueck, die dem angegebenen Typ zugeordnet sind. Falls kein Typ angegben wird...
add_banner_event(event, timestamp)
Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
update_file_if_changed(target_filename)
Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
append_to_custom_log(log_name, event)
Hänge eine neue Nachricht an ein spezfisches Protokoll an.
get_custom_log_filename(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück...
update_ntp_servers()
Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
update_dns_servers()
Übertrage die Liste der als DNS-Dienst announcierten Server in die dnsmasq-Konfiguration.
filter_reachable_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die erreichbar sind.
filter_enabled_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die nicht manuell ausgeblendet wurden...
uci_add_list(uci_path, new_item)
Füge einen neuen Wert zu einer UCI-Liste hinzu und achte dabei auf Einmaligkeit.
update_on_usergw_status()
Baue Verbindungen auf oder trenne sie - je nach Modul-Status.
clean_restart_log()
Alle Log-Einträge aus der banner-Datei entfernen.
_get_file_dict_value(key)
Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom.
get_mig_tunnel_servers()
Ermittle die Server für den gewünschen Dienst, die via Tunnel erreichbar sind. stype Dienst-Typ (z...
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
get_custom_log_content(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück...
captive_portal_reload()
Neukonfiguration der Captive-Portal-Software, falls Änderungen aufgetreten sind.
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.
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
initialize_olsrd_policy_routing()
Policy-Routing-Initialisierung nach dem System-Boot und nach Interface-Hotplug-Ereignissen.
update_mig_connection_status()
Je nach Status des Moduls: prüfe die VPN-Verbindungen bis mindestens eine Verbindung aufgebaut wurde ...