Sonntag, 3. Oktober 2021

OpenWrt mit Scripts konfigurieren

Das neue Release openwrt-21.02. bringt unter Umständen eine neue Herausforderung: Beim Versuch des Updates kommt die Meldung:

Please wipe config during upgrade (force required) or reinstall. Reason: Config cannot be migrated from swconfig to DSA Image check failed.

Die weitere Recherche ergibt: Es gibt keinen wirklichen Upgrade Pfad: Baue das System einfach neu auf. Oh Mist: was hatte ich nochmal alles wie konfiguriert und installiert?

  1. Switch Konfiguration: VLANs für einen LAN und zwei WAN Anschlüsse
  2. Interface definieren: LAN als DHCP Server, WAN als DHCP Client, mehrere Wireless Netzwerke
  3. DHCP Reservierung für die wichtigen Server/Geräte einrichten.
  4. Software installieren, Loadbalancing, Adblocker, Advanced Reboot
  5. Einiges "fein" konfigurieren

Tja und nun? Immer auf Luci und "Keep Configuration" verlassen. Jetzt oder nie: Konfigurationsscripte sind die beste Dokumentation!

Zum Nachlesen: The UCI systemDistributed Switch Architecture

Achtung: Meine Codebeispiele konfigurieren nicht alles. Der Router ist in einem Grundzustand, der kann von Gerät zu Gerät unterschiedlich sein! Eine mehrfache Ausführung der hier gezeigten Scripte kann zu fehlerhaften Konfigurationen führen.

Ich habe ja noch meine alte Easybox 803A, an der kann ich etwas üben (in der virtuellen Umgebung hat man nämlich keinen Switch zum konfigurieren) dummerweise wird die noch nicht per dsa konfiguriert. Aber alles andere kann man sich hier anschauen. Für die heiße Phase habe ich zwei Fallback Optionen:

  1. Mein WRT1900ACS hat zwei Firmwarepartitionen: Ich kann mittels Advanced Reboot jederzeit zum jetzigen Stand zurück.
  2. Vielleicht gelingt es die Easybox als temporäre Zwischenlösung zu konfigurieren und als Austausch in Betrieb zu nehmen?

Nimmt man den OpenWrt Router nach einem Reset oder Neu in Betrieb, steht das LAN Interface auf 192.168.1.1 und der DHCP Server ist aktiv. Man verbindet also entweder temporär einen PC mit dem LAN Kabel oder stellt die IP eines PC temporär um. Am Ende aller uci Scripte wird im Normalfall mit einem uci commit - gefolgt von einem reload_config - alles aktiviert. Manchmal arbeitet reload_config nicht perfekt - reboot macht eventuell alles gut.

Basiskonfiguration

Das Script konfiguriert:

  1. Zeitzone und Hostname
  2. LAN1 als LAN Interface mit DHCP Server
  3. LAN2 als extra Interface zur Konfiguration im bestehenden Netzwerk
  4. LAN3 und LAN4 als wan und wanb Interface zum Anschluss an DSL Router

Der Code ist für die OpenWrt Version ab 21.02.0 aber mit swconfig Konfiguration (EasyBox803a)!

NIP='192.168.1.1'      # Network IP for LAN1
# change TimeZone and Hostname
uci batch << EOI
set system.@system[0].zonename='Europe/Berlin'
set system.@system[0].timezone='CET-1CEST,M3.5.0,M10.5.0/3'
set system.@system[0].hostname='EasyBox'
EOI
# reconfigure br-lan and first switch
# for switch info: swconfig dev switch0 show
uci batch << EOI
delete network.@device[0].ports
add_list network.@device[0].ports='eth0.1'
set network.@switch_vlan[0].ports='0t 2'
EOI
# create new switches, first delete if exist
while uci -q delete network.@switch_vlan[1]; do :; done
for vlanid in 2 3 4
do
ports=''
[ $vlanid -eq 2 ] && ports='0t 3'
[ $vlanid -eq 3 ] && ports='0t 4'
[ $vlanid -eq 4 ] && ports='0t 5'
uci batch << EOI
add network switch_vlan
set network.@switch_vlan[-1].device="switch0"
set network.@switch_vlan[-1].vlan=$vlanid
set network.@switch_vlan[-1].vid=$vlanid
set network.@switch_vlan[-1].ports="$ports"
EOI
done
# reconfigure lan and wan interface, create new wanb und ctrl interface, add interfaces to the existing lan and wan firewall zone
uci batch << EOI
set network.lan.ipaddr=$NIP
delete network.wan
set network.wan='interface'
set network.wan.proto='dhcp'
set network.wan.device='eth0.3'
set network.wan.metric='10'
set network.wanb='interface'
set network.wanb.proto='dhcp'
set network.wanb.device='eth0.4'
set network.wanb.metric='20'
set network.wanb6='interface'
set network.wanb6.proto='dhcpv6'
set network.wanb6.device='@wan'
set network.ctrl='interface'
set network.ctrl.proto='dhcp'
set network.ctrl.device='eth0.2'
set network.ctrl.defaultroute='0'
add_list firewall.@zone[0].network='ctrl'
add_list firewall.@zone[1].network='wanb'
add_list firewall.@zone[1].network='wanb6'
EOI
# set wifi country 
i=0
while uci -q get wireless.@wifi-device[$i]
do 
  if uci -q get wireless.@wifi-device[$i].country  
  then 
      uci set wireless.@wifi-device[$i].country='DE'
      uci set wireless.@wifi-device[$i].cell_density='0'
      i=`expr $i + 1`
  fi
