Ein Modell der Firewall-Regeln bei OpenWrt

Um den Paketfilter bei OpenWrt zu verstehen, rufe ich mir den Weg eines Datagramms durch die Regelketten des Netfilter Frameworks, wie im Kapitel “Netfilter: Kernel-Komponenten” beschrieben, in das Gedächtnis zurück. Dieses Modell liefert mir die Einsprungspunkte in die Paketfilter-Tabellen beim Weg eines Datagramms durch den Netzwerkcode des Kernel. Die Einsprungspunkte in dem Modell liefern die Reihenfolge, in der die Tabellen des Netfilter-Frameworks befragt werden.

Betrachte ich ein Datagramm, das auf einem Interface ankommt und auf Grund seiner Zieladresse auf einem anderen Interface versendet werden soll, dann durchläuft es nacheinander die folgenden Stationen:

  • Tabelle raw, Kette PREROUTING
  • Connection Tracking Module
  • Tabelle mangle, Kette PREROUTING
  • Tabelle nat, Kette PREROUTING
  • Entscheidung über das Routing
  • Tabelle mangle, Kette FORWARD
  • Tabelle filter, Kette FORWARD
  • Tabelle mangle, Kette POSTROUTING
  • Tabelle nat, Kette POSTROUTING

Damit weiß ich, in welcher Reihenfolge ich die genannten Regelketten konsultieren muss, um zu analysieren, was das Netfilter-Framework mit diesem Datagramm macht.

Nun erlaubt das Netfilter-Framework beliebige Regelketten anzulegen, die aus den Einsprungsketten über Jump-Aktionen erreicht werden können. Diese Regelketten können das Arbeiten effizienter und übersichtlicher machen, sie können mich aber auch verwirren, wenn ich die zugrundeliegende Struktur nicht kenne.

Dazu begebe ich mich auf eine andere Abstraktionsebene und betrachte die Regelketten der Tabellen als Folgen von Anweisungen, um zu einer Aussage zu kommen, was mit einem Datagramm passieren soll, das bei einer vordefinierten Regelkette ankommt. Mit iptables-save beziehungsweise ip6tables-save erhalte ich alle Regelketten und Tabellen in einer Textdatei mit einer Regel pro Zeile. Diese Textdatei ohne Hilfsmittel auszuwerten, ist mühsam, da ich ohne Vorwissen immer bei den Standard-Regelketten anfangen muss, diese nacheinander betrachte und bei einer Jump-Aktion an anderer Stelle weiter mache, bis ich zu einer endgültigen Aktion komme, oder das Ende der vordefinierten Regelkette erreiche.

Hier hilft es mir, die Regelketten als gerichteten Graphen zu betrachten, dessen Knoten die Regelketten sind und dessen Kanten die Sprunganweisungen. Das funktioniert für einfache Firewalls mit wenigen Regeln und Regelketten recht gut, da kann ich dem Verlauf mit dem Finger auf einem genügend großen Ausdruck folgen.

Bei komplexeren Paketfiltern wird aber auch dass schon zu unübersichtlich. Hier betrachte ich nur die Regelketten und die zwischen ihnen existierenden oder möglichen Sprünge.

Die benutzerdefinierten Regelketten sind bei OpenWrt gut strukturiert, das Modell dafür hatte ich in [Weidner2012] bereits für die damals aktuelle Version grob beschrieben. Inzwischen hat sich OpenWrt weiterentwickelt und auch das Modell der Regelketten geändert. Darum gehe ich hier noch einmal detailliert auf die verschiedenen Tabellen der momentan aktuellen Version Barrier Breaker ein.

Einen guten Einstieg bietet die Dokumentation zur Firewall-Konfiguration im Wiki. Dort werden die Regelketten nach drei Typen unterschieden:

  • system: sind die vordefinierten Regelketten des Netfilter-Frameworks wie INPUT, OUTPUT, FORWARD, …
  • internal: sind Regelketten, die von der OpenWrt-Firewall intern verwendet werden. Diese enthalten Sprünge zu den anderen Regelketten oder Regeln, die via LuCI oder UCI konfiguriert wurden.
  • user: sind frei verfügbar und zunächst leer. Regeln für diese Ketten kann ich in der Datei /etc/firewall.user definieren.

Tabelle filter

Das nachfolgende Bild zeigt ein allgemeines Modell der Regelketten dieser Tabelle. Ganz links stehen die vordefinierten Regelketten, die den Einsprungspunkten in Kapitel “Netfilter: Kernel-Komponenten” entsprechen.

Modell der Tabelle filter
Modell der Tabelle filter

Die Namen der Regelketten folgen einem festen Schema, dem die Namen der Knoten im Graphen entsprechen. Dabei steht der Wortteil name für die entsprechende Zone bei der Netzkonfiguration des Routers. Das heißt, wenn zwei Zonen (zum Beispiel LAN und WAN) definiert sind, dann gibt es für den Knoten zone_name_forward im Modell, die beiden Regelketten zone_lan_forward und zone_wan_forward in der Tabelle filter, die die gleiche Funktion, wie im Modell haben, deren Regeln aber nur für die betreffende Zone gelten.

