Jeder, der irgendwann einmal ein Netzwerk in Betrieb genommen hat oder versucht hat, seinen Computer in die unendlichen Weiten des World Wide Web zu befördern, dürfte früher oder später über das Programm Ping gestolpert sein, welches auf einfach Weise die Erreichbarkeit eines Hostrechners überprüft. Hierzu wird per Kommandozeile die IP-Adresse oder der Domainname des Zielhosts übergeben. Um den Verbindungstest vorzunehmen, greift Ping auf das Netzwerkprotokoll ICMP (Internet Control Message Protocol) zurück und nutzt hierbei die Möglichkeit, über ICMP einen Echo Request zu erzeugen. Hierbei passiert nichts anderes, als dass ein ICMP-Packet versendet wird und der Zielhost anhand einer Kennung erkennt, dass dieses Paket ohne Veränderung an den Sender zurückgeschickt werden muss. Der Sender kann nun überprüfen, ob das empfangene Paket dem ursprünglich gesendeten entspricht und aus dieser Tatsache ableiten, ob der Zielhost erreichbar ist. Außerdem kann die Laufzeit des Pakets vom Senden bis zum Eintreffen des Echos ermittelt und somit eine Aussage über die Geschwindigkeit der getesteten Verbindung getroffen werden.
Internet Control Message Protocol
Das Internet Control Message Protocol ist eine Erweiterung des Internet Protocols (IP). Dies bedeutet, dass der Header eines IP-Pakets um die entsprechenden Felder des ICMP-Pakets erweitert wird. Abpictureung 1 zeigt die Felder, die hinzugefügt werden.
Über das Feld Code des ICMP-Headers lässt sich der mit Type ausgewählte ICMP-Type noch näher spezifizieren. Für unseren Anwendungsfall (Type=8: Echo request) wird dieses Feld jedoch nicht benötigt und daher auf den Wert 0 gesetzt.Im dritten Feld (16-Bit-Wort) wird die Prüfsumme der Nachricht verwaltet. Die Prüfsumme besteht aus dem Einerkomplement aller Einerkomplemente der 16-Bit-Wörter der Nachricht. Dies hört sich zunächst etwas kompliziert an, eine entsprechende Methode, die diese Arbeit für uns übernimmt, werden wir aber weiter unten kennen lernen.Die beiden nächsten ICMP-Felder, Identifier und Sequence Number, nehmen einen beliebigen Wert auf und erfüllen lediglich den Zweck einer eindeutigen Identifizierung des Pakets. Im letzten Feld nimmt das ICMP-Paket die eigentlichen Daten auf. Dies ist für die Implementierung einer Ping-Methode aber nicht von Bedeutung, weshalb dieser Bereich mit Nullen (8 Byte) gefüllt wird.
Somit hätten wir den Aufbau eines ICMP-Pakets geklärt und können uns nun dem wesentlich einfacheren Teil, der Implementierung unserer Ping-Klasse, widmen.
Ping-Klasse
Für die Realisierung einer ICMP-Message sollten Sie zunächst die Namensräume System.Net und System.Net.Sockets einbinden. Zum Ausführen des Ping implementieren wir eine gleichnamige statische Methode, die einen Rückgabewert vom Typ bool liefert, abhängig davon, ob der Host erreichbar war oder nicht. Die Methode Ping benötigt zwei Parameter. Der Hostname, der auf Erreichbarkeit geprüft werden soll, wird als String übergeben. Die maximale Zeit, die verstreichen darf, bis der Host einen Antwort gesendet hat (Timeout), wird in Millisekunden als int-Wert übergeben. Listing 1 zeigt die fertige Methode Ping.Listing 1
// Methode zum Ausführen des Ping-Befehlspublic static bool Ping(string host, int timeout){// DNS-Abfrage zur Ermittlung der IP-Adresse des Zielhosts// startenIPHostEntry ipHostEntry = Dns.Resolve(host);IPAddress ip = ipHostEntry.AddressList[0];// Port 7 des Hosts auswählenIPEndPoint ipEndPoint = new IPEndPoint(ip, 7);// Einen Socket für das ICMP-Protokoll erzeugenSocket socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);// ICMP-Packet (ICMP-Type = 8: Echo Request)byte[] icmpPacket = {8, 0, 0xF7, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};int result = socket.SendTo(icmpPacket, icmpPacket.Length, SocketFlags.None, ipEndPoint);ArrayList receivedData = new ArrayList();receivedData.Add(socket);Socket.Select(receivedData, null, null, timeout*1000);byte[] receiveICMPPacket = new byte[200];int bytes = 0;if (receivedData.Count > 0){// Daten jetzt auslesenbytes = socket.Receive(receiveICMPPacket, receiveICMPPacket.Length, SocketFlags.None);if (getICMPChecksum(receiveICMPPacket) == 0)return true;elsereturn false;}elsereturn false;}
Der End Point wird auf die ermittelte IP-Adresse eingestellt und mit Port 7 verbunden. Die Klasse IPEndPoint des .NET Frameworks übernimmt diese einfache Aufgabe für uns. Da nun die Zieladresse bekannt ist, kann eine Socket-Instanz angelegt werden. Der Konstruktor der gleichnamigen Klasse erwarte von uns drei Parameter, die den Verbindungstyp genauer spezifizieren. Zunächst übergeben wir an erster Stelle den Wert AdressFamily.InterNetwork und signalisieren, dass IPv4-gültige IP-Adressen zum Einsatz kommen. Über den zweiten Parameter stellen Sie den Socket Type ein. Hier verwenden wir SocketType.Raw. Zuletzt wird das Protokoll ausgewählt und für unseren Anwendungsfall natürlich auf ProtocolType.ICMP eingestellt.
Die Socket-Instanz ist nun für das Senden eines ICMP-Pakets vorbereitet. Um diese Aktion nun endlich auszuführen, fehlt uns noch das passende ICMP-Paket. Mit den Grundlagen, die wir bereits im ersten Abschnitt ausführlich geklärt haben, ist dieses Paket schnell erstellt:
byte[] icmpPacket = {8, 0, 0xF7, 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
Ist das ICMP-Paket in Form eines einfachen Byte-Arrays erzeugt, können wir die Daten zum Senden übergeben. Dies übernimmt für uns die Methode SendTo der Socket-Instanz. Als Parameter erwartet diese Methode das soeben erzeuge ICMP-Paket als Byte-Array, die Länge des Arrays und einen Wert vom Typ SocketFlags. Nach dem Senden überprüfen wir mittels der Methode Select aus der Klasse Socket, ob empfangene Daten nach einem angegeben Timeout vorliegen. Erst nachdem dieser Test positiv ausgefallen ist, was mit dem Ausdruck receivedData.Count > 0 überprüft wird, kann das Antwort-Paket ausgelesen werden. Anschließend muss sichergestellt werden, dass das Echo-Paket dem Ausgangspaket entspricht. Nur in diesem Fall kann davon ausgegangen werden, dass der Zielhost das Paket als Antwort auf den Echo Request verschickt hat. Hierzu muss durch die Methode getICMPChecksum die Prüfsumme gepictureet werden. Da die Prüfsumme aber bereits enthalten ist, muss das Ergebnis der Methode 0 sein. Die Implementierung der Checksum-Methode werden wir uns in Kürze ansehen.
Prüfsumme bilden
In Listing 1 verwenden wir die Methode getICMPChecksum um die Prüfsumme eines ICMP-Pakets zu pictureen. Als Parameter erhält diese Methode das komplette ICMP-Paket als Byte-Array. Der Rückgabewert ist vom Typ ushort.Die Prüfsumme ist das Einerkomplement der Einerkomplementsumme aller 16-Bit-Wörter des ICMP-Pakets. Im Beispiel aus Listing 1 wurde die Prüfsumme mit 0xF7 und 0xFF (zweimal 8 Bit) direkt eingesetzt. Wird zu einem Paket, welches bereits die eigene Prüfnummer gespeichert hat, ein weiteres Mal die Prüfsumme berechnet, erhält man das Ergebnis 0. Diese Eigenschaft setzten wir im Beispiel zur Überprüfung des empfangenen Datenpakets ein. Möchten Sie an dieser Stelle die Prüfsumme nicht, wie im Beispiel geschehen, manuell einsetzen, dann setzen Sie das dritte und vierte Byte der Nachricht auf 0 und übergeben das Array an die Methode getICMPChecksum. Den Rückgabewert können Sie anschließend an der betreffenden Stelle im Paket einsetzen. Dieses Verfahren müssen Sie anwenden, wenn die Prüfsumme des Pakets sich von Mal zu Mal unterscheiden kann. Dies kann zum Beispiel dann der Fall sein, wenn Sie eine Identifier für das Paket verwenden.
Der Identifier (ebenfalls 16-Bit und somit 2 Byte) befindet sich in unserem Array an fünfter und sechster Stelle. Um einen eindeutigen Identifier zu erhalten, bietet es sich an, einen Zufallswert vom Typ byte zu erzeugen:
byte random_ident = Convert.ToByte(rnd.Next(255).ToString());
ushort sum = GetTCPCheckSum(icmpPacket);byte[] checksum_array;checksum_array = BitConverter.GetBytes(sum);icmpPacket[3] = checksum_array[0];icmpPacket[2] = checksum_array[1];
|
Die Methode getICMPChecksum überprüft zunächst, ob das Paket, zu dem die Prüfsumme berechnet werden soll, durchgängig aus 16-Bit-Wörtern besteht. Fehlen am Ende 8 Bit zur Bildung eines vollständigen 16-Bit-Worts, werden diese durch das Anfügen eines Bytes ergänzt. Anschließend kann in einem Schleifendurchlauf mit der eigentlichen Berechnung begonnen werden. Listing 2 zeigt die komplette Methode getICMPChecksum.
Listing 2
static ushort getICMPChecksum(byte[] icmpP){byte[] buffer;if ((icmpP.Length % 2) > 0){buffer = new byte[icmpP.Length +1];icmpP.CopyTo(buffer, 0);buffer[buffer.Length - 1] = 0;}else{buffer = new byte[icmpP.Length];icmpP.CopyTo(buffer, 0);}int checksum = 0;for (int i = 0; i < buffer.Length; i += 2){byte firstByte = buffer[i+1];byte secondByte = buffer[i];ushort word = secondByte;word = (ushort)((word << 8) + firstByte);checksum += word;}checksum = (checksum >> 16) + (checksum & 0xFFFF);checksum += (checksum >> 16);// Einerkomplementreturn (ushort)(~checksum);}
bool result = PingService.Ping(192.168.1.1, 2000);if (result){resultLabel.Text = "Zielhost ist erreichbar";}else{resultLabel.Text = "Zielhost ist nicht erreichbar";}


