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 23 # Notfall-DNS-Eintrag, falls wir noch keine nameservice-Nachrichten erhalten haben 24 # aktuelle UGW-Server, sowie der DNS-Server von FoeBuD (https://digitalcourage.de/support/zensurfreier-dns-server) 25 FALLBACK_DNS_SERVERS=
"192.168.0.246 192.168.0.247 192.168.0.248 85.214.20.141" 26 # fuer Insel-UGWs benoetigen wir immer einen korrekten NTP-Server, sonst schlaegt die mesh-Verbindung fehl 27 # aktuelle UGW-Server, sowie der openwrt-Pool 28 FALLBACK_NTP_SERVERS=
"192.168.0.246 192.168.0.247 192.168.0.248 0.openwrt.pool.ntp.org" 29 LATEST_STABLE_FIRMWARE_BASE_URL=
"https://downloads.opennet-initiative.de/openwrt/stable/latest" 30 LATEST_STABLE_FIRMWARE_VERSION_INFO_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/version.txt" 31 LATEST_STABLE_FIRMWARE_UPGRADE_MAP_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/device-upgrade-map.csv" 32 CRON_LOCK_FILE=/var/run/on-cron.lock
33 CRON_LOCK_MAX_AGE_MINUTES=15
34 CRON_LOCK_WAIT_TIMEOUT_SECONDS=30
35 OPENNET_API_URL=
"https://api.opennet-initiative.de/api/v1" 38 # Aufteilung ueberlanger Zeilen 40 local line_length=
"$1" 41 # ersetze alle whitespace-Zeichen durch Nul 42 # Gib anschliessend soviele Token wie moeglich aus, bis die Zeilenlaenge erreicht ist. 43 tr
'\n\t ' '\0' | xargs -0 -s
"$line_length" echo
48 ## @param message Debug-Nachricht 49 ## @brief Debug-Meldungen ins syslog schreiben 50 ## @details Die Debug-Nachrichten landen im syslog (siehe ``logread``). 51 ## Falls das aktuelle Log-Level bei ``info`` oder niedriger liegt, wird keine Nachricht ausgegeben. 53 # bei der ersten Ausfuehrung dauerhaft speichern 54 [ -z
"$DEBUG_ENABLED" ] && \
55 DEBUG_ENABLED=$(uci_is_true
"$(uci_get on-core.settings.debug false)" && echo 1 || echo 0)
56 [
"$DEBUG_ENABLED" =
"0" ] || echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -t
"$(basename "$0
")[$$]" 61 ## @param message Log-Nachricht 62 ## @brief Informationen und Fehlermeldungen ins syslog schreiben 63 ## @details Die Nachrichten landen im syslog (siehe ``logread``). 64 ## Die info-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt. 66 echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -t
"$(basename "$0
")[$$]" 71 ## @param message Fehlermeldung 72 ## @brief Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben 73 ## @details Jede Meldung wird mit "ERROR" versehen, damit diese Meldungen von 74 ## "get_potential_error_messages" erkannt werden. 75 ## Die error-Nachrichten werden immer ausgegeben, da es kein höheres Log-Level als "debug" gibt. 77 echo
"$1" | _split_lines
"$LOG_MESSAGE_LENGTH" | logger -s -t
"$(basename "$0
")[$$]" "[ERROR] $1" 81 ## @fn append_to_custom_log() 82 ## @param log_name Name des Log-Ziels 83 ## @param event die Kategorie der Meldung (up/down/???) 84 ## @param msg die textuelle Beschreibung des Ereignis (z.B. "connection with ... closed") 85 ## @brief Hänge eine neue Nachricht an ein spezfisches Protokoll an. 86 ## @details Die Meldungen werden beispielsweise von den konfigurierten openvpn-up/down-Skripten gesendet. 93 echo
"$(date) openvpn [$event]: $msg" >>
"$logfile" 94 # Datei kuerzen, falls sie zu gross sein sollte
96 filesize=$(get_filesize
"$logfile")
97 [
"$filesize" -gt 10000 ] && sed -i
"1,30d" "$logfile" 102 ## @fn get_custom_log_filename() 103 ## @param log_name Name des Log-Ziels 104 ## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück. 105 ## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert). 108 # der Aufrufer darf sich darauf verlassen, dass er in die Datei schreiben kann 109 mkdir -p
"$LOG_BASE_DIR" 110 echo
"$LOG_BASE_DIR/${log_name}.log" 114 ## @fn get_custom_log_content() 115 ## @param log_name Name des Log-Ziels 116 ## @brief Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück. 117 ## @returns Zeilenweise Ausgabe der Protokollereignisse (aufsteigend nach Zeitstempel sortiert). 122 [ -e
"$logfile" ] ||
return 0
128 ## @param target_filename Name der Zieldatei 129 ## @brief Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte. 130 ## @details Der neue Inhalt der Datei wird auf der Standardeingabe erwartet. 131 ## Im Falle der Gleichheit von aktuellem Inhalt und zukünftigem Inhalt wird 132 ## keine Schreiboperation ausgeführt. Der Exitcode gibt an, ob eine Schreiboperation 133 ## durchgeführt wurde. 134 ## @return exitcode=0 (Erfolg) falls die Datei geändert werden musste 135 ## @return exitcode=1 (Fehler) falls es keine Änderung gab 137 local target_filename=
"$1" 140 if [ -e
"$target_filename" ] && echo
"$content" | cmp -s -
"$target_filename"; then
141 # the content did not change 142 trap
"" EXIT &&
return 1
146 dirname=$(dirname
"$target_filename")
147 [ -
d "$dirname" ] || mkdir -p
"$dirname" 148 echo
"$content" >
"$target_filename" 155 ## @brief Übertrage die Liste der als DNS-Dienst announcierten Server in die dnsmasq-Konfiguration. 156 ## @details Die Liste der DNS-Server wird in die separate dnsmasq-Servers-Datei geschrieben (siehe @sa DNSMASQ_SERVERS_FILE_DEFAULT). 157 ## Die Server-Datei wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts. 158 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden. 160 trap
'error_trap update_dns_servers "$*"' EXIT
164 # wenn wir eine VPN-Tunnel-Verbindung aufgebaut haben, sollten wir DNS-Anfragen über diese Crypto-Verbindung lenken 165 local preferred_servers
167 use_dns=$(uci_get on-core.settings.use_olsrd_dns)
168 #
return if we should not use DNS
servers provided via olsrd
169 uci_is_false
"$use_dns" &&
return 0
172 servers_file=$(uci_get
"dhcp.@dnsmasq[0].serversfile")
173 # aktiviere die "dnsmasq-serversfile"-Direktive, falls noch nicht vorhanden 174 if [ -z
"$servers_file" ]; then
175 servers_file=
"$DNSMASQ_SERVERS_FILE_DEFAULT" 176 uci
set "dhcp.@dnsmasq[0].serversfile=$servers_file" 177 uci commit
"dhcp.@dnsmasq[0]" 180 preferred_servers=$(
if is_function_available
"get_mig_tunnel_servers"; then
get_mig_tunnel_servers "DNS"; fi)
181 # wir sortieren alphabetisch - Naehe ist uns egal
186 [ -n
"$port" ] && [
"$port" !=
"53" ] && host=
"$host#$port" 187 # Host nur schreiben, falls kein bevorzugter Host gefunden wurde 188 [ -z
"$preferred_servers" ] && echo
"server=$host" 189 # Die interne Domain soll vorranging von den via olsrd verbreiteten DNS-Servern bedient werden. 190 # Dies ist vor allem fuer UGW-Hosts wichtig, die über eine zweite DNS-Quelle (lokaler uplink) 192 echo
"server=/$INTERN_DNS_DOMAIN/$host" 194 # eventuell bevorzugte Hosts einfuegen 195 for host in $preferred_servers;
do 199 # falls keine DNS-Namen bekannt sind, dann verwende eine (hoffentlich gueltige) Notfall-Option
200 [ -z
"$server_config" ] && server_config=$(echo
"$FALLBACK_DNS_SERVERS" | tr
' ' '\n' | sed
's/^/server=/')
202 # es gab eine Aenderung
204 # Konfiguration neu einlesen
205 killall -s HUP dnsmasq 2>/dev/
null ||
true 210 ## @brief Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration. 211 ## @details Die Liste der NTP-Server wird in die uci-Konfiguration geschrieben. 212 ## Die uci-Konfiguration wird nur bei Änderungen neu geschrieben. Dasselbe gilt für den Neustart des Diensts. 213 ## Diese Funktion sollte via olsrd-nameservice-Trigger oder via cron-Job ausgeführt werden. 214 ## @sa http://wiki.openwrt.org/doc/uci/system#remote_time_ntp 216 trap
'error_trap update_ntp_servers "$*"' EXIT
220 local preferred_servers
221 local previous_entries
223 previous_entries=$(uci_get
"system.ntp.server")
224 use_ntp=$(uci_get
"on-core.settings.use_olsrd_ntp")
225 # return if we should not use NTP servers provided via olsrd 226 uci_is_false
"$use_ntp" &&
return 227 preferred_servers=$(
if is_function_available
"get_mig_tunnel_servers"; then
get_mig_tunnel_servers "NTP"; fi)
228 # schreibe die Liste der NTP-Server neu
229 # wir sortieren alphabetisch - Naehe ist uns egal
230 if [ -n
"$preferred_servers" ]; then
231 for host in $preferred_servers;
do 238 [ -n
"$port" ] && [
"$port" !=
"123" ] && host=
"$host:$port" 242 # Wir wollen keine leere Liste zurücklassen (z.B. bei einem UGW ohne Mesh-Anbindung). 243 # Also alte Werte wiederherstellen, sowie zusaetzlich die default-Server. 244 # Vor allem fuer den https-Download der UGW-Server-Liste benoetigen wir eine korrekte Uhrzeit. 245 [ -z
"$(uci_get "system.ntp.server
")" ] && \
246 for host in $previous_entries $FALLBACK_NTP_SERVERS;
do uci_add_list "system.ntp.server" "$host";
done 251 ## @fn add_banner_event() 252 ## @param event Ereignistext 253 ## @param timestamp [optional] Der Zeitstempel-Text kann bei Bedarf vorgegeben werden. 254 ## @brief Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu. 255 ## @details Ein Zeitstempel, sowie hübsche Formatierung wird automatisch hinzugefügt. 257 trap
'error_trap add_banner_event "$*"' EXIT
259 # verwende den optionalen zweiten Parameter oder den aktuellen Zeitstempel 260 local timestamp=
"${2:-}" 261 [ -z
"$timestamp" ] && timestamp=$(date)
262 local line=
" - $timestamp - $event -" 263 # Steht unser Text schon im Banner? Ansonsten hinzufuegen ...
264 # bis einschliesslich Version v0.5.0 war
"clean_restart_log" das Schluesselwort
265 # ab v0.5.1 verwenden wir
"system events" 266 if ! grep -qE
'(clean_restart_log|system events)' /etc/banner; then
267 echo
" ------------------- system events -------------------" >>/etc/banner
269 # die Zeile auffuellen
270 while [
"${#line}" -lt 54 ];
do line=
"$line-";
done 271 echo
"$line" >>/etc/banner
276 ## @fn update_mesh_interfaces() 277 ## @brief Update mesh interfaces, routing daemons and policy routing 278 ## @details This function should be called whenever the list of interfaces changes. 281 if is_function_available update_olsr2_interfaces; then
282 update_olsr2_interfaces
287 ## @fn clean_restart_log() 288 ## @brief Alle Log-Einträge aus der banner-Datei entfernen. 290 awk
'{if ($1 != "-") print}' /etc/banner >/tmp/banner
291 mv /tmp/banner /etc/banner
296 ## @fn _get_file_dict_value() 297 ## @param key das Schlüsselwort 298 ## @brief Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom 299 ## @returns Den zum gegebenen Schlüssel gehörenden Wert aus dem Schlüssel/Wert-Eingabestrom 300 ## Falls kein passender Schlüssel gefunden wurde, dann ist die Ausgabe leer. 301 ## @details Jede Zeile der Standardeingabe enthält einen Feldnamen und einen Wert - beide sind durch 302 ## ein beliebiges whitespace-Zeichen getrennt. 303 ## Dieses Dateiformat wird beispielsweise für die Dienst-Zustandsdaten verwendet. 304 ## Zusätzlich ist diese Funktion auch zum Parsen von openvpn-Konfigurationsdateien geeignet. 308 ## @fn _get_file_dict_keys() 309 ## @brief Liefere alle Schlüssel aus einem Schlüssel/Wert-Eingabestrom. 310 ## @returns Liste aller Schlüssel aus dem Schlüssel/Wert-Eingabestrom. 311 ## @sa _get_file_dict_value 312 _get_file_dict_keys() { sed
's/[ \t].*//' "$@" 2>/dev/
null ||
true; }
315 ## @fn _set_file_dict_value() 316 ## @param field das Schlüsselwort 317 ## @param value der neue Wert 318 ## @brief Ersetzen oder Einfügen eines Werts in einen Schlüssel/Wert-Eingabestrom. 319 ## @sa _get_file_dict_value 320 _set_file_dict_value() {
321 local status_file=
"$1" 324 [ -z
"$field" ] &&
msg_error "Ignoring empty key for _set_file_dict_value" &&
return 325 # Filtere bisherige Zeilen mit dem key heraus. 326 # Fuege anschliessend die Zeile mit dem neuen Wert an. 327 # Die Sortierung sorgt fuer gute Vergleichbarkeit, um die Anzahl der 328 # Schreibvorgaenge (=Wahrscheinlichkeit von gleichzeitigem Zugriff) zu reduzieren. 330 grep -v -w -s
"$field" "$status_file" ||
true 331 echo
"$field $new_value" 336 ## @fn get_on_core_default()
337 ## @param key Name des Schlüssels 338 ## @brief Liefere einen der default-Werte der aktuellen Firmware zurück (Paket on-core). 339 ## @details Die default-Werte werden nicht von der Konfigurationsverwaltung uci verwaltet. 340 ## Somit sind nach jedem Upgrade imer die neuesten Standard-Werte verfügbar. 341 get_on_core_default() {
347 ## @fn get_on_firmware_version() 348 ## @brief Liefere die aktuelle Firmware-Version zurück. 349 ## @returns Die zurückgelieferte Zeichenkette beinhaltet den Versionsstring (z.B. "0.5.0"). 350 ## @details Per Konvention entspricht die Version jedes Firmware-Pakets der Firmware-Version. 351 ## Um locking-Probleme zu vermeiden, lesen wir den Wert direkt aus der control-Datei des Pakets. 352 ## Das ist nicht schoen - aber leider ist die lock-Datei nicht konfigurierbar. 353 get_on_firmware_version() {
354 trap
'error_trap get_on_firmware_version "$*"' EXIT
355 local status_file=
"${IPKG_INSTROOT:-}/usr/lib/opkg/info/on-core.control" 356 [ -e
"$status_file" ] ||
return 0
357 awk
'{if (/^Version:/) print $2;}' <
"$status_file" 361 ## @fn get_on_firmware_version_latest_stable() 362 ## @brief Liefere die aktuellste bekannte stabile Firmware-Version zurück. 363 get_on_firmware_version_latest_stable() {
364 trap
'error_trap get_on_firmware_version_latest "$*"' EXIT
366 if version=$(http_request
"$LATEST_STABLE_FIRMWARE_VERSION_INFO_URL"); then
369 # In den ersten Minuten nach dem Anschalten ist typischerweise noch keine 370 # Netzwerkverbindung verfügbar - also melden wir keine Fehler. 371 if [
"$(get_uptime_minutes)" -gt 10 ]; then
372 msg_error "Failed to retrieve firmware version. Maybe there is no network connection." 378 get_on_firmware_version_latest_stable_if_outdated() {
379 trap
'error_trap get_on_firmware_version_latest_stable_if_outdated "$*"' EXIT
380 local latest_stable_version
381 local current_numeric_version
382 local most_recent_version
383 latest_stable_version=$(get_on_firmware_version_latest_stable)
384 [ -z
"$latest_stable_version" ] &&
return 0
385 current_numeric_version=$(get_on_firmware_version | sed
's/-unstable-/-/')
386 most_recent_version=$(printf
'%s\n' "$latest_stable_version" "$current_numeric_version" | sort -V | tail -1)
387 if [
"$most_recent_version" !=
"$current_numeric_version" ]; then
388 echo
"$most_recent_version" 393 ## @fn get_on_firmware_upgrade_image_url() 394 ## @brief Versuche die URL eines Aktualisierungs-Image zu ermitteln. 395 ## @details Die Ausgabe ist leer, falls keine neuere stabile Version bekannt ist oder falls die 396 ## Device-Image-Map keinen Eintrag für das Geräte-Modell enthält. 397 get_on_firmware_upgrade_image_url() {
398 trap
'error_trap get_on_firmware_upgrade_image_url "$*"' EXIT
400 local device_model_id
401 local upgrade_image_path
402 if upgrade_map=$(http_request
"$LATEST_STABLE_FIRMWARE_UPGRADE_MAP_URL"); then
403 device_model_id=$(get_device_model_id)
404 if [ -n
"$device_model_id" ]; then
405 upgrade_image_path=$(echo
"$upgrade_map" | awk
'{ if ($1 == "'"$device_model_id"'") print $2; }')
406 if [ -n
"$upgrade_image_path" ]; then
407 echo
"$LATEST_STABLE_FIRMWARE_BASE_URL/$upgrade_image_path" 414 ## @fn get_device_model_id() 415 ## @brief Ermittle die OpenWrt-Bezeichnung des Gerätemodells 416 get_device_model_id() {
417 trap
'error_trap get_device_model_id "$*"' EXIT
418 # siehe /etc/board.json: model->id 420 . /usr/share/libubox/jshn.sh
421 $(jshn -R /etc/board.json)
430 ## @param on_id die ID des AP - z.B. "1.96" oder "2.54" 431 ## @param on_ipschema siehe "get_on_core_default on_ipschema" 432 ## @param interface_number 0..X (das WLAN-Interface ist typischerweise Interface #0) 433 ## @attention Manche Aufrufende verlassen sich darauf, dass *on_id_1* und 434 ## *on_id_2* nach dem Aufruf verfügbar sind (also _nicht_ als "local" 435 ## Variablen deklariert wurden). 438 local on_ipschema=
"$2" 439 local interface_number=
"$3" 442 # das "on_ipschema" erwartet die Variable "no" 443 # shellcheck disable=SC2034 444 local no=
"$interface_number" 445 echo
"$on_id" | grep -q
'\.' || on_id=1.$on_id
446 # shellcheck disable=SC2034 447 on_id_1=$(echo
"$on_id" | cut -
d . -f 1)
448 # shellcheck disable=SC2034
449 on_id_2=$(echo
"$on_id" | cut -
d . -f 2)
450 eval echo
"$on_ipschema" 455 ## @brief Liefere die aktuell konfigurierte Main-IP zurück. 456 ## @returns Die aktuell konfigurierte Main-IP des AP oder die voreingestellte IP. 457 ## @attention Seiteneffekt: die Variablen "on_id_1" und "on_id_2" sind anschließend verfügbar. 462 on_id=$(uci_get on-core.settings.on_id
"$(get_on_core_default on_id_preset)")
463 ipschema=$(get_on_core_default on_ipschema)
464 get_on_ip
"$on_id" "$ipschema" 0
468 ## @fn run_with_cron_lock() 469 ## @details Führe eine Aktion aus, falls das Lock für Cron-Jobs übernommen werden konnte 470 ## @params command alle Parameter werden als auszuführendes Kommando interpretiert 471 run_with_cron_lock() {
473 # Der Timeout ist nötig, weil alle cron-Jobs gleichzeitig gestartet werden. Somit treffen 474 # der minütige und der fünf-minütige cron-Job aufeinandern und möchten dasselbe Lock 475 # halten. Die maximale Wartezeit löst wahrscheinlich die meisten Konflikte. 476 if acquire_lock
"$CRON_LOCK_FILE" "$CRON_LOCK_MAX_AGE_MINUTES" "$CRON_LOCK_WAIT_TIMEOUT_SECONDS"; then
481 rm -f
"$CRON_LOCK_FILE" 487 is_lock_available() {
489 local max_age_minutes=
"$2" 490 # Fehlerfall: die Lock-Datei existiert und ist nicht alt genug 491 [ ! -e
"$lock_file" ] || is_file_timestamp_older_minutes
"$lock_file" "$max_age_minutes" 495 ## @fn acquire_lock() 496 ## @brief Prüfe ob eine Lock-Datei existiert und nicht veraltet ist. 497 ## @details Die folgenden Zustände werden behandelt: 498 ## A) die Datei existiert, ist jedoch veraltet -> Erfolg, Zeitstempel der Datei aktualisieren 499 ## B) die Datei existiert und ist noch nicht veraltet -> Fehlschlag 500 ## C) die Datei existiert nicht -> Erfolg, Datei wird angelegt 501 ## Warte notfalls einen Timeout ab, bis das Lock frei wird. 502 ## @returns Erfolg (Lock erhalten) oder Misserfolg (Lock ist bereits vergeben) 505 local max_age_minutes=
"$2" 508 timeout_limit=$(( $(date +%s) + timeout ))
509 while ! is_lock_available
"$lock_file" "$max_age_minutes";
do 510 if [
"$(date +%s)" -ge
"$timeout_limit" ]; then
511 msg_info "Failed to acquire lock file: $lock_file" 512 trap
"" EXIT &&
return 1
514 sleep
"$(( $(get_random 10) + 1 ))" 521 # Pruefe ob eine PID-Datei existiert und ob die enthaltene PID zu einem Prozess 522 # mit dem angegebenen Namen (nur Dateiname - ohne Pfad) verweist. 523 # Parameter PID-Datei: vollstaendiger Pfad 524 # Parameter Prozess-Name: Dateiname ohne Pfad 526 trap
'error_trap check_pid_file "$*"' EXIT
528 local process_name=
"$2" 530 local current_process
531 if [ -z
"$pid_file" ] || [ ! -e
"$pid_file" ]; then trap
"" EXIT &&
return 1; fi
532 pid=$(sed
's/[^0-9]//g' "$pid_file")
533 # leere/kaputte PID-Datei
534 [ -z
"$pid" ] && trap
"" EXIT &&
return 1
535 # Prozess-Datei ist kein symbolischer Link?
536 [ ! -L
"/proc/$pid/exe" ] && trap
"" EXIT &&
return 1
537 current_process=$(readlink
"/proc/$pid/exe")
538 [
"$process_name" !=
"$(basename "$current_process
")" ] && trap
"" EXIT &&
return 1
543 ## @fn apply_changes() 544 ## @param configs Einer oder mehrere uci-Sektionsnamen. 545 ## @brief Kombination von uci-commit und anschliessender Inkraftsetzung fuer verschiedene uci-Sektionen. 546 ## @details Dienst-, Netzwerk- und Firewall-Konfigurationen werden bei Bedarf angewandt. 547 ## Zuerst werden alle uci-Sektionen commited und anschliessend werden die Trigger ausgefuehrt. 550 # Zuerst werden alle Änderungen committed und anschließend die (veränderten) Konfiguration 551 # für den Aufruf der hook-Skript verwandt. 552 for config in
"$@";
do 553 # Opennet-Module achten auch auf nicht-uci-Aenderungen 554 if echo
"$config" | grep -q
"^on-"; then
555 uci -q commit
"$config" ||
true 557 elif [ -z
"$(uci -q changes "$config
")" ]; then
564 done | grep -v
"^$" | sort | uniq |
while read -r config;
do 565 run_parts
"${IPKG_INSTROOT:-}/usr/lib/opennet/hooks.d" "$config" 571 # Setzen einer Opennet-ID. 572 # 1) Hostnamen setzen 573 # 2) IPs fuer alle Opennet-Interfaces setzen 574 # 3) Main-IP in der olsr-Konfiguration setzen 575 # 4) IP des Interface "free" setzen 577 trap
'error_trap set_opennet_id "$*"' EXIT
586 # ID normalisieren (AP7 -> AP1.7) 587 echo
"$new_id" | grep -q
'\.' || new_id=1.$new_id
588 # ON_ID in on-core-Settings setzen 589 prepare_on_uci_settings
590 uci
set "on-core.settings.on_id=$new_id" 591 apply_changes on-core
592 # Hostnamen konfigurieren 593 find_all_uci_sections system system |
while read -r uci_prefix;
do 594 uci
set "${uci_prefix}.hostname=AP-$(echo "$new_id
" | tr . -)" 597 # IP-Adressen konfigurieren 598 ipschema=$(get_on_core_default on_ipschema)
599 netmask=$(get_on_core_default on_netmask)
600 main_ipaddr=$(get_on_ip
"$new_id" "$ipschema" 0)
601 for network in $(get_sorted_opennet_interfaces);
do 602 uci_prefix=network.$network
603 [
"$(uci_get "${uci_prefix}.proto
")" !=
"static" ] &&
continue 604 ipaddr=$(get_on_ip
"$new_id" "$ipschema" "$if_counter")
605 uci
set "${uci_prefix}.ipaddr=$ipaddr" 606 uci
set "${uci_prefix}.netmask=$netmask" 607 if_counter=$((if_counter + 1))
609 # OLSR-MainIP konfigurieren
610 olsr_set_main_ip
"$main_ipaddr" 611 apply_changes olsrd network
615 # Durchsuche eine Schluessel-Wert-Liste nach einem Schluessel und liefere den dazugehoerigen Wert zurueck.
618 # Der Separator ist konfigurierbar.
619 # Die Liste wird auf der Standardeingabe erwartet.
620 # Der erste und einzige Parameter ist der gewuenschte Schluessel.
621 get_from_key_value_list() {
622 local search_key=
"$1" 626 { sed
's/[ \t]\+/\n/g'; echo; } |
while read -r key_value;
do 627 key=$(echo
"$key_value" | cut -f 1 -
d "$separator")
628 [
"$key" =
"$search_key" ] && echo
"$key_value" | cut -f 2- -
d "$separator" &&
break 635 ## @fn replace_in_key_value_list()
636 ## @param search_key der Name des Schlüsselworts 637 ## @param separator der Name des Trennzeichens zwischen Wert und Schlüssel 638 ## @brief Ermittle aus einer mit Tabulatoren oder Leerzeichen getrennten Liste von Schlüssel-Wert-Paaren den Inhalt des Werts zu einem Schlüssel. 639 ## @returns die korrigierte Schlüssel-Wert-Liste wird ausgegeben (eventuell mit veränderten Leerzeichen oder Tabulatoren) 640 replace_in_key_value_list() {
641 local search_key=
"$1" 644 awk
'BEGIN { found=0; FS="'"$separator"'"; OFS=":"; RS="[ \t]"; ORS=" "; } 645 { if ($1 == "'"$search_key"'") { print "'"$search_key"'", '"$value"'; found=1; } else { print $0; } } 646 END { if (found == 0) print "'"$search_key"'", '"$value"' };' 650 # Wandle einen uebergebenene Parameter in eine Zeichenkette um, die sicher als Dateiname verwendet werden kann 651 get_safe_filename() {
652 echo
"$1" | sed
's/[^a-zA-Z0-9._\-]/_/g' 656 ## @fn get_uptime_minutes() 657 ## @brief Ermittle die seit dem Systemstart vergangene Zeit in Minuten 658 ## @details Diese Zeit ist naturgemäß nicht für die Speicherung an Orten geeignet, die einen reboot überleben. 659 get_uptime_minutes() {
660 awk
'{print int($1/60)}' /proc/uptime
664 ## @fn is_file_timestamp_older_minutes() 665 ## @brief Prüfe ob die Datei älter ist als die angegebene Zahl von Minuten. 666 ## @details Alle Fehlerfälle (Datei existiert nicht, Zeitstempel liegt in der Zukunft, ...) werden 667 ## als "veraltet" gewertet. 668 ## @returns True, falls die Datei existiert und älter als angegeben ist - ansonsten "False" 669 is_file_timestamp_older_minutes() {
670 trap
'error_trap is_file_timestamp_older_minutes "$*"' EXIT
672 local limit_minutes=
"$2" 673 [ -e
"$filename" ] ||
return 0
676 file_timestamp=$(date --reference
"$filename" +%s 2>/dev/
null | awk
'{ print int($1/60) }')
677 # Falls die Datei zwischendurch geloescht wurde, ist das Lock nun frei.
678 [ -z
"$file_timestamp" ] &&
return 0
679 timestamp_now=$(date +%s | awk
'{ print int($1/60) }')
682 # * Zeitstempel in der Zukunft 683 # * Zeitstempel älter als erlaubt 684 if [ -z
"$file_timestamp" ] \
685 || [
"$file_timestamp" -gt
"$timestamp_now" ] \
686 || [
"$((file_timestamp + limit_minutes))" -lt
"$timestamp_now" ]; then
689 trap
"" EXIT &&
return 1
694 ## @fn is_timestamp_older_minutes() 695 ## @param timestamp_minute der zu prüfende Zeitstempel (in Minuten seit dem Systemstart) 696 ## @param difference zulässige Zeitdifferenz zwischen jetzt und dem Zeitstempel 697 ## @brief Prüfe, ob ein gegebener Zeitstempel älter ist, als die vorgegebene Zeitdifferenz. 698 ## @returns Exitcode Null (Erfolg), falls der gegebene Zeitstempel mindestens 'difference' Minuten zurückliegt. 699 # Achtung: Zeitstempel aus der Zukunft oder leere Zeitstempel gelten immer als veraltet. 700 is_timestamp_older_minutes() {
701 local timestamp_minute=
"$1" 702 local difference=
"$2" 703 [ -z
"$timestamp_minute" ] &&
return 0
705 now=
"$(get_uptime_minutes)" 707 [
"$now" -ge
"$((timestamp_minute + difference))" ] &&
return 0
708 # timestamp in future -> invalid -> let's claim it is too old 709 [
"$now" -lt
"$timestamp_minute" ] && \
710 msg_info
"WARNING: Timestamp from future found: $timestamp_minute (minutes since epoch)" && \
712 trap
"" EXIT &&
return 1
716 ## @fn get_uptime_seconds() 717 ## @brief Ermittle die Anzahl der Sekunden seit dem letzten Bootvorgang. 718 get_uptime_seconds() {
719 cut -f 1 -
d . /proc/uptime
723 ## @fn run_delayed_in_background() 724 ## @param delay Verzögerung in Sekunden 725 ## @param command alle weiteren Token werden als Kommando und Parameter interpretiert und mit Verzögerung ausgeführt. 726 ## @brief Führe eine Aktion verzögert im Hintergrund aus. 727 run_delayed_in_background() {
730 (sleep
"$delay" &&
"$@") </dev/
null >/dev/
null 2>&1 &
734 ## @fn get_filesize() 735 ## @brief Ermittle die Größe einer Datei in Bytes. 736 ## @params filename Name der zu untersuchenden Datei. 739 wc -c
"$filename" | awk
'{ print $1 }' 744 # Der Name der erzeugten tar-Datei wird als Ergebnis ausgegeben. 746 trap
'error_trap generate_report "$*"' EXIT
752 temp_dir=$(mktemp -
d)
753 reports_dir=
"$temp_dir/report" 756 # die Skripte duerfen davon ausgehen, dass wir uns im Zielverzeichnis befinden 757 mkdir -p
"$reports_dir" 759 find /usr/lib/opennet/reports -type f | sort |
while read -r fname;
do 760 [ ! -x
"$fname" ] &&
msg_info "skipping non-executable report script: $fname" &&
continue 761 "$fname" ||
msg_error "reports script failed: $fname" 763 # "tar" unterstuetzt "-c" nicht - also komprimieren wir separat 764 tar cC
"$temp_dir" "report" | gzip >
"$tar_file" 766 mv
"$tar_file" "$REPORTS_FILE" 770 ## @fn get_potential_error_messages() 771 ## @param max_lines die Angabe einer maximalen Anzahl von Zeilen ist optional - andernfalls werden alle Meldungen ausgegeben 772 ## @brief Filtere aus allen zugänglichen Quellen mögliche Fehlermeldungen. 773 ## @details Falls diese Funktion ein nicht-leeres Ergebnis zurückliefert, kann dies als Hinweis für den 774 ## Nutzer verwendet werden, auf dass er einen Fehlerbericht einreicht. 775 get_potential_error_messages() {
776 local max_lines=
"${1:-}" 778 # 1) get_service_as_csv 779 # Wir ignorieren "get_service_as_csv"-Meldungen - diese werden durch asynchrone Anfragen des 780 # Web-Interface ausgeloest, die beim vorzeitigen Abbruch des Seiten-Lade-Vorgangs mit 781 # einem Fehler enden. 782 filters=
"${filters}|trapped.*get_service_as_csv" 783 # 2) openvpn.*Error opening configuration file 784 # Beim Booten des Systems wurde die openvpn-Config-Datei, die via uci referenziert ist, noch 785 # nicht erzeugt. Beim naechsten cron-Lauf wird dieses Problem behoben. 786 filters=
"${filters}|openvpn.*Error opening configuration file" 787 # 3) openvpn(...)[...]: Exiting due to fatal error 788 # Das Verzeichnis /var/etc/openvpn/ existiert beim Booten noch nicht. 789 filters=
"${filters}|openvpn.*Exiting due to fatal error" 790 # 4) openvpn(...)[...]: SIGUSR1[soft,tls-error] received, process restarting 791 # Diese Meldung taucht bei einem Verbindungsabbruch auf. Dieses Ereignis ist nicht 792 # ungewoehnlich und wird mittels des Verbindungsprotokolls bereits hinreichend gewuerdigt 793 filters=
"${filters}|openvpn.*soft,tls-error" 794 # 5) openvpn(...)[...]: TLS Error: TLS handshake failed 795 # Diese Meldung deutet einen fehlgeschlagenen Verbindungsversuch an. Dies ist nicht 796 # ungewoehnlich (beispielsweise auch fuer Verbindungstests). 797 filters=
"${filters}|openvpn.*TLS Error" 798 # 6) olsrd: /etc/rc.d/S65olsrd: startup-error: check via: '/usr/sbin/olsrd -f "/var/etc/olsrd.conf" -nofork' 799 # Falls noch kein Interface vorhanden ist (z.B. als wifi-Client), dann taucht diese Meldung 801 filters=
"${filters}|olsrd.*startup-error" 803 # Beim Booten tauchen Fehlermeldungen aufgrund nicht konfigurierter Netzwerk-Interfaces auf. 804 # TODO: ucarp nur noch als nachinstallierbares Paket markieren (erfordert Aenderung der Makefile-Erzeugung) 805 filters=
"${filters}|ucarp" 806 # 8) olsrd: /etc/rc.d/S65olsrd: ERROR: there is already an IPv4 instance of olsrd running (pid: '1099'), not starting. 807 # Dieser Fehler tritt auf, wenn der olsrd_check einen olsrd-Neustart ausloest, obwohl er schon laeuft. 808 filters=
"${filters}|olsrd: ERROR: there is already an IPv4 instance of olsrd running" 809 # 9) openvpn(...)[...]: Authenticate/Decrypt packet error 810 # Paketverschiebungen nach dem Verbindungsaufbau - anscheinend unproblematisch. 811 filters=
"${filters}|openvpn.*Authenticate/Decrypt packet error" 812 # 10) olsrd: ... olsrd_setup_smartgw_rules() Warning: kmod-ipip is missing. 813 # olsrd gibt beim Starten generell diese Warnung aus. Wir koennen sie ignorieren. 814 filters=
"${filters}|olsrd.*olsrd_setup_smartgw_rules" 815 # 11) olsrd: ... olsrd_write_interface() Warning: Interface '...' not found, skipped 816 # Falls das wlan-Interface beim Bootvorgang noch nicht aktiv ist, wenn olsrd startet, dann erscheint diese 818 filters=
"${filters}|olsrd.*Interface.*not found" 819 # 12) dropbear[...]: Exit (root): Error reading: Connection reset by peer 820 # Verbindungsverlust einer ssh-Verbindung. Dies darf passieren. 821 filters=
"${filters}|dropbear.*Connection reset by peer" 822 # 13) cron-error: nc.*: short write 823 # Falls die Routen via nc während eines olsrd-Neustarts ausgelesen werden, reisst eventuell die Socket- 824 # Verbindung ab - dies ist akzeptabel. 825 filters=
"${filters}|nc: short write" 826 # 14) openvpn(___service_name___)[...]: write UDPv4: Network is unreachable 827 # Beispielsweise bei einem olsrd-Neustart reisst die Verbindung zum UGW-Server kurz ab. 828 filters=
"${filters}|openvpn.*Network is unreachable" 829 # 15) wget: can't connect to remote host 830 # Eine frühe Geschwindigkeitsmessung (kurz nach dem Booten) darf fehlschlagen. 831 filters=
"${filters}|wget: can.t connect to remote host" 832 # 16) openvpn(...)[...]: Options error: Unrecognized option or missing parameter(s) in [PUSH-OPTIONS]:11: explicit-exit-notify (2.3.6) 833 # OpenVPN-Versionen, die ohne die "--extras"-Option gebaut wurden, unterstuetzen keine exit-Notification. 834 # Dies ist unproblematisch - es ist eher eine Sache der Höflichkeit.. 835 filters=
"${filters}|openvpn.*Options error.*explicit-exit-notify" 836 # 17) ddns-scripts[...]: myddns_ipv4: ... 837 # ddns meldet leidet beim Starten einen Fehler, solange es unkonfiguriert ist. 838 filters=
"${filters}|ddns-scripts.*myddns_ipv[46]" 839 # 18) Collected errors: 840 # opkg-Paketinstallationen via Web-Interface erzeugen gelegentlich Fehlermeldungen (z.B. Entfernung 841 # abhängiger Pakete), die dem Nutzer im Web-Interface angezeigt werden. Diese Fehlermeldungen landen 842 # zusätzlich auch im log-Buffer. Da der Nutzer sie bereits gesehen haben dürfte, können wir sie ignorieren 843 # (zumal die konkreten Fehlermeldungen erst in den folgenden Zeilen zu finden und somit schlecht zu filtern 845 filters=
"${filters}|Collected errors:" 846 # 19) uhttpd[...]: sh: write error: Broken pipe 847 # http-Requests die von seiten des Browser abgebrochen wurden 848 filters=
"${filters}|uhttpd.*: sh: write error: Broken pipe" 849 # 20) __main__ get_variable ... 850 # Der obige "Broken pipe"-Fehler unterbricht dabei auch die akuell laufende Funktion - dies ist 851 # sehr häufig die Variablen-Auslesung (seltsamerweise). 852 filters=
"${filters}|__main__ get_variable " 853 # 21) ERROR: Linux route add command failed 854 # Beim Aufbau er OpenVPN-Verbindung scheint gelegentlich noch eine alte Route verblieben zu sein. 855 # Diese Meldung ist wohl irrelevant. 856 filters=
"${filters}|ERROR: Linux route add command failed" 857 # 22) ... cannot open proc entry /proc/sys/net/ipv4/conf/none/ ... 858 # olsrd2 versucht auf /proc/-Eintraege zuzugreifen, bevor der Name des Netzwerk-Interface 859 # feststeht ("none"). Ignorieren. 860 filters=
"${filters}|cannot open proc entry /proc/sys/net/ipv4/conf/none/" 861 # 23) RTNETLINK answers: Network is unreachable 862 # bei einem OpenVPN-Verbindungsaufbau gehen die ersten Pakete verloren 863 filters=
"${filters}|RTNETLINK answers: Network is unreachable" 864 # 24) olsrd2: wrote '/var/run/olsrd2_dev' 865 # beim OLSRD2-Start wird diese Meldung auf stderr ausgegeben 866 filters=
"${filters}|olsrd2: wrote .*olsrd2_dev" 867 # 25) nl80211 not found 868 # Während der initialen wireless-Konfigurationsermittlung beim ersten Boot-Vorgang wird 869 # "iw" aufgerufen, auch wenn eventuell kein wifi-Interface vorhanden ist. In diesem Fall 870 # wird der obige Hinweis ausgegeben. 871 filters=
"${filters}|nl80211 not found" 872 # 26) OLSRd2[...]: WARN(os_interface) ...: Error, cannot open proc entry /proc/sys/net/ipv4/conf/on_wifi_1/... No such file or directory 873 # olsrd2 versucht auf /proc/-Eintraege mittels des Namens eines logischen 874 # Netzwerk-Interface (z.B. "on_eth_0") zuzugreifen, obwohl das System nur die physischen 876 filters=
"${filters}|cannot open proc entry /proc/sys/net/ipv4/conf/on_" 877 # 27) OLSRd2[...]: WARN(os_interface) ...: WARNING! Could not disable the IP spoof filter 878 # Im Anschluss an den obigen (26) Fehlversuch, fuer ein logisches Netzwerk-Interface den 879 # rp_filter zu deaktivieren, wird diese Warnung ausgegeben. Sie ist nicht relevant. 880 filters=
"${filters}"'|WARN\(os_interface\).*Could not disable (the IP spoof filter|ICMP redirects)' 881 # 28) MediaTek Nand driver init, version v2.1 Fix AHB virt2phys error 882 # Im Boot-Log des EdgeRouter (ERX) taucht diese informative Meldung auf, die leider das 883 # Wort "error" enthält. 884 filters=
"${filters}"'|MediaTek Nand driver init, version v.* Fix AHB virt2phys error' 885 # 29) ath10k_pci 0000:00:00.0: Direct firmware load for ath10k/pre-cal-pci-0000:00:00.0.bin failed with error -2 886 # Die Nanostation AC loco schreibt folgende Fehlermeldung ins Kernel-Log. 887 # Das WLAN-Interface funktioniert jedoch scheinbar problemlos. 888 filters=
"${filters}"'|ath10k_pci 0000:00:00.0: Direct firmware load for ath10k/pre-cal-pci-0000:00:00.0.bin failed with error -2' 889 # System-Fehlermeldungen (inkl. "trapped") 890 # Frühzeitig Broken-Pipe-Fehler ("uhttpd[...]: sh: write error: Broken pipe") sowie die darauffolgende 891 # Zeile entfernen. Diese Fehler treten auf, wenn der Nutzer das Laden der Webseite unterbricht (z.B. 892 # durch frühe Auswahl einer neuen URL). 893 prefilter=
"uhttpd.*: sh: write error: Broken pipe" 894 # "sed /FOO/{N;d;}" löscht die Muster-Zeile, sowie die direkt nachfolgende 895 logread | sed
"/$prefilter/{N;d;}" | grep -iE
"(error|crash)" | grep -vE
"(${filters#|})" |
if [ -z
"$max_lines" ]; then
896 # alle Einträge ausgeben 899 # nur die letzten Einträge ausliefern 905 # Ersetze eine Zeile durch einen neuen Inhalt. Falls das Zeilenmuster nicht vorhanden ist, wird eine neue Zeile eingefuegt. 906 # Dies entspricht der Funktionalitaet des "lineinfile"-Moduls von ansible. 907 # Parameter filename: der Dateiname 908 # Parameter pattern: Suchmuster der zu ersetzenden Zeile 909 # Parameter new_line: neue Zeile 911 trap
'error_trap line_in_file "$*"' EXIT
916 # Datei existiert nicht? Einfach mit dieser Zeile erzeugen. 917 [ ! -e
"$filename" ] && echo
"$new_line" >
"$filename" &&
return 0
918 # Datei einlesen - zum Muster passende Zeilen austauschen - notfalls neue Zeile anfuegen 920 while read -r line;
do 921 if echo
"$line" | grep -q
"$pattern"; then
922 [ -n
"$new_line" ] && echo
"$new_line" 923 # die Zeile nicht erneut schreiben - alle zukuenftigen Vorkommen loeschen 929 # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war 930 grep -q
"$pattern" "$filename" || echo
"$new_line" 935 # Pruefe, ob eine Liste ein bestimmtes Element enthaelt
936 # Die Listenelemente sind durch beliebigen Whitespace getrennt.
941 for token in $list;
do 942 [
"$token" =
"$target" ] &&
return 0
945 # kein passendes Token gefunden 946 trap
"" EXIT &&
return 1
950 # Liefere den Inhalt einer Variable zurueck. 951 # Dies ist beispielsweise fuer lua-Skripte nuetzlich, da diese nicht den shell-Namensraum teilen. 952 # Paramter: Name der Variable 955 eval
"echo \"\$$var_name\"" 959 # Pruefe, ob die angegebene Funktion definiert ist. 960 # Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete. 961 is_function_available() {
963 # "ash" liefert leider nicht den korrekten Wert "function" nach einem Aufruf von "type -t". 964 # Also verwenden wir die Textausgabe von "type". 965 # Die Fehlerausgabe von type wird ignoriert - im Falle der bash gibt es sonst unnoetige Ausgaben. 966 type
"$func_name" 2>/dev/
null | grep -q
"function$" &&
return 0
967 trap
"" EXIT &&
return 1
972 ## @brief Liefere eine Zufallszahl innerhalb des gegebenen Bereichs. 973 ## @returns Eine zufällige Ganzzahl. 977 # Setze eine "1" vor eine zufällige Anzahl von Ziffern (vermeide Oktal-Zahl-Behandlung). 978 # Begrenze die Anzahl von Ziffern, um Rundungen in awk zu vermeiden. 979 random_number=
"1$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | md5sum | tr -dc "0123456789
" | cut -c 1-6)" 980 printf
"%d %d" "$range" "$random_number" | awk
'{print $2 % $1; }' 984 ## @fn get_local_bias_numer() 985 ## @brief Ermittle eine lokale einzigartige Zahl, die als dauerhaft unveränderlich angenommen werden kann. 986 ## @returns Eine (initial zufällig ermittelte) Zahl zwischen 0 und 10^8-1, die unveränderlich zu diesem AP gehört. 987 ## @details Für ein paar gleichrangige Sortierungen (z.B. verwendete 988 ## UGW-Gegenstellen) benötigen wir ein lokales Salz, um strukturelle 989 ## Bevorzugungen zu vermeiden. 990 get_local_bias_number() {
991 trap
'error_trap get_local_bias_number "$*"' EXIT
993 bias=$(uci_get on-core.settings.local_bias_number)
994 # der Bias-Wert ist schon vorhanden - wir liefern ihn aus
995 if [ -z
"$bias" ]; then
996 # wir müssen einen Bias-Wert erzeugen: beliebige gehashte Inhalte ergeben eine akzeptable Zufallszahl 997 bias=$(get_random 100000000)
998 uci
set "on-core.settings.local_bias_number=$bias" 1001 echo -n
"$bias" &&
return 0
1005 ## @fn system_service_check()
1006 ## @brief Prüfe ob ein Dienst läuft und ob seine PID-Datei aktuell ist. 1007 ## @param executable Der vollständige Pfad zu dem auszuführenden Programm. 1008 ## @param pid_file Der Name einer PID-Datei, die von diesem Prozess verwaltet wird. 1009 ## @deteils Dabei wird die 'service_check'-Funktion aus der openwrt-Shell-Bibliothek genutzt. 1010 system_service_check() {
1011 local executable=
"$1" 1014 # shellcheck disable=SC2034 source=openwrt/package/base-files/files/lib/functions/network.sh 1015 result=$(
set +eu; . /lib/functions/service.sh; SERVICE_PID_FILE=
"$pid_file"; service_check
"$executable" && echo
"ok";
set -eu)
1016 [ -n
"$result" ] &&
return 0
1017 trap
"" EXIT &&
return 1
1021 ## @fn get_memory_size()
1022 ## @brief Ermittle die Größe des Arbeitsspeichers in Megabyte. 1023 ## @returns Der Rückgabewert (in Megabyte) ist etwas kleiner als der physische Arbeitsspeicher (z.B. 126 statt 128 MB). 1026 memsize_kb=$(grep
"^MemTotal:" /proc/meminfo | sed
's/[^0-9]//g')
1027 echo $((memsize_kb / 1024))
1031 # Liefere alle Dateien in einem Verzeichnis zurück, die entsprechend der
"run-parts"-Funktionalität
1032 # beachtet werden sollten.
1033 _get_parts_dir_files() {
1034 local parts_dir=
"$1" 1036 # Abbruch, falls es das Verzeichnis nicht gibt 1037 [ -e
"$parts_dir" ] ||
return 0
1038 # ignoriere Dateinamen mit ungueltigen Zeichen (siehe 'man run-parts') 1039 find
"$parts_dir" -maxdepth 1 | grep
'/[a-zA-Z0-9_-]\+$' | sort |
while read -r fname;
do 1040 # ignoriere verwaiste symlinks 1041 [ -f
"$fname" ] ||
continue 1042 # ignoriere Dateien ohne Ausführungsrechte 1043 [ -x
"$fname" ] ||
continue 1050 ## @brief Führe alle Skripte aus, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen. 1051 ## @param rundir Verzeichnis, das die auszuführenden Skripte enthält 1052 ## @param weitere Paramter (falls erforderlich) 1053 ## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 1054 ## Die Dateien müssen ausführbar sein. 1056 trap
'error_trap run_parts "$*"' EXIT
1060 _get_parts_dir_files
"$rundir" |
while read -r fname;
do 1061 msg_debug "on-run-parts: executing $fname" 1062 # ignoriere Fehler bei der Ausfuehrung 1063 "$fname" "$@" ||
true 1068 ## @fn schedule_parts() 1069 ## @brief Plant die Ausführung aller Skripte, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen. 1070 ## @param rundir Verzeichnis, das die auszuführenden Skripte enthält 1071 ## @param suffix optionaler Suffix wird ungefiltert an jeden auszufühenden Dateinamen gehängt (z.B. '2>&1 | logger -t cron-error') 1072 ## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 1073 ## Die Dateien müssen ausführbar sein. 1075 trap
'error_trap schedule_parts "$*"' EXIT
1077 local suffix=
"${2:-}" 1078 _get_parts_dir_files
"$rundir" |
while read -r fname;
do 1079 if [ -n
"$suffix" ]; then
1080 echo
"$fname $suffix" 1088 ## @fn run_scheduled_tasks() 1089 ## @brief Führe die zwischenzeitlich für die spätere Ausführung vorgemerkten Aufgaben aus. 1090 ## @details Unabhängig vom Ausführungsergebnis wird das Skript anschließend gelöscht. 1091 run_scheduled_tasks() {
1092 trap
'error_trap run_scheduled_tasks "$*"' EXIT
1096 [ -
d "$SCHEDULING_DIR" ] ||
return 0
1097 # keine Ausführung, falls noch mindestens ein alter Task aktiv ist 1098 running_tasks=$(find
"$SCHEDULING_DIR" -type f -name
"*.running" |
while read -r fname;
do 1099 # veraltete Dateien werden geloescht und ignoriert 1100 # wir müssen uns an dem langsamsten Cron-Job orientieren: 1101 # - MTU-Test für UGWs: ca. 5 Minuten 1102 # - update_olsr_services: mehr als 5 Minuten 1103 is_file_timestamp_older_minutes
"$fname" 30 && rm -f
"$fname" &&
continue 1104 # nicht-veraltete Dateien fuehren zum Abbruch der Funktion 1105 msg_info "Skipping 'run_scheduled_task' due to an ongoing operation: $(tail -1 "$fname
")" 1108 [ -n
"$running_tasks" ] &&
return 0
1109 # die ältesten Dateien zuerst ausführen
1110 find
"$SCHEDULING_DIR" -type f | grep -v
'\.running$' | xargs -r ls -tr |
while read -r fname;
do 1111 temp_fname=
"${fname}.running" 1112 # zuerst schnell wegbewegen, damit wir keine Ereignisse verpassen 1113 # Im Fehlerfall (eine race condition) einfach beim naechsten Eintrag weitermachen. 1114 mv
"$fname" "$temp_fname" 2>/dev/
null ||
continue 1115 { /bin/sh
"$temp_fname" | logger -t
"on-scheduled"; } 2>&1 | logger -t
"on-scheduled-error ($fname)" 1121 ## @fn schedule_task() 1122 ## @brief Erzeuge ein Start-Skript für die baldige Ausführung einer Aktion. 1123 ## @details Diese Methode sollte für Aufgaben verwendet werden, die nicht unmittelbar ausgeführt 1124 ## werden müssen und im Zweifelsfall nicht parallel ablaufen sollen (ressourcenschonend). 1126 trap
'error_trap schedule_task "$*"' EXIT
1127 local script_content
1129 script_content=$(cat -)
1130 # wir sorgen fuer die Wiederverwendung des Dateinamens, um doppelte Ausführungen zu verhindern
1131 unique_key=$(echo
"$script_content" | md5sum | awk
'{ print $1 }')
1132 local target_file=
"$SCHEDULING_DIR/$unique_key" 1133 # das Skript existiert? Nichts zu tun ... 1134 [ -e
"$target_file" ] &&
return 0
1135 mkdir -p
"$SCHEDULING_DIR" 1136 echo
"$script_content" >
"$target_file" 1140 ## @fn schedule_parts() 1141 ## @brief Merke alle Skripte in einem Verzeichnis für die spätere Ausführung via 'run_scheduled_tasks' vor. 1142 ## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug. 1143 ## Die Dateien müssen ausführbar sein. 1145 trap
'error_trap schedule_parts "$*"' EXIT
1146 local schedule_dir=
"$1" 1148 _get_parts_dir_files
"$schedule_dir" |
while read -r fname;
do 1149 msg_debug "on-schedule-parts: scheduling $fname" 1150 # ignoriere Fehler bei der Ausfuehrung 1151 echo
"$fname" | schedule_task
1156 ## @fn read_data_bytes() 1157 ## @brief Bytes von einem Blockdevice lesen 1158 ## @param source das Quell-Blockdevice (oder die Datei) 1159 ## @param size die Anzahl der zu uebertragenden Bytes 1160 ## @param transfer_blocksize die Blockgroesse bei der Uebertragung (Standard: 65536) 1161 ## @details Die verwendete Uebertragung in grossen Bloecken ist wesentlich schneller als das byteweise EinlesenaKopie.sh_backup 1162 ## Der abschliessende unvollstaendige Block wird byteweise eingelesen. 1165 local transfer_blocksize=
"${2:-65536}" 1166 # "conv=sync" ist fuer die "yes"-Quelle erforderlich - sonst fehlt gelegentlich der letzte Block. 1167 # Es scheint sich dazu bei um eine race-condition zu handeln. 1168 dd
"bs=$transfer_blocksize" "count=$((size / transfer_blocksize))" conv=sync 2>/dev/
null 1169 [
"$((size % transfer_blocksize))" -ne 0 ] && dd bs=1
"count=$((size % transfer_blocksize))" 2>/dev/
null 1174 ## @fn get_flash_backup() 1175 ## @brief Erzeuge einen rohen Dump des Flash-Speichers. Dieser ermöglicht den Austausch des Flash-Speichers. 1176 ## @param include_private Kopiere neben den nur-Lese-Bereichen auch die aktuelle Konfiguration inkl. eventueller privater Daten. 1177 ## @details Alle mtd-Partition bis auf den Kernel und die Firmware werden einzeln kopiert und dann komprimiert. 1178 ## Beispiel-Layout einer Ubiquiti Nanostation: 1179 ## dev: size erasesize name 1180 ## mtd0: 00040000 00010000 "u-boot" 1181 ## mtd1: 00010000 00010000 "u-boot-env" 1182 ## mtd2: 00760000 00010000 "firmware" 1183 ## mtd3: 00102625 00010000 "kernel" 1184 ## mtd4: 0065d9db 00010000 "rootfs" 1185 ## mtd5: 00230000 00010000 "rootfs_data" 1186 ## mtd6: 00040000 00010000 "cfg" 1187 ## mtd7: 00010000 00010000 "EEPROM" 1188 ## Dabei ignorieren wir bei Bedarf "rootfs_data" (beschreibbarer Bereich der Firmware). 1189 get_flash_backup() {
1190 trap
'error_trap get_flash_backup "$*"' EXIT
1191 local include_private=
"${1:-}" 1196 # shellcheck disable=SC2034 1197 grep
'^mtd[0-9]\+:' /proc/mtd |
while read -r name size blocksize label;
do 1198 # abschliessenden Doppelpunkt entfernen 1200 # hexadezimal-Zahl umrechnen 1201 size=$(echo | awk
"{print 0x$size }")
1202 # Anfuehrungszeichen entfernen
1203 label=$(echo
"$label" | cut -f 2 -
d '"')
1204 # Firmware-Partitionen ueberspringen 1205 if [
"$label" =
"rootfs" ]; then
1206 local rootfs_device=
"/dev/$name" 1207 local rootfs_full_size=
"$size" 1208 elif [
"$label" =
"rootfs_data" ]; then
1209 # schreibe das komplette rootfs _ohne_ das aktuelle rootfs_data 1210 echo >&2
"Read: root-RO $((rootfs_full_size - size))" 1211 # Transfer blockweise vornehmen - byteweise dauert es zu lang 1212 read_data_bytes
"($((rootfs_full_size - size)))" <
"$rootfs_device" 1213 if [ -z
"$include_private" ]; then
1214 echo >&2
"Read: root-zero ($size)" 1216 # siehe http://stackoverflow.com/a/10905109 1217 tr
'\0' '\377' </dev/zero | read_data_bytes
"$size" 1219 echo >&2
"Read: root-RW ($size)" 1220 # auch das private rootfs-Dateisystem (inkl. Schluessel, Passworte, usw.) auslesen 1221 read_data_bytes
"$size" <
"/dev/$name" 1223 elif [
"$label" =
"firmware" ]; then
1224 echo >&2
"Skip: $label ($size)" 1225 # ignoriere die meta-Partition (kernel + rootfs) 1228 echo >&2
"Read: $label ($size)" 1235 ## @fn has_flash_or_filesystem_error_indicators() 1236 ## @brief Prüfe ob typische Indikatoren (vor allem im Kernel-Log) vorliegen, die auf einen Flash-Defekt hinweisen. 1237 has_flash_or_filesystem_error_indicators() {
1238 trap
'error_trap get_flash_backup "$*"' EXIT
1239 dmesg | grep -q
"jffs2.*CRC" &&
return 0
1240 dmesg | grep -q
"SQUASHFS error" &&
return 0
1241 # keine Hinweise gefunden -> wir liefern "nein" 1242 trap
"" EXIT &&
return 1
1246 ## @fn request_field_from_api() 1247 ## @brief Request the content of a field from an API endpoint. 1248 ## @details The response is stored in memory - thus this function should not be used for huge 1250 request_field_from_api() {
1252 local json_query=
"$2" 1254 # wget returns a non-zero exitcode in case of an HTTP error status 1255 data=$(wget -q -O -
"$OPENNET_API_URL/$path" ||
true)
1256 [ -z
"$data" ] &&
return 1257 # the
"jsonfilter" call returns with an error,
if the wanted field does not exist
1258 echo
"$data" | jsonfilter -e
"$json_query" ||
true 1262 get_main_ip_for_ip() {
1264 result=$(request_field_from_api
"/accesspoint/$ip" "@.main_ip")
1265 [ -z
"$result" ] && result=$(request_field_from_api
"/interface/$ip/accesspoint/" "@.main_ip")
1270 get_name_for_main_ip() {
1272 echo
"$main_ip" | sed -E
's/^192\.168\.(\d+)\.(\d+)$/AP\1-\2/' 1276 get_location_for_main_ip() {
1278 request_field_from_api
"/accesspoint/$main_ip" "@.post_address" 1282 # Request a resource via HTTP. In case of HTTPS only public certificates are trusted. 1287 # Ende der Doku-Gruppe 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.
set eu case in network wireless firewall on function update_olsr_interfaces
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.
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.
uci_replace_list()
Replace the items in a list. Wanted items are expected via stdin (one per line, uci_path).
clean_restart_log()
Alle Log-Einträge aus der banner-Datei entfernen.
update_mesh_interfaces()
Update mesh interfaces, routing daemons and policy routing.
_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...
set eu grep root::etc shadow exit if command v chpasswd dev null
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...
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 uci q show dhcp grep dhcp dnsmasq dns uci true for fname in etc resolv conf tmp resolv conf auto var etc dnsmasq conf var run dnsmasq servers