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