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
8ON_CORE_DEFAULTS_FILE=
"${IPKG_INSTROOT:-}/usr/share/opennet/core.defaults"
9# Pfad zur dnsmasq-Server-Datei zur dynamischen Aktualisierung durch Dienste-Erkennung
10DNSMASQ_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
14REPORTS_FILE=
"${IPKG_INSTROOT:-}/tmp/on_report.tar.gz"
15# Basis-Verzeichnis für Log-Dateien
16LOG_BASE_DIR=
"${IPKG_INSTROOT:-}/var/log"
17# maximum length of message lines (logger seems to resctrict lines incl. timestamp to 512 characters)
19# Verzeichnis für auszuführende Aktionen
20SCHEDULING_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:
25FALLBACK_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
28FALLBACK_NTP_SERVERS=
"192.168.0.246 192.168.0.247 192.168.0.248 0.openwrt.pool.ntp.org"
29LATEST_STABLE_FIRMWARE_BASE_URL=
"https://downloads.opennet-initiative.de/openwrt/stable/latest"
30LATEST_STABLE_FIRMWARE_VERSION_INFO_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/version.txt"
31LATEST_STABLE_FIRMWARE_UPGRADE_MAP_URL=
"$LATEST_STABLE_FIRMWARE_BASE_URL/device-upgrade-map.csv"
32CRON_LOCK_FILE=/var/run/on-cron.lock
33CRON_LOCK_MAX_AGE_MINUTES=15
34CRON_LOCK_WAIT_TIMEOUT_SECONDS=30
35OPENNET_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
127## @fn update_file_if_changed()
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"
154## @fn update_dns_servers()
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]"
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 service dnsmasq restart 2>/dev/
null ||
true
209## @fn update_ntp_servers()
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.
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
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.
280 update_olsr_interfaces
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.
341get_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.
353get_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.
363get_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."
378get_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.
397get_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
416get_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## @param command alle Parameter werden als auszuführendes Kommando interpretiert
471run_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"
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"
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.
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.
621get_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)
640replace_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
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.
659get_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"
669is_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.
700is_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.
718get_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.
727run_delayed_in_background() {
730 (sleep
"$delay" &&
"$@") </dev/
null >/dev/
null 2>&1 &
735## @brief Ermittle die Größe einer Datei in Bytes.
736## @param 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.
775get_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 # 30) kern.err kernel: [..] print_req_error: I/O error, dev loop0, sector 490
890 # Beim Booten eines APU-Geräts tritt wohl die obige Fehlermeldung auf. Sie ist
891 # sicherlich irrelevant. Siehe https:
892 filters=
"${filters}"'|kern.err kernel:.*print_req_error: I/O error, dev loop0, sector '
893 # System-Fehlermeldungen (inkl. "trapped")
894 # Frühzeitig Broken-Pipe-Fehler ("uhttpd[...]: sh: write error: Broken pipe") sowie die darauffolgende
895 # Zeile entfernen. Diese Fehler treten auf, wenn der Nutzer das Laden der Webseite unterbricht (z.B.
896 # durch frühe Auswahl einer neuen URL).
897 prefilter=
"uhttpd.*: sh: write error: Broken pipe"
898 # "sed /FOO/{N;d;}" löscht die Muster-Zeile, sowie die direkt nachfolgende
899 logread | sed
"/$prefilter/{N;d;}" | grep -iE
"(error|crash)" | grep -vE
"(${filters#|})" |
if [ -z
"$max_lines" ]; then
900 # alle Einträge ausgeben
903 # nur die letzten Einträge ausliefern
909# Ersetze eine Zeile durch einen neuen Inhalt. Falls das Zeilenmuster nicht vorhanden ist, wird eine neue Zeile eingefuegt.
910# Dies entspricht der Funktionalitaet des "lineinfile"-Moduls von ansible.
911# Parameter filename: der Dateiname
912# Parameter pattern: Suchmuster der zu ersetzenden Zeile
913# Parameter new_line: neue Zeile
915 trap
'error_trap line_in_file "$*"' EXIT
920 # Datei existiert nicht? Einfach mit dieser Zeile erzeugen.
921 [ ! -e
"$filename" ] && echo
"$new_line" >
"$filename" &&
return 0
922 # Datei einlesen - zum Muster passende Zeilen austauschen - notfalls neue Zeile anfuegen
924 while read -r line;
do
925 if echo
"$line" | grep -q
"$pattern"; then
926 [ -n
"$new_line" ] && echo
"$new_line"
927 # die Zeile nicht erneut schreiben - alle zukuenftigen Vorkommen loeschen
933 # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war
934 grep -q
"$pattern" "$filename" || echo
"$new_line"
939# Pruefe, ob eine Liste ein bestimmtes Element enthaelt
940# Die Listenelemente sind durch beliebigen Whitespace getrennt.
945 for token in $list;
do
946 [
"$token" =
"$target" ] &&
return 0
949 # kein passendes Token gefunden
950 trap
"" EXIT &&
return 1
954# Liefere den Inhalt einer Variable zurueck.
955# Dies ist beispielsweise fuer lua-Skripte nuetzlich, da diese nicht den shell-Namensraum teilen.
956# Paramter: Name der Variable
959 eval
"echo \"\$$var_name\""
963# Pruefe, ob die angegebene Funktion definiert ist.
964# Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete.
965is_function_available() {
967 # "ash" liefert leider nicht den korrekten Wert "function" nach einem Aufruf von "type -t".
968 # Also verwenden wir die Textausgabe von "type".
969 # Die Fehlerausgabe von type wird ignoriert - im Falle der bash gibt es sonst unnoetige Ausgaben.
970 type
"$func_name" 2>/dev/
null | grep -q
"function$" &&
return 0
971 trap
"" EXIT &&
return 1
976## @brief Liefere eine Zufallszahl innerhalb des gegebenen Bereichs.
977## @returns Eine zufällige Ganzzahl.
981 # Setze eine "1" vor eine zufällige Anzahl von Ziffern (vermeide Oktal-Zahl-Behandlung).
982 # Begrenze die Anzahl von Ziffern, um Rundungen in awk zu vermeiden.
983 random_number=
"1$(dd if=/dev/urandom bs=10 count=1 2>/dev/null | md5sum | tr -dc "0123456789
" | cut -c 1-6)"
984 printf
"%d %d" "$range" "$random_number" | awk
'{print $2 % $1; }'
988## @fn get_local_bias_numer()
989## @brief Ermittle eine lokale einzigartige Zahl, die als dauerhaft unveränderlich angenommen werden kann.
990## @returns Eine (initial zufällig ermittelte) Zahl zwischen 0 und 10^8-1, die unveränderlich zu diesem AP gehört.
991## @details Für ein paar gleichrangige Sortierungen (z.B. verwendete
992## UGW-Gegenstellen) benötigen wir ein lokales Salz, um strukturelle
993## Bevorzugungen zu vermeiden.
994get_local_bias_number() {
995 trap
'error_trap get_local_bias_number "$*"' EXIT
997 bias=
$(uci_get on-core.settings.local_bias_number)
998 # der Bias-Wert ist schon vorhanden - wir liefern ihn aus
999 if [ -z
"$bias" ]; then
1000 # wir müssen einen Bias-Wert erzeugen: beliebige gehashte Inhalte ergeben eine akzeptable Zufallszahl
1001 bias=
$(get_random 100000000)
1002 uci set
"on-core.settings.local_bias_number=$bias"
1005 echo -n
"$bias" &&
return 0
1009## @fn system_service_check()
1010## @brief Prüfe ob ein Dienst läuft und ob seine PID-Datei aktuell ist.
1011## @param executable Der vollständige Pfad zu dem auszuführenden Programm.
1012## @param pid_file Der Name einer PID-Datei, die von diesem Prozess verwaltet wird.
1013## @deteils Dabei wird die 'service_check'-Funktion aus der openwrt-Shell-Bibliothek genutzt.
1014system_service_check() {
1015 local executable=
"$1"
1018 # shellcheck disable=SC2034 source=openwrt/package/base-files/files/lib/functions/network.sh
1019 result=
$(set +eu; . /lib/functions/service.sh; SERVICE_PID_FILE=
"$pid_file"; service_check
"$executable" && echo
"ok"; set -eu)
1020 [ -n
"$result" ] &&
return 0
1021 trap
"" EXIT &&
return 1
1025## @fn get_memory_size()
1026## @brief Ermittle die Größe des Arbeitsspeichers in Megabyte.
1027## @returns Der Rückgabewert (in Megabyte) ist etwas kleiner als der physische Arbeitsspeicher (z.B. 126 statt 128 MB).
1030 memsize_kb=
$(grep
"^MemTotal:" /proc/meminfo | sed
's/[^0-9]//g')
1031 echo
$((memsize_kb / 1024))
1035# Liefere alle Dateien in einem Verzeichnis zurück, die entsprechend der "run-parts"-Funktionalität
1036# beachtet werden sollten.
1037_get_parts_dir_files() {
1038 local parts_dir=
"$1"
1040 # Abbruch, falls es das Verzeichnis nicht gibt
1041 [ -e
"$parts_dir" ] ||
return 0
1042 # ignoriere Dateinamen mit ungueltigen Zeichen (siehe 'man run-parts')
1043 find
"$parts_dir" -maxdepth 1 | grep
'/[a-zA-Z0-9_-]\+$' | sort |
while read -r fname;
do
1044 # ignoriere verwaiste symlinks
1045 [ -f
"$fname" ] ||
continue
1046 # ignoriere Dateien ohne Ausführungsrechte
1047 [ -x
"$fname" ] ||
continue
1054## @brief Führe alle Skripte aus, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen.
1055## @param rundir Verzeichnis, das die auszuführenden Skripte enthält
1056## @param weitere Paramter (falls erforderlich)
1057## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug.
1058## Die Dateien müssen ausführbar sein.
1060 trap
'error_trap run_parts "$*"' EXIT
1064 _get_parts_dir_files
"$rundir" |
while read -r fname;
do
1065 msg_debug "on-run-parts: executing $fname"
1066 # ignoriere Fehler bei der Ausfuehrung
1067 "$fname" "$@" ||
true
1072## @fn schedule_parts()
1073## @brief Plant die Ausführung aller Skripte, die in einem bestimmten Verzeichnis liegen und gewissen Konventionen genügen.
1074## @param rundir Verzeichnis, das die auszuführenden Skripte enthält
1075## @param suffix optionaler Suffix wird ungefiltert an jeden auszufühenden Dateinamen gehängt (z.B. '2>&1 | logger -t cron-error')
1076## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug.
1077## Die Dateien müssen ausführbar sein.
1079 trap
'error_trap schedule_parts "$*"' EXIT
1081 local suffix=
"${2:-}"
1082 _get_parts_dir_files
"$rundir" |
while read -r fname;
do
1083 if [ -n
"$suffix" ]; then
1084 echo
"$fname $suffix"
1092## @fn run_scheduled_tasks()
1093## @brief Führe die zwischenzeitlich für die spätere Ausführung vorgemerkten Aufgaben aus.
1094## @details Unabhängig vom Ausführungsergebnis wird das Skript anschließend gelöscht.
1095run_scheduled_tasks() {
1096 trap
'error_trap run_scheduled_tasks "$*"' EXIT
1100 [ -
d "$SCHEDULING_DIR" ] ||
return 0
1101 # keine Ausführung, falls noch mindestens ein alter Task aktiv ist
1102 running_tasks=
$(find
"$SCHEDULING_DIR" -type f -name
"*.running" |
while read -r fname;
do
1103 # veraltete Dateien werden geloescht und ignoriert
1104 # wir müssen uns an dem langsamsten Cron-Job orientieren:
1105 # - MTU-Test für UGWs: ca. 5 Minuten
1106 # - update_olsr_services: mehr als 5 Minuten
1107 is_file_timestamp_older_minutes
"$fname" 30 && rm -f
"$fname" &&
continue
1108 # nicht-veraltete Dateien fuehren zum Abbruch der Funktion
1109 msg_info "Skipping 'run_scheduled_task' due to an ongoing operation: $(tail -1 "$fname
")"
1112 [ -n
"$running_tasks" ] &&
return 0
1113 # die ältesten Dateien zuerst ausführen
1114 find
"$SCHEDULING_DIR" -type f | grep -v
'\.running$' | xargs -r ls -tr |
while read -r fname;
do
1115 temp_fname=
"${fname}.running"
1116 # zuerst schnell wegbewegen, damit wir keine Ereignisse verpassen
1117 # Im Fehlerfall (eine race condition) einfach beim naechsten Eintrag weitermachen.
1118 mv
"$fname" "$temp_fname" 2>/dev/
null ||
continue
1119 { /bin/sh
"$temp_fname" | logger -t
"on-scheduled"; } 2>&1 | logger -t
"on-scheduled-error ($fname)"
1125## @fn schedule_task()
1126## @brief Erzeuge ein Start-Skript für die baldige Ausführung einer Aktion.
1127## @details Diese Methode sollte für Aufgaben verwendet werden, die nicht unmittelbar ausgeführt
1128## werden müssen und im Zweifelsfall nicht parallel ablaufen sollen (ressourcenschonend).
1130 trap
'error_trap schedule_task "$*"' EXIT
1131 local script_content
1133 script_content=
$(cat -)
1134 # wir sorgen fuer die Wiederverwendung des Dateinamens, um doppelte Ausführungen zu verhindern
1135 unique_key=
$(echo
"$script_content" | md5sum | awk
'{ print $1 }')
1136 local target_file=
"$SCHEDULING_DIR/$unique_key"
1137 # das Skript existiert? Nichts zu tun ...
1138 [ -e
"$target_file" ] &&
return 0
1139 mkdir -p
"$SCHEDULING_DIR"
1140 echo
"$script_content" >
"$target_file"
1144## @fn schedule_parts()
1145## @brief Merke alle Skripte in einem Verzeichnis für die spätere Ausführung via 'run_scheduled_tasks' vor.
1146## @details Die Namenskonventionen und das Verhalten entspricht dem verbreiteten 'run-parts'-Werkzeug.
1147## Die Dateien müssen ausführbar sein.
1149 trap
'error_trap schedule_parts "$*"' EXIT
1150 local schedule_dir=
"$1"
1152 _get_parts_dir_files
"$schedule_dir" |
while read -r fname;
do
1153 msg_debug "on-schedule-parts: scheduling $fname"
1154 # ignoriere Fehler bei der Ausfuehrung
1155 echo
"$fname" | schedule_task
1160## @fn read_data_bytes()
1161## @brief Bytes von einem Blockdevice lesen
1162## @param source das Quell-Blockdevice (oder die Datei)
1163## @param size die Anzahl der zu uebertragenden Bytes
1164## @param transfer_blocksize die Blockgroesse bei der Uebertragung (Standard: 65536)
1165## @details Die verwendete Uebertragung in grossen Bloecken ist wesentlich schneller als das byteweise EinlesenaKopie.sh_backup
1166## Der abschliessende unvollstaendige Block wird byteweise eingelesen.
1169 local transfer_blocksize=
"${2:-65536}"
1170 # "conv=sync" ist fuer die "yes"-Quelle erforderlich - sonst fehlt gelegentlich der letzte Block.
1171 # Es scheint sich dazu bei um eine race-condition zu handeln.
1172 dd
"bs=$transfer_blocksize" "count=$((size / transfer_blocksize))" conv=sync 2>/dev/
null
1173 [
"$((size % transfer_blocksize))" -ne 0 ] && dd bs=1
"count=$((size % transfer_blocksize))" 2>/dev/
null
1178## @fn get_flash_backup()
1179## @brief Erzeuge einen rohen Dump des Flash-Speichers. Dieser ermöglicht den Austausch des Flash-Speichers.
1180## @param include_private Kopiere neben den nur-Lese-Bereichen auch die aktuelle Konfiguration inkl. eventueller privater Daten.
1181## @details Alle mtd-Partition bis auf den Kernel und die Firmware werden einzeln kopiert und dann komprimiert.
1182## Beispiel-Layout einer Ubiquiti Nanostation:
1183## dev: size erasesize name
1184## mtd0: 00040000 00010000 "u-boot"
1185## mtd1: 00010000 00010000 "u-boot-env"
1186## mtd2: 00760000 00010000 "firmware"
1187## mtd3: 00102625 00010000 "kernel"
1188## mtd4: 0065d9db 00010000 "rootfs"
1189## mtd5: 00230000 00010000 "rootfs_data"
1190## mtd6: 00040000 00010000 "cfg"
1191## mtd7: 00010000 00010000 "EEPROM"
1192## Dabei ignorieren wir bei Bedarf "rootfs_data" (beschreibbarer Bereich der Firmware).
1194 trap
'error_trap get_flash_backup "$*"' EXIT
1195 local include_private=
"${1:-}"
1200 # shellcheck disable=SC2034
1201 grep
'^mtd[0-9]\+:' /proc/mtd |
while read -r name size blocksize label;
do
1202 # abschliessenden Doppelpunkt entfernen
1204 # hexadezimal-Zahl umrechnen
1205 size=
$(echo | awk
"{print 0x$size }")
1206 # Anfuehrungszeichen entfernen
1207 label=
$(echo
"$label" | cut -f 2 -
d '"')
1208 # Firmware-Partitionen ueberspringen
1209 if [
"$label" =
"rootfs" ]; then
1210 local rootfs_device=
"/dev/$name"
1211 local rootfs_full_size=
"$size"
1212 elif [
"$label" =
"rootfs_data" ]; then
1213 # schreibe das komplette rootfs _ohne_ das aktuelle rootfs_data
1214 echo >&2
"Read: root-RO $((rootfs_full_size - size))"
1215 # Transfer blockweise vornehmen - byteweise dauert es zu lang
1216 read_data_bytes
"($((rootfs_full_size - size)))" <
"$rootfs_device"
1217 if [ -z
"$include_private" ]; then
1218 echo >&2
"Read: root-zero ($size)"
1221 tr
'\0' '\377' </dev/zero | read_data_bytes
"$size"
1223 echo >&2
"Read: root-RW ($size)"
1224 # auch das private rootfs-Dateisystem (inkl. Schluessel, Passworte, usw.) auslesen
1225 read_data_bytes
"$size" <
"/dev/$name"
1227 elif [
"$label" =
"firmware" ]; then
1228 echo >&2
"Skip: $label ($size)"
1229 # ignoriere die meta-Partition (kernel + rootfs)
1232 echo >&2
"Read: $label ($size)"
1239## @fn has_flash_or_filesystem_error_indicators()
1240## @brief Prüfe ob typische Indikatoren (vor allem im Kernel-Log) vorliegen, die auf einen Flash-Defekt hinweisen.
1241has_flash_or_filesystem_error_indicators() {
1242 trap
'error_trap get_flash_backup "$*"' EXIT
1243 dmesg | grep -q
"jffs2.*CRC" &&
return 0
1244 dmesg | grep -q
"SQUASHFS error" &&
return 0
1245 # keine Hinweise gefunden -> wir liefern "nein"
1246 trap
"" EXIT &&
return 1
1250## @fn request_field_from_api()
1251## @brief Request the content of a field from an API endpoint.
1252## @details The response is stored in memory - thus this function should not be used for huge
1254request_field_from_api() {
1256 local json_query=
"$2"
1258 # wget returns a non-zero exitcode in case of an HTTP error status
1259 data=
$(wget -q -O -
"$OPENNET_API_URL/$path" ||
true)
1260 [ -z
"$data" ] &&
return
1261 # the "jsonfilter" call returns with an error, if the wanted field does not exist
1262 echo
"$data" | jsonfilter -e
"$json_query" ||
true
1266get_main_ip_for_ip() {
1268 result=
$(request_field_from_api
"/accesspoint/$ip" "@.main_ip")
1269 [ -z
"$result" ] && result=
$(request_field_from_api
"/interface/$ip/accesspoint/" "@.main_ip")
1274get_name_for_main_ip() {
1276 echo
"$main_ip" | sed -E
's/^192\.168\.(\d+)\.(\d+)$/AP\1-\2/'
1280get_location_for_main_ip() {
1282 request_field_from_api
"/accesspoint/$main_ip" "@.post_address"
1286# Request a URL via https or http.
1291# Ende der Doku-Gruppe
get_custom_log_content(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
append_to_custom_log(log_name, event)
Hänge eine neue Nachricht an ein spezfisches Protokoll an.
_get_file_dict_value(key)
Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom.
update_mesh_interfaces()
Update mesh interfaces, routing daemons and policy routing.
update_ntp_servers()
Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
msg_debug(message)
Debug-Meldungen ins syslog schreiben.
msg_error(message)
Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben.
clean_restart_log()
Alle Log-Einträge aus der banner-Datei entfernen.
update_file_if_changed(target_filename)
Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
get_custom_log_filename(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück.
add_banner_event(event, timestamp)
Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
get_mig_tunnel_servers(stype)
Ermittle die Server für den gewünschen Dienst, die via Tunnel erreichbar sind.
filter_enabled_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die nicht manuell ausgeblendet wurden.
get_services(service_type)
Liefere alle Dienste zurueck, die dem angegebenen Typ zugeordnet sind. Falls kein Typ angegben wird,...
get_service_value(key, default)
Auslesen eines Werts aus der Service-Datenbank.
filter_reachable_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die erreichbar sind.
uci_replace_list()
Replace the items in a list. Wanted items are expected via stdin (one per line, uci_path).
uci_add_list(uci_path, new_item)
Füge einen neuen Wert zu einer UCI-Liste hinzu und achte dabei auf Einmaligkeit.
if[-d "/etc/on-firewall.d/"]
set eu grep root::etc shadow exit if command v chpasswd dev null
set eu on function print_services services log for dir in etc on services d var on services volatile d