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 4054 2009-08-17 02:25:08Z 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 
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 /** Number of milliseconds to wait after the arrival of the last descriptor whose
00031  * IP needs to be resolved to geographic information, in case more descriptors
00032  * arrive. Then we can simply lump the IPs into a single request. */
00033 #define MIN_RESOLVE_QUEUE_DELAY   (10*1000)
00034 /** Maximum number of milliseconds to wait after the arrival of the first
00035  * IP address into the resolve queue, before we flush the entire queue. */
00036 #define MAX_RESOLVE_QUEUE_DELAY   (30*1000)
00037 
00038 
00039 /** Constructor. Loads settings from VidaliaSettings.
00040  * \param parent The parent widget of this NetViewer object.\
00041  */
00042 NetViewer::NetViewer(QWidget *parent)
00043   : VidaliaWindow("NetViewer", parent)
00044 {
00045   /* Invoke Qt Designer generated QObject setup routine */
00046   ui.setupUi(this);
00047 
00048 #if defined(Q_WS_MAC)
00049   ui.actionHelp->setShortcut(QString("Ctrl+?"));
00050 #endif
00051 
00052   /* Pressing 'Esc' or 'Ctrl+W' will close the window */
00053   ui.actionClose->setShortcut(QString("Esc"));
00054   Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
00055 
00056   /* Get the TorControl object */
00057   _torControl = Vidalia::torControl();
00058   connect(_torControl, SIGNAL(authenticated()),
00059           this, SLOT(onAuthenticated()));
00060   connect(_torControl, SIGNAL(disconnected()),
00061           this, SLOT(onDisconnected()));
00062 
00063   _torControl->setEvent(TorEvents::CircuitStatus);
00064   connect(_torControl, SIGNAL(circuitStatusChanged(Circuit)),
00065           this, SLOT(addCircuit(Circuit)));
00066 
00067   _torControl->setEvent(TorEvents::StreamStatus);
00068   connect(_torControl, SIGNAL(streamStatusChanged(Stream)),
00069           this, SLOT(addStream(Stream)));
00070 
00071   _torControl->setEvent(TorEvents::AddressMap);
00072   connect(_torControl, SIGNAL(addressMapped(QString, QString, QDateTime)),
00073           this, SLOT(addressMapped(QString, QString, QDateTime)));
00074 
00075   _torControl->setEvent(TorEvents::NewDescriptor);
00076   connect(_torControl, SIGNAL(newDescriptors(QStringList)),
00077           this, SLOT(newDescriptors(QStringList)));
00078 
00079   /* Change the column widths of the tree widgets */
00080   ui.treeRouterList->header()->
00081     resizeSection(RouterListWidget::StatusColumn, 25);
00082   ui.treeRouterList->header()->
00083     resizeSection(RouterListWidget::CountryColumn, 25);
00084   ui.treeCircuitList->header()->
00085     resizeSection(CircuitListWidget::ConnectionColumn, 235);
00086 
00087   /* Create the TorMapWidget and add it to the dialog */
00088 #if defined(USE_MARBLE)
00089   _map = new TorMapWidget();
00090   connect(_map, SIGNAL(displayRouterInfo(QString)),
00091           this, SLOT(displayRouterInfo(QString)));
00092   connect(ui.actionZoomFullScreen, SIGNAL(triggered()),
00093           this, SLOT(toggleFullScreen()));
00094   Vidalia::createShortcut("ESC", _map, this, SLOT(toggleFullScreen()));
00095 #else
00096   _map = new TorMapImageView();
00097   ui.actionZoomFullScreen->setVisible(false);
00098 #endif
00099   ui.gridLayout->addWidget(_map);
00100 
00101 
00102   /* Connect zoom buttons to TorMapWidget zoom slots */
00103   connect(ui.actionZoomIn, SIGNAL(triggered()), this, SLOT(zoomIn()));
00104   connect(ui.actionZoomOut, SIGNAL(triggered()), this, SLOT(zoomOut()));
00105   connect(ui.actionZoomToFit, SIGNAL(triggered()), _map, SLOT(zoomToFit()));
00106 
00107   /* Create the timer that will be used to update the router list once every
00108    * hour. We still receive the NEWDESC event to get new descriptors, but this
00109    * needs to be called to get rid of any descriptors that were removed. */
00110   _refreshTimer.setInterval(60*60*1000);
00111   connect(&_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
00112   
00113   /* Set up the timers used to group together GeoIP requests for new
00114    * descriptors arriving within MIN_RESOLVE_QUEUE_DELAY milliseconds, but no
00115    * more than MAX_RESOLVE_QUEUE_DELAY milliseconds of each other. */
00116   _minResolveQueueTimer.setSingleShot(true);
00117   connect(&_minResolveQueueTimer, SIGNAL(timeout()), this, SLOT(resolve()));
00118   _maxResolveQueueTimer.setSingleShot(true);
00119   connect(&_maxResolveQueueTimer, SIGNAL(timeout()), this, SLOT(resolve()));
00120 
00121   /* Connect the necessary slots and signals */
00122   connect(ui.actionHelp, SIGNAL(triggered()), this, SLOT(help()));
00123   connect(ui.actionRefresh, SIGNAL(triggered()), this, SLOT(refresh()));
00124   connect(ui.treeRouterList, SIGNAL(routerSelected(QList<RouterDescriptor>)),
00125                 this, SLOT(routerSelected(QList<RouterDescriptor>)));
00126   connect(ui.treeRouterList, SIGNAL(zoomToRouter(QString)),
00127           _map, SLOT(zoomToRouter(QString)));
00128   connect(ui.treeCircuitList, SIGNAL(circuitSelected(Circuit)),
00129           this, SLOT(circuitSelected(Circuit)));
00130   connect(ui.treeCircuitList, SIGNAL(circuitRemoved(CircuitId)),
00131           _map, SLOT(removeCircuit(CircuitId)));
00132   connect(ui.treeCircuitList, SIGNAL(zoomToCircuit(CircuitId)),
00133           _map, SLOT(zoomToCircuit(CircuitId)));
00134   connect(ui.treeCircuitList, SIGNAL(closeCircuit(CircuitId)),
00135           _torControl, SLOT(closeCircuit(CircuitId)));
00136   connect(ui.treeCircuitList, SIGNAL(closeStream(StreamId)),
00137           _torControl, SLOT(closeStream(StreamId)));
00138 
00139   /* Connect the slot to find out when geoip information has arrived */
00140   connect(&_geoip, SIGNAL(resolved(int, QList<GeoIp>)), 
00141              this,   SLOT(resolved(int, QList<GeoIp>)));
00142 }
00143 
00144 /** Called when the user changes the UI translation. */
00145 void
00146 NetViewer::retranslateUi()
00147 {
00148   ui.retranslateUi(this);
00149   ui.treeRouterList->retranslateUi();
00150   ui.treeCircuitList->retranslateUi();
00151 
00152   if (ui.treeRouterList->selectedItems().size()) {
00153     QList<RouterDescriptor> routers;
00154     foreach (QTreeWidgetItem *item, ui.treeRouterList->selectedItems()) {
00155       routers << dynamic_cast<RouterListItem *>(item)->descriptor();
00156     }
00157     ui.textRouterInfo->display(routers);
00158   } else if (ui.treeCircuitList->selectedItems().size()) {
00159     QList<RouterDescriptor> routers;
00160     QTreeWidgetItem *item = ui.treeCircuitList->selectedItems()[0];
00161     Circuit circuit = dynamic_cast<CircuitItem*>(item)->circuit();
00162     foreach (QString id, circuit.routerIDs()) {
00163       RouterListItem *item = ui.treeRouterList->findRouterById(id);
00164       if (item)
00165         routers.append(item->descriptor());
00166     }
00167     ui.textRouterInfo->display(routers);
00168   }
00169 }
00170 
00171 /** Display the network map window. If there are geoip requests waiting in the
00172  * queue, start the queue timers now. */
00173 void
00174 NetViewer::showWindow()
00175 {
00176   if (!_resolveQueue.isEmpty()) {
00177     _minResolveQueueTimer.start(MIN_RESOLVE_QUEUE_DELAY);
00178     _maxResolveQueueTimer.start(MAX_RESOLVE_QUEUE_DELAY);
00179   }
00180   VidaliaWindow::showWindow();
00181 }
00182 
00183 /** Loads data into map, lists and starts timer when we get connected*/
00184 void
00185 NetViewer::onAuthenticated()
00186 {
00187   _geoip.setSocksHost(_torControl->getSocksAddress(),
00188                       _torControl->getSocksPort());
00189   refresh();
00190   _refreshTimer.start();
00191   ui.actionRefresh->setEnabled(true);
00192 }
00193 
00194 /** Clears map, lists and stops timer when we get disconnected */
00195 void
00196 NetViewer::onDisconnected()
00197 {
00198   clear();
00199   _refreshTimer.stop();
00200   ui.actionRefresh->setEnabled(false);
00201 }
00202 
00203 /** Reloads the lists of routers, circuits that Tor knows about */
00204 void
00205 NetViewer::refresh()
00206 {
00207   /* Don't let the user refresh while we're refreshing. */
00208   ui.actionRefresh->setEnabled(false);
00209 
00210   /* Clear the data */
00211   clear();
00212 
00213   /* Load router information */
00214   loadNetworkStatus();
00215   /* Load existing address mappings */
00216   loadAddressMap();
00217   /* Load Circuits and Streams information */
00218   loadConnections();
00219 
00220   /* Ok, they can refresh again. */
00221   ui.actionRefresh->setEnabled(true);
00222 } 
00223 
00224 /** Clears the lists and the map */
00225 void
00226 NetViewer::clear()
00227 {
00228   /* Clear the resolve queue and map */
00229   _resolveMap.clear();
00230   _resolveQueue.clear();
00231   /* Clear the network map */
00232   _map->clear();
00233   _map->update();
00234   /* Clear the address map */
00235   _addressMap.clear();
00236   /* Clear the lists of routers, circuits, and streams */
00237   ui.treeRouterList->clearRouters();
00238   ui.treeCircuitList->clearCircuits();
00239   ui.textRouterInfo->clear();
00240 }
00241 
00242 /** Loads a list of all current address mappings. */
00243 void
00244 NetViewer::loadAddressMap()
00245 {
00246   /* We store the reverse address mappings, so we can go from a numeric value
00247    * back to a likely more meaningful hostname to display for the user. */
00248   _addressMap = _torControl->getAddressMap().reverse();
00249 }
00250 
00251 /** Loads a list of all current circuits and streams. */
00252 void
00253 NetViewer::loadConnections()
00254 {
00255   /* Load all circuits */
00256   CircuitList circuits = _torControl->getCircuits();
00257   foreach (Circuit circuit, circuits) {
00258     addCircuit(circuit);
00259   }
00260   /* Now load all streams */
00261   StreamList streams = _torControl->getStreams();
00262   foreach (Stream stream, streams) {
00263     addStream(stream);
00264   }
00265 
00266   /* Update the map */
00267   _map->update();
00268 }
00269 
00270 /** Adds <b>circuit</b> to the map and the list */
00271 void
00272 NetViewer::addCircuit(const Circuit &circuit)
00273 {
00274   /* Add the circuit to the list of all current circuits */
00275   ui.treeCircuitList->addCircuit(circuit);
00276   /* Plot the circuit on the map */
00277   _map->addCircuit(circuit.id(), circuit.routerIDs());
00278 }
00279 
00280 /** Adds <b>stream</b> to its associated circuit on the list of all circuits. */
00281 void
00282 NetViewer::addStream(const Stream &stream)
00283 {
00284   QString target = stream.targetAddress();
00285   QHostAddress addr(target);
00286   
00287   /* If the stream's target has an IP address instead of a host name,
00288    * check our cache for an existing reverse address mapping. */
00289   if (!addr.isNull() && _addressMap.isMapped(target)) {
00290     /* Replace the IP address in the stream event with the original 
00291      * hostname */
00292     ui.treeCircuitList->addStream(
00293       Stream(stream.id(), stream.status(), stream.circuitId(),
00294              _addressMap.mappedTo(target), stream.targetPort()));
00295   } else {
00296     ui.treeCircuitList->addStream(stream);
00297   }
00298 }
00299 
00300 void
00301 NetViewer::addressMapped(const QString &from, const QString &to,
00302                          const QDateTime &expires)
00303 {
00304   _addressMap.add(to, from, expires);
00305 }
00306 
00307 /** Called when the user selects the "Help" action from the toolbar. */
00308 void
00309 NetViewer::help()
00310 {
00311   emit helpRequested("netview");
00312 }
00313 
00314 /** Retrieves a list of all running routers from Tor and their descriptors,
00315  * and adds them to the RouterListWidget. */
00316 void
00317 NetViewer::loadNetworkStatus()
00318 {
00319   NetworkStatus networkStatus = _torControl->getNetworkStatus();
00320   foreach (RouterStatus rs, networkStatus) {
00321     if (!rs.isRunning())
00322       continue;
00323 
00324     RouterDescriptor rd = _torControl->getRouterDescriptor(rs.id());
00325     if (!rd.isEmpty())
00326       addRouter(rd);
00327   }
00328 }
00329 
00330 /** Adds a router to our list of servers and retrieves geographic location
00331  * information for the server. */
00332 void
00333 NetViewer::addRouter(const RouterDescriptor &rd)
00334 {
00335   /* Add the descriptor to the list of server */
00336   ui.treeRouterList->addRouter(rd);
00337 
00338   /* Add this IP to a list of addresses whose geographic location we'd like to
00339    * find, but not for special purpose descriptors (e.g., bridges). This
00340    * check is only valid for Tor >= 0.2.0.13-alpha. */
00341   if (_torControl->getTorVersion() >= 0x020013) {
00342     DescriptorAnnotations annotations =
00343       _torControl->getDescriptorAnnotations(rd.id());
00344     if (!annotations.contains("purpose"))
00345       addToResolveQueue(rd.ip(), rd.id());
00346   } else {
00347     addToResolveQueue(rd.ip(), rd.id());
00348   }
00349 }
00350 
00351 /** Called when a NEWDESC event arrives. Retrieves new router descriptors
00352  * for the router identities given in <b>ids</b> and updates the router
00353  * list and network map. */
00354 void
00355 NetViewer::newDescriptors(const QStringList &ids)
00356 {
00357   foreach (QString id, ids) {
00358     RouterDescriptor rd = _torControl->getRouterDescriptor(id);
00359     if (!rd.isEmpty())
00360       addRouter(rd); /* Updates the existing entry */
00361   }
00362 }
00363 
00364 /** Adds an IP address to the resolve queue and updates the queue timers. */
00365 void
00366 NetViewer::addToResolveQueue(const QHostAddress &ip, const QString &id)
00367 {
00368   QString ipstr = ip.toString();
00369   if (!_resolveMap.values(ipstr).contains(id)) {
00370     /* Remember which server ids belong to which IP addresses */
00371     _resolveMap.insertMulti(ipstr, id);
00372   }
00373  
00374   if (!_resolveQueue.contains(ip) && !_geoip.resolveFromCache(ip)) {
00375     /* Add the IP to the queue of IPs waiting for geographic information  */
00376     _resolveQueue << ip;
00377  
00378     /* Wait MIN_RESOLVE_QUEUE_DELAY after the last item inserted into the
00379      * queue, before sending the resolve request. */
00380     _minResolveQueueTimer.start(MIN_RESOLVE_QUEUE_DELAY);
00381     
00382     /* Do not wait longer than MAX_RESOLVE_QUEUE_DELAY from the time the first
00383      * item is inserted into the queue, before flushing and resolving the
00384      * queue. */
00385     if (_resolveQueue.size() == 1) {
00386       _maxResolveQueueTimer.start(MAX_RESOLVE_QUEUE_DELAY);
00387     }
00388   }
00389 }
00390 
00391 /** Called when the user selects a circuit from the circuit and streams
00392  * list. */
00393 void
00394 NetViewer::circuitSelected(const Circuit &circuit)
00395 {
00396   /* Clear any selected items. */
00397   ui.treeRouterList->deselectAll();
00398   ui.textRouterInfo->clear();
00399   _map->deselectAll();
00400 
00401   /* Select the items on the map and in the list */
00402   _map->selectCircuit(circuit.id());
00403 
00404   QList<RouterDescriptor> routers;
00405 
00406   foreach (QString id, circuit.routerIDs()) {
00407     /* Try to find and select each router in the path */
00408     RouterListItem *item = ui.treeRouterList->findRouterById(id);
00409     if (item)
00410       routers.append(item->descriptor());
00411   }
00412 
00413   ui.textRouterInfo->display(routers);
00414 }
00415 
00416 /** Called when the user selects one or more routers from the router list. */
00417 void
00418 NetViewer::routerSelected(const QList<RouterDescriptor> &routers)
00419 {
00420   _map->deselectAll();
00421   ui.textRouterInfo->clear();
00422   ui.textRouterInfo->display(routers);
00423 
00424   /* XXX: Ideally we would also be able to select multiple pinpoints on the
00425    *      map. But our current map sucks and you can't even tell when one is
00426    *      selected anyway. Worry about this when we actually get to Marble.
00427    */
00428   if (routers.size() == 1)
00429     _map->selectRouter(routers[0].id());
00430 }
00431 
00432 /** If there are any IPs in the resolve queue, do the request now. */
00433 void
00434 NetViewer::resolve()
00435 {
00436   if (!_resolveQueue.isEmpty()) {
00437     /* Send the request now if either the network map is visible, or the
00438      * request is for more than a quarter of the servers in the list. */
00439     if (isVisible() || 
00440         (_resolveQueue.size() >= ui.treeRouterList->topLevelItemCount()/4)) {
00441       vInfo("Sending GeoIP request for %1 IP addresses.")
00442                                .arg(_resolveQueue.size());
00443       /* Flush the resolve queue and stop the timers */
00444       _geoip.resolve(_resolveQueue);
00445       _resolveQueue.clear();
00446     }
00447   }
00448   /* Stop the queue timers. Only one should be active since the other is what
00449    * called this slot, but calling stop() on a stopped timer does not hurt. */
00450   _minResolveQueueTimer.stop();
00451   _maxResolveQueueTimer.stop();
00452 }
00453 
00454 /** Called when a list of GeoIp information has been resolved. */
00455 void
00456 NetViewer::resolved(int id, const QList<GeoIp> &geoips)
00457 {
00458   Q_UNUSED(id);
00459 
00460   QString ip;
00461   RouterListItem *router;
00462  
00463   foreach (GeoIp geoip, geoips) {
00464     /* Find all routers that are at this IP address */
00465     ip = geoip.ip().toString();
00466     QList<QString> ids = _resolveMap.values(ip);
00467     _resolveMap.remove(ip);
00468       
00469     /* Update their geographic location information with the results of this
00470      * GeoIP query. */
00471     foreach (QString id, ids) {
00472       router = ui.treeRouterList->findRouterById(id);
00473       if (router) {
00474         /* Save the location information in the descriptor */
00475         router->setLocation(geoip);
00476         /* Plot the router on the map */
00477         _map->addRouter(router->descriptor(), geoip);
00478       }
00479     }
00480   }
00481 
00482   /* Update the circuit lines */
00483   foreach (Circuit circuit, ui.treeCircuitList->circuits()) {
00484     _map->addCircuit(circuit.id(), circuit.routerIDs());
00485   }
00486 
00487   /* Repaint the map */
00488   _map->update();
00489 }
00490 
00491 /** Called when the user selects a router on the network map. Displays a 
00492  * dialog with detailed information for the router specified by
00493  * <b>id</b>.*/
00494 void
00495 NetViewer::displayRouterInfo(const QString &id)
00496 {
00497   RouterInfoDialog dlg(_map->isFullScreen() ? static_cast<QWidget*>(_map) 
00498                                             : static_cast<QWidget*>(this));
00499 
00500   /* Fetch the specified router's descriptor */
00501   QStringList rd = _torControl->getRouterDescriptorText(id);
00502   if (rd.isEmpty()) {
00503     VMessageBox::warning(this, tr("Relay Not Found"),
00504                          tr("No details on the selected relay are available."),
00505                          VMessageBox::Ok);
00506     return;
00507   }
00508 
00509   /* Fetch the router's network status information */
00510   RouterStatus rs = _torControl->getRouterStatus(id);
00511 
00512   dlg.setRouterInfo(rd, rs);
00513 
00514   /* Populate the UI with information learned from a previous GeoIP request */
00515   RouterListItem *item = ui.treeRouterList->findRouterById(id);
00516   if (item)
00517     dlg.setLocation(item->location());
00518   else
00519     dlg.setLocation(tr("Unknown"));
00520 
00521   dlg.exec();
00522 }
00523 
00524 /* XXX: The following zoomIn() and zoomOut() slots are a hack. MarbleWidget
00525  *      does have zoomIn() and zoomOut() slots to which we could connect the
00526  *      buttons, but these slots currently don't force a repaint. So to see
00527  *      the zoom effect, the user has to click on the map after clicking one
00528  *      of the zoom buttons. Instead, we use the zoomViewBy() method, which
00529  *      DOES force a repaint.
00530  */
00531 /** Called when the user clicks the "Zoom In" button. */
00532 void
00533 NetViewer::zoomIn()
00534 {
00535 #if defined(USE_MARBLE)
00536   _map->zoomViewBy(40);
00537 #else
00538   _map->zoomIn();
00539 #endif
00540 }
00541 
00542 /** Called when the user clicks the "Zoom Out" button. */
00543 void
00544 NetViewer::zoomOut()
00545 {
00546 #if defined(USE_MARBLE)
00547   _map->zoomViewBy(-40);
00548 #else
00549   _map->zoomOut();
00550 #endif
00551 }
00552 
00553 /** Called when the user clicks "Full Screen" or presses Escape on the map.
00554  * Toggles the map between normal and a full screen viewing modes. */
00555 void
00556 NetViewer::toggleFullScreen()
00557 {
00558   if (_map->isFullScreen()) {
00559     /* Disabling full screen mode. Put the map back in its container. */
00560     ui.gridLayout->addWidget(_map);
00561     _map->setWindowState(_map->windowState() & ~Qt::WindowFullScreen);
00562   } else {
00563     /* Enabling full screen mode. Remove the map from the QGridLayout
00564      * container and set its window state to full screen. */
00565     _map->setParent(0);
00566     _map->setWindowState(_map->windowState() | Qt::WindowFullScreen);
00567     _map->show();
00568   }
00569 }
00570 

Generated on 31 Mar 2010 for Vidalia by  doxygen 1.6.1