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 3953 2009-07-09 18:38:45Z edmanm $
00014 ** \brief Requests GeoIP information and caches the result
00015 */
00016 
00017 #include <torsocket.h>
00018 #include <vidalia.h>
00019 #include "geoipresolver.h"
00020 #include "config.h"
00021 
00022 #include <torsslsocket.h>
00023 
00024 /** Host for the geo ip information. */ 
00025 #define GEOIP_HOST    "geoip.vidalia-project.net"
00026 /** The SSL GeoIP service lives on port 1443. */
00027 #define GEOIP_SSL_PORT      1443
00028 /** Page that we request the geo ip information from. */
00029 #define GEOIP_PAGE    "/cgi-bin/geoip"
00030 
00031 
00032 /** Default constructor. */
00033 GeoIpResolver::GeoIpResolver()
00034 {
00035   _socksAddr = QHostAddress::LocalHost;
00036   _socksPort = 9050;
00037 
00038   if (! QSslSocket::addDefaultCaCertificates(":/geoip/cacert_root.crt"))
00039     vWarn("Failed to add the GeoIP CA certificate to the default CA "
00040           "certificate database.");
00041 }
00042 
00043 /** Sets the address and port of Tor, through which GeoIP requests will be
00044  * made. */
00045 void
00046 GeoIpResolver::setSocksHost(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(QHostAddress ip)
00057 {
00058   if (_cache.contains(ip)) {
00059     emit resolved(-1, QList<GeoIp>() << _cache.geoip(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(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       ips.removeAt(ips.indexOf(ip));
00076       cached << _cache.geoip(ip);
00077     }
00078   }
00079 
00080   /* If any were cached, emit their results now */
00081   if (cached.size() > 0) {
00082     vInfo("Resolved %1 GeoIP entries from cache.").arg(ips.size());
00083     emit resolved(-1, cached);
00084   }
00085   return ips;
00086 }
00087 
00088 /** Resolves a single IP to a geographic location. */
00089 int
00090 GeoIpResolver::resolve(QHostAddress ip)
00091 {
00092   return resolve(QList<QHostAddress>() << ip);
00093 }
00094 
00095 /** Called when the socket has connected to the Geo IP host. */
00096 void
00097 GeoIpResolver::connected()
00098 {
00099   /* Find the socket and request for whoever called this slot */ 
00100   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00101   if (!_requestList.contains(socket)) {
00102     return;
00103   }
00104   GeoIpRequest *req = (GeoIpRequest *)_requestList.value(socket);
00105   vInfo("Connected to the GeoIP host. Sending request for %1 uncached "
00106         "GeoIP entries. (request id %2)").arg(req->size()).arg(req->id());
00107 
00108   /* Send the request */
00109   socket->write(req->request());
00110 }
00111 
00112 /** Called when the socket has disconnected from the Geo IP host. */
00113 void
00114 GeoIpResolver::disconnected()
00115 {
00116   /* Find the socket and request for whoever called this slot */ 
00117   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00118   if (!_requestList.contains(socket)) {
00119     return;
00120   }
00121   GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
00122 
00123   /* Read and parse the response */
00124   GeoIpResponse response = GeoIpResponse(socket->readAll());
00125 
00126   /* Check the response code and see what we got */
00127   if (response.statusCode() == 200) {
00128     /* We got a 200 OK, so get the Geo IP information and cache the results */
00129     int numCached = 0, i = 0;
00130     QList<GeoIp> geoips = response.geoIps();
00131     foreach (GeoIp geoip, geoips) {
00132       QHostAddress ip = geoip.ip();
00133       
00134       if (request->contains(ip)) {
00135         /* This is a requested geoip item, so if it wasn't cached, then 
00136          * cache it now. */
00137         if (!_cache.contains(ip)) {
00138           _cache.cache(geoip);
00139           numCached++;
00140         }
00141         i++;
00142       } else {
00143         /* This item wasn't requested, so remove it. According to the Qt docs,
00144          * this is safe to do inside the foreach() loop because, "Qt
00145          * automatically takes a copy of the container when it enters a
00146          * foreach loop. If you modify the container as you are iterating,
00147          * that won't affect the loop." */
00148         vWarn("Received a GeoIP entry for IP address %1 that was not included "
00149               "in the initial request. (request id %2)").arg(ip)
00150                                                         .arg(request->id());
00151         geoips.removeAt(i);
00152       }
00153     }
00154     /* If new results were cached, save them to disk */
00155     if (numCached > 0) {
00156       _cache.saveToDisk();
00157     }
00158     /* Emit the results */
00159     vInfo("Parsed %1 entries from the GeoIP response. (request id %2)")
00160                                  .arg(geoips.size()).arg(request->id());
00161     emit resolved(request->id(), geoips);
00162   } else {
00163     /* We failed to get the Geo IP information, so emit resolveFailed and
00164      * include the HTTP status message. */
00165     vWarn("GeoIP resolution failed (request id %1): %2").arg(request->id())
00166                                              .arg(response.statusMessage());
00167     emit resolveFailed(request->id(), response.statusMessage());
00168   }
00169   /* Close the socket and clean up */
00170   socket->close();
00171   delete socket;
00172   delete request;
00173 }
00174 
00175 /** Called when an error has occurred requesting Geo IP information. */
00176 void
00177 GeoIpResolver::socketError(QString errorString)
00178 {
00179   /* Find the socket and request for whoever called this slot */ 
00180   QAbstractSocket *socket = dynamic_cast<QAbstractSocket *>(sender());
00181   if (!_requestList.contains(socket)) {
00182     return;
00183   }
00184   
00185   /* We expect a remote host to close the socket, because that's how the HTTP
00186    * server tells us he's done talkig to us. */
00187   if (socket->error() != QAbstractSocket::RemoteHostClosedError) {
00188     /* Emit the failure and clean up */
00189     GeoIpRequest *request = (GeoIpRequest *)_requestList.take(socket);
00190     emit resolveFailed(request->id(), errorString);
00191     socket->abort();
00192     vWarn("GeoIP request socket error (request id %1): %2").arg(request->id())
00193                                                            .arg(errorString);
00194     delete socket;
00195     delete request;
00196   }
00197 }
00198 
00199 /** Creates an HTTP request for Geo IP information. */
00200 GeoIpRequest*
00201 GeoIpResolver::createRequest(QList<QHostAddress> ips)
00202 {
00203   static int id = -1;
00204   GeoIpRequest *request = new GeoIpRequest(++id);
00205   request->setHost(GEOIP_HOST);
00206   request->setPage(GEOIP_PAGE);
00207   request->setRequest(ips);
00208   return request;
00209 }
00210 
00211 /** Resolves a list of IPs to a geographic location. */
00212 int
00213 GeoIpResolver::resolve(QList<QHostAddress> ips)
00214 {
00215   /* Resolve the cached IPs and get a list of IPs that still need to be
00216    * resolved to a lat and long. */
00217   QList<QHostAddress> uncached = resolveFromCache(ips);
00218   if (!uncached.size()) {
00219     return -1;
00220   }
00221 
00222   /* Create a socket used to request the geo ip information. */
00223   TorSslSocket *socket = new TorSslSocket(_socksAddr, _socksPort);
00224 
00225   connect(socket, SIGNAL(connectedToRemoteHost()), this, SLOT(connected()),
00226           Qt::QueuedConnection);
00227   connect(socket, SIGNAL(socketError(QString)), 
00228           this,   SLOT(socketError(QString)),
00229           Qt::QueuedConnection);
00230   connect(socket, SIGNAL(disconnected()), this, SLOT(disconnected()),
00231           Qt::QueuedConnection);
00232   GeoIpRequest *request = createRequest(ips);
00233   _requestList.insert(socket, request);
00234   
00235   /* Connect so we can send our request and return the request ID. */
00236   vInfo("Opening an SSL connection to the GeoIP host at %1:%2 (request id %3)")
00237                         .arg(GEOIP_HOST).arg(GEOIP_SSL_PORT).arg(request->id());
00238   socket->connectToRemoteHost(GEOIP_HOST, GEOIP_SSL_PORT, true);
00239 
00240   return request->id();
00241 }
00242 

Generated on Wed Dec 23 21:11:08 2009 for Vidalia by  doxygen 1.6.1