1 #region Copyright & License Information 2 /* 3 * Copyright 2007-2020 The OpenRA Developers (see AUTHORS) 4 * This file is part of OpenRA, which is free software. It is made 5 * available to you under the terms of the GNU General Public License 6 * as published by the Free Software Foundation, either version 3 of 7 * the License, or (at your option) any later version. For more 8 * information, see COPYING. 9 */ 10 #endregion 11 12 using System; 13 using System.IO; 14 using System.Linq; 15 using System.Net; 16 using System.Net.Sockets; 17 using System.Numerics; 18 using ICSharpCode.SharpZipLib.Zip; 19 20 namespace OpenRA.Network 21 { 22 public class GeoIP 23 { 24 class IP2LocationReader 25 { 26 public readonly DateTime Date; 27 readonly Stream stream; 28 readonly uint columnCount; 29 readonly uint v4Count; 30 readonly uint v4Offset; 31 readonly uint v6Count; 32 readonly uint v6Offset; 33 IP2LocationReader(Stream source)34 public IP2LocationReader(Stream source) 35 { 36 // Copy stream data for reuse 37 stream = new MemoryStream(); 38 source.CopyTo(stream); 39 stream.Position = 0; 40 41 if (stream.ReadUInt8() != 1) 42 throw new InvalidDataException("Only IP2Location type 1 databases are supported."); 43 44 columnCount = stream.ReadUInt8(); 45 var year = stream.ReadUInt8(); 46 var month = stream.ReadUInt8(); 47 var day = stream.ReadUInt8(); 48 Date = new DateTime(2000 + year, month, day); 49 50 v4Count = stream.ReadUInt32(); 51 v4Offset = stream.ReadUInt32(); 52 v6Count = stream.ReadUInt32(); 53 v6Offset = stream.ReadUInt32(); 54 } 55 AddressForIndex(long index, bool isIPv6)56 BigInteger AddressForIndex(long index, bool isIPv6) 57 { 58 var start = isIPv6 ? v6Offset : v4Offset; 59 var offset = isIPv6 ? 12 : 0; 60 stream.Seek(start + index * (4 * columnCount + offset) - 1, SeekOrigin.Begin); 61 return new BigInteger(stream.ReadBytes(isIPv6 ? 16 : 4).Append((byte)0).ToArray()); 62 } 63 CountryForIndex(long index, bool isIPv6)64 string CountryForIndex(long index, bool isIPv6) 65 { 66 // Read file offset for country entry 67 var start = isIPv6 ? v6Offset : v4Offset; 68 var offset = isIPv6 ? 12 : 0; 69 stream.Seek(start + index * (4 * columnCount + offset) + offset + 3, SeekOrigin.Begin); 70 var countryOffset = stream.ReadUInt32(); 71 72 // Read length-prefixed country name 73 stream.Seek(countryOffset + 3, SeekOrigin.Begin); 74 var length = stream.ReadUInt8(); 75 76 // "-" is used to represent an unknown country in the database 77 var country = stream.ReadASCII(length); 78 return country != "-" ? country : null; 79 } 80 LookupCountry(IPAddress ip)81 public string LookupCountry(IPAddress ip) 82 { 83 var isIPv6 = ip.AddressFamily == AddressFamily.InterNetworkV6; 84 if (!isIPv6 && ip.AddressFamily != AddressFamily.InterNetwork) 85 return null; 86 87 // Locate IP using a binary search 88 // The IP2Location python parser has an additional 89 // optimization that can jump directly to the row, but this adds 90 // extra complexity that isn't obviously needed for our limited database size 91 long low = 0; 92 long high = isIPv6 ? v6Count : v4Count; 93 94 // Append an empty byte to force the data to be treated as unsigned 95 var ipNumber = new BigInteger(ip.GetAddressBytes().Reverse().Append((byte)0).ToArray()); 96 while (low <= high) 97 { 98 var mid = (low + high) / 2; 99 var min = AddressForIndex(mid, isIPv6); 100 var max = AddressForIndex(mid + 1, isIPv6); 101 if (min <= ipNumber && ipNumber < max) 102 return CountryForIndex(mid, isIPv6); 103 104 if (ipNumber < min) 105 high = mid - 1; 106 else 107 low = mid + 1; 108 } 109 110 return null; 111 } 112 } 113 114 static IP2LocationReader database; 115 Initialize(string databasePath = R)116 public static void Initialize(string databasePath = "IP2LOCATION-LITE-DB1.IPV6.BIN.ZIP") 117 { 118 if (!File.Exists(databasePath)) 119 return; 120 121 try 122 { 123 using (var z = new ZipFile(databasePath)) 124 { 125 var entry = z.FindEntry("IP2LOCATION-LITE-DB1.IPV6.BIN", false); 126 database = new IP2LocationReader(z.GetInputStream(entry)); 127 } 128 } 129 catch (Exception e) 130 { 131 Log.Write("geoip", "DatabaseReader failed: {0}", e); 132 } 133 } 134 LookupCountry(IPAddress ip)135 public static string LookupCountry(IPAddress ip) 136 { 137 if (database != null) 138 { 139 try 140 { 141 return database.LookupCountry(ip); 142 } 143 catch (Exception e) 144 { 145 Log.Write("geoip", "LookupCountry failed: {0}", e); 146 } 147 } 148 149 return null; 150 } 151 } 152 } 153