Vidalia 0.2.12
|
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 ** \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