In diesem Praktikum verlassen wird die Anwendungsschicht und begeben uns eine Schicht tiefer, in die Transportschicht. Die beiden Transportprotokolle, die wir kennengelernt haben heißen TCP und UDP und wir werden deren Verhalten experimentell untersuchen, insbesondere das von TCP. TCP ist ein vergleichsweise komplexes Protokoll, aber von zentraler Bedeutung. Die Eigenschaften und das Verhalten von TCP zu kennen ist allerdings wichtig für Anwendungsprogrammierer, damit man versteht, welchen Dienst und welche Dienstgüte TCP anbietet. Heute geht es daher darum, das Verhalten von TCP zu verstehen, indem wir praktisch mit TCP und relevanten Werkzeugen arbeiten. Darüber hinaus werden wir ​Mininet kennenlernen, ein Tool, um ein beliebiges Netzwerk auf einem einzigen Rechner abzubilden und ​iperf​, ein Werkzeug, um den Durchsatz eines Pfades zu ermitteln. Letzteres wird benötigt, da wir eine Anwendung brauchen, die Daten erzeugt, damit wir das Verhalten von TCP beobachten können und iperf sendet Daten, so schnell es kann und das so lange man möchte. Da der Verhalten von TCP bei Belastung am interessantesten ist, bietet sich iperf für unsere Zwecke an.

Mininet

Mininet ist ein Tool, dass die Virtualisierungsmöglichkeiten des Linux-Kernels nutzt, um virtuelle Netzewerke aus verschiedenen Komponenten aufzubuauen, die zwar auf einem Host-System laufen, sich allerding genauso verhalten, als wären Sie über ein Netz miteinander verbunden. Was sich kompliziert und ressourcenintensiv anhört ist mit Hilfe von Mininet einfach und ressourcenschonend, so dass auch größere Netzwerke problemlos aufgebaut werden können. Vielleicht beschreibt es dieser Text von der Mininet-Webseite am besten:

Mininet creates a realistic virtual network, running real kernel, switch and application code, on a single machine (VM, cloud or native), in seconds, with a single command. Because you can easily interact with your network using the Mininet CLI (and API), customize it, share it with others, or deploy it on real hardware, Mininet is useful for development, teaching, and research.

Von http://mininet.org

Mininet ist aus Nutzersicht ein Kommandozeilentool, das nach dem Start eine eigene, neue Kommandozeile bereitstellt - die Mininet-CLI. Da Mininet priviligierte Befehle ausführt, muss Mininet mit Super-User-Rechten gestartet werden. Am einfachsten startet man Mininet so:

sudo mn

Wenn man diese Befehl ausgeführt hat, dann werden Statusnachrichten ausgegeben und relativ schnell erwartet einen die Mininet-CLI, die man am Prompt gut erkennt:

*** Creating network
*** Adding controller
*** Adding hosts:
h1 h2 
*** Adding switches:
s1 
*** Adding links:
(h1, s1) (h2, s1) 
*** Configuring hosts
h1 h2 
*** Starting controller
c0 
*** Starting 1 switches
s1 ...
*** Starting CLI:
mininet> 

Mininet teilt einem hier mit, was alles erstellt und angelegt wurde. Wir sehen z.B. zwei Hosts (h1, h2), einen Switch (s1) und Links, die die beiden Hosts mit dem Switch verbinden. Am Ende wartet die Mininet-CLI auf Eingaben. Bevor wir uns mit der CLI genauer beschäftigen fällt noch etwas auf. Der Controller. Diesen brauchen wir nicht, denn hier handelt es sich um einen OpenFlow-Controller, der einen OpenFlow-Switch steuern kann. Um es an dieser Stelle kurz zu machen, OpenFlow wird wenig in der Industrie eingesetzt und schwebt irgendwo zwischen Hype und Hoffnung als Technologie. Wir sind an Standardkomponenten interessiert, also einem einfachen Ethernet-Switch. Das müssen wir Mininet beim Start mitteilen. Dazu müssen wir Mininet zunächst beenden. In der Mininet-CLI einfach exit eingeben und Mininet neu starten, und diesmal so:

