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