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
12 DNS_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,/^Name:/d' | awk
'{print $3}' | 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
40 ## @fn get_ping_time()
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.
73 update_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_interfaces
"$ZONE_LOCAL"; get_zone_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.
119 get_zone_interfaces() {
120 trap
'error_trap get_zone_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
")"
135 ## @fn get_zone_raw_devices()
136 ## @brief Ermittle die physischen Netzwerkinterfaces, die direkt einer Firewall-Zone zugeordnet sind.
137 ## @details Hier werden _nicht_ die logischen Interfaces in die physischen aufgeloest, sondern
138 ## es wird lediglich der Inhalt des 'devices'-Eintrags einer Firewall-Zone ausgelesen.
140 trap
'error_trap get_zone_raw_devices "'"$*"'"' EXIT
143 uci_prefix=$(find_first_uci_section
"firewall" "zone" "name=$zone")
144 [ -z
"$uci_prefix" ] &&
msg_debug "Failed to retrieve raw devices of non-existing zone '$zone'" &&
return 0
149 # Ist das gegebene physische Netzwerk-Interface Teil einer Firewall-Zone?
150 is_device_in_zone() {
151 trap
'error_trap is_device_in_zone "'"$*"'"' EXIT
156 for log_interface in $(get_zone_interfaces
"$2");
do
158 [
"$device" =
"$item" ] &&
return 0
162 trap
"" EXIT &&
return 1
166 # Ist das gegebene logische Netzwerk-Interface Teil einer Firewall-Zone?
167 is_interface_in_zone() {
171 for item in $(get_zone_interfaces
"$zone");
do
172 [
"$item" =
"$interface" ] &&
return 0
175 trap
"" EXIT &&
return 1
179 ## @fn get_device_of_interface()
180 ## @brief Ermittle das physische Netzwerk-Gerät, das einem logischen Netzwerk entspricht.
181 ## @details Ein Bridge-Interface wird als Gerät betrachtet und zurückgeliefert (nicht seine Einzelteile).
186 # OpenWrt nutzt jetzt DSA anstatt swconfig. Daher sind fuer eine Uebergangszeit hier zwei Pruefungen notwendig.
188 # Bereite DSA Check vor. Ermittle aktuelle Anzahl an Devices laut network.@device[].
189 # (Alternative zur Schleife unten ist die Nutzung von config_foreach(), siehe https:
191 local max_dev_index=-1 # finde groessten Index
192 local MAX_BRIDGES=10 # oberes Limit gegen Endlosschleife
193 while [ $i -lt $MAX_BRIDGES ];
do
194 local dev_exists=
"-1"
195 dev_exists=
"$(uci_get "network.@device[$i].type
" -1)"
196 if [
"$dev_exists" !=
"-1" ]; then
199 elif [
"$dev_exists" ==
"-1" ]; then
206 # DSA: Ist Interface eine Bridge?
207 local phy_dev=
"$(uci_get "network.${interface}.device
")"
209 while [ $i -le $max_dev_index ];
do
210 if [
"$(uci_get "network.@device[$i].name
")" =
"${phy_dev}" ] && [
"$(uci_get "network.@device[$i].type
")" =
"bridge" ]; then
217 # swconfig: Ist Interface eine Bridge?
218 if [
"$(uci_get "network.${interface}.type
")" =
"bridge" ]; then
223 if [ $found_bridge -ne 1 ]; then
224 # Interface ist keine Bridge
230 # Ist das gegebene physische Netzwerk-Interface Teil einer Firewall-Zone?
231 is_device_in_zone() {
232 trap
'error_trap is_device_in_zone "'"$*"'"' EXIT
237 for log_interface in $(get_zone_interfaces
"$2");
do
239 [
"$device" =
"$item" ] &&
return 0
243 trap
"" EXIT &&
return 1
247 ## @fn _run_system_network_function()
248 ## @brief Führe eine der in /lib/functions/network.sh definierten Funktionen aus.
249 ## @params func: der Name der Funktion
250 ## @params ...: alle anderen Parameter werden der Funktion nach der Zielvariable (also ab
251 ## Parameter #2) übergeben
252 ## @returns: die Ausgabe der Funktion
259 # shellcheck source=openwrt/package/base-files/files/lib/functions/network.sh
260 . /lib/functions/network.sh
262 [ -n
"$result" ] && echo
"$result"
267 ## @fn get_subdevices_of_interface()
268 ## @brief Ermittle die physischen Netzwerk-Geräte (bis auf wifi), die zu einem logischen Netzwerk-Interface gehören.
269 ## @details Im Fall eines Bridge-Interface werden nur die beteiligten Komponenten zurückgeliefert.
270 ## Wifi-Geräte werden nur dann zurückgeliefert, wenn sie Teil einer Bridge sind. Andernfalls sind ihre Namen nicht
272 ## @returns Der oder die Namen der physischen Netzwerk-Geräte oder nichts.
274 trap
'error_trap get_subdevices_of_interface "'"$*"'"' EXIT
279 # kabelgebundene Geräte
280 for device in $(uci_get
"network.${interface}.device");
do
281 # entferne Alias-Nummerierungen
282 device=$(echo
"$device" | cut -f 1 -
d :)
283 [ -z
"$device" ] || [
"$device" =
"none" ] &&
continue
286 # wir fügen das Ergebnis der ubus-Abfrage hinzu (unten werden Duplikate entfernt)
288 } | tr
' ' '\n' | sort | uniq |
while read -r device;
do
289 # Falls das Verzeichnis existiert, ist es wohl eine Bridge, deren Bestandteile wir ausgeben.
290 # Ansonsten wird das Device ausgegeben.
291 ls
"/sys/devices/virtual/net/$device/brif/" 2>/dev/
null || echo
"$device"
292 done | sort | uniq | grep -v
"^none$" | grep -v
"^$" ||
true
296 ## @fn add_interface_to_zone()
297 ## @brief Fuege ein logisches Netzwerk-Interface zu einer Firewall-Zone hinzu.
298 ## @details Typischerweise ist diese Funktion nur fuer temporaere Netzwerkschnittstellen geeignet.
303 uci_prefix=$(find_first_uci_section
"firewall" "zone" "name=$zone")
304 [ -z
"$uci_prefix" ] &&
msg_debug "Failed to add interface '$interface' to non-existing zone '$zone'" &&
return 0
309 ## @fn del_interface_from_zone()
310 ## @brief Entferne ein logisches Interface aus einer Firewall-Zone.
315 uci_prefix=$(find_first_uci_section
"firewall" "zone" "name=$zone")
316 [ -z
"$uci_prefix" ] &&
msg_debug "Failed to remove interface '$interface' from non-existing zone '$zone'" && trap
"" EXIT &&
return 1
317 uci -q del_list
"${uci_prefix}.network=$interface"
321 ## @fn get_zone_of_device()
322 ## @brief Ermittle die Zone eines physischen Netzwerk-Interfaces.
323 ## @param interface Name eines physischen Netzwerk-Interface (z.B. eth0)
324 ## @details Das Ergebnis ist ein leerer String, falls zu diesem Interface keine Zone existiert
325 ## oder falls es das Interface nicht gibt.
327 trap
'error_trap get_zone_of_device "'"$*"'"' EXIT
333 find_all_uci_sections firewall zone |
while read -r uci_prefix;
do
334 zone=$(uci_get
"${uci_prefix}.name")
335 for interface in $(get_zone_interfaces
"$zone");
do
336 for current_device in \
339 [
"$current_device" =
"$device" ] && echo
"$device" &&
return 0
344 # keine Zone gefunden
348 ## @fn get_zone_of_interface()
349 ## @brief Ermittle die Zone eines logischen Netzwerk-Interfaces.
350 ## @param interface Name eines logischen Netzwerk-Interface (z.B. eth0)
351 ## @details Das Ergebnis ist ein leerer String, falls zu diesem Interface keine Zone existiert
352 ## oder falls es das Interface nicht gibt.
354 trap
'error_trap get_zone_of_interface "'"$*"'"' EXIT
359 find_all_uci_sections firewall zone |
while read -r uci_prefix;
do
360 zone=$(uci_get
"${uci_prefix}.name")
361 interfaces=$(get_zone_interfaces
"$zone")
362 is_in_list
"$interface" "$interfaces" && echo -n
"$zone" &&
return 0
365 # ein leerer Rueckgabewert gilt als Fehler
370 # Liefere die sortierte Liste der Opennet-Interfaces.
372 # 1. dem Netzwerk ist ein Geraet zugeordnet
373 # 2. Netzwerkname beginnend mit "on_wifi", "on_eth", ...
374 # 3. alphabetische Sortierung der Netzwerknamen
375 get_sorted_opennet_interfaces() {
376 trap
'error_trap get_sorted_opennet_interfaces "'"$*"'"' EXIT
379 # wir vergeben einfach statische Ordnungsnummern:
380 # 10 - konfigurierte Interfaces
381 # 20 - nicht konfigurierte Interfaces
382 # Offsets basierend auf dem Netzwerknamen:
386 for network in $(get_zone_interfaces
"$ZONE_MESH");
do
388 [ -z
"$(get_subdevices_of_interface "$network
")" ] && order=20
389 if [
"${network#on_wifi}" !=
"$network" ]; then
391 elif [
"${network#on_eth}" !=
"$network" ]; then
396 echo
"$order $network"
397 done | sort -n | cut -f 2 -
d " "
401 # Liefere alle vorhandenen logischen Netzwerk-Schnittstellen (lan, wan, ...) zurueck.
402 get_all_network_interfaces() {
404 # Die uci-network-Spezifikation sieht keine anonymen uci-Sektionen fuer Netzwerk-Interfaces vor.
405 # Somit ist es wohl korrekt, auf die Namen als Teil des uci-Pfads zu vertrauen.
406 find_all_uci_sections
"network" "interface" | cut -f 2 -
d . |
while read -r interface;
do
407 # ignoriere loopback-Interfaces und ungueltige
408 [ -z
"$interface" ] || [
"$interface" =
"none" ] || [
"$interface" =
"loopback" ] &&
continue
409 # alle uebrigen sind reale Interfaces
416 ## @fn delete_firewall_zone()
417 ## @brief Lösche eine Firewall-Zone, sowie alle Regeln, die sich auf diese Zone beziehen.
418 ## @param zone Name der Zone
419 ## @attention Anschließend ist ein "apply_changes firewall" erforderlich.
425 uci_prefix=$(find_first_uci_section firewall zone
"name=$zone")
427 for section in
"forwarding" "redirect" "rule";
do
428 for key in
"src" "dest";
do
429 find_all_uci_sections firewall
"$section" "${key}=$zone" |
while read -r uci_prefix;
do
437 ## @fn is_interface_up()
438 ## @brief Prüfe ob ein logisches Netzwerk-Interface aktiv ist.
439 ## @param interface Zu prüfendes logisches Netzwerk-Interface
440 ## @details Im Fall eines Bridge-Interface wird sowohl der Status der Bridge (muss aktiv sein), als
441 ## auch der Status der Bridge-Teilnehmer (mindestens einer muss aktiv sein) geprüft.
443 trap
'error_trap is_interface_up "'"$*"'"' EXIT
445 # falls es ein uebergeordnetes Bridge-Interface geben sollte, dann muss dies ebenfalls aktiv sein
446 if [
"$(uci_get "network.${interface}.type
")" =
"bridge" ]; then
447 # das Bridge-Interface existiert nicht (d.h. es ist down)
448 [ -z
"$(ip link show dev "br-${interface}
" 2>/dev/null || true)" ] && trap
"" EXIT &&
return 1
449 # Bridge ist aus? Damit ist das befragte Interface ebenfalls aus ...
450 ip link show dev
"br-${interface}" | grep -q
'[\t ]state DOWN[\ ]' && trap
"" EXIT &&
return 1
454 ip link show dev
"$device" | grep -q
'[\t ]state UP[\ ]' &&
return 0
457 trap
"" EXIT &&
return 1
461 ## @fn get_ipv4_of_mac()
462 ## @brief Ermittle die IPv4-Adresse zu einer MAC-Adresse
463 ## @param mac MAC-Adresse eines Nachbarn
466 awk
'{ if ($4 == "'"$ip"'") print $1; }' /proc/net/arp | sort | head -1
470 is_current_wifi_interface_channel_with_dfs() {
471 local wifi_interface=
"$1"
473 channel=$(iwinfo
"$wifi_interface" info | awk
'{ if ($3 == "Channel:") print $4 }')
474 echo
"$channel" | grep -q
"^[0-9]\+$" ||
return 1
475 [
"$channel" -gt 48 ]
480 trap
'error_trap run_iwinfo_scan "'"$*"'"' EXIT
481 local phy_device=
"$1"
482 local wifi_interface_uci
484 local original_channel
485 local needs_auto_channel
486 wifi_interface_uci=$(find_first_uci_section
"wireless" "wifi-iface" "device=$phy_device")
487 [ -z
"$wifi_interface_uci" ] &&
return 0
488 # the field
"device" is not strictly specified - thus fall back to a sane
default
489 wifi_interface=$(uci_get
"$wifi_interface_uci.device" "wlan0")
490 # try whether it works without any changes and exit early in case of success
491 iwinfo
"$wifi_interface" scan 2>/dev/
null &&
return 0
492 # Possible reason for failure: "scan" does not seem to work for a master on a DFS channel:
494 # (but sometimes it indeed works - thus we tried it in advance before, anyway)
495 # Try hard to prepare a situation where it is possible to scan the channels.
496 # Sadly this may break the HTTP connection (e.g. when accessing the wireless scan via the
497 # web interface) if the user accessed the IP of the wireless interface.
498 if [
"$(uci_get "$wifi_interface_uci.mode
")" =
"ap" ] && is_current_wifi_interface_channel_with_dfs
"$wifi_interface"; then
499 original_channel=$(uci_get
"wireless.$phy_device.channel")
500 needs_auto_channel=
"true"
502 needs_auto_channel=
"false"
504 if [
"$needs_auto_channel" =
"true" ]; then
505 # switch to a non-DFS channel
506 uci set
"wireless.$phy_device.channel=48"
507 uci commit
"wireless.$phy_device"
511 iwinfo
"$wifi_interface" scan
512 # revert to the original setup
513 if [
"$needs_auto_channel" =
"true" ]; then
514 uci set
"wireless.$phy_device.channel=$original_channel"
515 uci commit
"wireless.$phy_device"
521 get_potential_opennet_scan_results_for_device() {
522 trap
'error_trap get_potential_opennet_scan_results_for_device "'"$*"'"' EXIT
523 local phy_device=
"$1"
524 run_iwinfo_scan
"$phy_device" \
526 if ($1 == "ESSID:") { if ((name != "") && (encryption == "none")) print(signal"\t"channel"\t"quality"\t"name); split($0, tokens, /"/); name=tokens[2]; };
527 if ($1 == "Signal:") signal=$2;
528 if ($4 == "Quality:") quality=substr($5, 0, index($5, "/") - 1);
529 if ($3 == "Channel:") channel=$4;
530 if ($1 == "Encryption:") encryption=$2;
533 | grep -E
"(opennet|\bon\b)" \
534 | grep -vF
"join.opennet-initiative.de" ||
true
537 # 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...
add_interface_to_zone()
Fuege ein logisches Netzwerk-Interface zu einer Firewall-Zone hinzu.
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...
delete_firewall_zone(zone)
Lösche eine Firewall-Zone, sowie alle Regeln, die sich auf diese Zone beziehen.
get_ipv4_of_mac(mac)
Ermittle die IPv4-Adresse zu einer MAC-Adresse.
get_current_addresses_of_network()
Liefere die IP-Adressen eines logischen Interface inkl. Praefix-Laenge (z.B. 172.16....
is_interface_up(interface)
Prüfe ob ein logisches Netzwerk-Interface aktiv ist.
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 on function print_services services log for dir in etc on services d var on services volatile d