sudo mn --switch lxbr

lxbr steht dabei für Linux Bridge, und bridge ist ein anderer Begriff für switch.

Damit sind wir wieder in der Mininet-CLI, erkennbar am Prompt (mininet>). Mit der Mininet-CLI kann man das gesamte, gerade erzeugte Netz zentral steuern. D.h. heisst von hier aus kann man sich den Status des Netzes anzeigen lassen, Mininet-Befehle absetzten, oder, und das ist das wahrscheinlich wichtigste, man kann einfach Linux-Kommandos und Programme ausführen. Sprich, alles was auf dem Linux Host-System installiert ist, kann man auf den virtuellen Komponenten unseres Netzes ausführen, als wären es echte Komponenten, isoliert vom Host-System und isoliert von den anderen virtuellen Komponenten. Bevor wir dies tun aber noch ein Wort bezüglich des Hilfesystems in Mininet.

Mit:

help

kann man in der Mininet-CLI sich die von der Mininet-CLI bereitgestellten Befehle anzeigen lassen:

mininet> help

Documented commands (type help <topic>):
========================================
EOF    gterm  iperfudp  nodes        pingpair      py      switch
dpctl  help   link      noecho       pingpairfull  quit    time  
dump   intfs  links     pingall      ports         sh      x     
exit   iperf  net       pingallfull  px            source  xterm 

You may also send a command to a node using:
  <node> command {args}
For example:
  mininet> h1 ifconfig

The interpreter automatically substitutes IP addresses
for node names when a node is the first arg, so commands
like
  mininet> h2 ping h3
should work.

Some character-oriented interactive commands require
noecho:
  mininet> noecho h2 vi foo.py
However, starting up an xterm/gterm is generally better:
  mininet> xterm h2

Hier steht schon einiges an wichtiger Information. Die Tabelle gleich am Anfang der Hilfeseite zeigt die in der Mininet-CLI eingebauten Befehle. Wer mehr über einen dieser Befehle wissen möchte muss nur:

help <topic>

eingeben. D.h. wer mehr über den Befehl switch wissen möchte muss kann bekommt mit diesem Befehl eine kurze Erklärung dazu:

mininet> help switch
Starts or stops a switch

Manchmal sind diese Hilfetexte ein wenig kurz geraten, denn der obige Hilfetext sagt nichts über die Benutzung des Befehls. Aber wenn man es einfach ausprobiert, dann komm typischerweise ein Hilfetext, der die Nutzung beschreibt.

mininet> switch
invalid number of args: switch <switch name>{start, stop}

Viele Befehle geben Statusinformationen über das virtuelle Netz und seine Knoten aus. Dazu gehören z.B. die Befehle links, nodes, ports, net, intfs oder der Befehl, der eine Gesamtübersicht ausgibt dump.

mininet> dump
<Host h1: h1-eth0:10.0.0.1 pid=2976> 
<Host h2: h2-eth0:10.0.0.2 pid=2978> 
<LinuxBridge s1: lo:127.0.0.1,s1-eth1:None,s1-eth2:None pid=2984> 
<Controller c0: 127.0.0.1:6653 pid=2969> 

Oben sieht man z.B. die Ausgabe von dump. Man sieht es gibt zwei Endhosts namens h1 und h2, einen Switch s1 dem man nun ansieht, dass es ein ganz einfacher Standard-Switch ist. Den Controller vernachlässigen wir für unsere Experimente. Man sieht hier auch die IP Adressen der Hosts, was oft hilfreich ist.

Eine weitere Klasse von Befehlen ermitteln die Erreichbarkeit zwischen den Komponenten des Netzes. So kann z.B. mit pingall herausfinden, ob sich alle Hosts anpingen können. In dem kleinen Standardnetz von Mininet sieht das dann so aus:

