GeoIpResolver.cpp

Go to the documentation of this file.
00001 /*
00002 **  This file is part of Vidalia, and is subject to the license terms in the
00003 **  LICENSE file, found in the top level directory of this distribution. If you
00004 **  did not receive the LICENSE file with this file, you may obtain it from the
00005 **  Vidalia source package distributed by the Vidalia Project at
00006 **  http://www.vidalia-project.net/. No part of Vidalia, including this file,
00007 **  may be copied, modified, propagated, or distributed except according to the
00008 **  terms described in the LICENSE file.
00009 */
00010 
00011 /*
00012 ** \file GeoIpResolver.cpp
00013 ** \version $Id: GeoIpResolver.cpp 3768 2009-05-13 19:07:26Z edmanm $
00014 ** \brief Requests GeoIP information and caches the result
00015 */
00016 
00017 #include "GeoIpResolver.h"
00018 #include "GeoIpRequest.h"
00019 #include "GeoIpResponse.h"
00020 #include "GeoIp.h"
00021 #include "Vidalia.h"
00022 
00023 #include "stringutil.h"
00024 #include "TorSslSocket.h"
00025 
00026 /** Host for the GeoIP information. */ 
00027 #define GEOIP_HOST    "geoips.vidalia-project.net"
00028 /** The SSL GeoIP service runs on port 443. */
00029 #define GEOIP_SSL_PORT  443
00030 /** Page that we request the GeoIP information from. */
00031 #define GEOIP_PAGE    "/cgi-bin/geoip.py"
00032 
00033 
00034 /** Default constructor. */
00035 GeoIpResolver::GeoIpResolver(QObject *parent)
00036   : QObject(parent)
00037 {
00038   _socksAddr = QHostAddress::LocalHost;
00039   _socksPort = 9050;
00040   _cache = new GeoIpCache(this);
00041 }
00042 
00043 /** Sets the address and port of Tor, through which GeoIP requests will be
00044  * made. */
00045 void
00046 GeoIpResolver::setSocksHost(const QHostAddress &addr, quint16 port)
00047 {
00048   _socksAddr = addr;
00049   _socksPort = port;
00050 }
00051 
00052 /** Resolves <b>ip</b> to geographic information if it is cached. A resolved()
00053  * signal will be emitted and true returned if we have cached geographic
00054  * information for <b>ip</b>. Otherwise, this returns false. */
00055 bool
00056 GeoIpResolver::resolveFromCache(const QHostAddress &ip)
00057 {
00058   if (_cache->contains(ip)) {
00059     emit resolved(-1, QList<GeoIp>() << _cache->geoIpForAddress(ip));
00060     return true;
00061   }
00062   return false;
00063 }
00064 
00065 /** Resolves a list of IPs to a geographic location, but only those which are
00066  * cached. Returns a list of IPs that were not in the cache. */
00067 QList<QHostAddress>
00068 GeoIpResolver::resolveFromCache(const QList<QHostAddress> &ips)
00069 {
00070   QList<GeoIp> cached;
00071 
00072   /* Build a list of which IPs have cached GeoIp information */
00073   foreach (QHostAddress ip, ips) {
00074     if (_cache->contains(ip))
00075       cached << _cache->geoIpForAddress(ip);
00076   }
00077 
00078   /* If any were cached, emit their results now */
00079   if (cached.size() > 0) {
00080     vInfo("Resolved %1 GeoIP entries from cache.").arg(cached.size());
00081     emit resolved(-1, cached);
00082   }
00083   return ips;
00084 }
00085 
00086 /** Resolves a single IP to a geographic location. */
00087 int
00088 GeoIpResolver::resolve(const QHostAddress &ip)
00089 {
00090   return resolve(QList<QHostAddress>() << ip);
00091 }
00092 
00093 /** Called when the socket has connected to the Geo IP host. */
00094 void
00095 GeoIpResolver::connected()
00096 {
00097   /* Find the socket and request for whoever called this slot */ 
00098   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00099   if (!_requestList.contains(socket)) {
00100     return;
00101   }
00102   GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.value(socket));
00103 
00104   vInfo("Connected to the GeoIP host. Sending request for %1 uncached "
00105         "GeoIP entries. (request id %2)").arg(req->size()).arg(req->id());
00106 
00107   /* Send the request */
00108   socket->write(req->request());
00109 }
00110 
00111 /** Called when the socket has disconnected from the Geo IP host. */
00112 void
00113 GeoIpResolver::disconnected()
00114 {
00115   /* Find the socket and request for whoever called this slot */ 
00116   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00117   if (!_requestList.contains(socket)) {
00118     return;
00119   }
00120   GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
00121 
00122   /* Read and parse the response header */
00123   GeoIpResponse response = GeoIpResponse(socket->readAll());
00124 
00125   /* Check the response code and see what we got */
00126   if (response.statusCode() == 200) {
00127     /* We got a 200 OK, so get the Geo IP information from the response body
00128      * and cache the results. */
00129     parseGeoIpResponse(response.content(), req);
00130   } else {
00131     /* We failed to get the Geo IP information, so emit resolveFailed and
00132      * include the HTTP status message. */
00133     vWarn("GeoIP resolution failed (request id %1): %2").arg(req->id())
00134                                              .arg(response.statusMessage());
00135     emit resolveFailed(req->id(), response.statusMessage());
00136   }
00137   /* Close the socket and clean up */
00138   socket->close();
00139   delete socket;
00140   delete req;
00141 }
00142 
00143 void
00144 GeoIpResolver::parseGeoIpResponse(const QByteArray &response,
00145                                   GeoIpRequest *request)
00146 {
00147   QList<GeoIp> geoIpList;
00148   QHash<QString,QString> keyvals;
00149   QHostAddress ip, from, to;
00150   QString city, region, country, cc;
00151   float latitude, longitude;
00152   GeoIp geoIp;
00153   int numCached = 0;
00154   bool ok;
00155 
00156   QStringList lines = QString(response).split("\n", QString::SkipEmptyParts);
00157   foreach (QString line, lines) {
00158     /* Split the key=value formatted GeoIP record into keys and values */
00159     QHash<QString,QString> keyvals = string_parse_keyvals(line.trimmed(), &ok);
00160     if (! ok)
00161       goto err;
00162 
00163     /* Extract each of the required fields from the GeoIP record */
00164     ip = QHostAddress(keyvals.value("IP"));
00165     if (ip.isNull())
00166       goto err;
00167     latitude = keyvals.value("LAT").toFloat(&ok);
00168     if (! ok)
00169       goto err;
00170     longitude = keyvals.value("LON").toFloat(&ok);
00171     if (! ok)
00172       goto err;
00173 
00174     /* Each of these fields is optional */
00175     city    = keyvals.value("CITY");
00176     region  = keyvals.value("REGION");
00177     country = keyvals.value("COUNTRY");
00178     cc      = keyvals.value("CC");
00179     
00180     geoIp = GeoIp(ip, latitude, longitude, city, region, country, cc);
00181     if (! geoIp.isValid())
00182       goto err;
00183 
00184     if (request->contains(ip)) {
00185       if (! _cache->contains(ip)) {
00186         from = QHostAddress(keyvals.value("FROM"));
00187         to   = QHostAddress(keyvals.value("TO"));
00188         if (! from.isNull() && ! to.isNull())
00189           _cache->addToCache(from, to, geoIp);
00190         else
00191           _cache->addToCache(geoIp);
00192         numCached++;
00193       }
00194 
00195       geoIpList << geoIp;
00196       continue;
00197 
00198 err:
00199       vInfo("Ignored improperly formatted GeoIP record (request id %1): %2")
00200                                                .arg(line).arg(request->id());
00201     } else {
00202       /* This item wasn't requested, so just log it and ignore. */
00203       vWarn("Received a GeoIP entry for IP address %1 that was not included "
00204             "in the initial request. (request id %2)").arg(ip)
00205                                                       .arg(request->id());
00206     }
00207   }
00208   /* If new results were cached, save them to disk */
00209   if (numCached > 0)
00210     _cache->saveToDisk();
00211   
00212   /* Emit the results */
00213   vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
00214                                    .arg(geoIpList.size()).arg(request->id());
00215   emit resolved(request->id(), geoIpList);  
00216 }
00217 
00218 /** Called when an error has occurred requesting Geo IP information. */
00219 void
00220 GeoIpResolver::socketError(const QString &errorString)
00221 {
00222   /* Find the socket and request for whoever called this slot */ 
00223   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00224   if (!_requestList.contains(socket)) {
00225     return;
00226   }
00227   
00228   /* We expect a remote host to close the socket, because that's how the HTTP
00229    * server tells us he's done talking to us. */
00230   if (socket->error() != QAbstractSocket::RemoteHostClosedError) {
00231     /* Emit the failure and clean up */
00232     GeoIpRequest *req = static_cast<GeoIpRequest *>(_requestList.take(socket));
00233     emit resolveFailed(req->id(), errorString);
00234     socket->abort();
00235     vWarn("GeoIP request socket error (request id %1): %2").arg(req->id())
00236                                                            .arg(errorString);
00237     delete socket;
00238     delete req;
00239   }
00240 }
00241 
00242 /** Creates an HTTP request for Geo IP information. */
00243 GeoIpRequest*
00244 GeoIpResolver::createRequest(const QList<QHostAddress> &ips)
00245 {
00246   static int id = -1;
00247   GeoIpRequest *request = new GeoIpRequest(++id);
00248   request->setHost(GEOIP_HOST);
00249   request->setPage(GEOIP_PAGE);
00250   request->setRequest(ips);
00251   return request;
00252 }
00253 
00254 /** Resolves a list of IPs to a geographic location. */
00255 int
00256 GeoIpResolver::resolve(const QList<QHostAddress> &ips)
00257 {
00258   /* Resolve the cached IPs and get a list of IPs that still need to be
00259    * resolved to a lat and long. */
00260   QList<QHostAddress> uncached = resolveFromCache(ips);
00261   if (! uncached.size())
00262     return -1;
00263 
00264   /* Create a socket used to request the geo ip information. */
00265   TorSslSocket *socket = new TorSslSocket(_socksAddr, _socksPort);
00266   connect(socket, SIGNAL(connectedToRemoteHost()), this, SLOT(connected()),
00267           Qt::QueuedConnection);
00268   connect(socket, SIGNAL(socketError(QString)), 
00269           this,   SLOT(socketError(QString)),
00270           Qt::QueuedConnection);
00271   connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
00272           Qt::QueuedConnection);
00273   GeoIpRequest *request = createRequest(uncached);
00274   _requestList.insert(socket, request);
00275   
00276   /* Connect so we can send our request and return the request ID. */
00277   vInfo("Opening an SSL connection to the GeoIP host at %1:%2 (request id %3)")
00278                         .arg(GEOIP_HOST).arg(GEOIP_SSL_PORT).arg(request->id());
00279   socket->connectToRemoteHost(GEOIP_HOST, GEOIP_SSL_PORT, true);
00280 
00281   return request->id();
00282 }
00283 
Generated on Mon Aug 30 23:09:49 2010 for Vidalia by  doxygen 1.6.3