
Foto: Ben Stanfield
VNUML (Virtual Network User-Mode-Linux) ist ein Programm um Netzwerke zu simulieren. Dabei werden die beteiligten Rechner nicht nur simuliert, sondern als virtuelle Maschinen gestartet, wodurch sie mit echten Protokollen miteinander kommunizieren. Daher eignet sich dieses Tool sehr gut zum Testen und Lernen z.B von Routingprotokollen. Man kann damit jedoch auch Paketfiltering mittels
iptables sowie andere Dienste wie
ARP,
DNS oder
ICMP ausprobieren. Auch eigene Entwicklungen können so getestet werden.
Da für jeden Rechner ein
User-Mode-Linux System (englisch) – welches über einen echten Kernel und ein eigenes Dateisystem verfügt – gestartet wird, ist die Simulation sehr realitätsnah und die Rechner verhalten sich wie echte autonome Maschinen.
Für diesen Artikel ist es vorteilhaft, wenn man grundlegende Kenntnisse über Netzwerke besitzt (es wird z.B. vorausgesetzt, dass man weiß was
IP-Adressen,
Netze und Netzmasken sowie
Router sind)
UPDATE: Artikel nochmal überarbeitet
Installation
Für Debian basierte Distributionen gibt es ein .deb Paket, welches zumindest unter Ubuntu problemlos installiert werden kann. Ansonsten kann man sich den Quelltext herunter laden. Eine englische Installationsanleitung ist auch verfügbar.
Ist VNUML installiert, braucht man noch ein Dateisystem. Auf der Projektseite gibt es 2 verschiedene zum downloaden. Ein größeres, welches mehr Programme beinhaltet und ein sehr abgespecktes, welches nützlich ist um größere Netzwerke zu generieren, da für jeden virtuellen Rechner ein Dateisystem im Arbeitsspeicher liegt.
Man kopiert das gewählte Dateisystem nach /usr/share/vnuml/filesystems/filesystem.img. Für die Beispiele in diesem Artikel reicht das kleine, welches man als mini_fs speichern kann. Dafür können die folgenden Befehle benutzt werden, nachdem man die Datei n3vlr-0.11-vnuml-v0.1.tar.gz herunter geladen hat:
tar -xvzf n3vlr-0.11-vnuml-v0.1.tar.gz
sudo cp n3vlr-0.11-vnuml-v0.1.img /usr/share/vnuml/filesystems/mini_fs
Möchte man das Dateisystem ändern um z.B. neue Programme hinzuzufügen oder unbenötigte Dateien zu löschen, kann man dieses mit dem Befehl
sudo mkdir /mnt/vnuml
sudo mount -o loop /usr/share/vnuml/filesystems/filesystem.img /mnt/vnuml
einhängen. Nun kann man im Ordner /mnt/vnunml alle gewünschten Änderungen vornehmen und das Dateisystem danach mit sudo umount /mnt/vnuml wieder aushängen.
Scenariodatei erstellen
Die Netzwerktopologie wird in einer XML-Datei beschrieben. In dieser werden zuerst die globalen Einstellungen festgelegt, in denen unter anderem angegeben wird, wo das zu nutzende Dateisystem liegt und welchen ssh-Key man nutzen möchte um auf die virtuellen Rechner zuzugreifen.
Danach folgen die Definitionen der Netze gefolgt von den Rechnern.
Im folgenden eine minimale Beispielkonfiguration, die zwei Rechner beschreibt, die über ein Netz miteinander verbunden sind. Zuerst der globale Teil, in dem die VNUML-Version, der Name des Scenarios und weitere Einstellungen definiert werden. Dort wird auch das Management-Netz (192.168.0.0/24) definiert, über das man vom eigenen Rechner auf die verschiedenen virtualisierten Maschinen zugreifen kann.
Existiert noch kein öffentlicher ssh-key, so sollte man sich mit folgendem Kommando einen anlegen:
Bei der Frage nach einem Passwort drückt man einfach Enter ohne ein Passwort einzugeben. Der vorgeschlagene Pfad sollte stimmen, kann also beibehalten werden.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vnuml SYSTEM "/usr/share/xml/vnuml/vnuml.dtd">
<vnuml>
<global>
<version>1.8</version>
<simulation_name>beispiel</simulation_name>
<ssh_version>2</ssh_version>
<ssh_key>/root/.ssh/id_rsa.pub</ssh_key>
<automac/>
<vm_mgmt type="private" network="192.168.0.0" mask="24" offset="100" >
<host_mapping/>
</vm_mgmt>
<vm_defaults exec_mode="mconsole">
<filesystem type="cow">/usr/share/vnuml/filesystems/mini_fs</filesystem>
<kernel>/usr/share/vnuml/kernels/linux</kernel>
</vm_defaults>
</global>
Als nächstes werden die Netze definiert. In diesem Beispiel gibt es nur ein Netz, da man sich sonst um das Routing kümmern müsste (dazu später mehr).
<net name="net1" mode="virtual_bridge"/>
Im Modus “virtual_bridge” werden die Interfaces aller virtuellen Rechner sowie alle Netze über Bridges ans Hostsystem (den eigenen Rechner) gekoppelt, so dass man sie mit einem ifconfig sehen und mittels tcpdump oder Wireshark überwachen kann. Dafür wird jedoch das Paket “bridge-utils” benötigt. Alternativ kann man statt dessen auch “uml_switch” benutzen.
Nun folgen die Definitionen der virtuellen Rechner. Bei beiden Rechnern wird ein Interface definiert (es können aber auch mehrere sein, z.B. für Router).
<vm name="rechner1">
<if id="1" net="net1">
<ipv4 mask="255.255.255.0">10.0.1.1</ipv4>
</if>
</vm>
<vm name="r2">
<if id="1" net="net1">
<ipv4 mask="255.255.255.0">10.0.1.2</ipv4>
</if>
</vm>
</vnuml>
Es empfiehlt sich die Datei genauso wie das Scenario (die globale Eigenschaft simulation_name) zu benennen. In diesem Fall also beispiel.xml
Scenario starten und zu den Rechnern verbinden
Um das Scenario zu starten genügt der folgende Aufruf:
sudo vnumlparser.pl -t beispiel.xml
Nachdem das Scenario gestartet ist, kann man sich auf einen der Rechner einloggen:
Durch die Angabe des öffentlichen ssh-Keys sollte es keine Passwortabfrage geben. Ansonsten ist das Passwort per default xxxx. Nun kann man alles mögliche Testen. Man kann einen Ping versenden:
oder ein Interface überwachen.
Dafür loggt man sich am besten in einer anderen Konsole auf dem 2. Rechner ein:
und startet dort einen TCP-Dump:
Der Ping sollte die ganze Zeit weiter laufen, damit man auch etwas sehen kann. Danach kann man ihn mit Strg+c stoppen.
Mit exit kann man einen Rechner wieder verlassen.
Da man häufiger auf mehreren Rechnern gleichzeitig eingeloggt sein möchte wenn man mit VNUML arbeitet, bietet es sich an Screen zu benutzen.
Um die Simulation zu beenden führt man folgenden Befehl aus:
sudo vnumlparser.pl -P beispiel.xml
Dienste starten
Wie bereits erwähnt eignet sich VNUML besonders gut zum ausprobieren und Testen von Routingprotokollen. Daher sind auch die Quagga-Implementationen der bekanntesten Routingprotokolle RIP, OSPF und BGP bereits installiert und können recht einfach genutzt werden. Ich werde in diesem Artikel jedoch nur eine Minimalkonfiguration für RIP und OSPF angeben. Eine Einführung in diese Protokolle und deren Konfiguration würde an dieser Stelle zu weit führen.
Um das Routing grundsätzlich zu aktivieren wird das Tag
<forwarding type="ipv4" />
genutzt. Weitere Tags binden ein Konfigurationsverzeichnis conf ein (welches im gleichen Verzeichnis, wie die xml-Datei des Scenarios liegt) und starten die verschiedenen Daemons.
Im Verzeichnis conf sollten dann die folgenden Dateien vorhanden sein:
zebra.conf
!
hostname zebra
!
password xxxx
enable password xxxx
ripd.conf
!
hostname ripd
password xxxx
!
router rip
network 10.0.0.0/8
ospfd.conf
!
hostname ospfd
password xxxx
!
router ospf
network 10.0.0.0/8 area 0
Weitere Infos zu den Quagga-Routingprotokollen gibt es unter http://www.quagga.net.
Ein Scenario, welches der Einfachheit halber nur aus Routern besteht, auf denen man wahlweise OSPF oder RIP starten kann wird im folgenden Beispiel definiert:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE vnuml SYSTEM "/usr/share/xml/vnuml/vnuml.dtd">
<vnuml>
<global>
<version>1.8</version>
<simulation_name>mini</simulation_name>
<ssh_version>2</ssh_version>
<ssh_key>/root/.ssh/id_rsa.pub</ssh_key>
<automac/>
<vm_mgmt type="private" network="192.168.0.0" mask="24" offset="100" >
<host_mapping/>
</vm_mgmt>
<vm_defaults exec_mode="mconsole">
<filesystem type="cow">/usr/share/vnuml/filesystems/mini_fs</filesystem>
<kernel>/usr/share/vnuml/kernels/linux</kernel>
</vm_defaults>
</global>
<net name="net1" mode="virtual_bridge"/>
<net name="net2" mode="virtual_bridge"/>
<vm name="r1">
<if id="1" net="net1">
<ipv4 mask="255.255.255.0">10.0.1.1</ipv4>
</if>
<forwarding type="ipv4" />
<filetree root="/etc/quagga" seq="start">conf</filetree>
<exec seq="start" type="verbatim">sysctl -w net.ipv4.conf.all.rp_filter=0</exec>
<exec seq="start" type="verbatim">hostname</exec>
<exec seq="start" type="verbatim">/usr/lib/quagga/zebra -f /etc/quagga/zebra.conf -d</exec>
<exec seq="rip" type="verbatim">/usr/lib/quagga/ripd -f /etc/quagga/ripd.conf -d</exec>
<exec seq="ospf" type="verbatim">/usr/lib/quagga/ospfd -f /etc/quagga/ospfd.conf -d -P 2604</exec>
<exec seq="stop" type="verbatim">hostname</exec>
<exec seq="stop" type="verbatim">killall zebra</exec>
<exec seq="stop" type="verbatim">killall ripd</exec>
<exec seq="stop" type="verbatim">killall ospfd</exec>
<exec seq="rpfilter" type= "verbatim">
for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
</exec>
</vm>
<vm name="r2">
<if id="1" net="net1">
<ipv4 mask="255.255.255.0">10.0.1.2</ipv4>
</if>
<if id="2" net="net2">
<ipv4 mask="255.255.255.0">10.0.2.1</ipv4>
</if>
<forwarding type="ipv4" />
<filetree root="/etc/quagga" seq="start">conf</filetree>
<exec seq="start" type="verbatim">sysctl -w net.ipv4.conf.all.rp_filter=0</exec>
<exec seq="start" type="verbatim">hostname</exec>
<exec seq="start" type="verbatim">/usr/lib/quagga/zebra -f /etc/quagga/zebra.conf -d</exec>
<exec seq="rip" type="verbatim">/usr/lib/quagga/ripd -f /etc/quagga/ripd.conf -d</exec>
<exec seq="ospf" type="verbatim">/usr/lib/quagga/ospfd -f /etc/quagga/ospfd.conf -d -P 2604</exec>
<exec seq="stop" type="verbatim">hostname</exec>
<exec seq="stop" type="verbatim">killall zebra</exec>
<exec seq="stop" type="verbatim">killall ripd</exec>
<exec seq="stop" type="verbatim">killall ospfd</exec>
<exec seq="rpfilter" type= "verbatim">
for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
</exec>
</vm>
<vm name="r3">
<if id="1" net="net2">
<ipv4 mask="255.255.255.0">10.0.2.2</ipv4>
</if>
<forwarding type="ipv4" />
<filetree root="/etc/quagga" seq="start">conf</filetree>
<exec seq="start" type="verbatim">sysctl -w net.ipv4.conf.all.rp_filter=0</exec>
<exec seq="start" type="verbatim">hostname</exec>
<exec seq="start" type="verbatim">/usr/lib/quagga/zebra -f /etc/quagga/zebra.conf -d</exec>
<exec seq="rip" type="verbatim">/usr/lib/quagga/ripd -f /etc/quagga/ripd.conf -d</exec>
<exec seq="ospf" type="verbatim">/usr/lib/quagga/ospfd -f /etc/quagga/ospfd.conf -d -P 2604</exec>
<exec seq="stop" type="verbatim">hostname</exec>
<exec seq="stop" type="verbatim">killall zebra</exec>
<exec seq="stop" type="verbatim">killall ripd</exec>
<exec seq="stop" type="verbatim">killall ospfd</exec>
<exec seq="rpfilter" type= "verbatim">
for f in /proc/sys/net/ipv4/conf/*/rp_filter; do echo 0 > $f; done
</exec>
</vm>
</vnuml>

Die Topologie des Beispielscenarios mini.xml
Dieses Beispiel besteht aus 3 Routern, die mit zwei Netzen zu einer “Kette” verbunden sind (siehe Abbildung rechts). Startet man dieses Scenario, so kann man nicht von r1 (10.0.1.1) nach r3 (10.0.2.2) pingen, da die Routingdaemons noch nicht gestartet sind und r1 noch nichts von net2 weiß:
sudo vnumlparser.pl -t mini.xml
sudo ssh root@r1
ping 10.0.2.2
Wie man sieht, kommen die Pings nicht bei r3 an. Mit exit verläst man den Router r1 wieder.
Es gibt nun 4 Sequenzen, die ausgeführt werden können: start, stop, rip und ospf. Dabei wird durch ausführen der Sequenz start der Hostname gesetzt und der zebra-Daemon gestartet. Mit rip oder ospf wird der entsprechende Routingdaemon gestartet und stop beendet alle Routingprozesse.
Man startet nun also z.B. den RIP-Daemon mit den folgenden Befehlen:
sudo vnumlparser.pl -x start@mini.xml
sudo vnumlparser.pl -x rip@mini.xml
Nun kann man testen, ob das Routing auch funktioniert, indem man den Ping, der vorher fehlschlug wiederholt:
sudo ssh root@r1
ping 10.0.2.2
Jetzt sollte der Ping funktionierten. Bei größeren Scenarien kann es einige Sekunden dauern bis alle Router über die notwendigen Informationen verfügen (also bis das Scenario konvergent ist).
Natürlich gibt es noch viele weitere Optionen und Möglichkeiten, die VNUML zur Verfügung stellt. Dies soll nur eine kleine Einführung sein, um die ersten Schritte mit diesem System zu machen.
Links