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:
@@ -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)
|
||||
{
|
||||
|
||||
@@ -58,10 +58,6 @@
|
||||
<HintPath>..\thirdparty\download\ICSharpCode.SharpZipLib.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="MaxMind.Db">
|
||||
<HintPath>..\thirdparty\download\MaxMind.Db.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<ProjectReference Include="..\OpenRA.PostProcess\OpenRA.PostProcess.csproj">
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
|
||||
@@ -149,7 +149,8 @@ namespace OpenRA.Server
|
||||
|
||||
randomSeed = (int)DateTime.Now.ToBinary();
|
||||
|
||||
GeoIP.Initialize(settings.GeoIPDatabase);
|
||||
if (type != ServerType.Local && settings.EnableGeoIP)
|
||||
GeoIP.Initialize();
|
||||
|
||||
if (UPnP.Status == UPnPStatus.Enabled)
|
||||
UPnP.ForwardPort(Settings.ListenPort, Settings.ListenPort).Wait();
|
||||
|
||||
@@ -82,13 +82,12 @@ namespace OpenRA
|
||||
[Desc("Sets the timestamp format. Defaults to the ISO 8601 standard.")]
|
||||
public string TimestampFormat = "yyyy-MM-ddTHH:mm:ss";
|
||||
|
||||
[Desc("Path to a MaxMind GeoLite2 database to use for player geo-location.",
|
||||
"Database files can be downloaded from https://dev.maxmind.com/geoip/geoip2/geolite2/")]
|
||||
public string GeoIPDatabase = null;
|
||||
|
||||
[Desc("Allow clients to see anonymised IPs for other clients.")]
|
||||
public bool ShareAnonymizedIPs = true;
|
||||
|
||||
[Desc("Allow clients to see the country of other clients.")]
|
||||
public bool EnableGeoIP = true;
|
||||
|
||||
public ServerSettings Clone()
|
||||
{
|
||||
return (ServerSettings)MemberwiseClone();
|
||||
|
||||
Reference in New Issue
Block a user