-
IPToCountry.NET
This is my own implementation of an IP To Country static lookup class in VB.NET. It can be used to look up the country code and also full name of an IP address. I am sharing it in the spirit that the underlying data was shared: receive free, give free.
This all started when I found a downloadable CSV file of IP address ranges to country codes. It is cool that such a thing is available for free, and the file maintainers update that thing daily. But instead of hitting a web service or loading it into a database, querying that 5 MB of data in memory is even better. Plus, this was a fun opportunity to repurpose Array.BinarySearch for something it was not intended, namely matching the Int64 IP address to a private array of Range structures. This also required converting IP address strings (255.255.255.255) into 64-bit integers. I didn’t see anything VB.NET-ish enough for my tastes, so I whipped up a function that uses hex strings. Some hardcore programmer out there can directly convert a four byte array to an Int64 without the hex hack, I’m sure. (Update: I found a better solution here.)
This class is optimized to run inside of an ASP.NET website, but aside from using Server.MapPath to find the CSV file, it has no dependencies on System.Web. It’s static so that there will be only one copy of the object in memory. You use it by calling two static functions: IPToCountry.GetCountryCode(IP) returns a country code, and you can also look up the full name by calling IPToCountry.GetCountryName(Code).I realize that the code formatting is messed up (thanks Wordpress.com) but just create a new class called IPToCountry in your project and copy+paste from here on down; it should work. YMMVHooray Wordpress!Public Class IPToCountry Private Shared _Ranges() As Range Private Shared _Countries As Hashtable</code> 'The Range structure represents a single line in the IP range file. Structures are less heavy than objects. Private Structure Range Public FromIP As Long Public ToIP As Long Public Country As String 'Constructor using the file line, already split Public Sub New(ByVal Fields() As String) If Fields.Length > 4 Then FromIP = CLng(Fields(0)) ToIP = CLng(Fields(1)) Country = Fields(4) End If End Sub 'When loading the file, many of the lines overlap each other. This function is used to avoid loading the extra line Public Function Overlaps(ByVal Other As Range) As Boolean Return Me.Country = Other.Country AndAlso Me.FromIP = Other.ToIP + 1 End Function Public Function Matches(ByVal IP As Long) As Integer 'Return 1 if the IP is too low, -1 if it's too high 'I would have thought it was the other way around, but no If IP Then Return 1 ElseIf IP > ToIP Then Return -1 Else Return 0 'the IP falls within the range End If End Function Public Overrides Function ToString() As String Return Country &amp; ": " &amp; IPToCountry.IPLongToString(FromIP) &amp; " - " &amp; IPToCountry.IPLongToString(ToIP) End Function End Structure 'RangeFinder is used by Array.BinarySearch to see if an IP falls within a range Private Class RangeFinder Implements IComparer Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare Dim TheRange As Range = x Dim TheIP As Long = y Return TheRange.Matches(TheIP) End Function End Class 'RangeSorter is used to sort ranges by FromIP Private Class RangeSorter Implements IComparer Public Function Compare(ByVal x As Object, ByVal y As Object) As Integer Implements System.Collections.IComparer.Compare Dim RangeX As Range = CType(x, Range) Dim RangeY As Range = CType(y, Range) Return RangeX.FromIP.CompareTo(RangeY.FromIP) End Function End Class 'Return the full path to the CSV file Private Shared Function GetFilePath() As String Dim ctx As HttpContext = HttpContext.Current Dim Path As String = ctx.Server.MapPath("~/IpToCountry.csv") 'change this to the location of your CSV file Return Path End Function 'Set up the object for subsequent use; load the array list from the file Shared Sub New() Dim StartTime As Date = Now 'for debugging Dim FilePath As String = GetFilePath() Dim RangeList As New ArrayList Dim NeedsSort As Boolean = False Dim MaxFromRange As Long _Countries = New Hashtable If IO.File.Exists(FilePath) Then Dim Reader As IO.StreamReader = IO.File.OpenText(FilePath) Dim Line As String = Reader.ReadLine() Dim Fields() As String Dim LastRange As Range Do While Not Line Is Nothing 'read the file lines one at a time 'skip lines that start with "#" or blank lines If Not Line.StartsWith("#") AndAlso Line.Length > 0 Then Fields = Line.Replace("""", "").Split(",") Dim NewRange As New Range(Fields) 'Check to see if a sort will be needed at the end If NeedsSort OrElse (MaxFromRange > 0 AndAlso MaxFromRange < NewRange.FromIP) Then NeedsSort = True Else MaxFromRange = NewRange.FromIP End If 'To avoid creating more array elements than necessary, merge adjacent IP ranges here If NewRange.Overlaps(LastRange) AndAlso RangeList.Count > 0 Then LastRange.ToIP = NewRange.ToIP RangeList.Item(RangeList.Count - 1) = LastRange 'must reassign because structs don't do by ref Else RangeList.Add(NewRange) LastRange = NewRange 'add the full country name to the hashtable If Not _Countries.Contains(NewRange.Country) Then _Countries.Add(NewRange.Country, Fields(Fields.Length - 1)) End If End If End If Line = Reader.ReadLine() Loop Reader.Close() ReDim _Ranges(RangeList.Count - 1) RangeList.CopyTo(_Ranges) If NeedsSort Then Array.Sort(_Ranges, New RangeSorter) End If End If 'debugging output here Diagnostics.Debug.WriteLine("Loaded IpToCountry.csv in " &amp; Now.Subtract(StartTime).TotalMilliseconds &amp; " ms") Diagnostics.Debug.WriteLine(RangeList.Count &amp; " ranges, " &amp; _Countries.Count &amp; " countries; Max IP: " &amp; MaxFromRange.ToString) End Sub Public Shared Function GetCountryCode(ByVal IP As String) As String Return GetCountryCode(IPStringToLong(IP)) End Function Public Shared Function GetCountryCode(ByVal IP As Long) As String Dim Index As Integer = Array.BinarySearch(_Ranges, IP, New RangeFinder) If Index > -1 Then Return _Ranges(Index).Country End If End Function Public Shared Function GetCountryName(ByVal Code As String) As String Return _Countries.Item(Code) End Function 'Converts an IP string in x.x.x.x format to the numeric representation (Int64) Public Shared Function IPStringToLong(ByVal IPString As String) As Long Dim s() As String = IPString.Split(".") If s.Length = 4 Then Try s(0) = Hex(CByte(s(0))) s(1) = Hex(CByte(s(1))) s(2) = Hex(CByte(s(2))) s(3) = Hex(CByte(s(3))) Return Long.Parse(String.Join("", s), Globalization.NumberStyles.AllowHexSpecifier) Catch ex As Exception Return -1 End Try End If End Function 'Converts a numeric IP address into the x.x.x.x format for display Public Shared Function IPLongToString(ByVal IPLong As Long) As String If IPLong > 16777215 AndAlso IPLong &lt; 4294967295 Then 'valid IP addresses between 1.0.0.0 and 255.255.255.255 Dim IPHex As String = Hex(IPLong) If IPHex.Length &lt; 8 Then IPHex = IPHex.PadLeft(8, "0") Dim b(3) As Byte b(0) = Byte.Parse(IPHex.Substring(0, 2), Globalization.NumberStyles.AllowHexSpecifier) b(1) = Byte.Parse(IPHex.Substring(2, 2), Globalization.NumberStyles.AllowHexSpecifier) b(2) = Byte.Parse(IPHex.Substring(4, 2), Globalization.NumberStyles.AllowHexSpecifier) b(3) = Byte.Parse(IPHex.Substring(6, 2), Globalization.NumberStyles.AllowHexSpecifier) Return b(0) &amp; "." &amp; b(1) &amp; "." &amp; b(2) &amp; "." &amp; b(3) Else Return "0.0.0.0" End If End Function End Class


