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

Generated on Wed Nov 26 21:02:42 2008 for Vidalia by  doxygen 1.5.7.1