In .NET mit IPAddress arbeiten

In .NET mit IPAddress arbeiten

Das heutige Thema ist doch sehr spezifisch. Für Leute, die nicht gerade mit dieser .NET-Klasse arbeiten ist es vielleicht zu spezifisch. Ich habe jedoch einiges gelernt als ich mich mit dieser Repräsentation von IPs in .NET auseinander gesetzt habe. Programmiertechniken, die gar nicht direkt mit diesem Datentyp zusammenhängen, die aber praktisch sein können.

Nebenbei werden ein paar Extensions herauskommen, die du in deinen Programmen verwenden kannst und die dir vielleicht mal nützlich sein könnten.

Was war denn das Problem?

Ich musste mit IPs arbeiten und bin dabei auf Microsofts Implementierung von IPAddress gestoßen. Eine ziemlich nützliche Klasse, die IP Adressen kapselt, so dass man nicht mit strings hantieren muss. IPAddress arbeitet mit byte[] um die Informationen einer IP zu kapseln. Sowohl IPv4 als auch IPv6 wird unterstützt. Braucht man eine IP als string bietet die Klasse natürlich ToString() an. Genauso Methoden zum parsen von strings.

Wo die Möglichkeiten der Klasse aber aufhören ist beim Vergleichen von IPs. Was eigentlich ganz einfach klingt, entpuppte sich als eine doch fordernde Aufgabe. Eine Recherche im Internet ergab, dass tatsächlich schon viele auf ähnliche Probleme gestoßen sind. So manch eine Methode oder sogar kleine Libraries sind zu finden. Für meinen Fall habe ich allerdings nichts gefunden.

Oft sah ich, dass mit strings gearbeitet wurde, was mir überhaupt nicht gefiel.

Selbst das vergleichen von IPAddress Objekten ist nicht so einfach. Eine IPv4 besteht aus 4 bytes. Eine IPv6 aus 16.

Diese in Loops zu vergleichen war mein erster Ansatz. Der Ansatz war doof. Anders kann ich es nicht ausdrücken.

Die Lösung

Ich bin darauf gestoßen, dass man eine IP auch als Zahl darstellen kann. Als long um genau zu sein. Das macht das vergleichen natürlich einfacher. IPAddress hat sogar eine Property Address, die diesen long enthält. Allerdings ist diese Property als Deprecated markiert und könnte somit irgendwann entfernt werden. Schade, da sie doch so praktisch ist. Das Problem was diese Form mit sich bringt ist folgendes:

IPv4 kann in einen long gespeichert werden, da es sich um eine 64 Bit-Zahl handelt. Eine IPv6 jedoch hat 16 bytes. Es handelt sich um eine 128 Bit-Zahl. Einen solchen Datentyp gibt es in .NET nicht. Somit steht auch kein numerischer Typ zur Verfügung in dem sich eine IPv6 so einfach speichern ließe. Address funktioniert also nur für IPv4.

Die Lösung ist folgende:

Man speichert eine IPv6 in ein Array. Um genau zu sein ein ulong[] mit zwei Zahlen. Die erste repräsentiert die ersten 8 byte, die zweite die letzten 8 byte.

Hier ist meine Methode zum Konvertieren:

private static ulong[] ConvertIPBytesToULongArray(byte[] ip)
 {
     ulong[] addr = new ulong[2];
     addr[0] = BitConverter.ToUInt64(ip, 8);
     addr[1] = BitConverter.ToUInt64(ip, 0);
     return addr;
 }

Diese Arrays lassen sich nun vergleichen. Ich habe mir eine Hilfsmethode geschrieben, mit der ich zwei IPAddress gleichzeitig umwandeln kann.

private static Tuple<ulong[], ulong[]> ConvertIPsToULongs(IPAddress first, IPAddress second)
 {
     var firstIPAsBytes = first
         .MapToIPv6()
         .GetAddressBytes();
 ​
     var secondIPAsBytes = second
         .MapToIPv6()
         .GetAddressBytes();
 ​
     if (BitConverter.IsLittleEndian)
     {
         Array.Reverse(firstIPAsBytes);
         Array.Reverse(secondIPAsBytes);
     }
 ​
     return new Tuple<ulong[], ulong[]>(
         ConvertIPBytesToULongArray(firstIPAsBytes)
         , ConvertIPBytesToULongArray(secondIPAsBytes));
 }

