ltcp/bericht/nftables/nftables-perf.tex

75 lines
8.1 KiB
TeX

\subsubsection{Performance-Vergleich}
\paragraph{Testaufbau}
Um die Performance der beiden Firewall-Lösungen vergleichen zu könne, habe ich jeweils zwei unserer Zotac-Boards mit der Onboard-Netzwerkkarte an einen zusätzlichen Firewall-Rechner, der mir bereitgestellt wurde, verbunden. Die Spezifikationen der Zotac-Boards sind dem Abschnitt »Hardware« zu entnehmen. \\
Der zusätzliche Rechner war mit einem Intel Core 2 Duo E6750 mit 2,66 GHz, 2 GiB RAM ausgestattet und zwei Intel Gigabit-Netzwerkkarten ausgestattet (Intel 82566DM-2 und 82572EI). Die beiden Intel-Netzwerkkarten wurden direkt mit den beiden Zotac-Boards verbunden.
Der Firewall-Rechner wurde über die Onboard-Netzwerkkarte und die beiden Zotac-Boards über USB-Netzwerkkarten mit einem Switch und darüber mit der Headnode verbunden um einen Zugriff von außen bereitzustellen. \\
Der Firewall-Rechner soll nun über \texttt{iptables} und \texttt{nftables} den Datenverkehr zwischen den beiden Nodes kontrollieren. Die beiden Netzwerk-Interfaces der Firewall waren im Bridge-Modus verbunden. \\
\includegraphics{bilder/nft-layout.pdf}
\paragraph{Verwendete Software} Um schnell genug eine große Anzahl an Paketen pro Sekunde (PPS) erzeugen zu können um die Firewall-Lösungen zu benchmarken, habe ich \texttt{pktgen} verwendet. Hierbei handelt es sich um einen Paketgenerator, der als Kernel-Modul geladen wird und dadurch direkt im Kernel (ohne teure Kontext-Switches ins Userland) ausgeführt wird.
Zur Überwachung der Messdaten (Datenrate und PPS) habe ich auf allen vier Netzwerkschnittstellen das Tool \texttt{ifpps} aus dem \texttt{netsniff-ng}-Bundle laufen lassen.
\paragraph{Testablauf} Die Benchmarks habe ich einigen Bash- und Python-Scripts realisiert. Vor Beginn jedes Tests loggt sich der steuernde Rechner mit SSH auf der Firewall ein (über das äußere Konfigurationsnetzwerk) und generiert dort mit einem Python-Script die entsprechende Anzahl an Regeln für das jeweilige Firewall-System. Anschließend werden die Regeln mit dem entsprechenden Tool angewendet.
Danach wird der Paketgenerator \texttt{pktgen} beim Sender gestartet und zunächst zwei Sekunden laufen gelassen. Anschließend werden per SSH über \texttt{ifpps} die Netzwerkauslastung und mit \texttt{top} die Prozessorauslastung bzw. mit \texttt{ping} die Antwortzeit gemessen.
\pagebreak
\paragraph{Testeinstellung} Zunächst habe ich die Datenrate und die PPS in Abhängigkeit der Ethernet Frame-Größe gemessen um festzustellen, mit welcher Paketgröße die Messungen am besten durchzuführen sind. \\ \\
\includegraphics{benchmarks/nft-size-load-rate-send.pdf}
\vspace{0.5cm}
\includegraphics{benchmarks/nft-size-load-rate-recv.pdf}
Beim Sender betrug die Prozessor-Last dauerhaft 100 \%, für den Empfänger ist die CPU-Last zusätzlich grün eingetragen.
\pagebreak
Wie aus den Diagrammen ersichtlich wird, wird bei Frame-Größen, die an die MTU des Ethernets (1500 Bytes) heranreichen, das Gigabit-Netzwerk mit ca. 980 MBit/s und ca. 82000 PPS fast komplett ausgereizt. Je kleiner die Frame-Größe wird, desto größer wird zunächst die Anzahl der PPS, da das Netzwerk immer noch gut ausgelastet wird; die Datenrate sinkt zunächst zur langsam. \\
Beim Sender beginnt jedoch bei Frame-Größe kleiner 300 Byte die Datenrate linear zu sinken. Die PPS stagnieren dann bei ca. 450000 Paketen. Dies ist mit der Leistungsfähigkeit der Atom-Prozessoren zu erklären, da diese ab dieser Grenze komplett ausgelastet sind und die Pakete nicht schneller erzeugen können. \\
Beim Empfänger geschieht dieser Einbrauch schon bei ca. 450 Byte Frame-Größe. Hier stagniert die PPS bei ca. 250000. Auch dies lässt sich mit der Auslastung des Prozessors erklären. Der Unterschied zum Sender besteht darin, dass bei diesem die Pakete lediglich statisch zusammengesetzt und losgeschickt werden müssen. Beim Empfänger hingegen muss das Betriebssystem die Pakete zunächst auswerten, was den Bearbeitungszeit verlängert. Dadurch kommen beim Empfänger wesentlich weniger Pakete an, als der Sender losschickt. Die übrigen Pakete können nicht länger im Ringpuffer der Netzwerkkarte gespeichert werden und über PAUSE-Frames des Ethernet-Protokolls wird der Firewall mitgeteilt, weniger Pakete zu senden (mit \texttt{tcpdump} zu messen). \\
Um eine größtmögliche Auslastung des Gigabit-Netzwerkes zu ermöglichen und gleichzeitig die maximale Anzahl an PPS zu erhalten, habe ich also für die folgenden Benchmarks die Ethernet Frame-Größe auf 450 festgesetzt.
\paragraph{Anfängliche Probleme} Anfangs habe ich auf dem Empfänger keine »Anwendung« laufen lassen, die die Pakete des Senders entgegennimmt. Deshalb musste der Kernel zusätzliche ein ICMP-Paket mit »Connection Refused«-Flag an den Sender zurückschicken und daraufhin weitere Fehlerbehandlungen durchführen. Auf diese Weise habe ich am Empfänger zunächst nur ca. 110000 PPS empfangen können. Als Lösungsansatz habe ich dann \texttt{netcat} auf dem Port am Empfänger laufen lassen und die Daten nach \texttt{/dev/null} umgeleitet. Damit kamen keine Fehler mehr zustande und es konnten ca. 150000 PPS empfangen werden. Dies liegt jedoch immer noch weit unter den am Sender losgeschickten 450000 PPS und ist damit zu erklären, dass es für jedes Paket zu einem Kontext-Switch vom Kernel zu \texttt{netcat} kam und deshalb wieder die Bearbeitungszeit pro Paket stieg. \\
Letztendlich habe ich dazu entschieden, auf der Empfängerseite mit \texttt{iptables} die Pakete, die auf dem entsprechenden Port angekommen, direkt zu droppen. Dies hat auch keinen verzerrenden Einfluss auf den Benchmark, da letztendlich nur die Performance des Firewall-Systems gemessen werden soll, nicht der Empfängerhardware. Auf diese Weise konnten nun die zuvor erwähnten ca. 250000 PPS empfangen werden.
\pagebreak
\paragraph{Paketdurchsatz} Für den nachfolgenden Benchmark habe ich entsprechend viele Regeln generieren lassen, die besagen, dass von (zufällig gewählten) IP-Adressen alle Pakete gedropt werden sollen. \\
\includegraphics{benchmarks/nft-ipt-drop.pdf}
Zur besseren Anschaulichkeit auch in logarithmischer Ordinatenskalierung: \\
\includegraphics{benchmarks/nft-ipt-drop-log.pdf}
\pagebreak
Die Datenrate und die Anzahl an PPS liegen initial bei den bereits zuvor gemessenen ca. 980 MBit/s und 250000 Paketen pro Sekunde. Jedoch bricht die Leistungsfähigkeit bei beiden Firewall-Lösungen schnell erheblich ein. So kann \texttt{iptables} bei 5000 Regeln nur noch ca. 100 MBit/s bei 28000 Paketen verarbeiten, \texttt{nftables} schafft hier nur ca. 50 MBit/s bei 14000 Paketen. Im weiteren Verlauf wird jedoch in der logarithmischen Skalierung sichtbar, dass \texttt{nftables} bei mehr als ca. 25000 Regeln beginnt besser zu skalieren.
\paragraph{Antwortzeit} Unter Verwendung der vorherigen Regeln habe ich nun die Antwortzeit gemessen. \\
\includegraphics{benchmarks/nft-ipt-drop-response.pdf}
In diesem Diagramm erkennt man, dass hier \texttt{nftables}, im Gegensatz zum Paketdurchsatz, bei der Antwortzeit bei Skalierung auf sehr viele Firewall-Regeln besser abschneidet als \texttt{iptables}. Bis ca. 20000 Regeln reagieren beide Firewall-Lösungen in etwa gleich schnell, danach entwickeln sich die Reaktionszeiten linear auseinander. Bei 100000 Regeln hat hier \texttt{nftables} einen Vorteil von ca. 4 ms.
\paragraph{Weitere Regeln} Ursprünglich wollte ich für weitere Benchmarks noch andere Regeltypen verwenden. Dies scheiterte jedoch grundsätzlich an zwei Punkten:
\begin{itemize}
\item \texttt{nftables} ist bisher sehr schlecht dokumentiert. Es ist mir bspw. nicht gelungen eine Möglichkeit zu finden, um komplette Pakete (nicht nur den Header) nach bestimmten Strings zu durchsuchen. Während dies auf Kernel-Seite durch die virtuelle Maschine eigentlich ohne Weiteres möglich sein sollte, ist diese Möglichkeit im Userspace-Tool \texttt{nft} offenbar noch nicht enthalten (oder nirgends dokumentiert).
\item NAT-Regeln (oder generell modifizierende Regeltypen) lassen sich nicht ohne weiteres mit der Skalierung auf eine große Anzahl von Regeln benchmarken, da die Paketbearbeitung nach der ersten, den Filterkriterien entsprechenden Regel abbricht.
\end{itemize}
\pagebreak