Implement new GeoIPManager class.

This commit is contained in:
Vladimir Golovnev (Glassez)
2015-05-18 17:02:48 +03:00
parent c702a7e426
commit 79976fbfce
28 changed files with 1228 additions and 401 deletions

View File

@@ -0,0 +1,464 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include <QDebug>
#include <QFile>
#include <QDir>
#include <QHostAddress>
#include <QDateTime>
#include "core/logger.h"
#include "core/preferences.h"
#include "core/utils/fs.h"
#include "core/utils/gzip.h"
#include "downloadmanager.h"
#include "downloadhandler.h"
#include "private/geoipdatabase.h"
#include "geoipmanager.h"
static const char DATABASE_URL[] = "http://geolite.maxmind.com/download/geoip/database/GeoLite2-Country.mmdb.gz";
static const char GEOIP_FOLDER[] = "GeoIP";
static const char GEOIP_FILENAME[] = "GeoLite2-Country.mmdb";
static const int CACHE_SIZE = 1000;
static const int UPDATE_INTERVAL = 30; // Days between database updates
using namespace Net;
// GeoIPManager
GeoIPManager *GeoIPManager::m_instance = 0;
GeoIPManager::GeoIPManager()
: m_enabled(false)
, m_geoIPDatabase(0)
, m_cache(CACHE_SIZE)
{
configure();
connect(Preferences::instance(), SIGNAL(changed()), SLOT(configure()));
}
GeoIPManager::~GeoIPManager()
{
if (m_geoIPDatabase)
delete m_geoIPDatabase;
}
void GeoIPManager::initInstance()
{
if (!m_instance)
m_instance = new GeoIPManager;
}
void GeoIPManager::freeInstance()
{
if (m_instance) {
delete m_instance;
m_instance = 0;
}
}
GeoIPManager *GeoIPManager::instance()
{
return m_instance;
}
void GeoIPManager::loadDatabase()
{
if (m_geoIPDatabase) {
delete m_geoIPDatabase;
m_geoIPDatabase = 0;
}
QString filepath = Utils::Fs::expandPathAbs(
QString("%1%2/%3").arg(Utils::Fs::QDesktopServicesDataLocation())
.arg(GEOIP_FOLDER).arg(GEOIP_FILENAME));
QString error;
m_geoIPDatabase = GeoIPDatabase::load(filepath, error);
if (m_geoIPDatabase)
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
else
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
manageDatabaseUpdate();
}
void GeoIPManager::manageDatabaseUpdate()
{
if (!m_geoIPDatabase || (m_geoIPDatabase->buildEpoch().daysTo(QDateTime::currentDateTimeUtc()) >= UPDATE_INTERVAL))
downloadDatabaseFile();
}
void GeoIPManager::downloadDatabaseFile()
{
DownloadHandler *handler = DownloadManager::instance()->downloadUrl(DATABASE_URL);
connect(handler, SIGNAL(downloadFinished(QString, QByteArray)), SLOT(downloadFinished(QString, QByteArray)));
connect(handler, SIGNAL(downloadFailed(QString, QString)), SLOT(downloadFailed(QString, QString)));
}
QString GeoIPManager::lookup(const QHostAddress &hostAddr) const
{
if (m_enabled && m_geoIPDatabase) {
QString *country = m_cache.object(hostAddr);
if (country)
return *country;
QString code = m_geoIPDatabase->lookup(hostAddr);
m_cache.insert(hostAddr, new QString(code));
return code;
}
return QString();
}
// http://www.iso.org/iso/country_codes/iso_3166_code_lists/english_country_names_and_code_elements.htm
QString GeoIPManager::CountryName(const QString &countryISOCode)
{
static QHash<QString, QString> countries;
static bool initialized = false;
if (!initialized) {
countries[QString()] = "N/A";
countries["AP"] = "Asia/Pacific Region";
countries["EU"] = "Europe";
countries["AD"] = "Andorra";
countries["AE"] = "United Arab Emirates";
countries["AF"] = "Afghanistan";
countries["AG"] = "Antigua and Barbuda";
countries["AI"] = "Anguilla";
countries["AL"] = "Albania";
countries["AM"] = "Armenia";
countries["AN"] = "Netherlands Antilles";
countries["AO"] = "Angola";
countries["AQ"] = "Antarctica";
countries["AR"] = "Argentina";
countries["AS"] = "American Samoa";
countries["AT"] = "Austria";
countries["AU"] = "Australia";
countries["AW"] = "Aruba";
countries["AZ"] = "Azerbaijan";
countries["BA"] = "Bosnia and Herzegovina";
countries["BB"] = "Barbados";
countries["BD"] = "Bangladesh";
countries["BE"] = "Belgium";
countries["BF"] = "Burkina Faso";
countries["BG"] = "Bulgaria";
countries["BH"] = "Bahrain";
countries["BI"] = "Burundi";
countries["BJ"] = "Benin";
countries["BM"] = "Bermuda";
countries["BN"] = "Brunei Darussalam";
countries["BO"] = "Bolivia";
countries["BR"] = "Brazil";
countries["BS"] = "Bahamas";
countries["BT"] = "Bhutan";
countries["BV"] = "Bouvet Island";
countries["BW"] = "Botswana";
countries["BY"] = "Belarus";
countries["BZ"] = "Belize";
countries["CA"] = "Canada";
countries["CC"] = "Cocos (Keeling) Islands";
countries["CD"] = "Congo, The Democratic Republic of the";
countries["CF"] = "Central African Republic";
countries["CG"] = "Congo";
countries["CH"] = "Switzerland";
countries["CI"] = "Cote D'Ivoire";
countries["CK"] = "Cook Islands";
countries["CL"] = "Chile";
countries["CM"] = "Cameroon";
countries["CN"] = "China";
countries["CO"] = "Colombia";
countries["CR"] = "Costa Rica";
countries["CU"] = "Cuba";
countries["CV"] = "Cape Verde";
countries["CX"] = "Christmas Island";
countries["CY"] = "Cyprus";
countries["CZ"] = "Czech Republic";
countries["DE"] = "Germany";
countries["DJ"] = "Djibouti";
countries["DK"] = "Denmark";
countries["DM"] = "Dominica";
countries["DO"] = "Dominican Republic";
countries["DZ"] = "Algeria";
countries["EC"] = "Ecuador";
countries["EE"] = "Estonia";
countries["EG"] = "Egypt";
countries["EH"] = "Western Sahara";
countries["ER"] = "Eritrea";
countries["ES"] = "Spain";
countries["ET"] = "Ethiopia";
countries["FI"] = "Finland";
countries["FJ"] = "Fiji";
countries["FK"] = "Falkland Islands (Malvinas)";
countries["FM"] = "Micronesia, Federated States of";
countries["FO"] = "Faroe Islands";
countries["FR"] = "France";
countries["FX"] = "France, Metropolitan";
countries["GA"] = "Gabon";
countries["GB"] = "United Kingdom";
countries["GD"] = "Grenada";
countries["GE"] = "Georgia";
countries["GF"] = "French Guiana";
countries["GH"] = "Ghana";
countries["GI"] = "Gibraltar";
countries["GL"] = "Greenland";
countries["GM"] = "Gambia";
countries["GN"] = "Guinea";
countries["GP"] = "Guadeloupe";
countries["GQ"] = "Equatorial Guinea";
countries["GR"] = "Greece";
countries["GS"] = "South Georgia and the South Sandwich Islands";
countries["GT"] = "Guatemala";
countries["GU"] = "Guam";
countries["GW"] = "Guinea-Bissau";
countries["GY"] = "Guyana";
countries["HK"] = "Hong Kong";
countries["HM"] = "Heard Island and McDonald Islands";
countries["HN"] = "Honduras";
countries["HR"] = "Croatia";
countries["HT"] = "Haiti";
countries["HU"] = "Hungary";
countries["ID"] = "Indonesia";
countries["IE"] = "Ireland";
countries["IL"] = "Israel";
countries["IN"] = "India";
countries["IO"] = "British Indian Ocean Territory";
countries["IQ"] = "Iraq";
countries["IR"] = "Iran, Islamic Republic of";
countries["IS"] = "Iceland";
countries["IT"] = "Italy";
countries["JM"] = "Jamaica";
countries["JO"] = "Jordan";
countries["JP"] = "Japan";
countries["KE"] = "Kenya";
countries["KG"] = "Kyrgyzstan";
countries["KH"] = "Cambodia";
countries["KI"] = "Kiribati";
countries["KM"] = "Comoros";
countries["KN"] = "Saint Kitts and Nevis";
countries["KP"] = "Korea, Democratic People's Republic of";
countries["KR"] = "Korea, Republic of";
countries["KW"] = "Kuwait";
countries["KY"] = "Cayman Islands";
countries["KZ"] = "Kazakhstan";
countries["LA"] = "Lao People's Democratic Republic";
countries["LB"] = "Lebanon";
countries["LC"] = "Saint Lucia";
countries["LI"] = "Liechtenstein";
countries["LK"] = "Sri Lanka";
countries["LR"] = "Liberia";
countries["LS"] = "Lesotho";
countries["LT"] = "Lithuania";
countries["LU"] = "Luxembourg";
countries["LV"] = "Latvia";
countries["LY"] = "Libyan Arab Jamahiriya";
countries["MA"] = "Morocco";
countries["MC"] = "Monaco";
countries["MD"] = "Moldova, Republic of";
countries["MG"] = "Madagascar";
countries["MH"] = "Marshall Islands";
countries["MK"] = "Macedonia";
countries["ML"] = "Mali";
countries["MM"] = "Myanmar";
countries["MN"] = "Mongolia";
countries["MO"] = "Macau";
countries["MP"] = "Northern Mariana Islands";
countries["MQ"] = "Martinique";
countries["MR"] = "Mauritania";
countries["MS"] = "Montserrat";
countries["MT"] = "Malta";
countries["MU"] = "Mauritius";
countries["MV"] = "Maldives";
countries["MW"] = "Malawi";
countries["MX"] = "Mexico";
countries["MY"] = "Malaysia";
countries["MZ"] = "Mozambique";
countries["NA"] = "Namibia";
countries["NC"] = "New Caledonia";
countries["NE"] = "Niger";
countries["NF"] = "Norfolk Island";
countries["NG"] = "Nigeria";
countries["NI"] = "Nicaragua";
countries["NL"] = "Netherlands";
countries["NO"] = "Norway";
countries["NP"] = "Nepal";
countries["NR"] = "Nauru";
countries["NU"] = "Niue";
countries["NZ"] = "New Zealand";
countries["OM"] = "Oman";
countries["PA"] = "Panama";
countries["PE"] = "Peru";
countries["PF"] = "French Polynesia";
countries["PG"] = "Papua New Guinea";
countries["PH"] = "Philippines";
countries["PK"] = "Pakistan";
countries["PL"] = "Poland";
countries["PM"] = "Saint Pierre and Miquelon";
countries["PN"] = "Pitcairn Islands";
countries["PR"] = "Puerto Rico";
countries["PS"] = "Palestinian Territory";
countries["PT"] = "Portugal";
countries["PW"] = "Palau";
countries["PY"] = "Paraguay";
countries["QA"] = "Qatar";
countries["RE"] = "Reunion";
countries["RO"] = "Romania";
countries["RU"] = "Russian Federation";
countries["RW"] = "Rwanda";
countries["SA"] = "Saudi Arabia";
countries["SB"] = "Solomon Islands";
countries["SC"] = "Seychelles";
countries["SD"] = "Sudan";
countries["SE"] = "Sweden";
countries["SG"] = "Singapore";
countries["SH"] = "Saint Helena";
countries["SI"] = "Slovenia";
countries["SJ"] = "Svalbard and Jan Mayen";
countries["SK"] = "Slovakia";
countries["SL"] = "Sierra Leone";
countries["SM"] = "San Marino";
countries["SN"] = "Senegal";
countries["SO"] = "Somalia";
countries["SR"] = "Suriname";
countries["ST"] = "Sao Tome and Principe";
countries["SV"] = "El Salvador";
countries["SY"] = "Syrian Arab Republic";
countries["SZ"] = "Swaziland";
countries["TC"] = "Turks and Caicos Islands";
countries["TD"] = "Chad";
countries["TF"] = "French Southern Territories";
countries["TG"] = "Togo";
countries["TH"] = "Thailand";
countries["TJ"] = "Tajikistan";
countries["TK"] = "Tokelau";
countries["TM"] = "Turkmenistan";
countries["TN"] = "Tunisia";
countries["TO"] = "Tonga";
countries["TL"] = "Timor-Leste";
countries["TR"] = "Turkey";
countries["TT"] = "Trinidad and Tobago";
countries["TV"] = "Tuvalu";
countries["TW"] = "Taiwan";
countries["TZ"] = "Tanzania, United Republic of";
countries["UA"] = "Ukraine";
countries["UG"] = "Uganda";
countries["UM"] = "United States Minor Outlying Islands";
countries["US"] = "United States";
countries["UY"] = "Uruguay";
countries["UZ"] = "Uzbekistan";
countries["VA"] = "Holy See (Vatican City State)";
countries["VC"] = "Saint Vincent and the Grenadines";
countries["VE"] = "Venezuela";
countries["VG"] = "Virgin Islands, British";
countries["VI"] = "Virgin Islands, U.S.";
countries["VN"] = "Vietnam";
countries["VU"] = "Vanuatu";
countries["WF"] = "Wallis and Futuna";
countries["WS"] = "Samoa";
countries["YE"] = "Yemen";
countries["YT"] = "Mayotte";
countries["RS"] = "Serbia";
countries["ZA"] = "South Africa";
countries["ZM"] = "Zambia";
countries["ME"] = "Montenegro";
countries["ZW"] = "Zimbabwe";
countries["A1"] = "Anonymous Proxy";
countries["A2"] = "Satellite Provider";
countries["O1"] = "Other";
countries["AX"] = "Aland Islands";
countries["GG"] = "Guernsey";
countries["IM"] = "Isle of Man";
countries["JE"] = "Jersey";
countries["BL"] = "Saint Barthelemy";
countries["MF"] = "Saint Martin";
initialized = true;
}
return countries.value(countryISOCode, "N/A");
}
void GeoIPManager::configure()
{
const bool enabled = Preferences::instance()->resolvePeerCountries();
if (m_enabled != enabled) {
m_enabled = enabled;
if (m_enabled && !m_geoIPDatabase)
loadDatabase();
}
}
void GeoIPManager::downloadFinished(const QString &url, QByteArray data)
{
Q_UNUSED(url);
if (!Utils::Gzip::uncompress(data, data)) {
Logger::instance()->addMessage(tr("Could not uncompress GeoIP database file."), Log::WARNING);
return;
}
QString error;
GeoIPDatabase *geoIPDatabase = GeoIPDatabase::load(data, error);
if (geoIPDatabase) {
if (!m_geoIPDatabase || (geoIPDatabase->buildEpoch() > m_geoIPDatabase->buildEpoch())) {
if (m_geoIPDatabase)
delete m_geoIPDatabase;
m_geoIPDatabase = geoIPDatabase;
Logger::instance()->addMessage(tr("GeoIP database loaded. Type: %1. Build time: %2.")
.arg(m_geoIPDatabase->type()).arg(m_geoIPDatabase->buildEpoch().toString()),
Log::INFO);
QString targetPath = Utils::Fs::expandPathAbs(
Utils::Fs::QDesktopServicesDataLocation() + GEOIP_FOLDER);
if (!QDir(targetPath).exists())
QDir().mkpath(targetPath);
QFile targetFile(QString("%1/%2").arg(targetPath).arg(GEOIP_FILENAME));
if (!targetFile.open(QFile::WriteOnly) || (targetFile.write(data) == -1)) {
Logger::instance()->addMessage(
tr("Couldn't save downloaded GeoIP database file."), Log::WARNING);
}
else {
Logger::instance()->addMessage(tr("Successfully updated GeoIP database."), Log::INFO);
}
}
else {
delete geoIPDatabase;
}
}
else {
Logger::instance()->addMessage(tr("Couldn't load GeoIP database. Reason: %1").arg(error), Log::WARNING);
}
}
void GeoIPManager::downloadFailed(const QString &url, const QString &reason)
{
Q_UNUSED(url);
Logger::instance()->addMessage(tr("Couldn't download GeoIP database file. Reason: %1").arg(reason), Log::WARNING);
}

