Hi!
Ich beziehe mich hier auf die IPv4 - Notation.
Beispiel1: 192.168.1.3
Beispiel2: 192.168.1.0/28
Beispiel3: 192.168.1.3/28 (bisserl sinnlos)
Beispiel4: 192.168.1.3/32 (bisserl umständlich)
Beispiel1 addressiert genau eine IP. Eine IP besteht aus 4 Bytes, die bei IPv4-Notation dezimal geschrieben werden, durch "." segmentiert
Beispiel2 definiert zusätzlich eine Subnetzmaske der Länge 28. Das heist: Von der IP sind nur die ersten 28 Bits relevant, die letzten 4 Bits können frei gewählt werden. Damit ist ein Bereich von IPs addressiert.
Deshalb ist in Beispiel3 das ".3" bisserl sinnlos, denn das spezifiziert die 2 letzten Bits, die ja gar nicht relevant sind.
Beispiel4 ist umständlich, denn wenn alle 32 Bits relevant sind, kann man die NetzMasken-Angabe auch weglassen (s. Beispiel1).
Für einen IPScanner sind 2 Probleme zu lösen:
Alle IPs im Bereich ermitteln
IP konvertieren
Kernstück sind 2 Konverter-Methoden, die Integer und IPv4-Notation ineinander überführen können:
Damit kann man eine IP nach Integer konvertieren, und zum "scannen" den Integer dann einfach hochzählen.
Müssen wir noch die Untergrenze des Bereiches bestimmen, sowie die Länge.
Länge des IP-Bereichs und untere Grenze
Die Länge des Bereiches ist 2^AnzahlFreierBits. Also bei einer NetMaskLength von 28 ergäbe sich 2^4 = 16.
ZweierPotenzen lassen sich sehr performant als BitShift-coden:
Für die Untergrenze muß man halt die Subnetzmaske auf die IP anwenden ("maskieren"). Bekannt ist aber nur NetMaskLength - wir brauchen aber ein Bitmuster als Maske. Dieses ist sehr einfach gestrickt, es ist nämlich ein Integer, dessen höherwertige Bits gesetzt sind, bis zur angegebenen Subnetzmasken-Länge (Beispiel2: die 28 höherwertigen Bits sind gesetzt, die restlichen 4 Bits nicht). Wieder gibt es eine überaus performante Bit-Operation:Erstaunlich, nicht? Aber vergegenwärtigt man sich schnell am Beispiel, zB eine Length von 16: Length - 1 ergibt 15, also die 4 niederen Bits gesetzt. Davon die Inversion ist genau unsere NetzMaske mit den 28 gesetzten höheren Bits
Die Untergrenze erhält man dann, indem man die IP-Addresse mit der _NetBitMask maskiert
Alternative Notationen der SubnetMask
Es ist vlt. etwas irreführend, die /28 aus Beispiel2 als SubnetzMaske zu bezeichnen, denn es ist ja nur die Länge der eigentlichen Bit-Maske. Aber es ist die kürzeste Definition - vgl:
11111111.11111111.11111111.11110000 (Binäre Darstellung)
255.255.255.240 (Dezimale Darstellung)
Die dezimale Darstellung ist ebenfalls sehr häufig
erneuerte Betrachtung des IP-Addressraumes
Erst wenn man die bitweise Betrachtung einer IP verstanden hat, erschließt sich auch das listige Prinzip, wie man eine IP-Vergabe hierarchisch strukturieren kann:
Einem Büro kann etwa folgende 16 IPs zugeteilt bekommen:
192.168.1.0/28 - das geht von 192.168.1.0 bis 192.168.1.15.
Das nächste Büro kriegt die IPs:
192.168.1.16/28 - das geht von 192.168.1.16 bis 192.168.1.31.
Die übergeordnete Abteilung bekommt logisch:
192.168.1.0/27 - das geht von 192.168.1.0 bis 192.168.1.31...
...Und umfasst damit diese beiden Büros.
Geschnackelt? Da die Subnetzmaske um ein Bit verkleinert wurde, hat sich der frei wählbare Bereich verdoppelt.
Dieses hierarchische Prinzip wird genutzt einerseits um verschachtelte Addressräume zu definieren, aber auch die Server hangeln sich iwie bitweise die IP längs, um ein Datenpaket gewissermaßen "in Richtung" Addressat weiterzuleiten, an einen dem Addressaten näherliegenden Server.
Was es nicht geben kann, wäre ein Bereich etwa von 192.168.1.3 bis 192.168.1.18 (wären ja auch 16 IPs). Denn aus der Bitweisen Betrachtung ergibt sich, dass Bereichsgrenzen der binären Logik folgen: Also bei 32 als Netmask ist jede Zahl Bereichsgrenze, bei 31 jede 2. Zahl, bei 30 jede 4., bei 29 jede 8. usw..
Daher kann 192.168.1.3 nicht die Untergrenze eines Bereichs der Länge 16 sein, denn die 3 liegt nicht auf der 16er Schrittweite (vgl: 192.168.1.0/28, 192.168.1.16/28)
Ich hab das ganze Zeug in eine Klasse "IPData" gekapselt, wo man alle diese Informationen bequem abrufen kann:
Spoiler anzeigen
Threading
Unabdingbar.
Will man einen Bereich von 256 IPs durchpingen, und gesteht jedem Ping einen Timeout von 2s zu - hmm - ich glaub, etwas über 7min, richtig?
Verwendet man 256 Threads sinds nur 2s
Man muß aber auch die Anzahl der Threads begrenzen, die belegen nämlich jeder über 1MB Speicher. Da habichne Weile dran rumprobiert, um hinzukriegen, wie mehrere Thread-Methoden sich parallel immer die nächste IP abholen und verarbeiten. Herausgekommen ist etwas mit 2 Delegaten (addSuccess und addFail) und der anonymen Methode consume():
Die Delegaten sind erforderlich, um aus dem Nebenthread das Adden von ListboxItems an den GuiThread delegieren zu können. Die anonyme Methode macht die ganze Arbeit: instanziert ein Ping-Objekt, und holt sich im Do-Loop die jeweils aktuelle ID und pingt die mittm Ping.
Listig die Reaktivierung des Guis:
Also er incrementiert auf jeden Fall die aktuell zu testende nIP, auch wenn die den ubound schon überschritten hat. Ist die Überschreitung nun genauso hoch, wie threadCount vorgesehen waren, dann "weiß" der Thread, dass er der letzte ist, der ausläuft, und reaktiviert daher das Gui.
Aber das steht zunächstmal nur in der anonymen Methode, also ist nur deklariert, wird von sich aus nicht ausgeführt.
Erst ganz am Schluß werden Threads erzeugt, die diese Methode dann auch abfahren - und zwar mehrfach parallel.
Ich beziehe mich hier auf die IPv4 - Notation.
Beispiel1: 192.168.1.3
Beispiel2: 192.168.1.0/28
Beispiel3: 192.168.1.3/28 (bisserl sinnlos)
Beispiel4: 192.168.1.3/32 (bisserl umständlich)
Beispiel1 addressiert genau eine IP. Eine IP besteht aus 4 Bytes, die bei IPv4-Notation dezimal geschrieben werden, durch "." segmentiert
Beispiel2 definiert zusätzlich eine Subnetzmaske der Länge 28. Das heist: Von der IP sind nur die ersten 28 Bits relevant, die letzten 4 Bits können frei gewählt werden. Damit ist ein Bereich von IPs addressiert.
Deshalb ist in Beispiel3 das ".3" bisserl sinnlos, denn das spezifiziert die 2 letzten Bits, die ja gar nicht relevant sind.
Beispiel4 ist umständlich, denn wenn alle 32 Bits relevant sind, kann man die NetzMasken-Angabe auch weglassen (s. Beispiel1).
Für einen IPScanner sind 2 Probleme zu lösen:
- Aus obiger Notation müssen alle im Bereich liegenden IPs ermittelt werden.
- Die müssen angepingt werden, und zwar mit Threading, denn ein erfolgloser Ping dauert lange - nämlich bis zum Timeout.
Alle IPs im Bereich ermitteln
IP konvertieren
Kernstück sind 2 Konverter-Methoden, die Integer und IPv4-Notation ineinander überführen können:
VB.NET-Quellcode
- ''' <summary>gibt die Integer-Darstellung einer IP zurück</summary>
- Public Shared Function IP2Int(ByVal ip As String) As Int32
- 'split zerteilt ip in 4 strings, die je eine Zahl darstellen
- 'Byte.Parse wandelt je ein Segment in ein Byte um
- 'Select sammelt diese Bytes in ein IEnumerable(Of Bytes)
- 'ToArray macht daraus ein Byte-Array
- Dim bytes = (From s In ip.Split("."c) Select Byte.Parse(s)).ToArray
- Array.Reverse(bytes) 'dreht die Reihenfolge um
- Return BitConverter.ToInt32(bytes, 0)
- End Function
- ''' <summary>bildet aussm Integer die String-Darstellung einer IP</summary>
- Public Shared Function Int2IP(ByVal n As Int32) As String
- Dim bytes = BitConverter.GetBytes(n)
- Array.Reverse(bytes) 'reihenfolge umdrehen
- 'Byte-Array in ein String-Array konvertieren
- Dim strings = bytes.Select(Function(b) b.ToString)
- 'Strings verketten und returnen
- Return String.Join(".", strings)
- End Function
Damit kann man eine IP nach Integer konvertieren, und zum "scannen" den Integer dann einfach hochzählen.
Müssen wir noch die Untergrenze des Bereiches bestimmen, sowie die Länge.
Länge des IP-Bereichs und untere Grenze
Die Länge des Bereiches ist 2^AnzahlFreierBits. Also bei einer NetMaskLength von 28 ergäbe sich 2^4 = 16.
ZweierPotenzen lassen sich sehr performant als BitShift-coden:
Für die Untergrenze muß man halt die Subnetzmaske auf die IP anwenden ("maskieren"). Bekannt ist aber nur NetMaskLength - wir brauchen aber ein Bitmuster als Maske. Dieses ist sehr einfach gestrickt, es ist nämlich ein Integer, dessen höherwertige Bits gesetzt sind, bis zur angegebenen Subnetzmasken-Länge (Beispiel2: die 28 höherwertigen Bits sind gesetzt, die restlichen 4 Bits nicht). Wieder gibt es eine überaus performante Bit-Operation:Erstaunlich, nicht? Aber vergegenwärtigt man sich schnell am Beispiel, zB eine Length von 16: Length - 1 ergibt 15, also die 4 niederen Bits gesetzt. Davon die Inversion ist genau unsere NetzMaske mit den 28 gesetzten höheren Bits
Die Untergrenze erhält man dann, indem man die IP-Addresse mit der _NetBitMask maskiert
Alternative Notationen der SubnetMask
Es ist vlt. etwas irreführend, die /28 aus Beispiel2 als SubnetzMaske zu bezeichnen, denn es ist ja nur die Länge der eigentlichen Bit-Maske. Aber es ist die kürzeste Definition - vgl:
11111111.11111111.11111111.11110000 (Binäre Darstellung)
255.255.255.240 (Dezimale Darstellung)
Die dezimale Darstellung ist ebenfalls sehr häufig
erneuerte Betrachtung des IP-Addressraumes
Erst wenn man die bitweise Betrachtung einer IP verstanden hat, erschließt sich auch das listige Prinzip, wie man eine IP-Vergabe hierarchisch strukturieren kann:
Einem Büro kann etwa folgende 16 IPs zugeteilt bekommen:
192.168.1.0/28 - das geht von 192.168.1.0 bis 192.168.1.15.
Das nächste Büro kriegt die IPs:
192.168.1.16/28 - das geht von 192.168.1.16 bis 192.168.1.31.
Die übergeordnete Abteilung bekommt logisch:
192.168.1.0/27 - das geht von 192.168.1.0 bis 192.168.1.31...
...Und umfasst damit diese beiden Büros.
Geschnackelt? Da die Subnetzmaske um ein Bit verkleinert wurde, hat sich der frei wählbare Bereich verdoppelt.
Dieses hierarchische Prinzip wird genutzt einerseits um verschachtelte Addressräume zu definieren, aber auch die Server hangeln sich iwie bitweise die IP längs, um ein Datenpaket gewissermaßen "in Richtung" Addressat weiterzuleiten, an einen dem Addressaten näherliegenden Server.
Was es nicht geben kann, wäre ein Bereich etwa von 192.168.1.3 bis 192.168.1.18 (wären ja auch 16 IPs). Denn aus der Bitweisen Betrachtung ergibt sich, dass Bereichsgrenzen der binären Logik folgen: Also bei 32 als Netmask ist jede Zahl Bereichsgrenze, bei 31 jede 2. Zahl, bei 30 jede 4., bei 29 jede 8. usw..
Daher kann 192.168.1.3 nicht die Untergrenze eines Bereichs der Länge 16 sein, denn die 3 liegt nicht auf der 16er Schrittweite (vgl: 192.168.1.0/28, 192.168.1.16/28)
Ich hab das ganze Zeug in eine Klasse "IPData" gekapselt, wo man alle diese Informationen bequem abrufen kann:
VB.NET-Quellcode
- Public Class IPData
- ''' <summary>Länge der SubnetzMaske</summary>
- Public NetMaskLength As Int32 = Nothing
- Public Address As Int32 = Nothing
- ''' <summary>SubnetzMaske in segmentierter Darstellung</summary>
- Public ReadOnly Property NetBitMask As String
- Get
- Return Int2IP(_NetBitMask)
- End Get
- End Property
- Private ReadOnly Property _NetBitMask As Int32
- Get
- Return Not Length - 1
- End Get
- End Property
- ''' <summary>IP ohne Masken-Angabe</summary>
- Public Property Name As String
- Get
- Return Int2IP(Address)
- End Get
- Set(ByVal value As String)
- Address = IP2Int(value)
- End Set
- End Property
- ''' <summary>IP mit Maske, zB 168.230.16.3/24</summary>
- Public Property FullName As String
- Get
- Return Name & "/" & NetMaskLength
- End Get
- Set(ByVal value As String)
- Dim splits = value.Split("/"c)
- Name = (splits(0))
- NetMaskLength = If(splits.Length = 1, 32, Int32.Parse(splits(1)))
- End Set
- End Property
- ''' <summary>Untergrenze der SubnetMaske</summary>
- Public ReadOnly Property LBound As String
- Get
- Return Int2IP(LBoundAddress)
- End Get
- End Property
- ''' <summary>Obergrenze der SubnetMaske</summary>
- Public ReadOnly Property UBound As String
- Get
- Return Int2IP(LBoundAddress + Length - 1)
- End Get
- End Property
- ''' <summary>Untergrenze der SubnetMaske in Integer-Darstellung</summary>
- Public ReadOnly Property LBoundAddress As Integer
- Get
- Return Address And (Not Length - 1)
- End Get
- End Property
- ''' <summary>Größe des durch die SubnetMaske definierten Addressraumes</summary>
- Public ReadOnly Property Length As Integer
- Get
- Return 1 << (32 - NetMaskLength)
- End Get
- End Property
- ''' <summary>gibt die Integer-Darstellung einer IP zurück</summary>
- Public Shared Function IP2Int(ByVal ip As String) As Int32
- 'split zerteilt ip in 4 strings, die je eine Zahl darstellen
- 'Byte.Parse wandelt je ein Segment in ein Byte um
- 'Select sammelt diese Bytes in ein IEnumerable(Of Bytes)
- 'ToArray macht daraus ein Byte-Array
- Dim bytes = (From s In ip.Split("."c) Select Byte.Parse(s)).ToArray
- Array.Reverse(bytes) 'dreht die Reihenfolge um
- Return BitConverter.ToInt32(bytes, 0)
- End Function
- ''' <summary>bildet aussm Integer die String-Darstellung einer IP</summary>
- Public Shared Function Int2IP(ByVal n As Int32) As String
- Dim bytes = BitConverter.GetBytes(n)
- Array.Reverse(bytes) 'reihenfolge umdrehen
- 'Byte-Array in ein String-Array konvertieren
- Dim strings = bytes.Select(Function(b) b.ToString)
- 'Strings verketten und returnen
- Return String.Join(".", strings)
- End Function
- End Class
Threading
Unabdingbar.
Will man einen Bereich von 256 IPs durchpingen, und gesteht jedem Ping einen Timeout von 2s zu - hmm - ich glaub, etwas über 7min, richtig?
Verwendet man 256 Threads sinds nur 2s
Man muß aber auch die Anzahl der Threads begrenzen, die belegen nämlich jeder über 1MB Speicher. Da habichne Weile dran rumprobiert, um hinzukriegen, wie mehrere Thread-Methoden sich parallel immer die nächste IP abholen und verarbeiten. Herausgekommen ist etwas mit 2 Delegaten (addSuccess und addFail) und der anonymen Methode consume():
VB.NET-Quellcode
- Private Sub btStart_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btStart.Click
- UpdateGui(False)
- Array.ForEach({lstSuccess, lstFail}, Sub(lst) lst.Items.Clear())
- Dim ipData = New IPData With {.FullName = txtIP.Text}
- Dim threadCount = Integer.Parse(txtThreadCount.Text)
- Dim nIP = ipData.LBoundAddress - 1
- Dim ubound = nIP + ipData.Length
- Dim addSuccess = DirectCast(AddressOf lstSuccess.Items.Add, Action(Of String))
- Dim addFail = DirectCast(AddressOf lstFail.Items.Add, Action(Of String))
- Dim consume As ThreadStart = _
- Sub()
- Using _Ping = New Ping
- Do
- Dim exceedsIpRange As Boolean
- 'exklusiv für diesen Thread nIP inkrementieren und gegen ubound checken
- SyncLock Me
- nIP += 1
- exceedsIpRange = nIP > ubound
- End SyncLock
- If exceedsIpRange Then 'Thread läuft aus
- 'der letzte auslaufende Thread reEnabled das Gui
- If nIP = ubound + threadCount Then Me.BeginInvoke(UpdateGui, True)
- Exit Do 'auf jd. Fall aussteigen - Thread auslaufen lassen
- End If
- 'String-Darstellung von nIP pingen, Result-spezifisch adden
- Dim sIP = ipData.Int2IP(nIP)
- If _Ping.Send(sIP, 2000).Status = IPStatus.Success Then
- Me.BeginInvoke(addSuccess, sIP)
- Else
- Me.BeginInvoke(addFail, sIP)
- End If
- Loop
- End Using
- End Sub
- 'viele Threads starten
- For i = 0 To threadCount - 1
- Call New Thread(consume) With {.Priority = ThreadPriority.Lowest}.Start()
- Next
- End Sub
Die Delegaten sind erforderlich, um aus dem Nebenthread das Adden von ListboxItems an den GuiThread delegieren zu können. Die anonyme Methode macht die ganze Arbeit: instanziert ein Ping-Objekt, und holt sich im Do-Loop die jeweils aktuelle ID und pingt die mittm Ping.
Listig die Reaktivierung des Guis:
VB.NET-Quellcode
Aber das steht zunächstmal nur in der anonymen Methode, also ist nur deklariert, wird von sich aus nicht ausgeführt.
Erst ganz am Schluß werden Threads erzeugt, die diese Methode dann auch abfahren - und zwar mehrfach parallel.
Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()