1 /* interface_frame.cpp
2 * Display of interfaces, including their respective data, and the
3 * capability to filter interfaces by type
4 *
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
8 *
9 * SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 #include "config.h"
12 #include <ui_interface_frame.h>
13
14 #include "capture/capture_ifinfo.h"
15
16 #ifdef Q_OS_WIN
17 #include "capture/capture-wpcap.h"
18 #endif
19
20 #include "ui/qt/interface_frame.h"
21 #include <ui/qt/simple_dialog.h>
22 #include <ui/qt/wireshark_application.h>
23
24 #include <ui/qt/models/interface_tree_model.h>
25 #include <ui/qt/models/sparkline_delegate.h>
26
27 #include <ui/qt/utils/color_utils.h>
28
29
30 #include "extcap.h"
31
32 #include <ui/recent.h>
33 #include <wsutil/utf8_entities.h>
34
35 #include <QDesktopServices>
36 #include <QFrame>
37 #include <QHBoxLayout>
38 #include <QItemSelection>
39 #include <QLabel>
40 #include <QPushButton>
41 #include <QUrl>
42
43 #include <epan/prefs.h>
44
45 #define BTN_IFTYPE_PROPERTY "ifType"
46
47 #ifdef HAVE_LIBPCAP
48 const int stat_update_interval_ = 1000; // ms
49 #endif
50 const char *no_capture_link = "#no_capture";
51
InterfaceFrame(QWidget * parent)52 InterfaceFrame::InterfaceFrame(QWidget * parent)
53 : QFrame(parent),
54 ui(new Ui::InterfaceFrame)
55 , proxy_model_(Q_NULLPTR)
56 , source_model_(Q_NULLPTR)
57 , info_model_(this)
58 #ifdef HAVE_LIBPCAP
59 ,stat_timer_(NULL)
60 #endif // HAVE_LIBPCAP
61 {
62 ui->setupUi(this);
63
64 setStyleSheet(QString(
65 "QFrame {"
66 " border: 0;"
67 "}"
68 "QTreeView {"
69 " border: 0;"
70 "}"
71 ));
72
73 ui->warningLabel->hide();
74
75 #ifdef Q_OS_MAC
76 ui->interfaceTree->setAttribute(Qt::WA_MacShowFocusRect, false);
77 #endif
78
79 /* TODO: There must be a better way to do this */
80 ifTypeDescription.insert(IF_WIRED, tr("Wired"));
81 ifTypeDescription.insert(IF_AIRPCAP, tr("AirPCAP"));
82 ifTypeDescription.insert(IF_PIPE, tr("Pipe"));
83 ifTypeDescription.insert(IF_STDIN, tr("STDIN"));
84 ifTypeDescription.insert(IF_BLUETOOTH, tr("Bluetooth"));
85 ifTypeDescription.insert(IF_WIRELESS, tr("Wireless"));
86 ifTypeDescription.insert(IF_DIALUP, tr("Dial-Up"));
87 ifTypeDescription.insert(IF_USB, tr("USB"));
88 ifTypeDescription.insert(IF_EXTCAP, tr("External Capture"));
89 ifTypeDescription.insert(IF_VIRTUAL, tr ("Virtual"));
90
91 QList<InterfaceTreeColumns> columns;
92 columns.append(IFTREE_COL_EXTCAP);
93 columns.append(IFTREE_COL_DISPLAY_NAME);
94 columns.append(IFTREE_COL_STATS);
95 proxy_model_.setColumns(columns);
96 proxy_model_.setStoreOnChange(true);
97 proxy_model_.setSourceModel(&source_model_);
98
99 info_model_.setSourceModel(&proxy_model_);
100 info_model_.setColumn(columns.indexOf(IFTREE_COL_STATS));
101
102 ui->interfaceTree->setModel(&info_model_);
103
104 ui->interfaceTree->setItemDelegateForColumn(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS), new SparkLineDelegate(this));
105
106 ui->interfaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
107 connect(ui->interfaceTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
108
109 connect(wsApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
110 connect(wsApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
111
112 connect(ui->interfaceTree->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
113 this, SLOT(interfaceTreeSelectionChanged(const QItemSelection &, const QItemSelection &)));
114 }
115
~InterfaceFrame()116 InterfaceFrame::~InterfaceFrame()
117 {
118 delete ui;
119 }
120
getSelectionMenu()121 QMenu * InterfaceFrame::getSelectionMenu()
122 {
123 QMenu * contextMenu = new QMenu(this);
124 QList<int> typesDisplayed = proxy_model_.typesDisplayed();
125
126 QMap<int, QString>::const_iterator it = ifTypeDescription.constBegin();
127 while (it != ifTypeDescription.constEnd())
128 {
129 int ifType = it.key();
130
131 if (typesDisplayed.contains(ifType))
132 {
133 QAction *endp_action = new QAction(it.value(), this);
134 endp_action->setData(QVariant::fromValue(ifType));
135 endp_action->setCheckable(true);
136 endp_action->setChecked(proxy_model_.isInterfaceTypeShown(ifType));
137 connect(endp_action, SIGNAL(triggered()), this, SLOT(triggeredIfTypeButton()));
138 contextMenu->addAction(endp_action);
139 }
140 ++it;
141 }
142
143 #ifdef HAVE_PCAP_REMOTE
144 if (proxy_model_.remoteInterfacesExist())
145 {
146 QAction * toggleRemoteAction = new QAction(tr("Remote interfaces"), this);
147 toggleRemoteAction->setCheckable(true);
148 toggleRemoteAction->setChecked(! proxy_model_.remoteDisplay());
149 connect(toggleRemoteAction, SIGNAL(triggered()), this, SLOT(toggleRemoteInterfaces()));
150 contextMenu->addAction(toggleRemoteAction);
151 }
152 #endif
153
154 #if 0
155 // Disabled until bug 13354 is fixed
156 contextMenu->addSeparator();
157 QAction * toggleHideAction = new QAction(tr("Show hidden interfaces"), this);
158 toggleHideAction->setCheckable(true);
159 toggleHideAction->setChecked(! proxy_model_->filterHidden());
160 connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHiddenInterfaces()));
161 contextMenu->addAction(toggleHideAction);
162 #endif
163
164 return contextMenu;
165 }
166
interfacesHidden()167 int InterfaceFrame::interfacesHidden()
168 {
169 return proxy_model_.interfacesHidden();
170 }
171
interfacesPresent()172 int InterfaceFrame::interfacesPresent()
173 {
174 return source_model_.rowCount() - proxy_model_.interfacesHidden();
175 }
176
ensureSelectedInterface()177 void InterfaceFrame::ensureSelectedInterface()
178 {
179 #ifdef HAVE_LIBPCAP
180 if (interfacesPresent() < 1) return;
181
182 if (source_model_.selectedDevices().count() < 1) {
183 QModelIndex first_idx = info_model_.mapFromSource(proxy_model_.index(0, 0));
184 ui->interfaceTree->setCurrentIndex(first_idx);
185 }
186
187 ui->interfaceTree->setFocus();
188 #endif
189 }
190
hideEvent(QHideEvent *)191 void InterfaceFrame::hideEvent(QHideEvent *) {
192 #ifdef HAVE_LIBPCAP
193 if (stat_timer_)
194 stat_timer_->stop();
195 source_model_.stopStatistic();
196 #endif // HAVE_LIBPCAP
197 }
198
showEvent(QShowEvent *)199 void InterfaceFrame::showEvent(QShowEvent *) {
200
201 #ifdef HAVE_LIBPCAP
202 if (stat_timer_)
203 stat_timer_->start(stat_update_interval_);
204 #endif // HAVE_LIBPCAP
205 }
206
actionButton_toggled(bool checked)207 void InterfaceFrame::actionButton_toggled(bool checked)
208 {
209 QVariant ifType = sender()->property(BTN_IFTYPE_PROPERTY);
210 if (ifType.isValid())
211 {
212 proxy_model_.setInterfaceTypeVisible(ifType.toInt(), checked);
213 }
214
215 resetInterfaceTreeDisplay();
216 }
217
triggeredIfTypeButton()218 void InterfaceFrame::triggeredIfTypeButton()
219 {
220 QAction *sender = qobject_cast<QAction *>(QObject::sender());
221 if (sender)
222 {
223 int ifType = sender->data().value<int>();
224 proxy_model_.toggleTypeVisibility(ifType);
225
226 resetInterfaceTreeDisplay();
227 emit typeSelectionChanged();
228 }
229 }
230
interfaceListChanged()231 void InterfaceFrame::interfaceListChanged()
232 {
233 info_model_.clearInfos();
234 if (prefs.capture_no_extcap)
235 info_model_.appendInfo(tr("External capture interfaces disabled."));
236
237 resetInterfaceTreeDisplay();
238 // Ensure that device selection is consistent with the displayed selection.
239 updateSelectedInterfaces();
240
241 #ifdef HAVE_LIBPCAP
242 if (!stat_timer_) {
243 updateStatistics();
244 stat_timer_ = new QTimer(this);
245 connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
246 stat_timer_->start(stat_update_interval_);
247 }
248 #endif
249 }
250
toggleHiddenInterfaces()251 void InterfaceFrame::toggleHiddenInterfaces()
252 {
253 proxy_model_.toggleFilterHidden();
254
255 emit typeSelectionChanged();
256 }
257
258 #ifdef HAVE_PCAP_REMOTE
toggleRemoteInterfaces()259 void InterfaceFrame::toggleRemoteInterfaces()
260 {
261 proxy_model_.toggleRemoteDisplay();
262 emit typeSelectionChanged();
263 }
264 #endif
265
resetInterfaceTreeDisplay()266 void InterfaceFrame::resetInterfaceTreeDisplay()
267 {
268 ui->warningLabel->hide();
269 ui->warningLabel->clear();
270
271 ui->warningLabel->setStyleSheet(QString(
272 "QLabel {"
273 " border-radius: 0.5em;"
274 " padding: 0.33em;"
275 " margin-bottom: 0.25em;"
276 // We might want to transition this to normal colors this after a timeout.
277 " background-color: %2;"
278 "}"
279 ).arg(ColorUtils::warningBackground().name()));
280
281 #ifdef HAVE_LIBPCAP
282 #ifdef Q_OS_WIN
283 if (!has_wpcap) {
284 ui->warningLabel->setText(tr(
285 "<p>"
286 "Local interfaces are unavailable because no packet capture driver is installed."
287 "</p><p>"
288 "You can fix this by installing <a href=\"https://nmap.org/npcap/\">Npcap</a>"
289 " or <a href=\"https://www.winpcap.org/install/default.htm\">WinPcap</a>."
290 "</p>"));
291 } else if (!npf_sys_is_running()) {
292 ui->warningLabel->setText(tr(
293 "<p>"
294 "Local interfaces are unavailable because the packet capture driver isn't loaded."
295 "</p><p>"
296 "You can fix this by running <pre>net start npcap</pre> if you have Npcap installed"
297 " or <pre>net start npf</pre> if you have WinPcap installed."
298 " Both commands must be run as Administrator."
299 "</p>"));
300 }
301 #endif
302
303 if (!haveLocalCapturePermissions())
304 {
305 #ifdef Q_OS_MAC
306 //
307 // NOTE: if you change this text, you must also change the
308 // definition of PLATFORM_PERMISSIONS_SUGGESTION that is
309 // used if __APPLE__ is defined, so that it reflects the
310 // new message text.
311 //
312 QString install_chmodbpf_path = wsApp->applicationDirPath() + "/../Resources/Extras/Install ChmodBPF.pkg";
313 ui->warningLabel->setText(tr(
314 "<p>"
315 "You don't have permission to capture on local interfaces."
316 "</p><p>"
317 "You can fix this by <a href=\"file://%1\">installing ChmodBPF</a>."
318 "</p>")
319 .arg(install_chmodbpf_path));
320 #else
321 //
322 // XXX - should this give similar platform-dependent recommendations,
323 // just as dumpcap gives platform-dependent recommendations in its
324 // PLATFORM_PERMISSIONS_SUGGESTION #define?
325 //
326 ui->warningLabel->setText(tr("You don't have permission to capture on local interfaces."));
327 #endif
328 }
329
330 if (proxy_model_.rowCount() == 0)
331 {
332 ui->warningLabel->setText(tr("No interfaces found."));
333 ui->warningLabel->setText(proxy_model_.interfaceError());
334 if (prefs.capture_no_interface_load) {
335 ui->warningLabel->setText(tr("Interfaces not loaded (due to preference). Go to Capture " UTF8_RIGHTWARDS_ARROW " Refresh Interfaces to load."));
336 }
337 }
338
339 // XXX Should we have a separate recent pref for each message?
340 if (!ui->warningLabel->text().isEmpty() && recent.sys_warn_if_no_capture)
341 {
342 QString warning_text = ui->warningLabel->text();
343 warning_text.append(QString("<p><a href=\"%1\">%2</a></p>")
344 .arg(no_capture_link)
345 .arg(SimpleDialog::dontShowThisAgain()));
346 ui->warningLabel->setText(warning_text);
347
348 ui->warningLabel->show();
349 }
350 #endif // HAVE_LIBPCAP
351
352 if (proxy_model_.rowCount() > 0)
353 {
354 ui->interfaceTree->show();
355 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_EXTCAP));
356 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_DISPLAY_NAME));
357 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS));
358 }
359 else
360 {
361 ui->interfaceTree->hide();
362 }
363 }
364
365 // XXX Should this be in capture/capture-pcap-util.[ch]?
haveLocalCapturePermissions() const366 bool InterfaceFrame::haveLocalCapturePermissions() const
367 {
368 #ifdef Q_OS_MAC
369 QFileInfo bpf0_fi = QFileInfo("/dev/bpf0");
370 return bpf0_fi.isReadable() && bpf0_fi.isWritable();
371 #else
372 // XXX Add checks for other platforms.
373 return true;
374 #endif
375 }
376
updateSelectedInterfaces()377 void InterfaceFrame::updateSelectedInterfaces()
378 {
379 if (source_model_.rowCount() == 0)
380 return;
381 #ifdef HAVE_LIBPCAP
382 QItemSelection sourceSelection = source_model_.selectedDevices();
383 QItemSelection mySelection = info_model_.mapSelectionFromSource(proxy_model_.mapSelectionFromSource(sourceSelection));
384
385 ui->interfaceTree->selectionModel()->clearSelection();
386 ui->interfaceTree->selectionModel()->select(mySelection, QItemSelectionModel::SelectCurrent);
387 #endif
388 }
389
interfaceTreeSelectionChanged(const QItemSelection & selected,const QItemSelection & deselected)390 void InterfaceFrame::interfaceTreeSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
391 {
392 if (selected.count() == 0 && deselected.count() == 0)
393 return;
394 if (source_model_.rowCount() == 0)
395 return;
396
397 #ifdef HAVE_LIBPCAP
398 /* Take all selected interfaces, not just the newly ones */
399 QItemSelection allSelected = ui->interfaceTree->selectionModel()->selection();
400 QItemSelection sourceSelection = proxy_model_.mapSelectionToSource(info_model_.mapSelectionToSource(allSelected));
401
402 if (source_model_.updateSelectedDevices(sourceSelection))
403 emit itemSelectionChanged();
404 #endif
405 }
406
on_interfaceTree_doubleClicked(const QModelIndex & index)407 void InterfaceFrame::on_interfaceTree_doubleClicked(const QModelIndex &index)
408 {
409 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
410
411 if (! realIndex.isValid())
412 return;
413
414 #ifdef HAVE_LIBPCAP
415
416 QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
417 QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
418
419 /* We trust the string here. If this interface is really extcap, the string is
420 * being checked immediatly before the dialog is being generated */
421 if (extcap_string.length() > 0)
422 {
423 /* this checks if configuration is required and not yet provided or saved via prefs */
424 if (extcap_has_configuration((const char *)(device_name.toStdString().c_str()), TRUE))
425 {
426 emit showExtcapOptions(device_name);
427 return;
428 }
429 }
430 #endif
431 emit startCapture();
432 }
433
434 #ifdef HAVE_LIBPCAP
on_interfaceTree_clicked(const QModelIndex & index)435 void InterfaceFrame::on_interfaceTree_clicked(const QModelIndex &index)
436 {
437 if (index.column() == 0)
438 {
439 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
440
441 if (! realIndex.isValid())
442 return;
443
444 QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
445 QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
446
447 /* We trust the string here. If this interface is really extcap, the string is
448 * being checked immediatly before the dialog is being generated */
449 if (extcap_string.length() > 0)
450 {
451 /* this checks if configuration is required and not yet provided or saved via prefs */
452 if (extcap_has_configuration((const char *)(device_name.toStdString().c_str()), FALSE))
453 {
454 emit showExtcapOptions(device_name);
455 return;
456 }
457 }
458 }
459 }
460 #endif
461
updateStatistics(void)462 void InterfaceFrame::updateStatistics(void)
463 {
464 if (source_model_.rowCount() == 0)
465 return;
466
467 #ifdef HAVE_LIBPCAP
468
469 for (int idx = 0; idx < proxy_model_.rowCount(); idx++)
470 {
471 QModelIndex selectIndex = info_model_.mapFromSource(proxy_model_.mapFromSource(source_model_.index(idx, 0)));
472
473 /* Proxy model has not masked out the interface */
474 if (selectIndex.isValid())
475 source_model_.updateStatistic(idx);
476 }
477
478 #endif
479 }
480
481 /* Proxy Method so we do not need to expose the source model */
getPoints(int idx,PointList * pts)482 void InterfaceFrame::getPoints(int idx, PointList * pts)
483 {
484 source_model_.getPoints(idx, pts);
485 }
486
showRunOnFile(void)487 void InterfaceFrame::showRunOnFile(void)
488 {
489 ui->warningLabel->setText("Interfaces not loaded on startup (run on capture file). Go to Capture -> Refresh Interfaces to load.");
490 }
491
showContextMenu(QPoint pos)492 void InterfaceFrame::showContextMenu(QPoint pos)
493 {
494 QMenu ctx_menu;
495
496 ctx_menu.addAction(tr("Start capture"), this, SIGNAL(startCapture()));
497 ctx_menu.exec(ui->interfaceTree->mapToGlobal(pos));
498 }
499
on_warningLabel_linkActivated(const QString & link)500 void InterfaceFrame::on_warningLabel_linkActivated(const QString &link)
501 {
502 if (link.compare(no_capture_link) == 0) {
503 recent.sys_warn_if_no_capture = FALSE;
504 resetInterfaceTreeDisplay();
505 } else {
506 QDesktopServices::openUrl(QUrl(link));
507 }
508 }
509