done
# save and reload - maybe service network restart or reboot required
uci commit
reload_config

Das crtl Interface darf keinen Gateway Eintrag bekommen (defaultroute='0'), damit es nicht als wan (Upstream) Interface interpretiert wird.

Versionsabhängig

Mit der Version 21.02. ändert sich beim WRT1900ACS die Switchkonfiguration grundlegend im Syntax und die Interface Konfiguration macht jetzt die zwei Geräte (device und interface) sichtbar und konfigurierbar. Die Switchkonfiguration ist abhängig vom Gerät (dsa oder swconfig)!

Versionsabfrage in OpenWrt: Die erste Zeile schreibt Variablen (source dot), die zweite erzeugt eine Funktion um eine dreistellige Versionsnummer vergleichbar zu machen (aus 21.02.0 wird 210020000).

# set Variables with source dot, version formats e.g. 21.02.0 to 210020000
. "/etc/openwrt_release"
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }

Eventuell ist es anhand des oben verlinktem Artikels einfacher, die Konfiguration mit nano in der /etc/config/network zu übersetzen?

DHCP static Leases sichern/übertragen

Dazu hatte ich schon mal etwas geschrieben. Hier lese ich alle aktiven Leases als static Leases direkt in den zweiten Router. Damit wird beim Routerwechsel im Netzwerk erstmal nichts durcheinander gewirbelt.

"Mein" Umgang hier im Script mit ssh:

Der Scriptcode kommt in eine Variable, per Pipe wird der Code an ssh gesendet, im Remotehost wird er mit sh - "empfangen und ausgeführt, die Ausgabe landet wieder beim Localhost, dort wird weiter verarbeitet. Die finalen uci Kommandos werden in einem awk Script ausgeführt, damit die letzte Codezeile nicht so sperrig ist, habe ich den Code in eine Funktion gepackt. 

Der lokale Host hat möglicherweise keine Namensauflösung für den Remote Host -> IP Adresse verwenden!

# read HereDoc script code to Variable: get static and dynamic leases together
read -r -d '' script <<'EOI'
for i in $(uci show dhcp | grep -oE "host\[\d+\].ip"|grep -oE '\d+'); do
  printf "$(uci get dhcp.@host[$i].name) $(uci get dhcp.@host[$i].ip) $(uci get dhcp.@host[$i].mac)\n"
done
cat /tmp/dhcp.leases| awk '$4 != "*" { print( $4,$3,$2) }'
EOI
# function for split name|ip|mac and use uci to set the static lease
function rhpl { awk '{ system( "uci add dhcp host\nuci set dhcp.@host[-1].name="$1"\nuci set dhcp.@host[-1].ip="$2"\nuci set dhcp.@host[-1].mac="$3) }'; }
# execute Script remotly, format output, remove doubles and set static lease
echo "$script"|ssh user@remoteIP sh -|awk '{ print( $1,$2,toupper($3) ) }'|sort|uniq|rhpl

Da fehlt uci commit und reload_config?

Varianten mit einem dritten Host:

Auf diesem lokal abspeichern (sichern). Die Variable $script mit dem ersten Teil von oben erzeugen!  

echo "$script"|ssh user@Remotehost 'sh -' |awk '{ print( $1,$2,toupper($3) ) }'|sort|uniq > wpo.txt

Host1 auslesen und auf Host2 speichern - ohne direkte Verbindung zwischen host1 und host2:

