Kerberos: add kerberos_inject_longterm_key() helper function
[wireshark-sm.git] / ui / qt / interface_frame.cpp
blob5bf2b2ad4fc022bc25d21f838de6dc0cac8a9427
1 /* interface_frame.cpp
2 * Display of interfaces, including their respective data, and the
3 * capability to filter interfaces by type
5 * Wireshark - Network traffic analyzer
6 * By Gerald Combs <gerald@wireshark.org>
7 * Copyright 1998 Gerald Combs
9 * SPDX-License-Identifier: GPL-2.0-or-later
11 #include "config.h"
12 #include <ui_interface_frame.h>
14 #include "capture/capture_ifinfo.h"
16 #ifdef Q_OS_WIN
17 #include "capture/capture-wpcap.h"
18 #endif
20 #include "ui/qt/interface_frame.h"
21 #include <ui/qt/simple_dialog.h>
22 #include <ui/qt/main_application.h>
24 #include <ui/qt/models/interface_tree_model.h>
25 #include <ui/qt/models/sparkline_delegate.h>
27 #include <ui/qt/utils/color_utils.h>
30 #include "extcap.h"
32 #include <ui/recent.h>
33 #include "capture_opts.h"
34 #include "ui/capture_globals.h"
35 #include <ui/iface_lists.h>
36 #include <wsutil/utf8_entities.h>
38 #include <QDesktopServices>
39 #include <QFrame>
40 #include <QHBoxLayout>
41 #include <QItemSelection>
42 #include <QLabel>
43 #include <QPushButton>
44 #include <QUrl>
45 #include <QMutex>
46 #include <QDebug>
48 #include <epan/prefs.h>
50 #define BTN_IFTYPE_PROPERTY "ifType"
52 #ifdef HAVE_LIBPCAP
53 const int stat_update_interval_ = 1000; // ms
54 #endif
55 const char *no_capture_link = "#no_capture";
57 static QMutex scan_mutex;
59 InterfaceFrame::InterfaceFrame(QWidget * parent)
60 : QFrame(parent),
61 ui(new Ui::InterfaceFrame)
62 , proxy_model_(Q_NULLPTR)
63 , source_model_(Q_NULLPTR)
64 , info_model_(this)
65 #ifdef HAVE_LIBPCAP
66 ,stat_timer_(NULL)
67 #endif // HAVE_LIBPCAP
69 ui->setupUi(this);
71 setStyleSheet(QString(
72 "QFrame {"
73 " border: 0;"
74 "}"
75 "QTreeView {"
76 " border: 0;"
77 "}"
78 ));
80 ui->warningLabel->hide();
82 #ifdef Q_OS_MAC
83 ui->interfaceTree->setAttribute(Qt::WA_MacShowFocusRect, false);
84 #endif
86 /* TODO: There must be a better way to do this */
87 ifTypeDescription.insert(IF_WIRED, tr("Wired"));
88 ifTypeDescription.insert(IF_AIRPCAP, tr("AirPCAP"));
89 ifTypeDescription.insert(IF_PIPE, tr("Pipe"));
90 ifTypeDescription.insert(IF_STDIN, tr("STDIN"));
91 ifTypeDescription.insert(IF_BLUETOOTH, tr("Bluetooth"));
92 ifTypeDescription.insert(IF_WIRELESS, tr("Wireless"));
93 ifTypeDescription.insert(IF_DIALUP, tr("Dial-Up"));
94 ifTypeDescription.insert(IF_USB, tr("USB"));
95 ifTypeDescription.insert(IF_EXTCAP, tr("External Capture"));
96 ifTypeDescription.insert(IF_VIRTUAL, tr ("Virtual"));
98 QList<InterfaceTreeColumns> columns;
99 columns.append(IFTREE_COL_EXTCAP);
100 columns.append(IFTREE_COL_DISPLAY_NAME);
101 columns.append(IFTREE_COL_STATS);
102 proxy_model_.setColumns(columns);
103 proxy_model_.setStoreOnChange(true);
104 proxy_model_.setSortByActivity(true);
105 proxy_model_.setSourceModel(&source_model_);
107 info_model_.setSourceModel(&proxy_model_);
108 info_model_.setColumn(static_cast<int>(columns.indexOf(IFTREE_COL_STATS)));
110 ui->interfaceTree->setModel(&info_model_);
111 ui->interfaceTree->setSortingEnabled(true);
113 ui->interfaceTree->setItemDelegateForColumn(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS), new SparkLineDelegate(this));
115 ui->interfaceTree->setContextMenuPolicy(Qt::CustomContextMenu);
116 connect(ui->interfaceTree, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showContextMenu(QPoint)));
118 connect(mainApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged()));
119 connect(mainApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged()));
121 connect(ui->interfaceTree->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
122 this, SLOT(interfaceTreeSelectionChanged(const QItemSelection &, const QItemSelection &)));
125 InterfaceFrame::~InterfaceFrame()
127 delete ui;
130 QMenu * InterfaceFrame::getSelectionMenu()
132 QMenu * contextMenu = new QMenu(this);
133 QList<int> typesDisplayed = proxy_model_.typesDisplayed();
135 QMap<int, QString>::const_iterator it = ifTypeDescription.constBegin();
136 while (it != ifTypeDescription.constEnd())
138 int ifType = it.key();
140 if (typesDisplayed.contains(ifType))
142 QAction *endp_action = new QAction(it.value(), this);
143 endp_action->setData(QVariant::fromValue(ifType));
144 endp_action->setCheckable(true);
145 endp_action->setChecked(proxy_model_.isInterfaceTypeShown(ifType));
146 connect(endp_action, SIGNAL(triggered()), this, SLOT(triggeredIfTypeButton()));
147 contextMenu->addAction(endp_action);
149 ++it;
152 #ifdef HAVE_PCAP_REMOTE
153 if (proxy_model_.remoteInterfacesExist())
155 QAction * toggleRemoteAction = new QAction(tr("Remote interfaces"), this);
156 toggleRemoteAction->setCheckable(true);
157 toggleRemoteAction->setChecked(proxy_model_.remoteDisplay());
158 connect(toggleRemoteAction, SIGNAL(triggered()), this, SLOT(toggleRemoteInterfaces()));
159 contextMenu->addAction(toggleRemoteAction);
161 #endif
163 contextMenu->addSeparator();
164 QAction * toggleHideAction = new QAction(tr("Show hidden interfaces"), this);
165 toggleHideAction->setCheckable(true);
166 toggleHideAction->setChecked(! proxy_model_.filterHidden());
167 connect(toggleHideAction, SIGNAL(triggered()), this, SLOT(toggleHiddenInterfaces()));
168 contextMenu->addAction(toggleHideAction);
170 return contextMenu;
173 int InterfaceFrame::interfacesHidden()
175 return proxy_model_.interfacesHidden();
178 int InterfaceFrame::interfacesPresent()
180 return source_model_.rowCount() - proxy_model_.interfacesHidden();
183 void InterfaceFrame::ensureSelectedInterface()
185 #ifdef HAVE_LIBPCAP
186 if (interfacesPresent() < 1) return;
188 if (source_model_.selectedDevices().count() < 1) {
189 QModelIndex first_idx = info_model_.mapFromSource(proxy_model_.index(0, 0));
190 ui->interfaceTree->setCurrentIndex(first_idx);
193 ui->interfaceTree->setFocus();
194 #endif
197 void InterfaceFrame::hideEvent(QHideEvent *) {
198 #ifdef HAVE_LIBPCAP
199 if (stat_timer_)
200 stat_timer_->stop();
201 source_model_.stopStatistic();
202 #endif // HAVE_LIBPCAP
205 void InterfaceFrame::showEvent(QShowEvent *) {
207 #ifdef HAVE_LIBPCAP
208 if (stat_timer_)
209 stat_timer_->start(stat_update_interval_);
210 #endif // HAVE_LIBPCAP
213 #ifdef HAVE_LIBPCAP
214 void InterfaceFrame::scanLocalInterfaces(GList *filter_list)
216 GList *if_list = NULL;
217 if (scan_mutex.tryLock()) {
218 if (isVisible()) {
219 source_model_.stopStatistic();
220 if_stat_cache_t * stat_cache = capture_interface_stat_start(&global_capture_opts, &if_list);
221 source_model_.setCache(stat_cache);
223 mainApp->setInterfaceList(if_list);
224 free_interface_list(if_list);
225 scan_local_interfaces_filtered(filter_list, main_window_update);
226 mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged);
227 scan_mutex.unlock();
228 } else {
229 qDebug() << "scan mutex locked, can't scan interfaces";
232 #endif // HAVE_LIBPCAP
234 void InterfaceFrame::actionButton_toggled(bool checked)
236 QVariant ifType = sender()->property(BTN_IFTYPE_PROPERTY);
237 if (ifType.isValid())
239 proxy_model_.setInterfaceTypeVisible(ifType.toInt(), checked);
242 resetInterfaceTreeDisplay();
245 void InterfaceFrame::triggeredIfTypeButton()
247 QAction *sender = qobject_cast<QAction *>(QObject::sender());
248 if (sender)
250 int ifType = sender->data().value<int>();
251 proxy_model_.toggleTypeVisibility(ifType);
253 resetInterfaceTreeDisplay();
254 emit typeSelectionChanged();
258 void InterfaceFrame::interfaceListChanged()
260 info_model_.clearInfos();
261 if (prefs.capture_no_extcap)
262 info_model_.appendInfo(tr("External capture interfaces disabled."));
264 resetInterfaceTreeDisplay();
265 // Ensure that device selection is consistent with the displayed selection.
266 updateSelectedInterfaces();
268 #ifdef HAVE_LIBPCAP
269 if (!stat_timer_) {
270 updateStatistics();
271 stat_timer_ = new QTimer(this);
272 connect(stat_timer_, SIGNAL(timeout()), this, SLOT(updateStatistics()));
273 stat_timer_->start(stat_update_interval_);
275 #endif
278 void InterfaceFrame::toggleHiddenInterfaces()
280 source_model_.interfaceListChanged();
281 proxy_model_.toggleFilterHidden();
283 emit typeSelectionChanged();
286 #ifdef HAVE_PCAP_REMOTE
287 void InterfaceFrame::toggleRemoteInterfaces()
289 proxy_model_.toggleRemoteDisplay();
290 emit typeSelectionChanged();
292 #endif
294 void InterfaceFrame::resetInterfaceTreeDisplay()
296 ui->warningLabel->hide();
297 ui->warningLabel->clear();
299 ui->warningLabel->setStyleSheet(QString(
300 "QLabel {"
301 " border-radius: 0.5em;"
302 " padding: 0.33em;"
303 " margin-bottom: 0.25em;"
304 // We might want to transition this to normal colors this after a timeout.
305 " background-color: %2;"
307 ).arg(ColorUtils::warningBackground().name()));
309 #ifdef HAVE_LIBPCAP
310 #ifdef Q_OS_WIN
311 if (!has_wpcap) {
312 ui->warningLabel->setText(tr(
313 "<p>"
314 "Local interfaces are unavailable because no packet capture driver is installed."
315 "</p><p>"
316 "You can fix this by installing <a href=\"https://npcap.com/\">Npcap</a>."
317 "</p>"));
318 } else if (!npf_sys_is_running()) {
319 ui->warningLabel->setText(tr(
320 "<p>"
321 "Local interfaces are unavailable because the packet capture driver isn't loaded."
322 "</p><p>"
323 "You can fix this by running <pre>net start npcap</pre> if you have Npcap installed"
324 " or <pre>net start npf</pre> if you have WinPcap installed."
325 " Both commands must be run as Administrator."
326 "</p>"));
328 #endif
330 if (!haveLocalCapturePermissions())
332 #ifdef Q_OS_MAC
334 // NOTE: if you change this text, you must also change the
335 // definition of PLATFORM_PERMISSIONS_SUGGESTION that is
336 // used if __APPLE__ is defined, so that it reflects the
337 // new message text.
339 QString install_chmodbpf_path = mainApp->applicationDirPath() + "/../Resources/Extras/Install ChmodBPF.pkg";
340 ui->warningLabel->setText(tr(
341 "<p>"
342 "You don't have permission to capture on local interfaces."
343 "</p><p>"
344 "You can fix this by <a href=\"file://%1\">installing ChmodBPF</a>."
345 "</p>")
346 .arg(install_chmodbpf_path));
347 #else
349 // XXX - should this give similar platform-dependent recommendations,
350 // just as dumpcap gives platform-dependent recommendations in its
351 // PLATFORM_PERMISSIONS_SUGGESTION #define?
353 ui->warningLabel->setText(tr("You don't have permission to capture on local interfaces."));
354 #endif
357 if (proxy_model_.rowCount() == 0)
359 ui->warningLabel->setText(tr("No interfaces found."));
360 ui->warningLabel->setText(proxy_model_.interfaceError());
361 if (prefs.capture_no_interface_load) {
362 ui->warningLabel->setText(tr("Interfaces not loaded (due to preference). Go to Capture " UTF8_RIGHTWARDS_ARROW " Refresh Interfaces to load."));
366 // XXX Should we have a separate recent pref for each message?
367 if (!ui->warningLabel->text().isEmpty() && recent.sys_warn_if_no_capture)
369 QString warning_text = ui->warningLabel->text();
370 warning_text.append(QStringLiteral("<p><a href=\"%1\">%2</a></p>")
371 .arg(no_capture_link)
372 .arg(SimpleDialog::dontShowThisAgain()));
373 ui->warningLabel->setText(warning_text);
375 ui->warningLabel->show();
377 #endif // HAVE_LIBPCAP
379 if (proxy_model_.rowCount() > 0)
381 ui->interfaceTree->show();
382 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_EXTCAP));
383 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_DISPLAY_NAME));
384 ui->interfaceTree->resizeColumnToContents(proxy_model_.mapSourceToColumn(IFTREE_COL_STATS));
386 else
388 ui->interfaceTree->hide();
392 // XXX Should this be in capture/capture-pcap-util.[ch]?
393 bool InterfaceFrame::haveLocalCapturePermissions() const
395 #ifdef Q_OS_MAC
396 QFileInfo bpf0_fi = QFileInfo("/dev/bpf0");
397 return bpf0_fi.isReadable() && bpf0_fi.isWritable();
398 #else
399 // XXX Add checks for other platforms.
400 return true;
401 #endif
404 void InterfaceFrame::updateSelectedInterfaces()
406 if (source_model_.rowCount() == 0)
407 return;
408 #ifdef HAVE_LIBPCAP
409 QItemSelection sourceSelection = source_model_.selectedDevices();
410 QItemSelection mySelection = info_model_.mapSelectionFromSource(proxy_model_.mapSelectionFromSource(sourceSelection));
412 ui->interfaceTree->selectionModel()->clearSelection();
413 ui->interfaceTree->selectionModel()->select(mySelection, QItemSelectionModel::SelectCurrent);
414 #endif
417 void InterfaceFrame::interfaceTreeSelectionChanged(const QItemSelection & selected, const QItemSelection & deselected)
419 if (selected.count() == 0 && deselected.count() == 0)
420 return;
421 if (source_model_.rowCount() == 0)
422 return;
424 #ifdef HAVE_LIBPCAP
425 /* Take all selected interfaces, not just the newly ones */
426 QItemSelection allSelected = ui->interfaceTree->selectionModel()->selection();
427 QItemSelection sourceSelection = proxy_model_.mapSelectionToSource(info_model_.mapSelectionToSource(allSelected));
429 if (source_model_.updateSelectedDevices(sourceSelection))
430 emit itemSelectionChanged();
431 #endif
434 void InterfaceFrame::on_interfaceTree_doubleClicked(const QModelIndex &index)
436 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
438 if (! realIndex.isValid())
439 return;
441 QStringList interfaces;
443 #ifdef HAVE_LIBPCAP
445 QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
446 QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
448 interfaces << device_name;
450 /* We trust the string here. If this interface is really extcap, the string is
451 * being checked immediately before the dialog is being generated */
452 if (extcap_string.length() > 0)
454 /* this checks if configuration is required and not yet provided or saved via prefs */
455 if (extcap_requires_configuration((const char *)(device_name.toStdString().c_str())))
457 emit showExtcapOptions(device_name, true);
458 return;
461 #endif
463 // Start capture for all columns except the first one with extcap
464 if (IFTREE_COL_EXTCAP != realIndex.column()) {
465 startCapture(interfaces);
469 #ifdef HAVE_LIBPCAP
470 void InterfaceFrame::on_interfaceTree_clicked(const QModelIndex &index)
472 if (index.column() == 0)
474 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(index));
476 if (! realIndex.isValid())
477 return;
479 QString device_name = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_NAME).toString();
480 QString extcap_string = source_model_.getColumnContent(realIndex.row(), IFTREE_COL_EXTCAP_PATH).toString();
482 /* We trust the string here. If this interface is really extcap, the string is
483 * being checked immediately before the dialog is being generated */
484 if (extcap_string.length() > 0)
486 /* this checks if configuration is required and not yet provided or saved via prefs */
487 if (extcap_has_configuration((const char *)(device_name.toStdString().c_str())))
489 emit showExtcapOptions(device_name, false);
490 return;
495 #endif
497 void InterfaceFrame::updateStatistics(void)
499 if (source_model_.rowCount() == 0)
500 return;
502 #ifdef HAVE_LIBPCAP
504 for (int idx = 0; idx < source_model_.rowCount(); idx++)
506 QModelIndex selectIndex = info_model_.mapFromSource(proxy_model_.mapFromSource(source_model_.index(idx, 0)));
508 /* Proxy model has not masked out the interface */
509 if (selectIndex.isValid())
510 source_model_.updateStatistic(idx);
512 #endif
515 void InterfaceFrame::showRunOnFile(void)
517 ui->warningLabel->setText("Interfaces not loaded on startup (run on capture file). Go to Capture -> Refresh Interfaces to load.");
520 void InterfaceFrame::showContextMenu(QPoint pos)
522 QMenu * ctx_menu = new QMenu(this);
523 // Work around QTBUG-106718. For some reason Qt::WA_DeleteOnClose +
524 // Qt::QueuedConnection don't work here.
525 QObject::connect(ctx_menu, &QMenu::triggered, ctx_menu, &QMenu::deleteLater);
527 ctx_menu->addAction(tr("Start capture"), this, [=] () {
528 QStringList ifaces;
529 QModelIndexList selIndices = ui->interfaceTree->selectionModel()->selectedIndexes();
530 foreach(QModelIndex idx, selIndices)
532 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(idx));
533 if (realIndex.column() != IFTREE_COL_NAME)
534 realIndex = realIndex.sibling(realIndex.row(), IFTREE_COL_NAME);
535 QString name = realIndex.data(Qt::DisplayRole).toString();
536 if (! ifaces.contains(name))
537 ifaces << name;
540 startCapture(ifaces);
543 ctx_menu->addSeparator();
545 QModelIndex actIndex = ui->interfaceTree->indexAt(pos);
546 QModelIndex realIndex = proxy_model_.mapToSource(info_model_.mapToSource(actIndex));
547 bool isHidden = realIndex.sibling(realIndex.row(), IFTREE_COL_HIDDEN).data(Qt::UserRole).toBool();
548 QAction * hideAction = ctx_menu->addAction(tr("Hide Interface"), this, [=] () {
549 /* Attention! Only realIndex.row is a 1:1 correlation to all_ifaces */
550 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, realIndex.row());
551 device->hidden = ! device->hidden;
552 mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged);
554 hideAction->setCheckable(true);
555 hideAction->setChecked(isHidden);
557 ctx_menu->popup(ui->interfaceTree->mapToGlobal(pos));
560 void InterfaceFrame::on_warningLabel_linkActivated(const QString &link)
562 if (link.compare(no_capture_link) == 0) {
563 recent.sys_warn_if_no_capture = false;
564 resetInterfaceTreeDisplay();
565 } else {
566 QDesktopServices::openUrl(QUrl(link));