mininet> pingall
*** Ping: testing ping reachability
h1 -> h2 
h2 -> h1 
*** Results: 0% dropped (2/2 received)

Hier erreicht jeder jeden. Stellen wir mal den Switch aus und probieren es erneut.

mininet> switch s1 stop
mininet> pingall
*** Ping: testing ping reachability
h1 -> X 
h2 -> X 
*** Results: 100% dropped (0/2 received)

Und schon erreichen sich die Hosts nicht mehr (es dauert ein wenig, bis das Ergebnis vorliegt, da Ping es einige Male versucht).

Wie schon erwähnt erlaubt es Mininet die Hosts (hier h1 und h2) wie ganz normale (Linux-)Rechner zu verwenden. D.h. man könnte ganz einfach z.B. auf h1 das ping-Kommando selbst ausführen, um die Erreichbarkeit zu testen. Dazu gibt es zwei Möglichkeiten. Die erste ist es direkt aus der Mininet-CLI den Befehl zu starten. Dazu schreibt man als erstes (!) den symbolischen Namen des Hosts auf dem das Kommando ausgeführt werden soll und dann das Kommando. Dazu kann man das Kommando so schreiben, wie man es auf der Kommandozeile des Hosts selbst tun würde, oder in der Mininet-CLI kann man auch als Parameter die symbolischen Namen verwenden (nicht vergessen den Switch wieder einzuschalten mit switch s1 start):

h1 ping 10.0.0.2

oder

h1 ping h2

Die korrekte IP Adresse bekommt man z.B. mit dem eingebauten dump Befehl. Bei einfachen Kommandos wie ping funktionieren aber meist auch die symbolischen Namen.

Alternativ zur zentralen Steuerung durch die Mininet-CLI, kann man sich aber auch direkt eine Console auf den Komponenten öffnen mit xterm oder gterm:

xterm h1

Hier sollte sich ein neues Fenster mit einer Kommandozeile auf der gewünschten Komponente öffnen. gterm ist dabei die schönere Konsole, aber nicht in jeder Linux-Distribution vorhanden. Hier, können jetzt beliebige Befehle eingegeben werden und Anwendungen gestartet werden, die alle auf diesem virtuellen Rechner laufen. Wichtig dabei ist aber, dass die symbolischen Namen der Mininet-CLI nicht funktionieren, d.h. in dieser Konsole ist h1 oder h2 nicht bekannt und man muss die IP Adressen benutzen (also z.B. ping 10.0.0.2 in der Konsole von h1).

Manchmal stürzt Mininet ab, oder es fängt an sich seltsam zu verhalten. Dann sollte man Mininet neu starten, aber bevor Mininet in solchen Situationen neu startet sollte man unbedingt die möglichen Überbleibsel der virtuellen Umgebung aufräumen. Das macht man so:

sudo mn -c

Obiges macht man natülich nicht in der Mininet-CLI, sondern nachdem man Mininet verlassen hat/gecrashed ist und bevor man es neu startet.

Apropos starten. Wenn man Mininet startet, dann kann man mit einigen Kommandozeilenschaltern das Verhalten von Mininet, bzw. das Verhalten und die Struktur der Netzwerktopologie steuern, ohne, dass man hier skripten müssten, was auch möglich wäre. Hier die vielleicht gängigsten Schalter beim Start von Mininet.

Zuächste kann man die Topologie, d.h. wie die einzelnen Komponenten miteinander verbunden sind, steuern. Mininet kennt ein paar einfache, vorgefertigte Topologien, die man noch parametrisieren kann. Hier ein Beispiel:

sudo mn --switch lxbr --topo single,3

Hier wird eine Topologie erzeugt mit einem einzigen Switch und drei Hosts sind mit diesem verbunden. Die Anzahl der Hosts kann man beliebig einstellen. Wichtig ist, dass nach --topo nur ein Argument stehen darf, daher ist single,3 ein String ohne Leerzeichen. Weitere mögliche Topologien sind linear|minimal|reversed|single|torus|tree, wobei minimal die Standardtopologie mit dem Switch und den beiden Hosts ist, die wir schon kennengelernt haben.