Gehen wir die einzelnen Ketten und deren Funktion durch.

Ich beginne bei der Kette INPUT. Von hier aus geht es zur Kette delegate_input (system). Wie der Name verrät, delegiert diese zu verschiedenen anderen Ketten und zwar zu input_rule, syn_flood und zu zone_name_input. Außerdem enthält diese Kette einige hoch priorisierte Regeln, wie zum Beispiel -i lo -j ACCEPT, die dafür sorgt, dass lokaler Datenverkehr über Adresse 127.0.0.1 immer durchkommt.

Die erste angesprungene Kette ist input_rule (user). Diese Kette ist reserviert für benutzerdefinierte Regeln für Verbindungen zum Router, die über die Datei /etc/firewall.user konfiguriert werden.

Zur Kette syn_flood (internal) geht es nicht immer, sondern nur, wenn in der Konfiguration “SYN flood protection” aktiviert wurde. Genau dann wird in INPUT eine Regel eingefügt, die für alle TCP-Pakete mit gesetztem SYN-Flag zu dieser Kette springt. In der Kette sind zwei Regeln: eine, die ein Limit prüft und zur aufrufenden Regelkette zurückspringt und eine, die alle Datagramme, welche über dem Limit liegen, verwirft.

Die letzte Ketten, die von delegate_input aus angesprungen werden, sind im Modell mit zone_name_input (internal) bezeichnet. Wie viele Ketten das genau sind, hängt von den Firewall-Zonen ab, ich definiert habe Als erstes verzweigen diese Ketten zu den entsprechenden Ketten namens input_name_rule. Dann folgen die Regeln, die in LuCI oder UCI für die betreffende Zone konfiguriert wurden sowie einige automatisch generierte Regeln wie zum Beispiel die Regeln, die konfigurierte Port-Weiterleitungen zulassen. Als letztes verzweigen diese Ketten je nach gewählter Policy für die betreffende Zone zu einer der drei Ketten zone_name_src_ACCEPT, zone_name_src_DROP oder zone_name_src_REJECT.

Die Ketten zone_name_src_ACCEPT (internal), zone_name_src_DROP (internal) und zone_name_src_REJECT (internal) sind Policy-Ketten. Diese lösen die entsprechende Reaktion für Datagramme aus der betreffenden Zone aus, wenn bis dahin noch keine Entscheidung getroffen wurde.

Die Ketten zone_name_dest_REJECT und zone_name_src_REJECT enthalten jeweils einen Sprung zur Kette reject. Diesen habe ich im Modell nicht aufgenommen, um es nicht unnötig zu komplizieren. In der Kette reject werden alle Datagramme zurückgewiesen, und zwar mit TCP-Reset für TCP-Pakete und mit ICMP-Port-Unreachable für alle anderen.

Auf den ersten Blick mag das recht willkürlich erscheinen, es offenbart jedoch ein grundlegendes Design-Prinzip: Jede Regelkette hat einen ganz speziellen Zweck, auf den sich die darin enthaltenen Regeln konzentrieren.

Damit muss ich mir beim Hinzufügen von Regeln weniger Gedanken machen und kann mich ganz auf den Zweck der Regeln konzentrieren. Wenn ich die Policy für einen Bereich auswähle und damit den entsprechenden Sprung in der Kette zone_name_input festlege, muss ich nicht wissen, welche Interfaces dazugehören. Diese stehen in der entsprechenden Policy-Kette. Das gleiche gilt für die Input-Regeln der betreffenden Zone. Für diese werden die ankommenden Schnittstellen in der Kette delegate_input den Zonen zugeordnet.

Schauen wir uns als nächstes die FORWARD Regeln an. Dort finden wir nur einen Sprung zur Kette delegate_forward (internal). In dieser Kette geht es als erstes zur Kette forwarding_rule. Danach kommen einige automatisch generierte Regeln, wie zum Beispiel -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT, die dafür sorgt, dass Datagramme von bestehenden Verbindungen akzeptiert werden. Danach folgen Sprünge zu den Regelketten zone_name_forward, die anhand des Ingress-Interfaces ausgewählt werden. Schließlich werden alle Datagramme, für die noch keine Entscheidung gefällt wurde mit einem Sprung zur Regelkette reject abgewiesen.

Die Regelkette forwarding_rule (user) ist reserviert für Regeln, die ich in /etc/firewall.user definieren kann.

Die Ketten zone_name_forward (internal) enthalten einen Sprung zur Kette forwarding_name_rule sowie Regeln für die betreffende Zone. Da diese Regeln üblicherweise andere Zonen haben, gibt es hier Sprünge zu den Zonen zone_name_dest_ACCEPT, zone_name_dest_DROP und zone_name_dest_REJECT, die nur für Datagramme mit dem entsprechenden Egress-Interface greifen. Am Schluß der Kette zone_name_forward steht eine Policy-Regel, die bei Datagrammen greift, für die noch keine Entscheidung gefallen ist.

