1## @defgroup network Netzwerk
2## @brief Umgang mit uci-Netzwerk-Interfaces und Firewall-Zonen
3# Beginn der Doku-Gruppe
9# shellcheck disable=SC2034
11# diese Domain wird testweise abgefragt, um die Verfügbarkeit des on-DNS zu prüfen
12DNS_SERVICE_REFERENCE=
"opennet-initiative.de"
13# ein Timeout von einer Sekunde scheint zu kurz zu sein (langsame Geräte brauchen mindestens 0,5s - abhängig vom Load)
17# Liefere alle IPs fuer diesen Namen zurueck
19 nslookup
"$1" 2>/dev/
null | sed
'1,3d' | grep
"^Address:" | awk
'{print $2}' | sort -n
24 nslookup
"$1" 2>/dev/
null | tail -n 1 | awk
'{ printf "%s", $4 }'
28## @fn has_opennet_dns()
29## @brief Prüfe, ob *.on-Domains aufgelöst werden.
30## @returns Der Exitcode ist Null, falls on-DNS verfügbar ist.
31## @details Die maximale Laufzeit dieser Funktion ist auf eine Sekunde begrenzt.
33 trap
'error_trap has_opennet_dns "'"$*"'"' EXIT
34 # timeout ist kein shell-builtin - es benoetigt also ein global ausfuehrbares Kommando
35 [ -n
"$(timeout "$DNS_TIMEOUT
" on-function query_dns "$DNS_SERVICE_REFERENCE
")" ] &&
return 0
36 trap
"" EXIT &&
return 1
41## @brief Ermittle die Latenz eines Ping-Pakets auf dem Weg zu einem Ziel.
42## @param target IP oder DNS-Name des Zielhosts
43## @param duration die Dauer der Ping-Kommunikation in Sekunden (falls ungesetzt: 5)
44## @returns Ausgabe der mittleren Ping-Zeit in ganzen Sekunden; bei Nichterreichbarkit ist die Ausgabe leer
46 trap
'error_trap get_ping_time "'"$*"'"' EXIT
48 local duration=
"${2:-5}"
51 [ -z
"$ip" ] &&
return 0
52 ping -w
"$duration" -q
"$ip" 2>/dev/
null \
53 | grep
"min/avg/max" \
56 | awk
'{ print int($1 + 0.5); }'
60# Lege eine Weiterleitungsregel fuer die firewall an (firewall.@forwarding[?]=...)
61# WICHTIG: anschliessend muss "uci commit firewall" ausgefuehrt werden
62# Parameter: Quell-Zone und Ziel-Zone
64 trap
'error_trap add_zone_forward "'"$*"'"' EXIT
71# Das Masquerading in die Opennet-Zone soll nur fuer bestimmte Quell-Netze erfolgen.
72# Diese Funktion wird bei hotplug-Netzwerkaenderungen ausgefuehrt.
73update_opennet_zone_masquerading() {
74 trap
'error_trap update_opennet_zone_masquerading "'"$*"'"' EXIT
76 local network_with_prefix
78 uci_prefix=
$(find_first_uci_section firewall zone
"name=$ZONE_MESH")
79 # Abbruch, falls die Zone fehlt
80 [ -z
"$uci_prefix" ] &&
msg_info "failed to find opennet mesh zone ($ZONE_MESH)" &&
return 0
81 # alle masquerade-Netzwerke entfernen
83 # aktuelle Netzwerke wieder hinzufuegen
84 for network in
$(get_zone_log_interfaces
"$ZONE_LOCAL"; get_zone_log_interfaces
"$ZONE_WAN");
do
86 echo
"$network_with_prefix"
89 if [ -n
"$(uci_get "${uci_prefix}.masq_src
")" ]; then
90 # masquerading aktiveren (nur fuer die obigen Quell-Adressen)
91 uci set
"${uci_prefix}.masq=1"
93 # Es gibt keine lokalen Interfaces - also duerfen wir kein Masquerading aktivieren.
94 # Leider interpretiert openwrt ein leeres "masq_src" nicht als "masq fuer niemanden" :(
95 uci set
"${uci_prefix}.masq=0"
97 # Seit April 2017 (commit e751cde8) verwirft fw3 "INVALID"-Pakete (also beispielsweise
98 # asymmetrische Antworten), sofern Masquerading aktiv ist. Dies schalten wir ab.
99 uci set
"${uci_prefix}.masq_allow_invalid=1"
100 apply_changes firewall
104## @fn get_current_addresses_of_network()
105## @brief Liefere die IP-Adressen eines logischen Interface inkl. Praefix-Laenge (z.B. 172.16.0.1/24).
106## @param network logisches Netzwerk-Interface
107## @details Es werden sowohl IPv4- als auch IPv6-Adressen zurückgeliefert.
109 trap
'error_trap get_current_addresses_of_network "'"$*"'"' EXIT
114 } | xargs -r -n 1 echo
118# Liefere die logischen Netzwerk-Schnittstellen einer Zone zurueck.
119get_zone_log_interfaces() {
120 trap
'error_trap get_zone_log_interfaces "'"$*"'"' EXIT
124 uci_prefix=
$(find_first_uci_section firewall zone
"name=$zone")
125 # keine Zone -> keine Interfaces
126 [ -z
"$uci_prefix" ] &&
return 0
128 # falls 'network' und 'device' leer sind, dann enthaelt 'name' den Interface-Namen
130 [ -z
"$interfaces" ] && [ -z
"$(uci_get "${uci_prefix}.device
")" ] && interfaces=
"$(uci_get "${uci_prefix}.name
")"
131 for interface in $interfaces;
do
132 if [
"$interface" !=
"$(get_device_of_interface $interface)" ]; then
139## @fn get_zone_raw_devices()
140## @brief Ermittle die physischen Netzwerkinterfaces, die direkt einer Firewall-Zone zugeordnet sind.
141## @details Hier werden _nicht_ die logischen Interfaces in die physischen aufgeloest, sondern
142## es werden nur die physischen Interfaces zurückgegeben.
144 trap
'error_trap get_zone_raw_devices "'"$*"'"' EXIT
147 uci_prefix=
$(find_first_uci_section
"firewall" "zone" "name=$zone")
148 [ -z
"$uci_prefix" ] &&
msg_debug "Failed to retrieve raw devices of non-existing zone '$zone'" &&
return 0
149 # extrahiere die phys. Interfaces
158# Ist das gegebene physische Netzwerk-Interface Teil einer Firewall-Zone?
160 trap 'error_trap is_device_in_zone "'"$*"'"' EXIT
165 for log_interface in $(get_zone_log_interfaces "$2
"); do
166 for item in $(get_subdevices_of_interface "$log_interface
"); do
167 [ "$device
" = "$item
" ] && return 0
171 trap "" EXIT && return 1
175# Ist das gegebene logische Netzwerk-Interface Teil einer Firewall-Zone?
176is_interface_in_zone() {
180 for item in $(get_zone_log_interfaces "$zone
"); do
181 [ "$item
" = "$interface
" ] && return 0
184 trap "" EXIT && return 1
188## @fn get_device_of_interface()
189## @brief Ermittle das physische Netzwerk-Gerät, das einem logischen Netzwerk entspricht.
190## @details Ein Bridge-Interface wird als Gerät betrachtet und zurückgeliefert (nicht seine Einzelteile).
191## Entspricht der Parameter einem physischen Interface, dann wird es mit gleichem Namen unveraendert wieder zurueckgegeben.
192get_device_of_interface() {
196 # OpenWrt nutzt jetzt DSA anstatt swconfig. Daher sind fuer eine Uebergangszeit hier zwei Pruefungen notwendig.
198 # Bereite DSA Check vor. Ermittle aktuelle Anzahl an Devices laut network.@device[].
199 # (Alternative zur Schleife unten ist die Nutzung von config_foreach(), siehe https://openwrt.org/docs/guide-developer/config-scripting.)
201 local max_dev_index=-1 # finde groessten Index
202 local MAX_BRIDGES=10 # oberes Limit gegen Endlosschleife
203 while [ $i -lt $MAX_BRIDGES ]; do
204 local dev_exists="-1
"
205 dev_exists="$(uci_get
"network.@device[$i].type" -1)
"
206 if [ "$dev_exists
" != "-1
" ]; then
209 elif [ "$dev_exists
" == "-1
" ]; then
216 # DSA: Ist Interface eine Bridge?
217 local phy_dev="$(uci_get
"network.${interface}.device")
"
219 while [ $i -le $max_dev_index ]; do
220 if [ "$(uci_get
"network.@device[$i].name")
" = "${phy_dev}
" ] && [ "$(uci_get
"network.@device[$i].type")
" = "bridge
" ]; then
227 # swconfig: Ist Interface eine Bridge?
228 if [ "$(uci_get
"network.${interface}.type")
" = "bridge
" ]; then
233 if [ $found_bridge -ne 1 ]; then
234 # Interface ist keine Bridge
235 get_subdevices_of_interface "$interface
"
240# Ist das gegebene physische Netzwerk-Interface Teil einer Firewall-Zone?
242 trap 'error_trap is_device_in_zone "'"$*"'"' EXIT
247 for log_interface in $(get_zone_log_interfaces "$2
"); do
248 for item in $(get_subdevices_of_interface "$log_interface
"); do
249 [ "$device
" = "$item
" ] && return 0
253 trap "" EXIT && return 1
257## @fn _run_system_network_function()
258## @brief Führe eine der in /lib/functions/network.sh definierten Funktionen aus.
259## @params func: der Name der Funktion
260## @params ...: alle anderen Parameter werden der Funktion nach der Zielvariable (also ab
261## Parameter #2) übergeben
262## @returns: die Ausgabe der Funktion
263_run_system_network_function() {
269 # shellcheck source=openwrt/package/base-files/files/lib/functions/network.sh
270 . /lib/functions/network.sh
272 [ -n "$result
" ] && echo "$result
"
277## @fn get_subdevices_of_interface()
278## @brief Ermittle die physischen Netzwerk-Geräte (bis auf wifi), die zu einem logischen Netzwerk-Interface gehören.
279## @details Im Fall eines Bridge-Interface werden nur die beteiligten Komponenten zurückgeliefert.
280## Wifi-Geräte werden nur dann zurückgeliefert, wenn sie Teil einer Bridge sind. Andernfalls sind ihre Namen nicht
282## @returns Der oder die Namen der physischen Netzwerk-Geräte oder nichts.
283get_subdevices_of_interface() {
284 trap 'error_trap get_subdevices_of_interface "'"$*"'"' EXIT
289 # kabelgebundene Geräte
290 for device in $(uci_get "network.${interface}.device
"); do
291 # entferne Alias-Nummerierungen
292 device=$(echo "$device
" | cut -f 1 -d :)
293 [ -z "$device
" ] || [ "$device
" = "none
" ] && continue
296 # wir fügen das Ergebnis der ubus-Abfrage hinzu (unten werden Duplikate entfernt)
297 _run_system_network_function "network_get_physdev
" "$interface
"
298 } | tr ' ' '\n' | sort | uniq | while read -r device; do
299 # Falls das Verzeichnis existiert, ist es wohl eine Bridge, deren Bestandteile wir ausgeben.
300 # Ansonsten wird das Device ausgegeben.
301 ls "/sys/devices/
virtual/net/$device/brif/
" 2>/dev/null || echo "$device
"
302 done | sort | uniq | grep -v "^none$
" | grep -v "^
$" || true
306## @fn add_interface_to_zone()
307## @brief Fuege ein logisches Netzwerk-Interface zu einer Firewall-Zone hinzu.
308## @details Typischerweise ist diese Funktion nur fuer temporaere Netzwerkschnittstellen geeignet.
309add_interface_to_zone() {
313 uci_prefix=$(find_first_uci_section "firewall
" "zone
" "name=$zone
")
314 [ -z "$uci_prefix
" ] && msg_debug "Failed to add
interface '$interface' to non-existing zone '$zone'" &&
return 0
319## @fn del_interface_from_zone()
320## @brief Entferne ein logisches Interface aus einer Firewall-Zone.
325 uci_prefix=
$(find_first_uci_section
"firewall" "zone" "name=$zone")
326 [ -z
"$uci_prefix" ] &&
msg_debug "Failed to remove interface '$interface' from non-existing zone '$zone'" && trap
"" EXIT &&
return 1
327 uci -q del_list
"${uci_prefix}.network=$interface"
331## @fn get_zone_of_device()
332## @brief Ermittle die Zone eines physischen Netzwerk-Interfaces.
333## @param interface Name eines physischen Netzwerk-Interface (z.B. eth0)
334## @details Das Ergebnis ist ein leerer String, falls zu diesem Interface keine Zone existiert
335## oder falls es das Interface nicht gibt.
337 trap
'error_trap get_zone_of_device "'"$*"'"' EXIT
343 find_all_uci_sections firewall zone |
while read -r uci_prefix;
do
344 zone=
$(uci_get
"${uci_prefix}.name")
# e.g. on_mesh
345 for interface in
$(get_zone_log_interfaces "$zone");
do
346 for current_device
in \
349 [
"$current_device" =
"$device" ] && echo
"$device" &&
return 0
354 # keine Zone gefunden
358## @fn get_zone_of_interface()
359## @brief Ermittle die Zone eines logischen Netzwerk-Interfaces.
360## @param interface Name eines logischen Netzwerk-Interface (z.B. eth0)
361## @details Das Ergebnis ist ein leerer String, falls zu diesem Interface keine Zone existiert
362## oder falls es das Interface nicht gibt.
364 trap
'error_trap get_zone_of_interface "'"$*"'"' EXIT
369 find_all_uci_sections firewall zone |
while read -r uci_prefix;
do
370 zone=
$(uci_get
"${uci_prefix}.name")
371 interfaces=
$(get_zone_log_interfaces
"$zone")
372 is_in_list
"$interface" "$interfaces" && echo -n
"$zone" &&
return 0
375 # ein leerer Rueckgabewert gilt als Fehler
380# Liefere die sortierte Liste der Opennet-Interfaces.
382# 1. dem Netzwerk ist ein Geraet zugeordnet
383# 2. Netzwerkname beginnend mit "on_wifi", "on_eth", ...
384# 3. alphabetische Sortierung der Netzwerknamen
385get_sorted_opennet_interfaces() {
386 trap
'error_trap get_sorted_opennet_interfaces "'"$*"'"' EXIT
389 # wir vergeben einfach statische Ordnungsnummern:
390 # 10 - konfigurierte Interfaces
391 # 20 - nicht konfigurierte Interfaces
392 # Offsets basierend auf dem Netzwerknamen:
396 for network in
$(get_zone_log_interfaces
"$ZONE_MESH");
do
398 [ -z
"$(get_subdevices_of_interface "$network
")" ] && order=20
399 if [
"${network#on_wifi}" !=
"$network" ]; then
401 elif [ "
${network#on_eth}
" != "$network
" ]; then
406 echo "$order $network
"
407 done | sort -n | cut -f 2 -d " "
411# Liefere alle vorhandenen logischen Netzwerk-Schnittstellen (lan, wan, ...) zurueck.
412get_all_network_interfaces() {
414 # Die uci-network-Spezifikation sieht keine anonymen uci-Sektionen fuer Netzwerk-Interfaces vor.
415 # Somit ist es wohl korrekt, auf die Namen als Teil des uci-Pfads zu vertrauen.
416 find_all_uci_sections "network
" "interface
" | cut -f 2 -d . | while read -r interface; do
417 # ignoriere loopback-Interfaces und ungueltige
418 [ -z "$interface
" ] || [ "$interface
" = "none
" ] || [ "$interface
" = "loopback
" ] && continue
419 # alle uebrigen sind reale Interfaces
426## @fn delete_firewall_zone()
427## @brief Lösche eine Firewall-Zone, sowie alle Regeln, die sich auf diese Zone beziehen.
428## @param zone Name der Zone
429## @attention Anschließend ist ein "apply_changes firewall
" erforderlich.
430delete_firewall_zone() {
435 uci_prefix=$(find_first_uci_section firewall zone "name=$zone
")
436 uci_delete "$uci_prefix
"
437 for section in "forwarding
" "redirect
" "rule
"; do
438 for key in "src
" "dest
"; do
439 find_all_uci_sections firewall "$section
" "${
key}=$zone
" | while read -r uci_prefix; do
440 uci_delete "$uci_prefix
"
447## @fn is_interface_up()
448## @brief Prüfe ob ein logisches Netzwerk-Interface aktiv ist.
449## @param interface Zu prüfendes logisches Netzwerk-Interface
450## @details Im Fall eines Bridge-Interface wird sowohl der Status der Bridge (muss aktiv sein), als
451## auch der Status der Bridge-Teilnehmer (mindestens einer muss aktiv sein) geprüft.
453 trap 'error_trap is_interface_up "'"$*"'"' EXIT
455 # falls es ein uebergeordnetes Bridge-Interface geben sollte, dann muss dies ebenfalls aktiv sein
456 if [ "$(uci_get
"network.${interface}.type")
" = "bridge
" ]; then
457 # das Bridge-Interface existiert nicht (d.h. es ist down)
458 [ -z "$(ip link show dev
"br-${interface}" 2>/dev/
null ||
true)
" ] && trap "" EXIT && return 1
459 # Bridge ist aus? Damit ist das befragte Interface ebenfalls aus ...
460 ip link show dev "br-
${interface}
" | grep -q '[\t ]state DOWN[\ ]' && trap "" EXIT && return 1
463 for device in $(get_subdevices_of_interface "$interface
"); do
464 ip link show dev "$device
" | grep -q '[\t ]state UP[\ ]' && return 0
467 trap "" EXIT && return 1
471## @fn get_ipv4_of_mac()
472## @brief Ermittle die IPv4-Adresse zu einer MAC-Adresse
473## @param mac MAC-Adresse eines Nachbarn
476 awk '{ if ($4 == "'"$ip"'") print $1; }' /proc/net/arp | sort | head -1
480is_current_wifi_interface_channel_with_dfs() {
481 local wifi_interface="$1
"
483 channel=$(iwinfo "$wifi_interface
" info | awk '{ if ($3 == "Channel:
") print $4 }')
484 echo "$channel
" | grep -q "^[0-9]\+
$" || return 1
485 [ "$channel
" -gt 48 ]
490 trap 'error_trap run_iwinfo_scan "'"$*"'"' EXIT
491 local phy_device="$1
"
492 local wifi_interface_uci
494 local original_channel
495 local needs_auto_channel
496 wifi_interface_uci=$(find_first_uci_section "wireless
" "wifi-iface
" "device=$phy_device
")
497 [ -z "$wifi_interface_uci
" ] && return 0
498 # the field "device
" is not strictly specified - thus fall back to a sane default
499 wifi_interface=$(uci_get "$wifi_interface_uci.device
" "wlan0
")
500 # try whether it works without any changes and exit early in case of success
501 iwinfo "$wifi_interface
" scan 2>/dev/null && return 0
502 # Possible reason for failure: "scan
" does not seem to work for a master on a DFS channel:
503 # https://forum.openwrt.org/t/cannot-scan-on-5g-on-tl-wr902ac/42863/2
504 # (but sometimes it indeed works - thus we tried it in advance before, anyway)
505 # Try hard to prepare a situation where it is possible to scan the channels.
506 # Sadly this may break the HTTP connection (e.g. when accessing the wireless scan via the
507 # web interface) if the user accessed the IP of the wireless interface.
508 if [ "$(uci_get
"$wifi_interface_uci.mode")
" = "ap
" ] && is_current_wifi_interface_channel_with_dfs "$wifi_interface
"; then
509 original_channel=$(uci_get "wireless.$phy_device.channel
")
510 needs_auto_channel="true"
512 needs_auto_channel="false"
514 if [ "$needs_auto_channel
" = "true" ]; then
515 # switch to a non-DFS channel
516 uci set "wireless.$phy_device.channel=48
"
517 uci commit "wireless.$phy_device
"
521 iwinfo "$wifi_interface
" scan
522 # revert to the original setup
523 if [ "$needs_auto_channel
" = "true" ]; then
524 uci set "wireless.$phy_device.channel=$original_channel
"
525 uci commit "wireless.$phy_device
"
531get_potential_opennet_scan_results_for_device() {
532 trap 'error_trap get_potential_opennet_scan_results_for_device "'"$*"'"' EXIT
533 local phy_device="$1
"
534 run_iwinfo_scan "$phy_device
" \
536 if ($1 == "ESSID:
") { if ((name != "") && (encryption == "none
")) print(signal"\t
"channel"\t
"quality"\t
"name); split($0, tokens, /"/); name=tokens[2]; };
537 if ($1 ==
"Signal:") signal=$2;
538 if ($4 ==
"Quality:") quality=substr($5, 0, index($5,
"/") - 1);
539 if ($3 ==
"Channel:") channel=$4;
540 if ($1 ==
"Encryption:") encryption=$2;
543 | grep -E "(opennet|\bon\b)" \
544 | grep -vF "join.opennet-initiative.de" || true
547# Ende der Doku-Gruppe
msg_debug(message)
Debug-Meldungen ins syslog schreiben.
msg_info(message)
Informationen und Fehlermeldungen ins syslog schreiben.
get_subdevices_of_interface()
Ermittle die physischen Netzwerk-Geräte (bis auf wifi), die zu einem logischen Netzwerk-Interface geh...
get_zone_raw_devices()
Ermittle die physischen Netzwerkinterfaces, die direkt einer Firewall-Zone zugeordnet sind.
get_zone_of_device(interface)
Ermittle die Zone eines physischen Netzwerk-Interfaces.
get_device_of_interface()
Ermittle das physische Netzwerk-Gerät, das einem logischen Netzwerk entspricht.
get_ping_time(target, duration)
Ermittle die Latenz eines Ping-Pakets auf dem Weg zu einem Ziel.
has_opennet_dns()
Prüfe, ob *.on-Domains aufgelöst werden.
get_zone_of_interface(interface)
Ermittle die Zone eines logischen Netzwerk-Interfaces.
del_interface_from_zone()
Entferne ein logisches Interface aus einer Firewall-Zone.
_run_system_network_function()
Führe eine der in /lib/functions/network.sh definierten Funktionen aus. @params func: der Name der Fu...
get_current_addresses_of_network()
Liefere die IP-Adressen eines logischen Interface inkl. Praefix-Laenge (z.B. 172.16....
filter_routable_addresses()
Filtere aus einer Menge von Ziel-IPs diejenigen heraus, für die eine passende Routing-Regel existiert...
create_uci_section_if_missing()
Prüfe, ob eine definierte UCI-Sektion existiert und lege sie andernfalls an.
uci_delete(uci_path)
Lösche ein UCI-Element.
uci_replace_list()
Replace the items in a list. Wanted items are expected via stdin (one per line, uci_path).
uci_get_list(uci_path)
Liefere alle einzelnen Elemente einer UCI-Liste zurück.
uci_add_list(uci_path, new_item)
Füge einen neuen Wert zu einer UCI-Liste hinzu und achte dabei auf Einmaligkeit.
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