Die Verbindung der Komponenten untereinander ist nur ein Aspekt des Netzwerks, dass sich einstellen lässt. Die Eigenschaften der Links zwischen der Komponenten, aber auch die Tiefe der Puffer der Switches kann eingestellt werden, und zwar mit dem --link Schalter (auch hier darauf achten, dass alles hinter dem --link Schalter ein String ohne Leerzeichen ist):

sudo mn --switch lxbr --topo single,3 --link tc,bw=10,delay=10ms,loss=1,max_queue_size=200

Nicht alle Parameter müssen benutzt werden, aber der Vollständigkeit halber sind hier die gängigsten aufgezeigt. Auch die Reihenfolge ist egal. Dabei bedeuten die Parameter folgendes:

Soviel zu Mininet.

iperf

iperf ist, wie schon erwähnt, ein Kommandozeilenwerkzeug, um die verfügbare Bandbreite zu ermitteln, bzw. um einfach Verkehr zu erzeugen. Die man-page sagt es wohl wieder am besten:

iperf is a tool for performing network throughput measurements. It can test either TCP or UDP throughput. To perform an iperf test the user must establish both a server (to discard traffic) and a client (to generate traffic).

Da diese Operationen wenig komplex sind, ist es kaum verwunderlich, dass iperf selbst wenig komplex in der Bedienung ist. Man braucht auf der einen Seite einen iperf-Server und auf der anderen einen iperf-Client. Der Client erzeugt die Pakete und der Server verwirft diese einfach. Beide erzeugen Statistiken, die nach jedem Test ausgegeben werden.

Beim Start muss man den Client und der Server natürlich richtig konfigurieren. Da iperf sowohl TCP als auch UDP unterstützt und entweder als Client oder als Server fungiert gibt es zunächst diese vier Grundkonfigurationen:

Dazu können sich noch ein paar andere Schalter gesellen. Einer den wir später noch brauchen werden ist -P nach dem die Zahl der Threads folgt, die den Test durchführen sollen. Bei TCP erzeugt jeder Thread eine eigene TCP-Verbindung. Also mit iperf -c 10.0.0.1 -P 2 wird ein TCP iperf-Client erzeugt gestartet, der zwei Threads nutzt und damit zwei TCP-Verbindungen. Der Server als Gegenstück läuft dabei auf der 10.0.0.1. Die länge der Datenübertragung kann man mit -t steuern. -t erwartet die Messdauer in Sekunden. Ein weiterer Schalter wird beim UDP Client benötigt und zwar -b. Nach -b kommt die Geschwindigkeit mit der der UDP Client senden soll. Mit iperf -u -c 10.0.0.1 -t 30 -b 5M wird ein UDP Client gestartet, der 30 Sekunden lang mit 5 Mbit/s sendet.

Soviel zu iperf. Jetzt kann es losgehen.

Viel Erfolg!

Zunächst ein paar Fingerübungen, um mit Mininet vertraut zu werden. Wir werden Mininet ab jetzt auch in den folgenden Praktika nutzen, daher ist der sichere Umgang damit wichtig.

Wir benutzen Mininet heute mit einer einfachen Topologie, bei der drei Hosts mit einem Switch verbunden sind.

sudo mn --switch lxbr --topo single,3  --link tc,bw=10,max_queue_size=200,delay=10ms

Was hier erzeugt wird, sollte Sie nun wissen.