Bleiben als letztes die Regeln für den abgehenden Verkehr.

Von der vordefinierten Kette OUTPUT geht es zunächst zur Regelkette delegate_output (internal), die zu den Regelketten output_rule und den Ketten für die einzelnen Zonen zone_name_output verzweigt. Außerdem enthält diese Kette Firewallregeln mit hoher Priorität, wie zum Beispiel die Regel -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT, dafür sorgt, dass Datenpakete von bestehenden Verbindungen – die an anderer Stelle im Regelwerk erlaubt wurden – sofort durchgelassen werden.

Die Regelkette output_rule (user) ist für benutzerdefinierte Regeln für abgehende Verbindungen vom Router reserviert. Diese können wir über die Datei /etc/firewall.user konfigurieren.

Die Regelketten zone_name_output (internal) enthalten einen zunächst einen Sprung zur zugehörigen Kette output_name_rule. Darauf folgen die Firewall-Regeln, die die entsprechende abgehende Zone betreffen und schließlich eine Policy-Regel für die betreffende Zone.

Die Kette output_name_rule (user) ist wie output_rule für benutzerdefinierte Regeln definiert, die ich in /etc/firewall.user angeben kann. Im Gegensatz zu output_rule sehen die Regeln in dieser Kette aber nur Datenverkehr, der zu einer bestimmten Zone hinausgeht.

Tabelle nat

Für die Tabelle nat ist das Modell einfacher. Hier haben wir im Netfilter-Framework die drei Einsprungspunkte PREROUTING, OUTPUT und POSTROUTING, von denen die Firewall von OpenWrt nur PREROUTING und POSTROUTING verwendet.

Modell der Tabelle nat
Modell der Tabelle nat

Von PREROUTING aus geht es in die Kette delegate_prerouting (internal). In dieser Kette finden wir einen Sprung zur Kette prerouting_rule, ein paar globale Pre-Routing-Regeln und die Verzweigungen zu den Ketten zone_name_prerouting für die einzelnen Firewall-Zonen.

Die Regelkette prerouting_rule (user) ist für benutzerspezifische Regeln, die ich in der Datei /etc/firewall.user spezifiziere.

Die Ketten zone_name_prerouting (internal) enthalten einen Sprung zur Kette prerouting_name_rule und die DNAT-Regeln, die ich mit LuCI oder UCI für die betreffende Zone.

Die Regelketten prerouting_name_rule wiederum können benutzerspezifische Regeln für die betreffende Zone aufnehmen, die in /etc/firewall.user spezifiziert werden.

Betrachten wir die Kette POSTROUTING, so finden wir dort nur einen Sprung zur Kette delegate_postrouting (internal). Darin finden wir einen Sprung zu postrouting_rule, globale Post-Routing-NAT-Regeln und Verzweigungen zu den Ketten zone_name_postrouting.

Die Regelkette postrouting_rule (user) ist für globale benutzerspezifische NAT-Regeln, die ich in der Datei /etc/firewall.user konfigurieren kann.

In den Ketten zone_name_postrouting (internal) finden wir einen Sprung zu postrouting_name_rule sowie NAT-Regeln für die betreffende Zone. Das sind SNAT- und MASQERADE-Regeln, wie im Graphen bereits angedeutet.

Die Regelkette postrouting_name_rule (user) kann ich in der Datei /etc/firewall.user nach eigenem Gutdünken mit NAT-Regeln für die betreffende Zone füllen.

Tabelle mangle

In dieser Tabelle gibt es nur zwei benutzerdefinierte Ketten.

Modell der Tabelle mangle
Modell der Tabelle mangle

Von PREROUTING aus geht es zur Kette fwmark (internal), die für MARK-Regeln verwendet wird.

Von FORWARD aus geht es zur Kette mssfix (internal). In dieser Kette kann ich die TCPMSS-Regeln finden. Das sind Regeln, die bei Datagrammen, welche eine neue TCP-Verbindung aufbauen (mit gesetztem SYN-Bit) die TCP-Option MSS (Maximum Segment Size) auf die MTU der abgehenden Schnittstelle beschränkt. Damit wird die Path-MTU für TCP bei Verbindungen zu Netzen mit kleinerer MTU (zum Beispiel PPPoE-Verbindungen) automatisch angepasst.

Tabelle raw

Diese Tabelle wird nur für Datagramme genutzt, die vom Connection Tracking ausgenommen werden sollen.

Modell der Tabelle raw
Modell der Tabelle raw

Von PREROUTING aus geht es zur Kette delegate_notrack (internal), die ihrerseits zu den Ketten zone_name_notrack verzweigt.

Die Ketten zone_name_notrack (internal) enthalten für die entsprechende Zone Regeln für Datagramme, die vom Connection Tracking ausgenommen werden sollen.