Um alle IPs gleich verarbeiten zu können werden IPv4 auf IPv6 gemappt.

Dann werden die bytes der IPs ermittelt.

Windows nutzt Little Endian, so dass die byte[]s umgekehrt werden müssen.

Die Methode gibt dann ein Tuple<ulong[], ulong[]> mit den beiden IPs zurück.

Die numerischen Werte können nun einfacher verglichen werden als strings oder byte[]s.

Hier ist die Extensionmethod, die ich geschrieben habe um zwei IPAddress zu vergleichen:

 public static int Compare(this IPAddress address, IPAddress compareAddress)
 {
     var ipsAsULongs = ConvertIPsToULongs(address, compareAddress);
 ​
     if (ipsAsULongs.Item1[0] < ipsAsULongs.Item2[0])
         return -1;
 ​
     if (ipsAsULongs.Item1[0] > ipsAsULongs.Item2[0])
         return 1;
 ​
     if (ipsAsULongs.Item1[0] == ipsAsULongs.Item2[0])
     {
         if (ipsAsULongs.Item1[1] < ipsAsULongs.Item2[1])
             return -1;
 ​
         if (ipsAsULongs.Item1[1] > ipsAsULongs.Item2[1])
             return 1;
     }
 ​
     return 0;
 }

Ich vergleich hier einfach nur die ulong und geben -1 für kleiner, 0 für gleich und 1 für größer zurück. So lassen sich IPAddress-Objekte vergleichen und sogar sortieren.

Ein bisschen Zucker

Mit dieser Methode lässt sich z.B. auch eine Liste von IPs in einem Bereich zurückgeben.

public static IList<IPAddress> GetIpRange(this IPAddress start, IPAddress end)
 {
     var addresses = new List<IPAddress>();
     var ipsAsULongs = ConvertIPsToULongs(start, end);
 ​
     try
     {
         checked
         {
             for (var i1 = ipsAsULongs.Item1[0]; i1 <= ipsAsULongs.Item2[0]; i1++)
             {
                 try
                 {
                     checked
                     {
                         for (var i2 = ipsAsULongs.Item1[1]; i2 <= ipsAsULongs.Item2[1]; i2++)
                         {
                             byte[] ipBytes = new byte[16];
                             var convertedBytes1 = BitConverter.GetBytes(i1);
                             var convertedBytes2 = BitConverter.GetBytes(i2);
                             convertedBytes1.CopyTo(ipBytes, 8);
                             convertedBytes2.CopyTo(ipBytes, 0);
 ​
                             if (BitConverter.IsLittleEndian)
                                 Array.Reverse(ipBytes);
 ​
                             var ip = new IPAddress(ipBytes);
 ​
                             if (ip.IsIPv4MappedToIPv6)
                                 ip = ip.MapToIPv4();
 ​
                             addresses.Add(ip);
                         }
                     }
                 }
                 catch (OverflowException) { }
             }
         }
     }
     catch (OverflowException) { }
 ​
     return addresses;
 }

Hier werden wieder die Anfangs- und End-IPs in ulong[]s umgewandelt.

In for-Schleifen werden nun alle ulong durchgelaufen und wieder in IPAddress umgewandelt.

Hier ist noch etwas, dass ich vorher noch nie gesehen habe. Deshalb eine kleine Erklärung.

Die Loops befinden sich innerhalb eines checked {}. Dieser Block wirft OverflowExceptions, wenn ein numerischer Wert über sein Maximum hinaus iteriert wird. Diese OverflowExceptions fange ich ab, so dass meine ulong keine ungültigen Werte annehmen können.

Marcel Melzig

Softwareentwicklung in C# .NET, ASP.NET MVC, PHP, Javascript, HTML, CSS, Java

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert