Die zentrale Konfigurationsschnittstelle UCI

UCI steht für Unified Configuration Interface, die zentrale Schnittstelle für die Konfiguration von OpenWrt.

Es gibt ein Kommandozeilenprogramm (uci) sowie API-Bindungen für verschiedene Programmiersprachen. Das im vorigen Kapitel besprochene Webinterface LuCI verwendet UCI als Backend für die Konfiguration.

Ich verwende UCI direkt über das Kommandozeilenprogramm,

  • wenn kein Webinterface installiert ist,
  • für die automatisierte Konfiguration und
  • wenn ich bereits via SSH angemeldet bin und schnell etwas anpassen will.

Allgemeine Prinzipien

UCI arbeitet mit Konfigurationsdateien im Verzeichnis /etc/config. Jede dieser Dateien deckt einen Bereich der Konfiguration ab, wie zum Beispiel network für die Netzwerk-Einstellungen und firewall für die Firewall-Einstellungen.

Ich kann die Dateien mit einem beliebigen Texteditor bearbeiten oder mit dem Kommandozeilenprogramm uci. In eigenen Programmen kann ich auf die API für verschiedene Programmiersprachen (C, Lua, Shell) zurückgreifen.

Damit eine Änderung in der Konfiguration wirksam wird, muss anschließend der entsprechende Dienst neu gestartet werden, zum Beispiel für die Firewall:

# /etc/init.d/firewall restart

Manchmal heißen die Skripts zum Neustart eines Dienstes unter /etc/init.d/ genau wie die Konfigurationsdatei in /etc/config/. In einigen Fällen muss ich andere Skripts zum Neustart des Dienstes verwenden. Einen Hinweis, welches Skript zu welcher Konfiguration gehört, liefert die Datei /etc/config/lucitrack aus dem Paket luci-base.

Syntax der Konfigurationsdateien

Die Syntax der Dateien unter /etc/config/ ist recht einfach. Die Anweisungen stehen auf jeweils einer Zeile und beginnen mit einer Direktive auf die spezifische Argumente folgen.

Es gibt die folgenden Direktiven:

  • package $name - leitet einen Konfigurationsbereich ein.

    Fehlt diese Direktive, dient der Dateiname als Name für den Bereich.

    Beispiel:

    package network
    
  • config $styp [ $sname ] - leitet eine neue Sektion in einem Bereich ein. Alle folgenden Direktiven bis zum nächsten config gelten für diese Sektion. Zur besseren Lesbarkeit sind diese oft eingerückt, das ist aber nicht notwendig.

    Beispiel:

    config interface 'lan'
    
  • option $oname $ovalue - weist in einer Sektion einem Bezeichner einen Wert zu. Die Bedeutung des Bezeichners und des Wertes hängt von der Sektion ab.

    Beispiel:

    option ipaddr '192.168.1.1'
    
  • list $lname $lvalue - weist einer Liste einen weiteren Wert zu. Ähnlich der Direktive option ist die Bedeutung vom Bereich und der Sektion abhängig.

    Beispiel:

    list network 'wan1'
    list network 'wan2'
    

Als Boolesche Werte kann ich ‘1’, ‘on’, ‘yes’ beziehungsweise ‘0’, ‘off’, ‘no’ verwenden.

Alle anderen Werte können beliebige Zeichenketten sein. Enthalten diese Leerzeichen oder Tabulatoren, muss ich sie mit einfachen oder doppelten Anführungszeichen begrenzen.

Die Bezeichner und Dateinamen dürfen nur die Zeichen _, A-Z, a-z und 0-9 enthalten.

Das Kommandozeilenprogramm uci

An dieser Stelle gebe ich nur eine kurze Einführung in das Kommandozeilenprogramm. Dieses zeigt alle unterstützte Optionen und Befehle an, wenn man es ganz ohne Argumente aufruft.

Die wichtigsten Befehle sind für mich:

  • uci show [$config[.$section[.$option]]]

    Damit zeigt uci die spezifizierten Daten (oder alles) in der Form an, in der ich es mit uci add oder uci set konfigurieren kann.

  • `uci get $config.$section[.$option]

    Dieser Aufruf liefert nur den Wert, den ich dann in einem Skript weiter verwenden kann.
    Um zum Beispiel den DHCP-Dämon neu zu starten, kann ich folgendes aufrufen und brauche nicht zu wissen, dass das Init-Skript dnsmasq heißt:

    /etc/init.d/$(uci get ucitrack.@dhcp[0].init) restart
    

    Beim Aufruf von Hand bin ich wahrscheinlich schneller, wenn ich direkt in /etc/init.d/ nachsehe. Ein Skript wird damit jedoch robuster gegenüber Änderungen.

  • uci export [$config]

    Dieser Aufruf zeigt die Konfiguration in der Form, wie sie in den Konfigurationsdateien steht. Mit uci import kann ich das wieder importieren, so dass sich diese beiden Befehle für Backup und Restore der Konfiguration eignen.

  • uci import

    Liest den mit uci export erstellten Text wieder ein.

  • uci changes

    listet Änderungen, die noch nicht mit uci commit bestätigt wurden.

  • uci revert $config[.$section[.$option]]

    macht unbestätigte Änderungen rückgängig.

  • `uci commit [$config]

    bestätigt die Änderungen, indem es sie in die Konfigurationsdatei unter /etc/config/ schreibt.

  • uci add|delete|add_list|del_list|set ...

    sind für einzelne Änderungen.

Neben den genannten Befehlen interessiert mich noch die Option -p, mit der ich einen Suchpfad für die variablen Konfigurationsdaten vorgeben kann. So finden sich in /var/state/network Laufzeitinformation zur Netzkonfiguration, die uci mir nur anzeigt, wenn ich den Suchpfad auf /var/state lege:

# uci -p /var/state network
network.lan.up=1
network.lan.device=eth0 eth1 eth2
network.lan.ifname=br-lan
network.loopback.up=1
network.loopback.device=lo

Habe ich eine neue Listenoption angelegt, kann ich diese mit dem Index -1 referenzieren und muss mich nicht um die genaue Position in der Liste kümmern:

# uci add firewall rule
# uci set firewall.@rule[-1].src = wan
# uci set firewall.@rule[-1].target = ACCEPT
# uci set firewall.@rule[-1].proto = tcp
# uci set firewall.@rule[-1].dest_port = 22
# uci commit
# /etc/init.d/firewall restart

Konfiguration der Firewall

Bei der Firewall kennt UCI die folgenden Sektionen:

  • defaults
  • zone
  • rule
  • include
  • forwarding
  • redirect
  • ipset