Bearbeiten Sie nun folgende Aufgaben in der Mininet-Topologie:

  1. Testen Sie die Konnektivität unter den Hosts (z.B. mit pingall) und sehen Sie sich die Information aller Hosts (nodes) der Links (links) und Netzwerkschnittstellen (intfs) an. Nutzen Sie auch das ping Programm, um die Latenz zwischen h1 und h3 zu testen. Entspricht die Latenz Ihren Erwartungen? Sie sollte im Bereich von 40ms liegen. Warum ist das so?
  2. Öffnen sein ein Terminal auf Host 1 (xterm h1). Pingen Sie von diesem Host aus die anderen beiden Hosts an (nicht vergessen, Sie müssen die IP Adressen nutzen, die symbolischen Namen h1, h2... sind nur in der Mininet-CLI verfügbar).
  3. Schließen Sie das xterm-Fenster wieder und nutzen Sie die Mininet-CLI, um einen Webserver (eingebaut in Python) zu starten (h1 python3 -m http.server 80 &). Mit dem ihnen schon bekannten Tool curl können Sie nun die Webseiteanfragen (h2 curl h1). Wie sie sehen haben Sie ein virtuelles Netz auf Ihrem PC und können dort beliebige Programme starten und über dieses Netz darauf zugreifen. Jetzt sind Sie bereit sich TCP genauer anzuschauen. Lassen Sie Mininet für die weiteren Aufgaben laufen. Beenden Sie aber den Webserver mit: h1 killall python3.

Starten Sie ein xterm-Fenster auf allen 3 Hosts. Schauen wir zuerst, ob TCP es schafft die 10 Mbit/s zwischen zwei Hosts bei 40ms Rundlaufzeit zu nutzen. Sollte das nicht funktionieren, wäre das keine Gute Nachricht. Starten Sie einen iperf TCP-Server auf Host 1 (iperf -s &).

  1. Starten Sie einen iperf-Client auf Host 2, der sich mit dem Serverauf Host 1 verbindet und 20 Sekunden misst.
  2. Das sollte funktioniert haben. Jetzt starten wir einen zweiten iperf-Server auf Host 1 und zwar einen, der UDP nutzt anstelle von TCP (iperf -u -s &). Geben Sie auf Host 2 das Kommando ein, um einen UDP iperf-Client zu starten, der 20 Sekunden mit 5 Mbit/s sendet, aber geben Sie noch kein ein! Geben Sie das Kommando ein, um auf Host 3 einen iperf-Client mit TCP zu starten der ebenfalls 20 Sekunden sendet, aber geben Sie noch kein ein. Versuchen Sie jetzt beide möglichst zügig hintereinander zu starten. Wie wird die Bandbreite aufgeteilt und warum?
  3. Wiederholen Sie den Versuch in 3., aber der UDP iperf-Client soll nun mit 8 Mbit/s senden. Wie wird die Bandbreite aufgeteilt und warum?
  4. Wiederholen Sie den Versuch in 3., aber starten Sie von Host 2 und 3 jeweils einen TCP iperf-Client. Wie wird die Bandbreite aufgeteilt und warum? (hier müssen im virtuellen Netz und mit echten Implementierungen evtl. Abstriche gemacht werden, überlegen Sie wie die Bandbreite modellhaft aufgeteilt werden müsste)
  5. Wiederholen Sie den Versuch in 3., aber starten Sie von Host 2 und 3 jeweils einen TCP iperf-Client, wobei ein Client 4 parallele TCP-Verbindungen nutzen soll (Schalter -P 4). Wie wird die Bandbreite aufgeteilt und warum? (hier müssen im virtuellen Netz und mit echten Implementierungen evtl. Abstriche gemacht werden, überlegen Sie wie die Bandbreite modellhaft aufgeteilt werden müsste)
  6. Beenden Sie Mininet und starten Mininet neu mit der gleichen Topologie, allerdings fügen wir etwas Paketverlust hinzu (sudo mn --switch lxbr --topo single,3 --link tc,bw=10,max_queue_size=200,delay=10ms,loss=1). Starten Sie nun auf einem Host wieder einen iperf TCP-Server und testen Sie die Bandbreite von einem anderen Host zu diesem Server. Die Bandbreite sollte geringer sein, aber warum?
  7. Beenden Sie Mininet und starten Mininet neu mit der gleichen Topologie, allerdings reduzieren wir jetzt den delay (sudo mn --switch lxbr --topo single,3 --linktc,bw=10,max_queue_size=200,delay=1ms,loss=1). Starten Sie nun auf einem Host wieder einen iperf TCP-Server. Auf dem Host, auf dem der iperf-Client laufen soll, starten Sie Wireshark und die Aufzeichung der Pakete. Testen Sie anschliessend die Bandbreite von diesem Host zum Server. Die Bandbreite sollte wieder gestiegen sein, aber warum? Schauen Sie sich den Paketverlust in Wireshark an. Finden Sie duplizierte ACKs und die Neu-übertragung von Paketen (wann werden diese neu gesendet)?

