Opennet Firmware
core.sh
gehe zur Dokumentation dieser Datei
1 ## @defgroup core Kern
2 ## @brief Logging, Datei-Operationen, DNS- und NTP-Dienste, Dictionary-Dateien, PID- und Lock-Behandlung, Berichte
3 # Beginn der Doku-Gruppe
4 ## @{
5 
6 
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
12 INTERN_DNS_DOMAIN=on
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
22 DEBUG_ENABLED=
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"
36 
37 
38 # Aufteilung ueberlanger Zeilen
39 _split_lines() {
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
44 }
45 
46 
47 ## @fn msg_debug()
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.
52 msg_debug() {
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")[$$]"
57 }
58 
59 
60 ## @fn msg_info()
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.
65 msg_info() {
66  echo "$1" | _split_lines "$LOG_MESSAGE_LENGTH" | logger -t "$(basename "$0")[$$]"
67 }
68 
69 
70 ## @fn msg_error()
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.
76 msg_error() {
77  echo "$1" | _split_lines "$LOG_MESSAGE_LENGTH" | logger -s -t "$(basename "$0")[$$]" "[ERROR] $1"
78 }
79 
80 
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.
88  local log_name="$1"
89  local event="$2"
90  local msg="$3"
91  local logfile
92  logfile=$(get_custom_log_filename "$log_name")
93  echo "$(date) openvpn [$event]: $msg" >>"$logfile"
94  # Datei kuerzen, falls sie zu gross sein sollte
95  local filesize
96  filesize=$(get_filesize "$logfile")
97  [ "$filesize" -gt 10000 ] && sed -i "1,30d" "$logfile"
98  return 0
99 }
100 
101 
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).
107  local log_name="$1"
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"
111 }
112 
113 
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).
119  local log_name="$1"
120  local logfile
121  logfile=$(get_custom_log_filename "$log_name")
122  [ -e "$logfile" ] || return 0
123  cat "$logfile"
124 }
125 
126 
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"
138  local content
139  content="$(cat -)"
140  if [ -e "$target_filename" ] && echo "$content" | cmp -s - "$target_filename"; then
141  # the content did not change
142  trap "" EXIT && return 1
143  else
144  # updated content
145  local dirname
146  dirname=$(dirname "$target_filename")
147  [ -d "$dirname" ] || mkdir -p "$dirname"
148  echo "$content" > "$target_filename"
149  return 0
150  fi
151 }
152 
153 
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
161  local host
162  local port
163  local service
164  # wenn wir eine VPN-Tunnel-Verbindung aufgebaut haben, sollten wir DNS-Anfragen über diese Crypto-Verbindung lenken
165  local preferred_servers
166  local use_dns
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
170  local servers_file
171  local server_config
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]"
178  reload_config
179  fi
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
182  server_config=$(
183  get_services "dns" | filter_reachable_services | filter_enabled_services | sort | while read -r service; do
184  host=$(get_service_value "$service" "host")
185  port=$(get_service_value "$service" "port")
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)
191  # verfügen.
192  echo "server=/$INTERN_DNS_DOMAIN/$host"
193  done
194  # eventuell bevorzugte Hosts einfuegen
195  for host in $preferred_servers; do
196  echo "server=$host"
197  done
198  )
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=/')
201  echo "$server_config" | update_file_if_changed "$servers_file" || return 0
202  # es gab eine Aenderung
203  msg_info "updating DNS servers"
204  # Konfiguration neu einlesen
205  killall -s HUP dnsmasq 2>/dev/null || true
206 }
207 
208 
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.
214 ## @sa http://wiki.openwrt.org/doc/uci/system#remote_time_ntp
216  trap 'error_trap update_ntp_servers "$*"' EXIT
217  local host
218  local port
219  local service
220  local preferred_servers
221  local previous_entries
222  local use_ntp
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
232  echo "$host"
233  done
234  else
235  get_services "ntp" | filter_reachable_services | filter_enabled_services | sort | while read -r service; do
236  host=$(get_service_value "$service" "host")
237  port=$(get_service_value "$service" "port")
238  [ -n "$port" ] && [ "$port" != "123" ] && host="$host:$port"
239  echo "$host"
240  done
241  fi | uci_replace_list "system.ntp.server"
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
247  apply_changes system
248 }
249 
250 
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
258  local event="$1"
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
268  fi
269  # die Zeile auffuellen
270  while [ "${#line}" -lt 54 ]; do line="$line-"; done
271  echo "$line" >>/etc/banner
272  sync
273 }
274 
275 
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
283  fi
284 }
285 
286 
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
292  sync
293 }
294 
295 
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.
305 _get_file_dict_value() { local key="$1"; shift; { grep "^$key"'[[:space:]]' "$@" 2>/dev/null || true; } | while read -r key value; do echo -n "$value"; done; }
306 
307 
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; }
313 
314 
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"
322  local field="$2"
323  local new_value="$3"
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.
329  (
330  grep -v -w -s "$field" "$status_file" || true
331  echo "$field $new_value"
332  ) | sort | update_file_if_changed "$status_file" || true
333 }
334 
335 
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() {
342  local key="$1"
343  _get_file_dict_value "$key" "$ON_CORE_DEFAULTS_FILE"
344 }
345 
346 
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"
358 }
359 
360 
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
365  local version
366  if version=$(http_request "$LATEST_STABLE_FIRMWARE_VERSION_INFO_URL"); then
367  echo "$version"
368  else
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."
373  fi
374  fi
375 }
376 
377 
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"
389  fi
390 }
391 
392 
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
399  local upgrade_map
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"
408  fi
409  fi
410  fi
411 }
412 
413 
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
419  sh <<EOF
420  . /usr/share/libubox/jshn.sh
421  $(jshn -R /etc/board.json)
422  json_select model
423  json_get_vars id
424  echo "\$id"
425 EOF
426 }
427 
428 
429 ## @fn get_on_ip()
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).
436 get_on_ip() {
437  local on_id="$1"
438  local on_ipschema="$2"
439  local interface_number="$3"
440  local on_id_1
441  local on_id_2
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"
451 }
452 
453 
454 ## @fn get_main_ip()
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.
458 ## @sa get_on_ip
459 get_main_ip() {
460  local on_id
461  local ipschema
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
465 }
466 
467 
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() {
472  local returncode
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
477  set +e
478  "$@"
479  returncode=$?
480  set -e
481  rm -f "$CRON_LOCK_FILE"
482  return "$returncode"
483  fi
484 }
485 
486 
487 is_lock_available() {
488  local lock_file="$1"
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"
492 }
493 
494 
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)
503 acquire_lock() {
504  local lock_file="$1"
505  local max_age_minutes="$2"
506  local timeout="$3"
507  local timeout_limit
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
513  fi
514  sleep "$(( $(get_random 10) + 1 ))"
515  done
516  touch "$lock_file"
517  return 0
518 }
519 
520 
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
525 check_pid_file() {
526  trap 'error_trap check_pid_file "$*"' EXIT
527  local pid_file="$1"
528  local process_name="$2"
529  local pid
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
539  return 0
540 }
541 
542 
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.
548 apply_changes() {
549  local config
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
556  echo "$config"
557  elif [ -z "$(uci -q changes "$config")" ]; then
558  # keine Aenderungen?
559  true
560  else
561  uci commit "$config"
562  echo "$config"
563  fi
564  done | grep -v "^$" | sort | uniq | while read -r config; do
565  run_parts "${IPKG_INSTROOT:-}/usr/lib/opennet/hooks.d" "$config"
566  done
567  return 0
568 }
569 
570 
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
576 set_opennet_id() {
577  trap 'error_trap set_opennet_id "$*"' EXIT
578  local new_id="$1"
579  local network
580  local uci_prefix
581  local ipaddr
582  local main_ipaddr
583  local ipschema
584  local netmask
585  local if_counter=0
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 . -)"
595  done
596  apply_changes system
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))
608  done
609  # OLSR-MainIP konfigurieren
610  olsr_set_main_ip "$main_ipaddr"
611  apply_changes olsrd network
612 }
613 
614 
615 # Durchsuche eine Schluessel-Wert-Liste nach einem Schluessel und liefere den dazugehoerigen Wert zurueck.
616 # Beispiel:
617 # foo=bar baz=nux
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"
623  local separator="$2"
624  local key_value
625  local key
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
629  true
630  done
631  return 0
632 }
633 
634 
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"
642  local separator="$2"
643  local value="$3"
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"' };'
647 }
648 
649 
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'
653 }
654 
655 
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
661 }
662 
663 
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
671  local filename="$1"
672  local limit_minutes="$2"
673  [ -e "$filename" ] || return 0
674  local file_timestamp
675  local timestamp_now
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) }')
680  # veraltet, falls:
681  # * kein Zeitstempel
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
687  return 0
688  else
689  trap "" EXIT && return 1
690  fi
691 }
692 
693 
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
704  local now
705  now="$(get_uptime_minutes)"
706  # it is older
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)" && \
711  return 0
712  trap "" EXIT && return 1
713 }
714 
715 
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
720 }
721 
722 
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() {
728  local delay="$1"
729  shift
730  (sleep "$delay" && "$@") </dev/null >/dev/null 2>&1 &
731 }
732 
733 
734 ## @fn get_filesize()
735 ## @brief Ermittle die Größe einer Datei in Bytes.
736 ## @params filename Name der zu untersuchenden Datei.
737 get_filesize() {
738  local filename="$1"
739  wc -c "$filename" | awk '{ print $1 }'
740 }
741 
742 
743 # Bericht erzeugen
744 # Der Name der erzeugten tar-Datei wird als Ergebnis ausgegeben.
745 generate_report() {
746  trap 'error_trap generate_report "$*"' EXIT
747  local fname
748  local pid
749  local reports_dir
750  local temp_dir
751  local tar_file
752  temp_dir=$(mktemp -d)
753  reports_dir="$temp_dir/report"
754  tar_file=$(mktemp)
755  msg_debug "Creating a report"
756  # die Skripte duerfen davon ausgehen, dass wir uns im Zielverzeichnis befinden
757  mkdir -p "$reports_dir"
758  cd "$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"
762  done
763  # "tar" unterstuetzt "-c" nicht - also komprimieren wir separat
764  tar cC "$temp_dir" "report" | gzip >"$tar_file"
765  rm -r "$temp_dir"
766  mv "$tar_file" "$REPORTS_FILE"
767 }
768 
769 
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:-}"
777  local filters=
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
800  # beim Booten auf.
801  filters="${filters}|olsrd.*startup-error"
802  # 7) ucarp
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
817  # harmlose Meldung.
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
844  # sind).
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
875  # Interfaces kennt.
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
897  cat -
898  else
899  # nur die letzten Einträge ausliefern
900  tail -n "$max_lines"
901  fi
902 }
903 
904 
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
910 line_in_file() {
911  trap 'error_trap line_in_file "$*"' EXIT
912  local filename="$1"
913  local pattern="$2"
914  local new_line="$3"
915  local line
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
919  (
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
924  new_line=
925  else
926  echo "$line"
927  fi
928  done <"$filename"
929  # die neue Zeile hinzufuegen, falls das Muster in der alten Datei nicht vorhanden war
930  grep -q "$pattern" "$filename" || echo "$new_line"
931  ) | update_file_if_changed "$filename" || true
932 }
933 
934 
935 # Pruefe, ob eine Liste ein bestimmtes Element enthaelt
936 # Die Listenelemente sind durch beliebigen Whitespace getrennt.
937 is_in_list() {
938  local target="$1"
939  local list="$2"
940  local token
941  for token in $list; do
942  [ "$token" = "$target" ] && return 0
943  true
944  done
945  # kein passendes Token gefunden
946  trap "" EXIT && return 1
947 }
948 
949 
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
953 get_variable() {
954  local var_name="$1"
955  eval "echo \"\$$var_name\""
956 }
957 
958 
959 # Pruefe, ob die angegebene Funktion definiert ist.
960 # Dies ersetzt opkg-basierte Pruefungen auf installierte opennet-Firmware-Pakete.
961 is_function_available() {
962  local func_name="$1"
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
968 }
969 
970 
971 ## @fn get_random()
972 ## @brief Liefere eine Zufallszahl innerhalb des gegebenen Bereichs.
973 ## @returns Eine zufällige Ganzzahl.
974 get_random() {
975  local range="$1"
976  local random_number
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; }'
981 }
982 
983 
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
992  local bias
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"
999  uci commit on-core
1000  fi
1001  echo -n "$bias" && return 0
1002 }
1003 
1004 
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"
1012  local pid_file="$2"
1013  local result
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
1018 }
1019 
1020 
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).
1024 get_memory_size() {
1025  local memsize_kb
1026  memsize_kb=$(grep "^MemTotal:" /proc/meminfo | sed 's/[^0-9]//g')
1027  echo $((memsize_kb / 1024))
1028 }
1029 
1030 
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"
1035  local fname
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
1044  echo "$fname"
1045  done
1046 }
1047 
1048 
1049 ## @fn run_parts()
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.
1055 run_parts() {
1056  trap 'error_trap run_parts "$*"' EXIT
1057  local rundir="$1"
1058  shift
1059  local fname
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
1064  done
1065 }
1066 
1067 
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.
1074 schedule_parts() {
1075  trap 'error_trap schedule_parts "$*"' EXIT
1076  local rundir="$1"
1077  local suffix="${2:-}"
1078  _get_parts_dir_files "$rundir" | while read -r fname; do
1079  if [ -n "$suffix" ]; then
1080  echo "$fname $suffix"
1081  else
1082  echo "$fname"
1083  fi | schedule_task
1084  done
1085 }
1086 
1087 
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
1093  local fname
1094  local temp_fname
1095  local running_tasks
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")"
1106  echo "$fname"
1107  done)
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)"
1116  rm -f "$temp_fname"
1117  done
1118 }
1119 
1120 
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).
1125 schedule_task() {
1126  trap 'error_trap schedule_task "$*"' EXIT
1127  local script_content
1128  local unique_key
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"
1137 }
1138 
1139 
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.
1144 schedule_parts() {
1145  trap 'error_trap schedule_parts "$*"' EXIT
1146  local schedule_dir="$1"
1147  local fname
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
1152  done
1153 }
1154 
1155 
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.
1163 read_data_bytes() {
1164  local size="$1"
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
1170  true
1171 }
1172 
1173 
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:-}"
1192  local name
1193  local size
1194  local blocksize
1195  local label
1196  # shellcheck disable=SC2034
1197  grep '^mtd[0-9]\+:' /proc/mtd | while read -r name size blocksize label; do
1198  # abschliessenden Doppelpunkt entfernen
1199  name="${name%:}"
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)"
1215  # erzeuge 0xFF
1216  # siehe http://stackoverflow.com/a/10905109
1217  tr '\0' '\377' </dev/zero | read_data_bytes "$size"
1218  else
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"
1222  fi
1223  elif [ "$label" = "firmware" ]; then
1224  echo >&2 "Skip: $label ($size)"
1225  # ignoriere die meta-Partition (kernel + rootfs)
1226  true
1227  else
1228  echo >&2 "Read: $label ($size)"
1229  cat "/dev/$name"
1230  fi
1231  done
1232 }
1233 
1234 
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
1243 }
1244 
1245 
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
1249 ## amounts of data.
1250 request_field_from_api() {
1251  local path="$1"
1252  local json_query="$2"
1253  local data
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
1259 }
1260 
1261 
1262 get_main_ip_for_ip() {
1263  local ip="$1"
1264  result=$(request_field_from_api "/accesspoint/$ip" "@.main_ip")
1265  [ -z "$result" ] && result=$(request_field_from_api "/interface/$ip/accesspoint/" "@.main_ip")
1266  echo "$result"
1267 }
1268 
1269 
1270 get_name_for_main_ip() {
1271  local main_ip="$1"
1272  echo "$main_ip" | sed -E 's/^192\.168\.(\d+)\.(\d+)$/AP\1-\2/'
1273 }
1274 
1275 
1276 get_location_for_main_ip() {
1277  local main_ip="$1"
1278  request_field_from_api "/accesspoint/$main_ip" "@.post_address"
1279 }
1280 
1281 
1282 # Request a resource via HTTP. In case of HTTPS only public certificates are trusted.
1283 http_request() {
1284  wget -q -O - "$@"
1285 }
1286 
1287 # Ende der Doku-Gruppe
1288 ## @}
get_services(service_type)
Liefere alle Dienste zurueck, die dem angegebenen Typ zugeordnet sind. Falls kein Typ angegben wird...
Definition: services.sh:68
add_banner_event(event, timestamp)
Füge ein Ereignis zum dauerhaften Ereignisprotokoll (/etc/banner) hinzu.
Definition: core.sh:68
update_file_if_changed(target_filename)
Aktualisiere eine Datei, falls sich ihr Inhalt geändert haben sollte.
Definition: core.sh:49
while read r key value
Definition: core.sh:85
append_to_custom_log(log_name, event)
Hänge eine neue Nachricht an ein spezfisches Protokoll an.
Definition: core.sh:29
set eu case in network wireless firewall on function update_olsr_interfaces
Definition: 500-on-olsr:9
get_custom_log_filename(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück...
Definition: core.sh:34
update_ntp_servers()
Übertrage die Liste der als NTP-Dienst announcierten Server in die sysntpd-Konfiguration.
Definition: core.sh:62
update_dns_servers()
Definition: core.sh:55
filter_reachable_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die erreichbar sind.
Definition: services.sh:44
filter_enabled_services()
Filtere aus einer Reihe eingehender Dienste diejenigen heraus, die nicht manuell ausgeblendet wurden...
Definition: services.sh:49
uci_add_list(uci_path, new_item)
Füge einen neuen Wert zu einer UCI-Liste hinzu und achte dabei auf Einmaligkeit.
Definition: uci.sh:10
uci_replace_list()
Replace the items in a list. Wanted items are expected via stdin (one per line, uci_path).
Definition: uci.sh:41
clean_restart_log()
Alle Log-Einträge aus der banner-Datei entfernen.
Definition: core.sh:75
update_mesh_interfaces()
Update mesh interfaces, routing daemons and policy routing.
Definition: core.sh:72
local key
Definition: core.sh:85
_get_file_dict_value(key)
Auslesen eines Werts aus einem Schlüssel/Wert-Eingabestrom.
Definition: core.sh:85
get_mig_tunnel_servers()
Ermittle die Server für den gewünschen Dienst, die via Tunnel erreichbar sind. stype Dienst-Typ (z...
Definition: on-openvpn.sh:77
set eu grep root::etc shadow exit if command v chpasswd dev null
Definition: on-password:12
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
Definition: core.sh:15
get_custom_log_content(log_name)
Liefere den Inhalt eines spezifischen Logs (z.B. das OpenVPN-Verbindungsprotokoll) zurück...
Definition: core.sh:39
msg_debug(message)
Debug-Meldungen ins syslog schreiben.
Definition: core.sh:9
set eu on function print_services services log for dir in etc on services d var on services volatile d
Definition: services:13
get_service_value(key, default)
Auslesen eines Werts aus der Service-Datenbank.
Definition: services.sh:86
msg_error(message)
Die Fehlermeldungen werden in die Standard-Fehlerausgabe und ins syslog geschrieben.
Definition: core.sh:22
shift
Definition: core.sh:85
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
Definition: dns:14
done
Definition: core.sh:85