echo "$script"|ssh user@host1 'sh -' |awk '{ print( $1,$2,toupper($3) ) }'|sort|uniq|ssh root@host2 'cat > wpo.txt'

Auf Host2 wieder eingelesen:

function rhpl { awk '{ system( "uci add dhcp host\nuci set dhcp.@host[-1].name="$1"\nuci set dhcp.@host[-1].ip="$2"\nuci set dhcp.@host[-1].mac="$3) }'; }
rhpl < wpo.txt

Anmerkung: Leider ist es mir nicht gelungen, das von einem Windows Host (Powershell oder CMD) fehlerfrei auszuführen. Da stimmt irgendetwas mit der Pipe nicht?

Wifi Interfaces Konfiguration

Im Handbuch steht eine gute Vorlage für ein guest Wifi Netzwerk Script, aber offenbar etwas älter. Da sind zwei Mängel: Es wird ein offenes Wlan und die Bridge br-guest wird ohne den Eintrag ports erzeugt. Ich komme derzeit noch nicht mit der Umsetzung dieser Idee (mit der Konfiguration der Firewall) klar. Deshalb erst einmal ein/das default wlan konfigurieren.

Achtung: Das default_radio0 "OpenWrt" ist ohne Sicherheit konfiguriert und nicht deaktiviert (disabled='1'). Stattdessen ist das radio0 (wifi-device) deaktiviert. Radio0 wird aber aktiviert wenn man ein neues Wlan definiert und dieses einfach startet. Damit wird das default_radio0 aber auch aktiviert. 

# change default_radio0 wifi
ssid='MyWlan'
key='MySecurePassword'
uci batch << EOI
set wireless.default_radio0.ssid="$ssid"
set wireless.default_radio0.encryption='psk2'
set wireless.default_radio0.key="$key"
EOI

Dieses Wifi Netzwerk ist mit dem lan Interface verbunden, hat die gleiche DHCP Konfiguration und ist in der gleichen Firewall Zone. 

Wifi verbunden mit vorhandenem Interface

Ein neues Wlan welches mit einem bestimmten Interface (network) verbunden ist:

# create additional wifi network
lanid='lan'
ssid='MyWlan3'
key='MySecurePassword3'
uci batch << EOI
# /etc/config/wireless
set wireless.${ssid}=wifi-iface
set wireless.${ssid}.device='radio0'
set wireless.${ssid}.network=${lanid}
set wireless.${ssid}.mode='ap'
set wireless.${ssid}.ssid=${ssid}
set wireless.${ssid}.encryption='psk2'
set wireless.${ssid}.key=${key}
set wireless.${ssid}.disabled='1'
EOI
# enable radio0 if disabled
uci -q get wireless.radio0.disabled && uci del wireless.radio0.disabled
# commit
uci commit
reload_config

Dieses Wifi wird mit radio0 verbunden.

Wifi alles extra

Will man ein separates Wifi Netzwerk mit eigenem DHCP und Firewall definieren, erzeugt man zuerst ein neues "Device" und verbindet ein neues Interface damit. Siehe UserGuide.

Ansonsten entsteht ein Art "Hidden Device" welches man ab Version 21.02 zwar im neuen Reiter Device sehen, aber nicht weiter konfigurieren kann. In früheren Version tauchte das Device "geisterhaft" auf, z.B. in der Interface Bezeichnung. 

Achtung: Im Code wird aus der Variable ssid auch die Bezeichnung der Abschnitte in der config erzeugt. Das funktioniert wahrscheinlich nur ohne Leer- und Sonderzeichen in der ssid.

# create additional wifi network
ssid='MyWlan2'
key='MySecurePassword2'
IP='192.168.3.1'      # IP for additonal wifi 
uci batch << EOI
# /etc/config/network
# interface
set network.${ssid}=interface
set network.${ssid}.proto='static'
set network.${ssid}.ipaddr=$IP
set network.${ssid}.netmask='255.255.255.0'
set network.${ssid}.device="br-${ssid}"
# Bridge Device
set network.${ssid}_dev="device" 
set network.${ssid}_dev.type='bridge'
set network.${ssid}_dev.name="br-${ssid}"
# /etc/config/wireless
set wireless.${ssid}=wifi-iface
set wireless.${ssid}.device='radio0'
set wireless.${ssid}.mode='ap'
set wireless.${ssid}.ssid=${ssid}
set wireless.${ssid}.encryption='psk2'
set wireless.${ssid}.key=${key}
set wireless.${ssid}.network=${ssid}
# /etc/config/dhcp
set dhcp.${ssid}=dhcp
set dhcp.${ssid}.interface=${ssid}
set dhcp.${ssid}.start='100'
set dhcp.${ssid}.limit='150'
set dhcp.${ssid}.leasetime='12h'
# /etc/config/firewall
add_list firewall.@zone[0].network=${ssid}
EOI
# activate radio0 if disabled
uci -q get wireless.radio0.disabled && uci del wireless.radio0.disabled
# commit
uci commit
reload_config