Jetzt wollen wir uns TCP auch nochmal in Wireshark anschauen. Laden Sie die Datei tcp-trace.pcap herunter und öffnen Sie diese in Wireshark. Im Trace finden Sie einen HTTP-Post Request. Damit dieser Sie nicht verwirrt und sie nur TCP-Segmente sehen, können Sie Wireshark instruieren HTTP nicht zu interpretieren. Dazu entfernen Sie das Häkchen im Menü Analyze/Enable Protocols bei HTTP.

  1. Finden Sie den TCP-Verbindungsaufbau! Woran erkennen Sie diesen?
  2. Welches ist das erste Paket mit Daten (das dritte darf laut Standard schon Daten enthalten)?
  3. Finden Sie die HTTP POST Anweisung. (Denken Sie daran, Sie haben die Analyse von HTTP ausgeschaltet, aber im Datenteil der TCP-Segmente sind diese Daten natürlich immer noch verfügbar). Schauen Sie sich die Sequenznummer des Pakets mit der POST Anweisung an (und die der darauf folgenden sechs Pakete vom Sender (relative Sequenznummern)). Was sagen diese Sequenznummern aus, d.h. worauf beziehen sich die Sequenznummern?
  4. Finden Sie die ACK Pakete zu den sechs Paketen vom Sender ab (und inkl.) dem HTTP POST. Woran haben Sie die richtigen ACK Pakete erkannt?
  5. Versuchen Sie den Durchsatz der TCP Verbindung anhand des Wireshark Traces zu berechnen.
  6. Wie groß ist das initiale Staufenster? (Hinweis: versuchen Sie den Moment zu finden, wo der Sender nach der Handshake warten muss)

Zu guter Letzt wollen wir uns TCP auch nochmal live anschauen. Starten Sie Aufzeichnung mit Wireshark und nutzen Sie den Browser (ganz ohne Mininet), um eine beliebige Webseite zu laden. Stoppen Sie die Aufzeichnung und finden Sie einen beliebigen TCP Verbindungsaufbau.

TCP ist durch Optionen erweiterbar. Sie sollten z.B. im SYN und SYN/ACK die Window Scale Option vorfinden. In dieser steht ein Wert x der bedeutet, dass der Wert des Receive Windows (Empfangsfenster) mit 2 hoch x multipliziert werden muss, um den wahren Wert des Receive Windows zu berechnen.

Ermitteln Sie folgende Werte:

  1. Wie groß ist der wahre Wert des Receive Windows des Senders beim Verbindungsaufbau?
  2. Wie groß ist der wahre Wert des Receive Windows des Empfängers beim Verbindungsaufbau?
  3. Ermitteln Sie die Rundlaufzeit (RTT) exemplarisch anhand der Zeit die zwischen SYN und SYN/ACK liegt. Mit dieser Rundlaufzeit, ermitteln sie die maximale Rate, die durch das Receive Window begrenzt wird für a) den Senders, b) den Empfänger und c) ohne die Window Scale Option (Hinweis: pro RTT kann ein ganzes Fenster versendet werden).

Ich würde mich über Ihre Kritik und Verbesserungsvorschläge freuen. Auch Lob, klar. Eine kurze Umfrage finden Sie hier.