Vidalia 0.2.10
|
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