Wifi aktivieren / deaktivieren

Einzelen Wifi MyWlan2 aktivieren, falls der Sender (radio0) auch deaktiviert wurde (luci macht das) muss dies auch aktiviert werden:

uci del wireless.MyWlan2.disabled
uci -q get wireless.radio0.disabled && uci del wireless.radio0.disabled
uci commit wireless
wifi reload

Einzelnes Wifi MyWlan2 deaktivieren:

uci set wireless.MyWlan2.disabled='1'
uci commit wireless
wifi reload

Sender radio0 deaktivieren

uci set wireless.radio0.disabled='1'
uci commit wireless
wifi reload

Anmerkung: Es sind im Web und in der OpenWrt Doku unterschiedliche reload Kommandos zu finden. Ich habe ermittelt: wifi reload funktioniert hier - ein reload_config bewirkt hier "nichts". Eigentlich sollte reload_config die notwendigen Dienste ermitteln und neu starten.

Ein luci-reload Kommando habe ich auch irgendwo gefunden, das bewirkt gar nichts. 

Software Installation

Software installieren (libustream-wolfssl ist ab der Version 21.02.0 schon installiert)

# install Software
opkg update
opkg install libustream-wolfssl luci-app-advanced-reboot
opkg install mwan3 luci-app-mwan3
opkg install adblock luci-app-adblock tcpdump-mini

Achtung: Ein upgrade über opkg ist nicht ratsam. Siehe auch UserGuide

Dies und Das

Arrays der Konfiguration abfragen

Man kann den höchsten Index ermitteln und anschließend in einer Schleife die einzelnen Werte abfragen. die erste Schleife läuft am Ende in eine Fehler und bricht nach dem höchsten Index ab. Hier im Beispiel sollen Werte der config=network, section=device ermittelt werden.

i=0
while uci get network.@device[$i] &> /dev/null ; do
	let i++;
done
let i--
for i in $(seq 0 $i) ;do
   uci get network.@device[$i].name
done

Oder man ermittelt die Indexwerte direkt als Sequenz. Mit einem zweistufigen grep ermittelt man zunächst die section anhand einer signifikanten option und dann extrahiert man die Ziffer. Hier zunächst die drei Stufen zum nachvollziehen, relevant ist im Beispiel der section=device und option=name. Beide Strings sind also je nach gewünschter Abfrage anzupassen!

uci show network
uci show network | grep -oE 'device\[\d+\].name'
uci show network | grep -oE "device\[\d+\].name"|grep -oE '\d+'

Da diese Ausgabe eine Sequenz der Indizes erzeugt, kann man diese direkt in der Schleife verwenden.

for i in $(uci show network | grep -oE 'device\[\d+\].name'|grep -oE '\d+') ;do
   uci get network.@device[$i].name
done


Bootpartition abfragen und umschalten

fw_printenv -n boot_part                            # get active Firmware Partition
fw_setenv boot_part 2                               # switch to number 2 for next boot
fw_setenv boot_part $(( $(fw_printenv -n boot_part) % 2 + 1 )) # toggle boot partition


Idee für zukünftige Scripts

Noch ein Ansatz um Scripts vom Windows Host / Powershell nach Linux zu bringen. Der String wird Linux konform bezüglich der Zeilenumbrüche und Set-Content sorgt für die richtige Kodierung (utf8)

$script=@'
for i in $(uci show dhcp | grep -oE "host\[\d+\].ip"|grep -oE '\d+'); do
  printf "$(uci get dhcp.@host[$i].name) $(uci get dhcp.@host[$i].ip) $(uci get dhcp.@host[$i].mac)\n"
done
cat /tmp/dhcp.leases| awk '$4 != "*" { print( $4,$3,$2) }'
'@

$script -replace "`r`n",'`n'| Set-Content -NoNewline script.txt
scp script.txt root@wrt1900:
ssh root@wrt1900 'sh script.txt'


Wie man die gesamte Installation beim Upgrade erhalten habe ich hier beschrieben.

Keine Kommentare:

Kommentar veröffentlichen