Vidalia  0.3.1
NetViewer.cpp
Go to the documentation of this file.
1 /*
2 ** This file is part of Vidalia, and is subject to the license terms in the
3 ** LICENSE file, found in the top level directory of this distribution. If you
4 ** did not receive the LICENSE file with this file, you may obtain it from the
5 ** Vidalia source package distributed by the Vidalia Project at
6 ** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7 ** including this file, may be copied, modified, propagated, or distributed
8 ** except according to the terms described in the LICENSE file.
9 */
10 
11 /*
12 ** \file NetViewer.cpp
13 ** \brief Displays a map of the Tor network and the user's circuits
14 */
15 
16 #include "NetViewer.h"
17 #include "RouterInfoDialog.h"
18 #include "RouterListItem.h"
19 #include "Vidalia.h"
20 #include "VMessageBox.h"
21 
22 #include <QMessageBox>
23 #include <QToolBar>
24 #include <QHeaderView>
25 #include <QCoreApplication>
26 
27 #define IMG_MOVE ":/images/22x22/move-map.png"
28 #define IMG_ZOOMIN ":/images/22x22/zoom-in.png"
29 #define IMG_ZOOMOUT ":/images/22x22/zoom-out.png"
30 
31 #if 0
32 /** Number of milliseconds to wait after the arrival of the last descriptor whose
33  * IP needs to be resolved to geographic information, in case more descriptors
34  * arrive. Then we can simply lump the IPs into a single request. */
35 #define MIN_RESOLVE_QUEUE_DELAY (10*1000)
36 /** Maximum number of milliseconds to wait after the arrival of the first
37  * IP address into the resolve queue, before we flush the entire queue. */
38 #define MAX_RESOLVE_QUEUE_DELAY (30*1000)
39 #endif
40 
41 /** Constructor. Loads settings from VidaliaSettings.
42  * \param parent The parent widget of this NetViewer object.\
43  */
44 NetViewer::NetViewer(QWidget *parent)
45  : VidaliaTab(tr("Network Map"), "", parent)
46 {
47  /* Invoke Qt Designer generated QObject setup routine */
48  ui.setupUi(this);
49 
50 #if defined(Q_WS_MAC)
51  ui.actionHelp->setShortcut(QString("Ctrl+?"));
52 #endif
53 
54  /* Pressing 'Esc' or 'Ctrl+W' will close the window */
55 // ui.actionClose->setShortcut(QString("Esc"));
56 // Vidalia::createShortcut("Ctrl+W", this, ui.actionClose, SLOT(trigger()));
57 
58  /* Get the TorControl object */
60  connect(_torControl, SIGNAL(authenticated()),
61  this, SLOT(onAuthenticated()));
62  connect(_torControl, SIGNAL(disconnected()),
63  this, SLOT(onDisconnected()));
64 
66  connect(_torControl, SIGNAL(circuitStatusChanged(Circuit)),
67  this, SLOT(addCircuit(Circuit)));
68 
70  connect(_torControl, SIGNAL(streamStatusChanged(Stream)),
71  this, SLOT(addStream(Stream)));
72 
74  connect(_torControl, SIGNAL(addressMapped(QString, QString, QDateTime)),
75  this, SLOT(addressMapped(QString, QString, QDateTime)));
76 
78  connect(_torControl, SIGNAL(newDescriptors(QStringList)),
79  this, SLOT(newDescriptors(QStringList)));
80 
81  /* Change the column widths of the tree widgets */
82  ui.treeRouterList->header()->
83  resizeSection(RouterListWidget::StatusColumn, 25);
84  ui.treeRouterList->header()->
85  resizeSection(RouterListWidget::CountryColumn, 25);
86  ui.treeCircuitList->header()->
87  resizeSection(CircuitListWidget::ConnectionColumn, 235);
88 
89  /* Create the TorMapWidget and add it to the dialog */
90 #if defined(USE_MARBLE)
91  _map = new TorMapWidget();
92  connect(_map, SIGNAL(displayRouterInfo(QString)),
93  this, SLOT(displayRouterInfo(QString)));
94  connect(ui.actionZoomFullScreen, SIGNAL(triggered()),
95  this, SLOT(toggleFullScreen()));
96  Vidalia::createShortcut("ESC", _map, this, SLOT(toggleFullScreen()));
97 #else
98  _map = new TorMapImageView();
99  ui.actionZoomFullScreen->setVisible(false);
100 #endif
101  ui.gridLayout->addWidget(_map);
102 
103  /* Connect zoom buttons to TorMapWidget zoom slots */
104  connect(ui.actionZoomIn, SIGNAL(triggered()), this, SLOT(zoomIn()));
105  connect(ui.actionZoomOut, SIGNAL(triggered()), this, SLOT(zoomOut()));
106  connect(ui.actionZoomToFit, SIGNAL(triggered()), _map, SLOT(zoomToFit()));
107 
108  /* Create the timer that will be used to update the router list once every
109  * hour. We still receive the NEWDESC event to get new descriptors, but this
110  * needs to be called to get rid of any descriptors that were removed. */
111  _refreshTimer.setInterval(60*60*1000);
112  connect(&_refreshTimer, SIGNAL(timeout()), this, SLOT(refresh()));
113 
114  /* Connect the necessary slots and signals */
115  connect(ui.actionHelp, SIGNAL(triggered()), this, SLOT(help()));
116  connect(ui.actionRefresh, SIGNAL(triggered()), this, SLOT(refresh()));
117  connect(ui.treeRouterList, SIGNAL(routerSelected(QList<RouterDescriptor>)),
118  this, SLOT(routerSelected(QList<RouterDescriptor>)));
119  connect(ui.treeRouterList, SIGNAL(zoomToRouter(QString)),
120  _map, SLOT(zoomToRouter(QString)));
121  connect(ui.treeCircuitList, SIGNAL(circuitSelected(Circuit)),
122  this, SLOT(circuitSelected(Circuit)));
123  connect(ui.treeCircuitList, SIGNAL(circuitRemoved(CircuitId)),
124  _map, SLOT(removeCircuit(CircuitId)));
125  connect(ui.treeCircuitList, SIGNAL(zoomToCircuit(CircuitId)),
126  _map, SLOT(zoomToCircuit(CircuitId)));
127  connect(ui.treeCircuitList, SIGNAL(closeCircuit(CircuitId)),
128  _torControl, SLOT(closeCircuit(CircuitId)));
129  connect(ui.treeCircuitList, SIGNAL(closeStream(StreamId)),
130  _torControl, SLOT(closeStream(StreamId)));
131  connect(ui.lineRouterSearch, SIGNAL(returnPressed()),
132  this, SLOT(onRouterSearch()));
133  connect(ui.lineRouterSearch, SIGNAL(textChanged(QString)),
134  ui.treeRouterList, SLOT(onRouterSearch(QString)));
135 
137 
138  QToolBar *tb = new QToolBar();
139  tb->addAction(ui.actionRefresh);
140  tb->addAction(ui.actionHelp);
141  tb->addAction(ui.actionZoomIn);
142  tb->addAction(ui.actionZoomOut);
143  tb->addAction(ui.actionZoomToFit);
144  tb->addAction(ui.actionZoomFullScreen);
145 
146  tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
147  ui.horizontalLayout->addWidget(tb);
148 }
149 
150 /** Called when the user changes the UI translation. */
151 void
153 {
154  ui.retranslateUi(this);
155  setTitle(tr("Network Map"));
156  ui.treeRouterList->retranslateUi();
157  ui.treeCircuitList->retranslateUi();
158 
159  if (ui.treeRouterList->selectedItems().size()) {
160  QList<RouterDescriptor> routers;
161  foreach (QTreeWidgetItem *item, ui.treeRouterList->selectedItems()) {
162  routers << dynamic_cast<RouterListItem *>(item)->descriptor();
163  }
164  ui.textRouterInfo->display(routers);
165  } else if (ui.treeCircuitList->selectedItems().size()) {
166  QList<RouterDescriptor> routers;
167  QTreeWidgetItem *item = ui.treeCircuitList->selectedItems()[0];
168  Circuit circuit = dynamic_cast<CircuitItem*>(item)->circuit();
169  foreach (QString id, circuit.routerIDs()) {
170  RouterListItem *item = ui.treeRouterList->findRouterById(id);
171  if (item)
172  routers.append(item->descriptor());
173  }
174  ui.textRouterInfo->display(routers);
175  }
176 }
177 
178 void
180 {
181  VidaliaSettings settings;
182 
183 #if defined(USE_GEOIP)
184  if (settings.useLocalGeoIpDatabase()) {
185  QString databaseFile = settings.localGeoIpDatabase();
186  if (! databaseFile.isEmpty()) {
187  _geoip.setLocalDatabase(databaseFile);
189  vInfo("Using local database file for relay mapping: %1")
190  .arg(databaseFile);
191  return;
192  }
193  }
194 #endif
195  vInfo("Using Tor's GeoIP database for country-level relay mapping.");
197 }
198 
199 /** Loads data into map, lists and starts timer when we get connected*/
200 void
202 {
203  refresh();
204  _refreshTimer.start();
205  ui.actionRefresh->setEnabled(true);
206 }
207 
208 /** Clears map, lists and stops timer when we get disconnected */
209 void
211 {
212  clear();
213  _refreshTimer.stop();
214  ui.actionRefresh->setEnabled(false);
215 }
216 
217 /** Reloads the lists of routers, circuits that Tor knows about */
218 void
220 {
221  /* Don't let the user refresh while we're refreshing. */
222  ui.actionRefresh->setEnabled(false);
223 
224  /* Clear the data */
225  clear();
226 
227  /* Load router information */
229  /* Load existing address mappings */
230  loadAddressMap();
231  /* Load Circuits and Streams information */
232  loadConnections();
233 
234  /* Ok, they can refresh again. */
235  ui.actionRefresh->setEnabled(true);
236 }
237 
238 /** Clears the lists and the map */
239 void
241 {
242  /* Clear the network map */
243  _map->clear();
244  _map->update();
245  /* Clear the address map */
246  _addressMap.clear();
247  /* Clear the lists of routers, circuits, and streams */
248  ui.treeRouterList->clearRouters();
249  ui.treeCircuitList->clearCircuits();
250  ui.textRouterInfo->clear();
251 }
252 
253 /** Called when the search of a router is triggered by the signal
254  * returnPressed from the search field. */
255 void
257 {
258  ui.treeRouterList->searchNextRouter(ui.lineRouterSearch->text());
259 }
260 
261 /** Loads a list of all current address mappings. */
262 void
264 {
265  /* We store the reverse address mappings, so we can go from a numeric value
266  * back to a likely more meaningful hostname to display for the user. */
268 }
269 
270 /** Loads a list of all current circuits and streams. */
271 void
273 {
274  /* Load all circuits */
275  CircuitList circuits = _torControl->getCircuits();
276  foreach (Circuit circuit, circuits) {
277  addCircuit(circuit);
278  }
279  /* Now load all streams */
280  StreamList streams = _torControl->getStreams();
281  foreach (Stream stream, streams) {
282  addStream(stream);
283  }
284 
285  /* Update the map */
286  _map->update();
287 }
288 
289 /** Adds <b>circuit</b> to the map and the list */
290 void
292 {
293  /* Add the circuit to the list of all current circuits */
294  ui.treeCircuitList->addCircuit(circuit);
295  /* Plot the circuit on the map */
296  _map->addCircuit(circuit.id(), circuit.routerIDs());
297 }
298 
299 /** Adds <b>stream</b> to its associated circuit on the list of all circuits. */
300 void
302 {
303  /* If the new stream's target has an IP address instead of a host name,
304  * check our cache for an existing reverse address mapping. */
305  if (stream.status() == Stream::New) {
306  QString target = stream.targetAddress();
307  if (! QHostAddress(target).isNull() && _addressMap.isMapped(target)) {
308  /* Replace the IP address in the stream event with the original
309  * hostname */
310  ui.treeCircuitList->addStream(
311  Stream(stream.id(), stream.status(), stream.circuitId(),
312  _addressMap.mappedTo(target), stream.targetPort()));
313  }
314  } else {
315  ui.treeCircuitList->addStream(stream);
316  }
317 }
318 
319 void
320 NetViewer::addressMapped(const QString &from, const QString &to,
321  const QDateTime &expires)
322 {
323  _addressMap.add(to, from, expires);
324 }
325 
326 /** Called when the user selects the "Help" action from the toolbar. */
327 void
329 {
330  emit helpRequested("netview");
331 }
332 
333 /** Retrieves a list of all running routers from Tor and their descriptors,
334  * and adds them to the RouterListWidget. */
335 void
337 {
338  NetworkStatus networkStatus = _torControl->getNetworkStatus();
339  foreach (RouterStatus rs, networkStatus) {
340  if (!rs.isRunning())
341  continue;
342 
344  if (!rd.isEmpty())
345  addRouter(rd);
346 
347  QCoreApplication::processEvents();
348  }
349 }
350 
351 /** Adds a router to our list of servers and retrieves geographic location
352  * information for the server. */
353 void
355 {
356  /* Add the descriptor to the list of server */
357  RouterListItem *item = ui.treeRouterList->addRouter(rd);
358  if (! item)
359  return;
360 
361  /* Attempt to map this relay to an approximate geographic location. The
362  * accuracy of the result depends on the database information currently
363  * available to the GeoIP resolver. */
364  if (! item->location().isValid() || rd.ip() != item->location().ip()) {
365  GeoIpRecord location = _geoip.resolve(rd.ip());
366  if (location.isValid()) {
367  item->setLocation(location);
368  _map->addRouter(rd, location);
369  }
370  }
371 }
372 
373 /** Called when a NEWDESC event arrives. Retrieves new router descriptors
374  * for the router identities given in <b>ids</b> and updates the router
375  * list and network map. */
376 void
377 NetViewer::newDescriptors(const QStringList &ids)
378 {
379  foreach (QString id, ids) {
381  if (!rd.isEmpty())
382  addRouter(rd); /* Updates the existing entry */
383  }
384 }
385 
386 /** Called when the user selects a circuit from the circuit and streams
387  * list. */
388 void
390 {
391  /* Clear any selected items. */
392  ui.treeRouterList->deselectAll();
393  ui.textRouterInfo->clear();
394  _map->deselectAll();
395 
396  /* Select the items on the map and in the list */
397  _map->selectCircuit(circuit.id());
398 
399  QList<RouterDescriptor> routers;
400 
401  foreach (QString id, circuit.routerIDs()) {
402  /* Try to find and select each router in the path */
403  RouterListItem *item = ui.treeRouterList->findRouterById(id);
404  if (item)
405  routers.append(item->descriptor());
406  }
407 
408  ui.textRouterInfo->display(routers);
409 }
410 
411 /** Called when the user selects one or more routers from the router list. */
412 void
413 NetViewer::routerSelected(const QList<RouterDescriptor> &routers)
414 {
415  _map->deselectAll();
416  ui.textRouterInfo->clear();
417  ui.textRouterInfo->display(routers);
418 
419  /* XXX: Ideally we would also be able to select multiple pinpoints on the
420  * map. But our current map sucks and you can't even tell when one is
421  * selected anyway. Worry about this when we actually get to Marble.
422  */
423  if (routers.size() == 1)
424  _map->selectRouter(routers[0].id());
425 }
426 
427 /** Called when the user selects a router on the network map. Displays a
428  * dialog with detailed information for the router specified by
429  * <b>id</b>.*/
430 void
432 {
433  RouterInfoDialog dlg(_map->isFullScreen() ? static_cast<QWidget*>(_map)
434  : static_cast<QWidget*>(this));
435 
436  /* Fetch the specified router's descriptor */
437  QStringList rd = _torControl->getRouterDescriptorText(id);
438  if (rd.isEmpty()) {
439  VMessageBox::warning(this, tr("Relay Not Found"),
440  tr("No details on the selected relay are available."),
442  return;
443  }
444 
445  /* Fetch the router's network status information */
447 
448  dlg.setRouterInfo(rd, rs);
449 
450  /* Populate the UI with information learned from a previous GeoIP request */
451  RouterListItem *item = ui.treeRouterList->findRouterById(id);
452  if (item)
453  dlg.setLocation(item->location().toString());
454  else
455  dlg.setLocation(tr("Unknown"));
456 
457  dlg.exec();
458 }
459 
460 /* XXX: The following zoomIn() and zoomOut() slots are a hack. MarbleWidget
461  * does have zoomIn() and zoomOut() slots to which we could connect the
462  * buttons, but these slots currently don't force a repaint. So to see
463  * the zoom effect, the user has to click on the map after clicking one
464  * of the zoom buttons. Instead, we use the zoomViewBy() method, which
465  * DOES force a repaint.
466  */
467 /** Called when the user clicks the "Zoom In" button. */
468 void
470 {
471 #if defined(USE_MARBLE)
472  _map->zoomViewBy(40);
473 #else
474  _map->zoomIn();
475 #endif
476 }
477 
478 /** Called when the user clicks the "Zoom Out" button. */
479 void
481 {
482 #if defined(USE_MARBLE)
483  _map->zoomViewBy(-40);
484 #else
485  _map->zoomOut();
486 #endif
487 }
488 
489 /** Called when the user clicks "Full Screen" or presses Escape on the map.
490  * Toggles the map between normal and a full screen viewing modes. */
491 void
493 {
494  if (_map->isFullScreen()) {
495  /* Disabling full screen mode. Put the map back in its container. */
496  ui.gridLayout->addWidget(_map);
497  _map->setWindowState(_map->windowState() & ~Qt::WindowFullScreen);
498  } else {
499  /* Enabling full screen mode. Remove the map from the QGridLayout
500  * container and set its window state to full screen. */
501  _map->setParent(0);
502  _map->setWindowState(_map->windowState() | Qt::WindowFullScreen);
503  _map->show();
504  }
505 }
506