Switch GeoIP database from MaxMind to IP2Location.

The IP2Location data is licensed under CC BY-SA, which
allows us to distribute the database with releases.
This commit is contained in:
Paul Chote
2020-04-12 14:36:35 +01:00
committed by abcdefg30
parent 6828c4c1e9
commit 9c4faddc0f
15 changed files with 137 additions and 74 deletions

View File

@@ -11,63 +11,119 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using ICSharpCode.SharpZipLib.GZip;
using MaxMind.Db;
using System.Net.Sockets;
using System.Numerics;
using ICSharpCode.SharpZipLib.Zip;
namespace OpenRA.Network
{
public class GeoIP
{
public class GeoIP2Record
class IP2LocationReader
{
[Constructor]
public GeoIP2Record(GeoIP2Country country)
public readonly DateTime Date;
readonly Stream stream;
readonly uint columnCount;
readonly uint v4Count;
readonly uint v4Offset;
readonly uint v6Count;
readonly uint v6Offset;
public IP2LocationReader(Stream source)
{
Country = country;
// Copy stream data for reuse
stream = new MemoryStream();
source.CopyTo(stream);
stream.Position = 0;
if (stream.ReadUInt8() != 1)
throw new InvalidDataException("Only IP2Location type 1 databases are supported.");
columnCount = stream.ReadUInt8();
var year = stream.ReadUInt8();
var month = stream.ReadUInt8();
var day = stream.ReadUInt8();
Date = new DateTime(2000 + year, month, day);
v4Count = stream.ReadUInt32();
v4Offset = stream.ReadUInt32();
v6Count = stream.ReadUInt32();
v6Offset = stream.ReadUInt32();
}
public GeoIP2Country Country { get; set; }
}
public class GeoIP2Country
{
[Constructor]
public GeoIP2Country(GeoIP2CountryNames names)
BigInteger AddressForIndex(long index, bool isIPv6)
{
Names = names;
var start = isIPv6 ? v6Offset : v4Offset;
var offset = isIPv6 ? 12 : 0;
stream.Seek(start + index * (4 * columnCount + offset) - 1, SeekOrigin.Begin);
return new BigInteger(stream.ReadBytes(isIPv6 ? 16 : 4).Append((byte)0).ToArray());
}
public GeoIP2CountryNames Names { get; set; }
}
public class GeoIP2CountryNames
{
[Constructor]
public GeoIP2CountryNames(string en)
string CountryForIndex(long index, bool isIPv6)
{
English = en;
// Read file offset for country entry
var start = isIPv6 ? v6Offset : v4Offset;
var offset = isIPv6 ? 12 : 0;
stream.Seek(start + index * (4 * columnCount + offset) + offset + 3, SeekOrigin.Begin);
var countryOffset = stream.ReadUInt32();
// Read length-prefixed country name
stream.Seek(countryOffset + 3, SeekOrigin.Begin);
var length = stream.ReadUInt8();
// "-" is used to represent an unknown country in the database
var country = stream.ReadASCII(length);
return country != "-" ? country : null;
}
public string English { get; set; }
public string LookupCountry(IPAddress ip)
{
var isIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6;
if (!isIPv6 && ip.AddressFamily != AddressFamily.InterNetwork)
return null;
// Locate IP using a binary search
// The IP2Location python parser has an additional
// optimization that can jump directly to the row, but this adds
// extra complexity that isn't obviously needed for our limited database size
long low = 0;
long high = isIPv6 ? v6Count : v4Count;
// Append an empty byte to force the data to be treated as unsigned
var ipNumber = new BigInteger(ip.GetAddressBytes().Reverse().Append((byte)0).ToArray());
while (low <= high)
{
var mid = (low + high) / 2;
var min = AddressForIndex(mid, isIPv6);
var max = AddressForIndex(mid + 1, isIPv6);
if (min <= ipNumber && ipNumber < max)
return CountryForIndex(mid, isIPv6);
if (ipNumber < min)
high = mid - 1;
else
low = mid + 1;
}
return null;
}
}
static Reader database;
static IP2LocationReader database;
public static void Initialize(string databasePath)
public static void Initialize(string databasePath = "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP")
{
if (string.IsNullOrEmpty(databasePath))
if (!File.Exists(databasePath))
return;
try
{
using (var fileStream = new FileStream(databasePath, FileMode.Open, FileAccess.Read))
using (var z = new ZipFile(databasePath))
{
if (databasePath.EndsWith(".gz"))
using (var gzipStream = new GZipInputStream(fileStream))
database = new Reader(gzipStream);
else
database = new Reader(fileStream);
var entry = z.FindEntry("IP2LOCATION-LITE-DB1.IPV6.BIN", false);
database = new IP2LocationReader(z.GetInputStream(entry));
}
}
catch (Exception e)
@@ -82,9 +138,7 @@ namespace OpenRA.Network
{
try
{
var record = database.Find<GeoIP2Record>(ip);
if (record != null)
return record.Country.Names.English;
return database.LookupCountry(ip);
}
catch (Exception e)
{