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