Vidalia 0.2.10

NetViewer.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 NetViewer.cpp
00013 ** \version $Id: NetViewer.cpp 4378 2010-08-05 20:28:54Z edmanm $
00014 ** \brief Displays a map of the Tor network and the user's circuits
00015 */
00016 
00017 #include "NetViewer.h"
00018 #include "RouterInfoDialog.h"
00019 #include "RouterListItem.h"
00020 #include "Vidalia.h"
00021 #include "VMessageBox.h"
00022 
00023 #include <QMessageBox>
00024 #include <QHeaderView>
00025 #include <QCoreApplication>
00026 
00027 #define IMG_MOVE    ":/images/22x22/move-map.png"
00028 #define IMG_ZOOMIN  ":/images/22x22/zoom-in.png"
00029 #define IMG_ZOOMOUT ":/images/22x22/zoom-out.png"
00030 
00031 #if 0
00032 /** Number of milliseconds to wait after the arrival of the last descriptor whose
00033  * IP needs to be resolved to geographic information, in case more descriptors
00034  * arrive. Then we can simply lump the IPs into a single request. */
00035 #define MIN_RESOLVE_QUEUE_DELAY   (10*1000)
00036 /** Maximum number of milliseconds to wait after the arrival of the first
00037  * IP address into the resolve queue, before we flush the entire queue. */
00038 #define MAX_RESOLVE_QUEUE_DELAY   (30*1000)
00039 #endif
00040 
00041 /** Constructor. Loads settings from VidaliaSettings.
00042  * \param parent The parent widget of this NetViewer object.\
00043  */
00044 NetViewer::NetViewer(QWidget *parent)
00045   : VidaliaWindow("NetViewer", parent)
00046 {
00047   /* Invoke Qt Designer generated QObject setup routine */
00048   ui.setupUi(this);
00049 
00050 #if defined(Q_WS_MAC)
00051   ui.actionHelp->setShortcut(QString("Ctrl+?"));
00052 #endif
00053 
00054   /* Pressing 'Esc' or 'Ctrl+W' will close the window */
00055   ui.actionClose->setShortcut(QString("Esc"));
00056   Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00057 
00058   /* Get the TorControl object */
00059   _torControl = Vidalia::torControl();
00060   connect(_torControl, SIGNAL(authenticated()),
00061           this, SLOT(onAuthenticated()));
00062   connect(_torControl, SIGNAL(disconnected()),
00063           this, SLOT(onDisconnected()));
00064 
00065   _torControl->setEvent(TorEvents::CircuitStatus);
00066   connect(_torControl, SIGNAL(circuitStatusChanged(Circuit)),
00067           this, SLOT(addCircuit(Circuit)));
00068 
00069   _torControl->setEvent(TorEvents::StreamStatus);
00070   connect(_torControl, SIGNAL(streamStatusChanged(Stream)),
00071           this, SLOT(addStream(Stream)));
00072 
00073   _torControl->setEvent(TorEvents::AddressMap);
00074   connect(_torControl, SIGNAL(addressMapped(QString, QString, QDateTime)),
00075           this, SLOT(addressMapped(QString, QString, QDateTime)));
00076 
00077   _torControl->setEvent(TorEvents::NewDescriptor);
00078   connect(_torControl, SIGNAL(newDescriptors(QStringList)),
00079           this, SLOT(newDescriptors(QStringList)));
00080 
00081   /* Change the column widths of the tree widgets */
00082   ui.treeRouterList->header()->
00083     resizeSection(RouterListWidget::StatusColumn, 25);
00084   ui.treeRouterList->header()->
00085     resizeSection(RouterListWidget::CountryColumn, 25);
00086   ui.treeCircuitList->header()->
00087     resizeSection(CircuitListWidget::ConnectionColumn, 235);
00088 
00089   /* Create the TorMapWidget and add it to the dialog */
00090 #if defined(USE_MARBLE)
00091   _map = new TorMapWidget();
00092   connect(_map, SIGNAL(displayRouterInfo(QString)),
00093           this, SLOT(displayRouterInfo(QString)));
00094   connect(ui.actionZoomFullScreen, SIGNAL(triggered()),
00095           this, SLOT(toggleFullScreen()));
00096   Vidalia::createShortcut("ESC", _map, this, SLOT(toggleFullScreen()));
00097 #else
00098   _map = new TorMapImageView();
00099   ui.actionZoomFullScreen->setVisible(false);
00100 #endif
00101   ui.gridLayout->addWidget(_map);
00102 
00103 
00104   /* Connect zoom buttons to TorMapWidget zoom slots */
00105   connect(ui.actionZoomIn, SIGNAL(triggered()), this, SLOT(zoomIn()));
00106   connect(ui.actionZoomOut, SIGNAL(triggered()), this, SLOT(zoomOut()));
00107   connect(ui.actionZoomToFit, SIGNAL(triggered()), _map, SLOT(zoomToFit()));
00108 
00109   /* Create the timer that will be used to update the router list once every
00110    * hour. We still receive the NEWDESC event to get new descriptors, but this
00111    * needs to be called to get rid of any descriptors that were removed. */
00112   _refreshTimer.setInterval(60*60*1000);
00113   connect(&_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
00114  
00115   /* Connect the necessary slots and signals */
00116   connect(ui.actionHelp, SIGNAL(triggered()), this, SLOT(help()));
00117   connect(ui.actionRefresh, SIGNAL(triggered()), this, SLOT(refresh()));
00118   connect(ui.treeRouterList, SIGNAL(routerSelected(QList<RouterDescriptor>)),
00119                 this, SLOT(routerSelected(QList<RouterDescriptor>)));
00120   connect(ui.treeRouterList, SIGNAL(zoomToRouter(QString)),
00121           _map, SLOT(zoomToRouter(QString)));
00122   connect(ui.treeCircuitList, SIGNAL(circuitSelected(Circuit)),
00123           this, SLOT(circuitSelected(Circuit)));
00124   connect(ui.treeCircuitList, SIGNAL(circuitRemoved(CircuitId)),
00125           _map, SLOT(removeCircuit(CircuitId)));
00126   connect(ui.treeCircuitList, SIGNAL(zoomToCircuit(CircuitId)),
00127           _map, SLOT(zoomToCircuit(CircuitId)));
00128   connect(ui.treeCircuitList, SIGNAL(closeCircuit(CircuitId)),
00129           _torControl, SLOT(closeCircuit(CircuitId)));
00130   connect(ui.treeCircuitList, SIGNAL(closeStream(StreamId)),
00131           _torControl, SLOT(closeStream(StreamId)));
00132 
00133   setupGeoIpResolver();
00134 }
00135 
00136 /** Called when the user changes the UI translation. */
00137 void
00138 NetViewer::retranslateUi()
00139 {
00140   ui.retranslateUi(this);
00141   ui.treeRouterList->retranslateUi();
00142   ui.treeCircuitList->retranslateUi();
00143 
00144   if (ui.treeRouterList->selectedItems().size()) {
00145     QList<RouterDescriptor> routers;
00146     foreach (QTreeWidgetItem *item, ui.treeRouterList->selectedItems()) {
00147       routers << dynamic_cast<RouterListItem *>(item)->descriptor();
00148     }
00149     ui.textRouterInfo->display(routers);
00150   } else if (ui.treeCircuitList->selectedItems().size()) {
00151     QList<RouterDescriptor> routers;
00152     QTreeWidgetItem *item = ui.treeCircuitList->selectedItems()[0];
00153     Circuit circuit = dynamic_cast<CircuitItem*>(item)->circuit();
00154     foreach (QString id, circuit.routerIDs()) {
00155       RouterListItem *item = ui.treeRouterList->findRouterById(id);
00156       if (item)
00157         routers.append(item->descriptor());
00158     }
00159     ui.textRouterInfo->display(routers);
00160   }
00161 }
00162 
00163 void
00164 NetViewer::setupGeoIpResolver()
00165 {
00166   VidaliaSettings settings;
00167 
00168 #if defined(USE_GEOIP)
00169   if (settings.useLocalGeoIpDatabase()) {
00170     QString databaseFile = settings.localGeoIpDatabase();
00171     if (! databaseFile.isEmpty()) {
00172       _geoip.setLocalDatabase(databaseFile);
00173       _geoip.setUseLocalDatabase(true);
00174       vInfo("Using local database file for relay mapping: %1")
00175                                             .arg(databaseFile);
00176       return;
00177     }
00178   }
00179 #endif
00180   vInfo("Using Tor's GeoIP database for country-level relay mapping.");
00181   _geoip.setUseLocalDatabase(false);
00182 }
00183 
00184 /** Loads data into map, lists and starts timer when we get connected*/
00185 void
00186 NetViewer::onAuthenticated()
00187 {
00188   refresh();
00189   _refreshTimer.start();
00190   ui.actionRefresh->setEnabled(true);
00191 }
00192 
00193 /** Clears map, lists and stops timer when we get disconnected */
00194 void
00195 NetViewer::onDisconnected()
00196 {
00197   clear();
00198   _refreshTimer.stop();
00199   ui.actionRefresh->setEnabled(false);
00200 }
00201 
00202 /** Reloads the lists of routers, circuits that Tor knows about */
00203 void
00204 NetViewer::refresh()
00205 {
00206   /* Don't let the user refresh while we're refreshing. */
00207   ui.actionRefresh->setEnabled(false);
00208 
00209   /* Clear the data */
00210   clear();
00211 
00212   /* Load router information */
00213   loadNetworkStatus();
00214   /* Load existing address mappings */
00215   loadAddressMap();
00216   /* Load Circuits and Streams information */
00217   loadConnections();
00218 
00219   /* Ok, they can refresh again. */
00220   ui.actionRefresh->setEnabled(true);
00221 } 
00222 
00223 /** Clears the lists and the map */
00224 void
00225 NetViewer::clear()
00226 {
00227   /* Clear the network map */
00228   _map->clear();
00229   _map->update();
00230   /* Clear the address map */
00231   _addressMap.clear();
00232   /* Clear the lists of routers, circuits, and streams */
00233   ui.treeRouterList->clearRouters();
00234   ui.treeCircuitList->clearCircuits();
00235   ui.textRouterInfo->clear();
00236 }
00237 
00238 /** Loads a list of all current address mappings. */
00239 void
00240 NetViewer::loadAddressMap()
00241 {
00242   /* We store the reverse address mappings, so we can go from a numeric value
00243    * back to a likely more meaningful hostname to display for the user. */
00244   _addressMap = _torControl->getAddressMap().reverse();
00245 }
00246 
00247 /** Loads a list of all current circuits and streams. */
00248 void
00249 NetViewer::loadConnections()
00250 {
00251   /* Load all circuits */
00252   CircuitList circuits = _torControl->getCircuits();
00253   foreach (Circuit circuit, circuits) {
00254     addCircuit(circuit);
00255   }
00256   /* Now load all streams */
00257   StreamList streams = _torControl->getStreams();
00258   foreach (Stream stream, streams) {
00259     addStream(stream);
00260   }
00261 
00262   /* Update the map */
00263   _map->update();
00264 }
00265 
00266 /** Adds <b>circuit</b> to the map and the list */
00267 void
00268 NetViewer::addCircuit(const Circuit &circuit)
00269 {
00270   /* Add the circuit to the list of all current circuits */
00271   ui.treeCircuitList->addCircuit(circuit);
00272   /* Plot the circuit on the map */
00273   _map->addCircuit(circuit.id(), circuit.routerIDs());
00274 }
00275 
00276 /** Adds <b>stream</b> to its associated circuit on the list of all circuits. */
00277 void
00278 NetViewer::addStream(const Stream &stream)
00279 {
00280   /* If the new stream's target has an IP address instead of a host name,
00281    * check our cache for an existing reverse address mapping. */
00282   if (stream.status() == Stream::New) {
00283     QString target = stream.targetAddress();
00284     if (! QHostAddress(target).isNull() && _addressMap.isMapped(target)) {
00285       /* Replace the IP address in the stream event with the original 
00286        * hostname */
00287       ui.treeCircuitList->addStream(
00288         Stream(stream.id(), stream.status(), stream.circuitId(),
00289                _addressMap.mappedTo(target), stream.targetPort()));
00290     }
00291   } else {
00292     ui.treeCircuitList->addStream(stream);
00293   }
00294 }
00295 
00296 void
00297 NetViewer::addressMapped(const QString &from, const QString &to,
00298                          const QDateTime &expires)
00299 {
00300   _addressMap.add(to, from, expires);
00301 }
00302 
00303 /** Called when the user selects the "Help" action from the toolbar. */
00304 void
00305 NetViewer::help()
00306 {
00307   emit helpRequested("netview");
00308 }
00309 
00310 /** Retrieves a list of all running routers from Tor and their descriptors,
00311  * and adds them to the RouterListWidget. */
00312 void
00313 NetViewer::loadNetworkStatus()
00314 {
00315   NetworkStatus networkStatus = _torControl->getNetworkStatus();
00316   foreach (RouterStatus rs, networkStatus) {
00317     if (!rs.isRunning())
00318       continue;
00319 
00320     RouterDescriptor rd = _torControl->getRouterDescriptor(rs.id());
00321     if (!rd.isEmpty())
00322       addRouter(rd);
00323 
00324     QCoreApplication::processEvents();
00325   }
00326 }
00327 
00328 /** Adds a router to our list of servers and retrieves geographic location
00329  * information for the server. */
00330 void
00331 NetViewer::addRouter(const RouterDescriptor &rd)
00332 {
00333   /* Add the descriptor to the list of server */
00334   RouterListItem *item = ui.treeRouterList->addRouter(rd);
00335   if (! item)
00336     return;
00337 
00338   /* Attempt to map this relay to an approximate geographic location. The
00339    * accuracy of the result depends on the database information currently
00340    * available to the GeoIP resolver. */
00341   if (! item->location().isValid() || rd.ip() != item->location().ip()) {
00342     GeoIpRecord location = _geoip.resolve(rd.ip());
00343     if (location.isValid()) {
00344       item->setLocation(location);
00345       _map->addRouter(rd, location);
00346     }
00347   }
00348 }
00349 
00350 /** Called when a NEWDESC event arrives. Retrieves new router descriptors
00351  * for the router identities given in <b>ids</b> and updates the router
00352  * list and network map. */
00353 void
00354 NetViewer::newDescriptors(const QStringList &ids)
00355 {
00356   foreach (QString id, ids) {
00357     RouterDescriptor rd = _torControl->getRouterDescriptor(id);
00358     if (!rd.isEmpty())
00359       addRouter(rd); /* Updates the existing entry */
00360   }
00361 }
00362 
00363 /** Called when the user selects a circuit from the circuit and streams
00364  * list. */
00365 void
00366 NetViewer::circuitSelected(const Circuit &circuit)
00367 {
00368   /* Clear any selected items. */
00369   ui.treeRouterList->deselectAll();
00370   ui.textRouterInfo->clear();
00371   _map->deselectAll();
00372 
00373   /* Select the items on the map and in the list */
00374   _map->selectCircuit(circuit.id());
00375 
00376   QList<RouterDescriptor> routers;
00377 
00378   foreach (QString id, circuit.routerIDs()) {
00379     /* Try to find and select each router in the path */
00380     RouterListItem *item = ui.treeRouterList->findRouterById(id);
00381     if (item)
00382       routers.append(item->descriptor());
00383   }
00384 
00385   ui.textRouterInfo->display(routers);
00386 }
00387 
00388 /** Called when the user selects one or more routers from the router list. */
00389 void
00390 NetViewer::routerSelected(const QList<RouterDescriptor> &routers)
00391 {
00392   _map->deselectAll();
00393   ui.textRouterInfo->clear();
00394   ui.textRouterInfo->display(routers);
00395 
00396   /* XXX: Ideally we would also be able to select multiple pinpoints on the
00397    *      map. But our current map sucks and you can't even tell when one is
00398    *      selected anyway. Worry about this when we actually get to Marble.
00399    */
00400   if (routers.size() == 1)
00401     _map->selectRouter(routers[0].id());
00402 }
00403 
00404 /** Called when the user selects a router on the network map. Displays a 
00405  * dialog with detailed information for the router specified by
00406  * <b>id</b>.*/
00407 void
00408 NetViewer::displayRouterInfo(const QString &id)
00409 {
00410   RouterInfoDialog dlg(_map->isFullScreen() ? static_cast<QWidget*>(_map) 
00411                                             : static_cast<QWidget*>(this));
00412 
00413   /* Fetch the specified router's descriptor */
00414   QStringList rd = _torControl->getRouterDescriptorText(id);
00415   if (rd.isEmpty()) {
00416     VMessageBox::warning(this, tr("Relay Not Found"),
00417                          tr("No details on the selected relay are available."),
00418                          VMessageBox::Ok);
00419     return;
00420   }
00421 
00422   /* Fetch the router's network status information */
00423   RouterStatus rs = _torControl->getRouterStatus(id);
00424 
00425   dlg.setRouterInfo(rd, rs);
00426 
00427   /* Populate the UI with information learned from a previous GeoIP request */
00428   RouterListItem *item = ui.treeRouterList->findRouterById(id);
00429   if (item)
00430     dlg.setLocation(item->location().toString());
00431   else
00432     dlg.setLocation(tr("Unknown"));
00433 
00434   dlg.exec();
00435 }
00436 
00437 /* XXX: The following zoomIn() and zoomOut() slots are a hack. MarbleWidget
00438  *      does have zoomIn() and zoomOut() slots to which we could connect the
00439  *      buttons, but these slots currently don't force a repaint. So to see
00440  *      the zoom effect, the user has to click on the map after clicking one
00441  *      of the zoom buttons. Instead, we use the zoomViewBy() method, which
00442  *      DOES force a repaint.
00443  */
00444 /** Called when the user clicks the "Zoom In" button. */
00445 void
00446 NetViewer::zoomIn()
00447 {
00448 #if defined(USE_MARBLE)
00449   _map->zoomViewBy(40);
00450 #else
00451   _map->zoomIn();
00452 #endif
00453 }
00454 
00455 /** Called when the user clicks the "Zoom Out" button. */
00456 void
00457 NetViewer::zoomOut()
00458 {
00459 #if defined(USE_MARBLE)
00460   _map->zoomViewBy(-40);
00461 #else
00462   _map->zoomOut();
00463 #endif
00464 }
00465 
00466 /** Called when the user clicks "Full Screen" or presses Escape on the map.
00467  * Toggles the map between normal and a full screen viewing modes. */
00468 void
00469 NetViewer::toggleFullScreen()
00470 {
00471   if (_map->isFullScreen()) {
00472     /* Disabling full screen mode. Put the map back in its container. */
00473     ui.gridLayout->addWidget(_map);
00474     _map->setWindowState(_map->windowState() & ~Qt::WindowFullScreen);
00475   } else {
00476     /* Enabling full screen mode. Remove the map from the QGridLayout
00477      * container and set its window state to full screen. */
00478     _map->setParent(0);
00479     _map->setWindowState(_map->windowState() | Qt::WindowFullScreen);
00480     _map->show();
00481   }
00482 }
00483