1 /* interface_tree_model.cpp
2 * Model for the interface data for display in the interface frame
3 *
4 * Wireshark - Network traffic analyzer
5 * By Gerald Combs <gerald@wireshark.org>
6 * Copyright 1998 Gerald Combs
7 *
8 * SPDX-License-Identifier: GPL-2.0-or-later
9 */
10
11 #include "config.h"
12
13 #include <ui/qt/models/interface_tree_model.h>
14
15 #ifdef HAVE_LIBPCAP
16 #include "ui/capture.h"
17 #include "capture/capture-pcap-util.h"
18 #include "capture_opts.h"
19 #include "ui/capture_ui_utils.h"
20 #include "ui/capture_globals.h"
21 #endif
22
23 #include "wsutil/filesystem.h"
24
25 #include <ui/qt/utils/qt_ui_utils.h>
26 #include <ui/qt/utils/stock_icon.h>
27 #include "wireshark_application.h"
28
29 /* Needed for the meta type declaration of QList<int>* */
30 #include <ui/qt/models/sparkline_delegate.h>
31
32 #include "extcap.h"
33
34 const QString InterfaceTreeModel::DefaultNumericValue = QObject::tr("default");
35
36 /**
37 * This is the data model for interface trees. It implies, that the index within
38 * global_capture_opts.all_ifaces is identical to the row. This is always the case, even
39 * when interfaces are hidden by the proxy model. But for this to work, every access
40 * to the index from within the view, has to be filtered through the proxy model.
41 */
InterfaceTreeModel(QObject * parent)42 InterfaceTreeModel::InterfaceTreeModel(QObject *parent) :
43 QAbstractTableModel(parent)
44 #ifdef HAVE_LIBPCAP
45 ,stat_cache_(NULL)
46 #endif
47 {
48 connect(wsApp, &WiresharkApplication::appInitialized, this, &InterfaceTreeModel::interfaceListChanged);
49 connect(wsApp, &WiresharkApplication::localInterfaceListChanged, this, &InterfaceTreeModel::interfaceListChanged);
50 }
51
~InterfaceTreeModel(void)52 InterfaceTreeModel::~InterfaceTreeModel(void)
53 {
54 #ifdef HAVE_LIBPCAP
55 if (stat_cache_) {
56 capture_stat_stop(stat_cache_);
57 stat_cache_ = NULL;
58 }
59 #endif // HAVE_LIBPCAP
60 }
61
interfaceError()62 QString InterfaceTreeModel::interfaceError()
63 {
64 #ifdef HAVE_LIBPCAP
65 //
66 // First, see if there was an error fetching the interfaces.
67 // If so, report it.
68 //
69 if (global_capture_opts.ifaces_err != 0)
70 {
71 return tr(global_capture_opts.ifaces_err_info);
72 }
73
74 //
75 // Otherwise, if there are no rows, there were no interfaces
76 // found.
77 //
78 if (rowCount() == 0)
79 {
80 return tr("No interfaces found.");
81 }
82
83 //
84 // No error. Return an empty string.
85 //
86 return "";
87 #else
88 //
89 // We were built without pcap support, so we have no notion of
90 // local interfaces.
91 //
92 return tr("This version of Wireshark was built without packet capture support.");
93 #endif
94 }
95
rowCount(const QModelIndex &) const96 int InterfaceTreeModel::rowCount(const QModelIndex &) const
97 {
98 #ifdef HAVE_LIBPCAP
99 return (global_capture_opts.all_ifaces ? global_capture_opts.all_ifaces->len : 0);
100 #else
101 /* Currently no interfaces available for libpcap-less builds */
102 return 0;
103 #endif
104 }
105
columnCount(const QModelIndex &) const106 int InterfaceTreeModel::columnCount(const QModelIndex &) const
107 {
108 /* IFTREE_COL_MAX is not being displayed, it is the definition for the maximum numbers of columns */
109 return ((int) IFTREE_COL_MAX);
110 }
111
data(const QModelIndex & index,int role) const112 QVariant InterfaceTreeModel::data(const QModelIndex &index, int role) const
113 {
114 #ifdef HAVE_LIBPCAP
115 bool interfacesLoaded = true;
116 if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len == 0)
117 interfacesLoaded = false;
118
119 if (!index.isValid())
120 return QVariant();
121
122 int row = index.row();
123 InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
124
125 if (interfacesLoaded)
126 {
127 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row);
128
129 /* Data for display in cell */
130 if (role == Qt::DisplayRole)
131 {
132 /* Only the name is being displayed */
133 if (col == IFTREE_COL_NAME)
134 {
135 return QString(device->name);
136 }
137 else if (col == IFTREE_COL_DESCRIPTION)
138 {
139 return QString(device->friendly_name);
140 }
141 else if (col == IFTREE_COL_DISPLAY_NAME)
142 {
143 return QString(device->display_name);
144 }
145 else if (col == IFTREE_COL_PIPE_PATH)
146 {
147 return QString(device->if_info.name);
148 }
149 else if (col == IFTREE_COL_CAPTURE_FILTER)
150 {
151 if (device->cfilter && strlen(device->cfilter) > 0)
152 return html_escape(QString(device->cfilter));
153 }
154 else if (col == IFTREE_COL_EXTCAP_PATH)
155 {
156 return QString(device->if_info.extcap);
157 }
158 else if (col == IFTREE_COL_SNAPLEN)
159 {
160 return device->has_snaplen ? QString::number(device->snaplen) : DefaultNumericValue;
161 }
162 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
163 else if (col == IFTREE_COL_BUFFERLEN)
164 {
165 return QString::number(device->buffer);
166 }
167 #endif
168 else if (col == IFTREE_COL_TYPE)
169 {
170 return QVariant::fromValue((int)device->if_info.type);
171 }
172 else if (col == IFTREE_COL_COMMENT)
173 {
174 QString comment = gchar_free_to_qstring(capture_dev_user_descr_find(device->name));
175 if (comment.length() > 0)
176 return comment;
177 else
178 return QString(device->if_info.vendor_description);
179 }
180 else if (col == IFTREE_COL_DLT)
181 {
182 // XXX - this is duplicated in
183 // InterfaceTreeWidgetItem::updateInterfaceColumns;
184 // it should be done in common code somewhere.
185 QString linkname;
186 if (device->active_dlt == -1)
187 linkname = "Unknown";
188 else {
189 linkname = QObject::tr("DLT %1").arg(device->active_dlt);
190 for (GList *list = device->links; list != Q_NULLPTR; list = gxx_list_next(list)) {
191 link_row *linkr = gxx_list_data(link_row *, list);
192 if (linkr->dlt == device->active_dlt) {
193 linkname = linkr->name;
194 break;
195 }
196 }
197 }
198
199 return linkname;
200 }
201 else
202 {
203 /* Return empty string for every other DisplayRole */
204 return QVariant();
205 }
206 }
207 else if (role == Qt::CheckStateRole)
208 {
209 if (col == IFTREE_COL_HIDDEN)
210 {
211 /* Hidden is a de-selection, therefore inverted logic here */
212 return device->hidden ? Qt::Unchecked : Qt::Checked;
213 }
214 else if (col == IFTREE_COL_PROMISCUOUSMODE)
215 {
216 return device->pmode ? Qt::Checked : Qt::Unchecked;
217 }
218 #ifdef HAVE_PCAP_CREATE
219 else if (col == IFTREE_COL_MONITOR_MODE)
220 {
221 return device->monitor_mode_enabled ? Qt::Checked : Qt::Unchecked;
222 }
223 #endif
224 }
225 /* Used by SparkLineDelegate for loading the data for the statistics line */
226 else if (role == Qt::UserRole)
227 {
228 if (col == IFTREE_COL_STATS)
229 {
230 if (points.contains(device->name))
231 return QVariant::fromValue(points[device->name]);
232 }
233 else if (col == IFTREE_COL_HIDDEN)
234 {
235 return QVariant::fromValue((bool)device->hidden);
236 }
237 }
238 /* Displays the configuration icon for extcap interfaces */
239 else if (role == Qt::DecorationRole)
240 {
241 if (col == IFTREE_COL_EXTCAP)
242 {
243 if (device->if_info.type == IF_EXTCAP)
244 return QIcon(StockIcon("x-capture-options"));
245 }
246 }
247 else if (role == Qt::TextAlignmentRole)
248 {
249 if (col == IFTREE_COL_EXTCAP)
250 {
251 return Qt::AlignRight;
252 }
253 }
254 /* Displays the tooltip for each row */
255 else if (role == Qt::ToolTipRole)
256 {
257 return toolTipForInterface(row);
258 }
259 }
260 #else
261 Q_UNUSED(index)
262 Q_UNUSED(role)
263 #endif
264
265 return QVariant();
266 }
267
headerData(int section,Qt::Orientation orientation,int role) const268 QVariant InterfaceTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
269 {
270 if (orientation == Qt::Horizontal)
271 {
272 if (role == Qt::DisplayRole)
273 {
274 if (section == IFTREE_COL_HIDDEN)
275 {
276 return tr("Show");
277 }
278 else if (section == IFTREE_COL_NAME)
279 {
280 return tr("Interface Name");
281 }
282 else if (section == IFTREE_COL_DESCRIPTION)
283 {
284 return tr("Friendly Name");
285 }
286 else if (section == IFTREE_COL_DISPLAY_NAME)
287 {
288 return tr("Friendly Name");
289 }
290 else if (section == IFTREE_COL_PIPE_PATH)
291 {
292 return tr("Local Pipe Path");
293 }
294 else if (section == IFTREE_COL_COMMENT)
295 {
296 return tr("Comment");
297 }
298 else if (section == IFTREE_COL_DLT)
299 {
300 return tr("Link-Layer Header");
301 }
302 else if (section == IFTREE_COL_PROMISCUOUSMODE)
303 {
304 return tr("Promiscuous");
305 }
306 else if (section == IFTREE_COL_SNAPLEN)
307 {
308 return tr("Snaplen (B)");
309 }
310 #ifdef CAN_SET_CAPTURE_BUFFER_SIZE
311 else if (section == IFTREE_COL_BUFFERLEN)
312 {
313 return tr("Buffer (MB)");
314 }
315 #endif
316 #ifdef HAVE_PCAP_CREATE
317 else if (section == IFTREE_COL_MONITOR_MODE)
318 {
319 return tr("Monitor Mode");
320 }
321 #endif
322 else if (section == IFTREE_COL_CAPTURE_FILTER)
323 {
324 return tr("Capture Filter");
325 }
326 }
327 }
328
329 return QVariant();
330 }
331
getColumnContent(int idx,int col,int role)332 QVariant InterfaceTreeModel::getColumnContent(int idx, int col, int role)
333 {
334 return InterfaceTreeModel::data(index(idx, col), role);
335 }
336
337 #ifdef HAVE_PCAP_REMOTE
isRemote(int idx)338 bool InterfaceTreeModel::isRemote(int idx)
339 {
340 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
341 if (device->remote_opts.src_type == CAPTURE_IFREMOTE)
342 return true;
343 return false;
344 }
345 #endif
346
347 /**
348 * The interface list has changed. global_capture_opts.all_ifaces may have been reloaded
349 * or changed with current data. beginResetModel() and endResetModel() will signalize the
350 * proxy model and the view, that the data has changed and the view has to reload
351 */
interfaceListChanged()352 void InterfaceTreeModel::interfaceListChanged()
353 {
354 emit beginResetModel();
355
356 points.clear();
357
358 emit endResetModel();
359 }
360
361 /*
362 * Displays the tooltip code for the given device index.
363 */
toolTipForInterface(int idx) const364 QVariant InterfaceTreeModel::toolTipForInterface(int idx) const
365 {
366 #ifdef HAVE_LIBPCAP
367 if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
368 return QVariant();
369
370 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
371
372 QString tt_str = "<p>";
373 if (device->no_addresses > 0)
374 {
375 tt_str += QString("%1: %2")
376 .arg(device->no_addresses > 1 ? tr("Addresses") : tr("Address"))
377 .arg(html_escape(device->addresses))
378 .replace('\n', ", ");
379 }
380 else if (device->if_info.type == IF_EXTCAP)
381 {
382 tt_str = QString(tr("Extcap interface: %1")).arg(get_basename(device->if_info.extcap));
383 }
384 else
385 {
386 tt_str = tr("No addresses");
387 }
388 tt_str += "<br/>";
389
390 QString cfilter = device->cfilter;
391 if (cfilter.isEmpty())
392 {
393 tt_str += tr("No capture filter");
394 }
395 else
396 {
397 tt_str += QString("%1: %2")
398 .arg(tr("Capture filter"))
399 .arg(html_escape(cfilter));
400 }
401 tt_str += "</p>";
402
403 return tt_str;
404 #else
405 Q_UNUSED(idx)
406
407 return QVariant();
408 #endif
409 }
410
411 #ifdef HAVE_LIBPCAP
stopStatistic()412 void InterfaceTreeModel::stopStatistic()
413 {
414 if (stat_cache_)
415 {
416 capture_stat_stop(stat_cache_);
417 stat_cache_ = NULL;
418 }
419 }
420 #endif
421
updateStatistic(unsigned int idx)422 void InterfaceTreeModel::updateStatistic(unsigned int idx)
423 {
424 #ifdef HAVE_LIBPCAP
425 if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
426 return;
427
428 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
429
430 if (device->if_info.type == IF_PIPE)
431 return;
432
433 if (!stat_cache_)
434 {
435 // Start gathering statistics using dumpcap
436 // We crash (on macOS at least) if we try to do this from ::showEvent.
437 stat_cache_ = capture_stat_start(&global_capture_opts);
438 }
439
440 struct pcap_stat stats;
441 unsigned diff = 0;
442
443 if (capture_stats(stat_cache_, device->name, &stats))
444 {
445 if ((int)(stats.ps_recv - device->last_packets) >= 0)
446 {
447 diff = stats.ps_recv - device->last_packets;
448 device->packet_diff = diff;
449 }
450 device->last_packets = stats.ps_recv;
451 }
452
453 points[device->name].append(diff);
454 emit dataChanged(index(idx, IFTREE_COL_STATS), index(idx, IFTREE_COL_STATS));
455 #else
456 Q_UNUSED(idx)
457 #endif
458 }
459
getPoints(int idx,PointList * pts)460 void InterfaceTreeModel::getPoints(int idx, PointList *pts)
461 {
462 #ifdef HAVE_LIBPCAP
463 if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
464 return;
465
466 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
467 if (points.contains(device->name))
468 pts->append(points[device->name]);
469 #else
470 Q_UNUSED(idx)
471 Q_UNUSED(pts)
472 #endif
473 }
474
selectedDevices()475 QItemSelection InterfaceTreeModel::selectedDevices()
476 {
477 QItemSelection mySelection;
478 #ifdef HAVE_LIBPCAP
479 for (int idx = 0; idx < rowCount(); idx++)
480 {
481 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
482
483 if (device->selected)
484 {
485 QModelIndex selectIndex = index(idx, 0);
486 mySelection.merge(
487 QItemSelection(selectIndex, index(selectIndex.row(), columnCount() - 1)),
488 QItemSelectionModel::SelectCurrent
489 );
490 }
491 }
492 #endif
493 return mySelection;
494 }
495
updateSelectedDevices(QItemSelection sourceSelection)496 bool InterfaceTreeModel::updateSelectedDevices(QItemSelection sourceSelection)
497 {
498 bool selectionHasChanged = false;
499 #ifdef HAVE_LIBPCAP
500 QList<int> selectedIndices;
501
502 QItemSelection::const_iterator it = sourceSelection.constBegin();
503 while (it != sourceSelection.constEnd())
504 {
505 QModelIndexList indeces = ((QItemSelectionRange) (*it)).indexes();
506
507 QModelIndexList::const_iterator cit = indeces.constBegin();
508 while (cit != indeces.constEnd())
509 {
510 QModelIndex index = (QModelIndex) (*cit);
511 if (! selectedIndices.contains(index.row()))
512 {
513 selectedIndices.append(index.row());
514 }
515 ++cit;
516 }
517 ++it;
518 }
519
520 global_capture_opts.num_selected = 0;
521
522 for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
523 {
524 interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
525 if (selectedIndices.contains(idx))
526 {
527 if (! device->selected)
528 selectionHasChanged = true;
529 device->selected = TRUE;
530 global_capture_opts.num_selected++;
531 } else {
532 if (device->selected)
533 selectionHasChanged = true;
534 device->selected = FALSE;
535 }
536 }
537 #else
538 Q_UNUSED(sourceSelection)
539 #endif
540 return selectionHasChanged;
541 }
542