Alle Sektionen sind Listeneinträge, das heißt ich greife darauf mit der Notation `firewall.@sektion[$index]’ zu.

Detaillierte Informationen finde ich auf der Wikiseite zur Firewall-Konfiguration

Sektion defaults

Ich kann mir die Einstellungen anzeigen lassen mit:

# uci show firewall.@default[0]

Es gibt nur einen Listeneintrag.

Hier kann ich die globalen Einstellungen treffen, die ich in LuCI bei der Startseite für die Firewall finde.

Den Schutz vor SYN-Flood-Atacken aktiviere ich mit

# uci set firewall.@defaults[0].syn_flood=1

Um ungültige Datenpakete zu verwerfen gebe ich das folgende ein:

# uci set firewall.@defaults[0].drop_invalid=1

Entsprechend gebe ich die Policies für die drei Einsprungsketten vor:

# uci set firewall.@defaults[0].input=DROP
# uci set firewall.@defaults[0].output=ACCEPT
# uci set firewall.@defaults[0].forward=REJECT

Da iptables keine REJECT Policy kennt, landet in diesem Fall ein Sprung zur Kette reject in der Kette delegate_forward.

Sektion zone

Diese Liste enthält so viele Elemente, wie Zonen definiert sind.

Eine Übersicht über die Einstellungen aller Zonen bekomme ich mit

# uci show firewall|grep zone

Ich kann in dieser Sektion die allgemeinen Einstellungen für eine Zone vorgeben. Um beispielsweise eine neue Zone für die WAN-Schnittstelle zu definieren würde ich das folgende eingeben:

# uci add firewall zone
# uci set firewall.@zone[-1].name=wan
# uci set firewall.@zone[-1].input=DROP
# uci set firewall.@zone[-1].output=DROP
# uci set firewall.@zone[-1].forward=DROP
# uci set firewall.@zone[-1].masq=1
# uci set firewall.@zone[-1].mtu_fix=1
# uci set firewall.@zone[-1].network=ppp0

Der erste Aufruf legt eine neue Zone an und der zweite benennt diese.

Die Optionen input, output und forward bestimmen die Policies für diese Zone.

Mit der Option masq kann ich bestimmen, dass alle Datagramme für diese Zone mit der selben Absenderadresse hinausgehen sollen.

Die Option mtu_fix sorgt dafür, dass die MSS beim TCP-Verbindungsaufbau an die MTU der Verbindung angepasst wird.

Die Option network schließlich bezeichnet die Interfaces in den Netzwerkeinstellungen, die dieser Firewall-Zone zugeordnet werden.

Sektion rule

In LuCI wird bei den Firewallregeln unterschieden nach

  • Regeln, die einen Port am Router öffnen und
  • Regeln für die Weiterleitung

Diese gebe ich auf unterschiedliche Art ein.

Regeln, die einen Port am Router öffnen

Um beispielsweise den SSH-Port auf der WAN-Seite zu öffnen, gebe ich folgendes ein:

# uci add firewall rule
# uci set firewall.@rule[-1].name=Allow-SSH
# uci set firewall.@rule[-1].target=ACCEPT
# uci set firewall.@rule[-1].src=wan
# uci set firewall.@rule[-1].proto=tcp
# uci set firewall.@rule[-1].dest_port=22

Der erste Aufruf legt die Regel an und der zweite benennt sie.

Die Option target gibt an, wie mit den passenden Datagrammen zu verfahren ist, ACCEPT heißt zulassen.

Die Option src legt die Zone (wan) fest, proto (tcp) und dest_port (22) sind spezifisch für SSH.

Regeln für das Weiterleiten von Traffic

Um Datenverkehr in andere Zonen weiterzuleiten, muss ich sowohl die Quell- als auch die Ziel-Zone angeben, wie in folgendem Beispiel:

# uci add firewall rule
# uci set firewall.@rule[-1].name=Allow-LAN-WAN
# uci set firewall.@rule[-1].enabled=1
# uci set firewall.@rule[-1].target=ACCEPT
# uci set firewall.@rule[-1].src=lan
# uci set firewall.@rule[-1].dest=wan

Sektion include

Diese Sektion enthält nur einen Eintrag: den Namen der Datei, in der ich eigene Regeln ablege, die ich mit UCI oder LuCI nicht einstellen kann.

Hier ist der Name /etc/firewall.user voreingestellt.

Habe ich eine experimentelle Konfiguration, zu der ich schnell umschalten will, dann kann ich das mit den folgenden Befehlen:

# uci set firewall.@include[0].path=/etc/firewall.other
# /etc/init.d/firewall restart

Dabei gehe ich davon aus, dass die experimentelle Firewall-Konfiguration in der Datei /etc/firewall.other abgelegt ist.

Ich kann auch mehrere Skript-Dateien angeben, die nacheinander aufgerufen werden.

Sektion forwarding

Die Sektion forwarding steuert den Datenverkehr zwischen den Zonen und kann MSS-Clamping in bestimmten Richtungen aktivieren.

Eine forwarding Regel deckt nur eine Richtung ab. Um den Verkehr in beiden Richtungen zu regulieren, muss ich daher zwei Regeln definieren.

# uci add firewall forwarding
# uci set firewall.@forwarding[-1].src=lan
# uci set firewall.@forwarding[-1].dest=wan

Sektion redirect

In dieser Sektion landen die Regeln für die Adressumsetzung.

# uci add firewall redirect
# uci set firewall.@redirect[-1].name=Masquerade
# uci set firewall.@redirect[-1].enabled=1
# uci set firewall.@redirect[-1].target=SNAT
# uci set firewall.@redirect[-1].src=lan
# uci set firewall.@redirect[-1].dest=wan
# uci set firewall.@redirect[-1].proto=all
# uci set firewall.@redirect[-1].src_dip=192.168.1.1

Section ipset

Die UCI firewall ab Version 3 kann mit IPsets umgehen. Das bietet die Möglichkeit, große Adress- und/oder Portlisten in einer einzigen Regel abzuhandeln. Damit wird die Firewall entlastet, da IPsets sehr effizient beim Behandeln von großen Listen sind. Außerdem kann ich über die IPsets dynamisch die Listen ändern, ohne jedesmal die Firewall neu konfigurieren zu müssen.

Um IPsets verwenden zu können, muss das Paket ipset installiert sein, entweder bereits im OpenWrt-Image oder mit:

# opkg install ipset

Mit UCI kann ich IPsets lediglich anlegen beziehungsweise extern angelegte IPsets referenzieren. Um konkrete Adressen oder Ports zum IPset hinzuzufügen oder zu entfernen, verwende ich den ipset Befehl direkt oder in einem Skript.

Dieses Skript kann ich über die Sektion include automatisch beim Modifizieren der Firewall aufrufen lassen oder via cron, wenn das Skript die Daten von externen Quellen holt.