View File

@@ -0,0 +1,77 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
* Copyright (C) 2010 Christophe Dumez <chris@qbittorrent.org>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#ifndef NET_GEOIPMANAGER_H
#define NET_GEOIPMANAGER_H
#include <QObject>
#include <QCache>
class QHostAddress;
class QString;
class GeoIPDatabase;
namespace Net
{
class GeoIPManager : public QObject
{
Q_OBJECT
public:
static void initInstance();
static void freeInstance();
static GeoIPManager *instance();
QString lookup(const QHostAddress &hostAddr) const;
static QString CountryName(const QString &countryISOCode);
private slots:
void configure();
void downloadFinished(const QString &url, QByteArray data);
void downloadFailed(const QString &url, const QString &reason);
private:
GeoIPManager();
~GeoIPManager();
void loadDatabase();
void manageDatabaseUpdate();
void downloadDatabaseFile();
bool m_enabled;
GeoIPDatabase *m_geoIPDatabase;
mutable QCache<QHostAddress, QString> m_cache;
static GeoIPManager *m_instance;
};
}
#endif // NET_GEOIPMANAGER_H

View File

@@ -0,0 +1,631 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#include <QDebug>
#include <QCoreApplication>
#include <QVariant>
#include <QHash>
#include <QList>
#include <QScopedArrayPointer>
#include <QScopedPointer>
#include <QHostAddress>
#include <QDateTime>
#include <QFile>
#include "core/types.h"
#include "geoipdatabase.h"
struct Node
{
quint32 left;
quint32 right;
};
struct GeoIPData
{
// Metadata
quint16 ipVersion;
quint16 recordSize;
quint32 nodeCount;
QDateTime buildEpoch;
// Search data
QList<Node> index;
QHash<quint32, QString> countries;
};
namespace
{
const quint32 __ENDIAN_TEST__ = 0x00000001;
const bool __IS_LITTLE_ENDIAN__ = (reinterpret_cast<const uchar *>(&__ENDIAN_TEST__)[0] == 0x01);
BEGIN_SCOPED_ENUM(DataType)
{
Unknown = 0,
Pointer = 1,
String = 2,
Double = 3,
Bytes = 4,
Integer16 = 5,
Integer32 = 6,
Map = 7,
SignedInteger32 = 8,
Integer64 = 9,
Integer128 = 10,
Array = 11,
DataCacheContainer = 12,
EndMarker = 13,
Boolean = 14,
Float = 15
}
END_SCOPED_ENUM
struct DataFieldDescriptor
{
DataType fieldType;
union
{
quint32 fieldSize;
quint32 offset; // Pointer
};
};
const int MAX_FILE_SIZE = 10485760; // 10MB
const char DB_TYPE[] = "GeoLite2-Country";
const quint32 MAX_METADATA_SIZE = 131072; // 128KB
const char METADATA_BEGIN_MARK[] = "\xab\xcd\xefMaxMind.com";
const char DATA_SECTION_SEPARATOR[16] = { 0 };
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_IPV6ADDR createMappedAddress(quint32 ip4);
#endif
class Loader
{
Q_DECLARE_TR_FUNCTIONS(GeoIPDatabase)
public:
GeoIPData *load(const QString &filename);
GeoIPData *load(const QByteArray &data);
QString error() const;
private:
bool parseMetadata(const QVariantHash &metadata);
bool loadDB();
QVariantHash readMetadata();
QVariant readDataField(quint32 &offset);
bool readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out);
void fromBigEndian(uchar *buf, quint32 len);
QVariant readMapValue(quint32 &offset, quint32 count);
QVariant readArrayValue(quint32 &offset, quint32 count);
template<typename T>
QVariant readPlainValue(quint32 &offset, quint8 len)
{
T value = 0;
const uchar *const data = m_data + offset;
const quint32 availSize = m_size - offset;
if ((len > 0) && (len <= sizeof(T) && (availSize >= len))) {
// copy input data to last 'len' bytes of 'value'
uchar *dst = reinterpret_cast<uchar *>(&value) + (sizeof(T) - len);
memcpy(dst, data, len);
fromBigEndian(reinterpret_cast<uchar *>(&value), sizeof(T));
offset += len;
}
return QVariant::fromValue(value);
}
private:
const uchar *m_data;
quint32 m_size;
QString m_error;
GeoIPData *m_geoIPData;
};
}
// GeoIPDatabase
GeoIPDatabase::GeoIPDatabase(GeoIPData *geoIPData)
: m_geoIPData(geoIPData)
{
}
GeoIPDatabase *GeoIPDatabase::load(const QString &filename, QString &error)
{
GeoIPDatabase *db = 0;
Loader loader;
GeoIPData *geoIPData = loader.load(filename);
if (!geoIPData)
error = loader.error();
else
db = new GeoIPDatabase(geoIPData);
return db;
}
GeoIPDatabase *GeoIPDatabase::load(const QByteArray &data, QString &error)
{
GeoIPDatabase *db = 0;
Loader loader;
GeoIPData *geoIPData = loader.load(data);
if (!geoIPData)
error = loader.error();
else
db = new GeoIPDatabase(geoIPData);
return db;
}
GeoIPDatabase::~GeoIPDatabase()
{
delete m_geoIPData;
}
QString GeoIPDatabase::type() const
{
return DB_TYPE;
}
quint16 GeoIPDatabase::ipVersion() const
{
return m_geoIPData->ipVersion;
}
QDateTime GeoIPDatabase::buildEpoch() const
{
return m_geoIPData->buildEpoch;
}
QString GeoIPDatabase::lookup(const QHostAddress &hostAddr) const
{
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_IPV6ADDR addr = hostAddr.protocol() == QAbstractSocket::IPv4Protocol
? createMappedAddress(hostAddr.toIPv4Address())
: hostAddr.toIPv6Address();
#else
Q_IPV6ADDR addr = hostAddr.toIPv6Address();
#endif
const quint32 nodeCount = static_cast<quint32>(m_geoIPData->index.size());
Node node = m_geoIPData->index[0];
for (int i = 0; i < 16; ++i) {
for (int j = 0; j < 8; ++j) {
bool right = static_cast<bool>((addr[i] >> (7 - j)) & 1);
quint32 id = (right ? node.right : node.left);
if (id == nodeCount)
return QString();
else if (id > nodeCount)
return m_geoIPData->countries[id];
else
node = m_geoIPData->index[id];
}
}
return QString();
}
namespace
{
// Loader
GeoIPData *Loader::load(const QString &filename)
{
QFile file(filename);
if (file.size() > MAX_FILE_SIZE) {
m_error = tr("Unsupported database file size.");
return 0;
}
if (!file.open(QFile::ReadOnly)) {
m_error = file.errorString();
return 0;
}
m_size = file.size();
QScopedArrayPointer<uchar> data(new uchar[m_size]);
m_data = data.data();
if (file.read((char *)m_data, m_size) != m_size) {
m_error = file.errorString();
return 0;
}
QScopedPointer<GeoIPData> geoIPData(new GeoIPData);
m_geoIPData = geoIPData.data();
if (!parseMetadata(readMetadata()) || !loadDB())
return 0;
return geoIPData.take();
}
GeoIPData *Loader::load(const QByteArray &data)
{
if (data.size() > MAX_FILE_SIZE) {
m_error = tr("Unsupported database file size.");
return 0;
}
m_size = data.size();
m_data = reinterpret_cast<const uchar *>(data.constData());
QScopedPointer<GeoIPData> geoIPData(new GeoIPData);
m_geoIPData = geoIPData.data();
if (!parseMetadata(readMetadata()) || !loadDB())
return 0;
return geoIPData.take();
}
QString Loader::error() const
{
return m_error;
}
#define CHECK_METADATA_REQ(key, type) \
if (!metadata.contains(#key)) { \
m_error = errMsgNotFound.arg(#key); \
return false; \
} \
else if (metadata.value(#key).userType() != QMetaType::type) { \
m_error = errMsgInvalid.arg(#key); \
return false; \
}
#define CHECK_METADATA_OPT(key, type) \
if (metadata.contains(#key)) { \
if (metadata.value(#key).userType() != QMetaType::type) { \
m_error = errMsgInvalid.arg(#key); \
return false; \
} \
}
bool Loader::parseMetadata(const QVariantHash &metadata)
{
const QString errMsgNotFound = tr("Metadata error: '%1' entry not found.");
const QString errMsgInvalid = tr("Metadata error: '%1' entry has invalid type.");
qDebug() << "Parsing MaxMindDB metadata...";
CHECK_METADATA_REQ(binary_format_major_version, UShort);
CHECK_METADATA_REQ(binary_format_minor_version, UShort);
uint versionMajor = metadata.value("binary_format_major_version").toUInt();
uint versionMinor = metadata.value("binary_format_minor_version").toUInt();
if (versionMajor != 2) {
m_error = tr("Unsupported database version: %1.%2").arg(versionMajor).arg(versionMinor);
return false;
}
CHECK_METADATA_REQ(ip_version, UShort);
m_geoIPData->ipVersion = metadata.value("ip_version").value<quint16>();
if (m_geoIPData->ipVersion != 6) {
m_error = tr("Unsupported IP version: %1").arg(m_geoIPData->ipVersion);
return false;
}
CHECK_METADATA_REQ(record_size, UShort);
m_geoIPData->recordSize = metadata.value("record_size").value<quint16>();
if (m_geoIPData->recordSize != 24) {
m_error = tr("Unsupported record size: %1").arg(m_geoIPData->recordSize);
return false;
}
CHECK_METADATA_REQ(node_count, UInt);
m_geoIPData->nodeCount = metadata.value("node_count").value<quint32>();
CHECK_METADATA_REQ(database_type, QString);
QString dbType = metadata.value("database_type").toString();
if (dbType != DB_TYPE) {
m_error = tr("Invalid database type: %1").arg(dbType);
return false;
}
CHECK_METADATA_REQ(build_epoch, ULongLong);
m_geoIPData->buildEpoch = QDateTime::fromTime_t(metadata.value("build_epoch").toULongLong());
CHECK_METADATA_OPT(languages, QVariantList);
CHECK_METADATA_OPT(description, QVariantHash);
return true;
}
bool Loader::loadDB()
{
qDebug() << "Parsing MaxMindDB index tree...";
const int nodeSize = m_geoIPData->recordSize / 4; // in bytes
const int indexSize = m_geoIPData->nodeCount * nodeSize;
if ((m_size < (indexSize + sizeof(DATA_SECTION_SEPARATOR)))
|| (memcmp(m_data + indexSize, DATA_SECTION_SEPARATOR, sizeof(DATA_SECTION_SEPARATOR)) != 0)) {
m_error = tr("Database corrupted: no data section found.");
return false;
}
m_geoIPData->index.reserve(m_geoIPData->nodeCount);
const int recordBytes = nodeSize / 2;
const uchar *ptr = m_data;
bool left = true;
Node node;
for (quint32 i = 0; i < (2 * m_geoIPData->nodeCount); ++i) {
uchar buf[4] = { 0 };
memcpy(&buf[4 - recordBytes], ptr, recordBytes);
fromBigEndian(buf, 4);
quint32 id = *(reinterpret_cast<quint32 *>(buf));
if ((id > m_geoIPData->nodeCount) && !m_geoIPData->countries.contains(id)) {
const quint32 offset = id - m_geoIPData->nodeCount - sizeof(DATA_SECTION_SEPARATOR);
quint32 tmp = offset + indexSize + sizeof(DATA_SECTION_SEPARATOR);
QVariant val = readDataField(tmp);
if (val.userType() == QMetaType::QVariantHash) {
m_geoIPData->countries[id] = val.toHash()["country"].toHash()["iso_code"].toString();
}
else if (val.userType() == QVariant::Invalid) {
m_error = tr("Database corrupted: invalid data type at DATA@%1").arg(offset, 8, 16, QLatin1Char('0'));
return false;
}
else {
m_error = tr("Invalid database: unsupported data type at DATA@%1").arg(offset, 8, 16, QLatin1Char('0'));
return false;
}
}
if (left) {
node.left = id;
}
else {
node.right = id;
m_geoIPData->index << node;
}
left = !left;
ptr += recordBytes;
}
return true;
}
QVariantHash Loader::readMetadata()
{
const char *ptr = reinterpret_cast<const char *>(m_data);
quint32 size = m_size;
if (m_size > MAX_METADATA_SIZE) {
ptr += m_size - MAX_METADATA_SIZE;
size = MAX_METADATA_SIZE;
}
const QByteArray data = QByteArray::fromRawData(ptr, size);
int index = data.lastIndexOf(METADATA_BEGIN_MARK);
if (index >= 0) {
if (m_size > MAX_METADATA_SIZE)
index += (m_size - MAX_METADATA_SIZE); // from begin of all data
quint32 offset = static_cast<quint32>(index + strlen(METADATA_BEGIN_MARK));
QVariant metadata = readDataField(offset);
m_size = index; // truncate m_size to not contain metadata section
if (metadata.userType() == QMetaType::QVariantHash)
return metadata.toHash();
}
return QVariantHash();
}
QVariant Loader::readDataField(quint32 &offset)
{
DataFieldDescriptor descr;
if (!readDataFieldDescriptor(offset, descr))
return QVariant();
quint32 locOffset = offset;
bool usePointer = false;
if (descr.fieldType == DataType::Pointer) {
usePointer = true;
// convert offset from data section to global
locOffset = descr.offset + (m_geoIPData->nodeCount * m_geoIPData->recordSize / 4) + sizeof(DATA_SECTION_SEPARATOR);
if (!readDataFieldDescriptor(locOffset, descr))
return QVariant();
}
QVariant fieldValue;
switch (descr.fieldType) {
case DataType::Pointer:
qDebug() << "* Illegal Pointer using";
break;
case DataType::String:
fieldValue = QString::fromUtf8(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Double:
if (descr.fieldSize == 8)
fieldValue = readPlainValue<double>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Double";
break;
case DataType::Bytes:
fieldValue = QByteArray(reinterpret_cast<const char *>(m_data + locOffset), descr.fieldSize);
locOffset += descr.fieldSize;
break;
case DataType::Integer16:
fieldValue = readPlainValue<quint16>(locOffset, descr.fieldSize);
break;
case DataType::Integer32:
fieldValue = readPlainValue<quint32>(locOffset, descr.fieldSize);
break;
case DataType::Map:
fieldValue = readMapValue(locOffset, descr.fieldSize);
break;
case DataType::SignedInteger32:
fieldValue = readPlainValue<qint32>(locOffset, descr.fieldSize);
break;
case DataType::Integer64:
fieldValue = readPlainValue<quint64>(locOffset, descr.fieldSize);
break;
case DataType::Integer128:
qDebug() << "* Unsupported data type: Integer128";
break;
case DataType::Array:
fieldValue = readArrayValue(locOffset, descr.fieldSize);
break;
case DataType::DataCacheContainer:
qDebug() << "* Unsupported data type: DataCacheContainer";
break;
case DataType::EndMarker:
qDebug() << "* Unsupported data type: EndMarker";
break;
case DataType::Boolean:
fieldValue = QVariant::fromValue(static_cast<bool>(descr.fieldSize));
break;
case DataType::Float:
if (descr.fieldSize == 4)
fieldValue = readPlainValue<float>(locOffset, descr.fieldSize);
else
qDebug() << "* Invalid field size for type: Float";
break;
}
if (!usePointer)
offset = locOffset;
return fieldValue;
}
bool Loader::readDataFieldDescriptor(quint32 &offset, DataFieldDescriptor &out)
{
const uchar *dataPtr = m_data + offset;
int availSize = m_size - offset;
if (availSize < 1) return false;
out.fieldType = static_cast<DataType>((dataPtr[0] & 0xE0) >> 5);
if (out.fieldType == DataType::Pointer) {
int size = ((dataPtr[0] & 0x18) >> 3);
if (availSize < (size + 2)) return false;
if (size == 0)
out.offset = ((dataPtr[0] & 0x07) << 8) + dataPtr[1];
else if (size == 1)
out.offset = ((dataPtr[0] & 0x07) << 16) + (dataPtr[1] << 8) + dataPtr[2] + 2048;
else if (size == 2)
out.offset = ((dataPtr[0] & 0x07) << 24) + (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 526336;
else if (size == 3)
out.offset = (dataPtr[1] << 24) + (dataPtr[2] << 16) + (dataPtr[3] << 8) + dataPtr[4];
offset += size + 2;
return true;
}
out.fieldSize = dataPtr[0] & 0x1F;
if (out.fieldSize <= 28) {
if (out.fieldType == DataType::Unknown) {
out.fieldType = static_cast<DataType>(dataPtr[1] + 7);
if ((out.fieldType <= DataType::Map) || (out.fieldType > DataType::Float) || (availSize < 3))
return false;
offset += 2;
}
else {
offset += 1;
}
}
else if (out.fieldSize == 29) {
if (availSize < 2) return false;
out.fieldSize = dataPtr[1] + 29;
offset += 2;
}
else if (out.fieldSize == 30) {
if (availSize < 3) return false;
out.fieldSize = (dataPtr[1] << 8) + dataPtr[2] + 285;
offset += 3;
}
else if (out.fieldSize == 31) {
if (availSize < 4) return false;
out.fieldSize = (dataPtr[1] << 16) + (dataPtr[2] << 8) + dataPtr[3] + 65821;
offset += 4;
}
return true;
}
void Loader::fromBigEndian(uchar *buf, quint32 len)
{
if (__IS_LITTLE_ENDIAN__)
std::reverse(buf, buf + len);
}
QVariant Loader::readMapValue(quint32 &offset, quint32 count)
{
QVariantHash map;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() != QMetaType::QString)
return QVariant();
QString key = field.toString();
field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
map[key] = field;
}
return map;
}
QVariant Loader::readArrayValue(quint32 &offset, quint32 count)
{
QVariantList array;
for (quint32 i = 0; i < count; ++i) {
QVariant field = readDataField(offset);
if (field.userType() == QVariant::Invalid)
return QVariant();
array.append(field);
}
return array;
}
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
Q_IPV6ADDR createMappedAddress(quint32 ip4)
{
Q_IPV6ADDR ip6;
memset(&ip6, 0, sizeof(ip6));
int i;
for (i = 15; ip4 != 0; i--) {
ip6[i] = ip4 & 0xFF;
ip4 >>= 8;
}
ip6[11] = 0xFF;
ip6[10] = 0xFF;
return ip6;
}
#endif
}

View File

@@ -0,0 +1,60 @@
/*
* Bittorrent Client using Qt and libtorrent.
* Copyright (C) 2015 Vladimir Golovnev <glassez@yandex.ru>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* In addition, as a special exception, the copyright holders give permission to
* link this program with the OpenSSL project's "OpenSSL" library (or with
* modified versions of it that use the same license as the "OpenSSL" library),
* and distribute the linked executables. You must obey the GNU General Public
* License in all respects for all of the code used other than "OpenSSL". If you
* modify file(s), you may extend this exception to your version of the file(s),
* but you are not obligated to do so. If you do not wish to do so, delete this
* exception statement from your version.
*/
#ifndef GEOIPDATABASE_H
#define GEOIPDATABASE_H
#include <QtGlobal>
class QHostAddress;
class QString;
class QByteArray;
class QDateTime;
struct GeoIPData;
class GeoIPDatabase
{
public:
static GeoIPDatabase *load(const QString &filename, QString &error);
static GeoIPDatabase *load(const QByteArray &data, QString &error);
~GeoIPDatabase();
QString type() const;
quint16 ipVersion() const;
QDateTime buildEpoch() const;
QString lookup(const QHostAddress &hostAddr) const;
private:
GeoIPDatabase(GeoIPData *geoIPData);
GeoIPData *m_geoIPData;
};
#endif // GEOIPDATABASE_H