IP matchning med VB.Net
Förord
Som webmaster för en sajt fick jag för ett tag sedan problem med "abusers". I mitt fall handlade det om ett flertal personer som under ett gemensamt alias skrev rasistiska inlägg vårat forum. Givetvis kollade jag upp server loggarna vilka jag fick skickade till mig av administratören. Utifrån dessa kunde jag sedan se från vilka datorer och tidpunkter som meddelandena skickades.Innehåll
»»
»
»
»
»
Vad gör man då åt detta?
Det första jag gjorde var att inte göra någonting utöver att i forumet skriva ett inlägg att de måste sluta. Några dagar senare gick jag igenom forumet och samlade in allt skräp de hade skrivit inklusive de provocerande svaren på mitt inlägg. Dessa skickade jag tillsammans med server loggar till två stycken Internet leverantörer de gått genom i hopp om att de skulle bli avstängda. Men för de här leverantörerna krävs tydligen mer än en uppsjö rasistiska inlägg för att de ska ta till någon åtgärd.I och med att dessa personer inte blev avstängda fick jag åtminstone se till att de inte kunde surfa in på den sajt jag var webmaster för. Jag gjorde två åtgärder vilket idag lett till att inga "abusers" finns kvar i huvud taget:
- Bytte ut forum systemet mot ett annat där registrering krävs för att både läsa och skriva inlägg.
- Skrev några klasser som hjälper mig att spärra vissa IP-adresser och även hela serier av IP-adresser.
Det är dessa klasser kommer jag att diskutera i den här artikeln.
Koncept
Vad vi kommer att göra här är att utveckla några enkla klasser där vi kan ange en eller flera IP-adresser och sedan se om en specifik IP-adress matchar någon av dessa. En del av er kanske undrar: Varför inte bara använda Internet Services Manager och utnyttja de redan befintliga funktionerna i IIS? Först och främst därför att man vanligtvis inte har tillgång till det verktyget så vida du inte kör på en egen server eller en server du kan logga in på som användare. Dessutom får vi ytterligare några klasser vi kan återanvända i framtida system där behovet finns att göra någon form av IP-matchning. Det första vi måste göra är att reda ut vad vi vill göra. För att göra detta kan man exempelvis skriva ned några punkter.
Vi vill kunna:
- Ange en IP-adress i taget att matcha mot.
- Ange en hel serie IP-adresser att matcha mot.
- Kombinera ovanstående metoder för att skapa mer komplexa matchningar.
Modellering
Med dessa tre påståenden kan vi nu börja modellera vår lösning i exempelvis UML (Unified Modeling Language). Genom att göra detta kommer det att bli en barnlek att koda sedan när det blir aktuellt. Dessutom kan vi lättare skapa en mer strukturerad och kanske mindre komplex lösning. Vår lösning består av fem klasser och en enumerator (visas ej i diagrammet).
IP-matchning kommer att ske mot de regler som vi sätter upp. Men innan vi går vidare måste vi reda upp vad en regel är. Det gör vi också genom några punkter.
En regel:
- Måste implementera metoden RuleType() vilken talar om vad det är för typ av regel.
- Måste ta emot en eller flera IP-adresser vilka vi kan matcha mot.
- Måste implementera metoden IsMatch() vilken vi använder för att fråga objektet om det innehåller en specifik IP-adress. '
En regel består av basklassen Rule och måste implementera metoderna RuleType() och IsMatch(). Klassen Rule är abstrakt och kan därför inte instansieras direkt utan du måste använda någon av underklasserna RuleSingle eller RuleRange. Låter det krångligt? Det är det inte. Men valet av lösning är en aningen krångligare än om vi strikt skulle hålla oss till målet och ignorera ev. vidareutveckling av dessa klasser. Genom att först skapa den abstrakta klassen Rule vilken definierar obligatoriska metoder kan vi sedan skapa så många regler vi vill. Utöver de två befintliga reglerna kan vi alltså skapa ytterligare regler om vi önskar.
Implementering
IPAddressDen första klassen vi ska koda är IPAddress (vilken inte bör förväxlas med System.Net.IPAddress). I och med att det faktiskt finns en klassmetod i IPAddress kan den inte klassas som en entitet i dess striktaste mening, men den kommer i huvudsak att fungera som en sådan. Dvs. övriga objekt i vår lösning kommer enbart att använda klassen IPAddress som en data bärare.
IPAddress består främst av fyra publika Byte (0-255) variabler. Dessa representerar de fyra siffergrupperna i en IP-adress. Du kan tilldela de här egenskaperna värden manuellt om du önskar men det blir lite smidigare är att använda den konstruktor som tar en parameter av typen String. Du kan alltså skapa ett IPAddress objekt och tilldela den värden på två sätt.
Metod 1:
Dim MyAddress As IPAddress = New IPAddress()
MyAddress.A = 127
MyAddress.B = 0
MyAddress.C = 0
MyAddress.D = 1
Metod 2 (användning av konstruktor):
Dim MyAddress As IPAddress = New IPAddress("127.0.0.1")
Det är också den senare metoden som indirekt gör att IPAddress inte kan klassas som en entitet. I och med att vi tillåts att ange en IP-adress som vanlig text måste vi också se till att validera den. Metoden IsValid() gör just detta. Man skulle kunna deklarera den som en privat metod i klassen. Istället har jag valt att deklarera den som en klassmetod (Shared) vilket gör att klassen inte måste instancieras innan vi använder just den metoden.
Källkod för klassen IPAddress:
Namespace IPMatching
Public Class IPAddress
Public A As Byte = 0
Public B As Byte = 0
Public C As Byte = 0
Public D As Byte = 0
Public Sub New(ByVal Ip As String)
' Description:
' Create new IPAddress object based on a String.
If Me.IsValid(Ip) = True Then Dim splitIp() As String = Ip.Split(".")
Me.A = CType(splitIp(0), Byte)
Me.B = CType(splitIp(1), Byte)
Me.C = CType(splitIp(2), Byte)
Me.D = CType(splitIp(3), Byte)
Else
' IP-address was not valid, throw exception.
Throw New System.Exception("Invalid IP-address (IPMatching.IPAddress!New)")
End If
End Sub
Public Sub New()
' Default constructor, do nothing.
End Sub
Public Shared Function IsValid(ByVal Ip As String) As Boolean
' Description:
' A function that uses the System.Net.IPAddress.Parse function to
' validate an IP-address.
' Supresses exceptions and returns a Boolean instead.
Dim testIp As System.Net.IPAddress
Try
testIp = System.Net.IPAddress.Parse(Ip)
Catch
' Invalid IP-address.
Return (False)
End Try
' No exception accured, IP-address is valid.
Return (True)
End Function
Public Overrides Function ToString() As String
' Description:
' Overrides ToString method of baseclass (Object) to return
' current IP-address instead of class name.
Return (Me.A & "." & Me.B & "." & Me.C & "." & Me.D)
End Function
End Class
End Namespace ' IPMatching
I den konstruktor som tar en String som parameter ser ni hur IP-adressen först valideras innan egenskaperna A, B, C och D får sina värden. Själva valideringen av IP-adressen finner ni längre ned i IsValid() metoden. Istället att från grunden skriva en egen IP validator använder jag den som redan finns i Parse() funktionen i System.Net.IPAddress klassen. Detta gör jag genom att försöka skapa ett System.Net.IPAddress objekt med vår parameter och istället för att returnera ett eventuellt Exception returnerar jag en Boolean. Slutligen kan också nämnas att en omdefinition har gjorts av ToString() metoden. Den returnerar nu den aktuella IP-adressen istället för det fullt kvalificerade klassnamnet som är standard.
Rule
Det här är en väldigt enkel klass. Detta därför att den endast är till för att indikera vilka metoder som måste implementeras av alla underklasser. I det här fallet handlar det om RuleSingle och RuleRange.
Klassen är designad för att vara abstrakt vilket innebär att vi måste använda nyckelordet MustInherit:
Namespace IPMatching
Public Enum EnumRuleType
RuleSingle = 1
RuleRange = 2
End Enum
' Abstract class (sub: RuleSingle, RuleRange)
Public MustInherit Class Rule
Public MustOverride ReadOnly Property RuleType() As EnumRuleType
Public MustOverride Function IsMatch(ByVal Ip As IPAddress) As Boolean
End Class
End Namespace ' IPMatching
RuleSingle
Den enklast tänkbara regeln är en ensam IP-adress. Vi använder den här regeln när vi vill jämföra en IP-adress mot en annan. För att göra det skapar vi en publik metod (Property) som endast är läsbar. Den tilldelar vi en IP-adress samtidigt som vi skapar ett objekt med hjälp av den enda konstruktorn. När det är gjort kan vi göra matchningar mot den med hjälp av den obligatoriska IsMatch() metoden.
Källkod för klassen RuleSingle:
Namespace IPMatching
Public Class RuleSingle : Inherits Rule
' Property variable:
Private ipValue As IPMatching.IPAddress
' Constructor:
Public Sub New(ByVal Ip As IPMatching.IPAddress)
ipValue = Ip
End Sub
' Properties:
Public Overrides ReadOnly Property RuleType() As EnumRuleType
Get
Return (EnumRuleType.RuleSingle)
End Get
End Property
Public ReadOnly Property Value() As IPMatching.IPAddress
Get
Return (ipValue)
End Get
End Property
' Functions:
Public Overrides Function IsMatch(ByVal Ip As IPMatching.IPAddress) As Boolean
' Description:
' Compare provided IP-address with current (Me).
If Not Ip.A = Me.Value.A Then Return (False)
If Not Ip.B = Me.Value.B Then Return (False)
If Not Ip.C = Me.Value.C Then Return (False)
If Not Ip.D = Me.Value.D Then Return (False)
' The provided IP-address matches with current (Me).
Return (True)
End Function
End Class
End Namespace ' IPMatching
I ovanstående kod finns båda de obligatoriska metoderna implementerade, dessutom finns ytterligare ett par. Det finns alltså ingenting som hindrar att man utökar reglerna med nya metoder.
RuleRange
Det här är en aning mer komplex regel. Här tillåts man tilldela en hel serie IP-adresser istället för tidigare endast en. Man kan t.ex. tilldela den IP-adresserna 192.168.0.0 - 192.168.255.255 och göra en matchning mot dessa. Metoden IsMatch() returnerar True på IP-adresser inom serien samt dess gränser. Dvs. även i det här fallet adresserna 192.168.0.0 och 192.168.255.255.
IP-serierna anges som en start och en stopp adress. Dessa måste anges vid instanciering då objektet endast är läsbart. Jag har gjort så för att hålla exemplet så enkelt som möjligt. Det bör också påpekas att start får vara högre än stopp adressen när man skapar ett objekt. Men man bör då också vara medveten om att objektet kommer att skifta om dessa två IP-adresser själv så att den lägsta adressen blir start och den högsta blir stopp.
Källkod för klassen RuleRange:
' P.g.a. källkodens storlek väljer jag att inte visa den här. I högra kolumnen hittar du en länk där du kan ladda hem komplett källkod.
RulesCollection
Sista klassen i vår lösning är RulesCollection. Båda reglerna tidigare nämnda kan användas separat var för sig, men det är den här klassen som gör att vi kan kombinera dem och skapa mer intressanta matchningar.
Egenskapen Rules har ett 1..* förhållande med Rule klassen. Det innebär att man kan lägga till flera regler till den egenskapen så länge det objekt man lägger till har Rule som basklass. Eftersom både RuleSingle och RuleRange ärver från Rule så kan man lägga till dem. För att göra exemplet så kort och enkelt som möjligt har jag valt att deklarera den här egenskapen som en publik ArrayList. Det innebär att man kan lägga till vilket typ av objekt man vill. Det innebär däremot inte att du ska göra det då IsMatch() metoden endast förväntar sig Rule objekt och ingenting annat.
Källkod för klassen RulesCollection:
Namespace IPMatching
Public Class RulesCollection
' Attribute:
Public Rules As System.Collections.ArrayList = New ArrayList()
' Function:
Public Function IsMatch(ByVal Ip As IPAddress) As Boolean
' Description:
' Iterates through the ArrayList of rules to find a match.
Dim ruleIterator As Int32
For ruleIterator = 0 To Me.Rules.Count - 1
If Me.Rules(ruleIterator).IsMatch(Ip) Then Return (True)
Next
' No match found.
Return (False)
End Function
End Class
End Namespace ' IPMatching
Att använda klasserna
Då har vi äntligen alla nödvändiga klasser för att börja göra lite matchningar. Vi börjar med de enklaste typerna av matchningar till mer komplexa i slutet. Enkel IP-adress
Dim myRule As RuleSingle = New RuleSingle(New IPAddress("127.0.0.1"))
Debug.WriteLine(myRule.IsMatch(New IPAddress("127.0.0.1"))) ' Returns True.
Debug.WriteLine(myRule.IsMatch(New IPAddress("192.168.0.1"))) ' Returns False.
Serie med IP-adresser
Dim myRule As RuleRange = New RuleRange(New IPAddress("192.168.0.5"), _
New IPAddress("192.168.255.255"))
Debug.WriteLine(myRule.IsMatch(New IPAddress("192.168.0.1"))) ' Returns False.
Debug.WriteLine(myRule.IsMatch(New IPAddress("192.168.40.1"))) ' Returns True.
Debug.WriteLine(myRule.IsMatch(New IPAddress("192.168.255.255"))) ' Returns True.
Kombination av regler:
Dim myRules As RulesCollection = New RulesCollection()
' Create rules:
Dim myRuleRange As RuleRange = New RuleRange(New IPAddress("192.168.0.5"), _
New IPAddress("192.168.255.255"))
Dim myRuleSingle As RuleSingle = New RuleSingle(New IPAddress("127.0.0.1"))
' Add rules to RulesCollection:
myRules.Rules.Add(myRuleRange)
myRules.Rules.Add(myRuleSingle)
Debug.WriteLine(myRules.IsMatch(New IPAddress("192.168.0.1"))) ' Returns False.
Debug.WriteLine(myRules.IsMatch(New IPAddress("192.168.40.1"))) ' Returns True.
Debug.WriteLine(myRules.IsMatch(New IPAddress("192.168.255.255"))) ' Returns True.
Debug.WriteLine(myRules.IsMatch(New IPAddress("127.0.0.1"))) ' Returns True.
Debug.WriteLine(myRules.IsMatch(New IPAddress("192.168.0.1"))) ' Returns False.
Som ni ser kan man med ganska enkla medel skapa komplexa matchningar. Vill man kan dessutom ska nya egna regler. Ett litet tips när ni använder både RuleSingle och RuleRange objekt i kombination är att först lägga till alla RuleRange och sedan RuleSingle. Detta eftersom det är större chans att en IP-adress matchar en serie än ett enskilt IP-nummer.
Avslutningsvis kan vi titta tillbaka på de punkter jag tog upp i början. Där skrev vi ned att man både skulle lägga till en eller flera IP-adresser samt göra matchningar mot dessa. Inte nog med att vi har infriat dessa mål, vi har även skapat en skalbar lösning där vi kan lägga till nya regler om vi vill. Dessutom har vi fått en klassmetod för att validera IP-adresser vilken vi kan använda i vilket sammanhang som helst.
Exempelfil
Här kan ni tanka ner exempelfilen: ipmatchning.zip./Håkan Wennerberg
0 Kommentarer