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 */ 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 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 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 96 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 106 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 112 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 268 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 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 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 */ 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 */ 364 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 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 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 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 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 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