From e58484231f1547e08f8abea324b392fe17aec784 Mon Sep 17 00:00:00 2001 From: Duke Leto Date: Mon, 8 Feb 2021 16:22:59 -0500 Subject: [PATCH] Render banned peers in the peers tab --- silentdragon.pro | 2 + src/bannedpeerstablemodel.cpp | 128 ++++++++++++++++++++++++++++++++++ src/bannedpeerstablemodel.h | 36 ++++++++++ src/mainwindow.cpp | 93 ++++++++++++++++++++---- src/mainwindow.ui | 31 +++++++- src/peerstablemodel.cpp | 28 -------- src/peerstablemodel.h | 2 - src/rpc.cpp | 49 +++++++++++-- src/rpc.h | 9 +++ 9 files changed, 328 insertions(+), 50 deletions(-) create mode 100644 src/bannedpeerstablemodel.cpp create mode 100644 src/bannedpeerstablemodel.h diff --git a/silentdragon.pro b/silentdragon.pro index a45aa74..a2022cd 100644 --- a/silentdragon.pro +++ b/silentdragon.pro @@ -50,6 +50,7 @@ SOURCES += \ src/senttxstore.cpp \ src/txtablemodel.cpp \ src/peerstablemodel.cpp \ + src/bannedpeerstablemodel.cpp \ src/qrcodelabel.cpp \ src/connection.cpp \ src/fillediconlabel.cpp \ @@ -75,6 +76,7 @@ HEADERS += \ src/settings.h \ src/txtablemodel.h \ src/peerstablemodel.h \ + src/bannedpeerstablemodel.h \ src/senttxstore.h \ src/qrcodelabel.h \ src/connection.h \ diff --git a/src/bannedpeerstablemodel.cpp b/src/bannedpeerstablemodel.cpp new file mode 100644 index 0000000..c25cae9 --- /dev/null +++ b/src/bannedpeerstablemodel.cpp @@ -0,0 +1,128 @@ +// Copyright 2019-2021 The Hush developers +// Released under the GPLv3 +#include "settings.h" +#include "rpc.h" + +BannedPeersTableModel::BannedPeersTableModel(QObject *parent) + : QAbstractTableModel(parent) { + headers << QObject::tr("Address") << QObject::tr("Subnet") << QObject::tr("Banned Until"); +} + +BannedPeersTableModel::~BannedPeersTableModel() { + delete modeldata; +} + +void BannedPeersTableModel::addData(const QList& data) { + bannedPeers = new QList(); + std::copy(data.begin(), data.end(), std::back_inserter(*bannedPeers)); + + updateAllData(); +} + +void BannedPeersTableModel::updateAllData() { + auto newmodeldata = new QList(); + + // Copy peer data so GUI can use it + if (bannedPeers != nullptr) std::copy( bannedPeers->begin(), bannedPeers->end(), std::back_inserter(*newmodeldata)); + + // Sort by banned_until + std::sort(newmodeldata->begin(), newmodeldata->end(), [=] (auto a, auto b) { + return a.banned_until > b.banned_until; // reverse sort + }); + + // And then swap out the modeldata with the new one. + delete modeldata; + modeldata = newmodeldata; + + // do magic + dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1)); + layoutChanged(); +} + +int BannedPeersTableModel::rowCount(const QModelIndex&) const +{ + if (modeldata == nullptr) return 0; + return modeldata->size(); +} + +int BannedPeersTableModel::columnCount(const QModelIndex&) const +{ + return headers.size(); +} + + + QVariant BannedPeersTableModel::data(const QModelIndex &index, int role) const + { + auto dat = modeldata->at(index.row()); + if (role == Qt::DisplayRole) { + switch (index.column()) { + case 0: return dat.address; + case 1: return dat.subnet; + case 2: return QDateTime::fromSecsSinceEpoch(dat.banned_until).toLocalTime().toString(); + } + } + + // we show mask because it's possible to ban ranges of addresses + if (role == Qt::ToolTipRole) { + switch (index.column()) { + case 0: return "Network Address"; + case 1: return "Subnet Mask"; + case 2: return "Banned Until " + QDateTime::fromSecsSinceEpoch(dat.banned_until).toUTC().toString(); + } + } + + //TODO: show different icons for IP vs Tor vs other kinds of connections + /* + if (role == Qt::DecorationRole && index.column() == 0) { + if (!dat.memo.isEmpty()) { + // If the memo is a Payment URI, then show a payment request icon + if (dat.memo.startsWith("hush:")) { + QIcon icon(":/icons/res/paymentreq.gif"); + return QVariant(icon.pixmap(16, 16)); + } else { + // Return the info pixmap to indicate memo + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + return QVariant(icon.pixmap(16, 16)); + } + } else { + // Empty pixmap to make it align + QPixmap p(16, 16); + p.fill(Qt::white); + return QVariant(p); + } + } + */ + + return QVariant(); + } + + +QVariant BannedPeersTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + //if (role == Qt::TextAlignmentRole && section == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); + if (role == Qt::TextAlignmentRole) return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + + if (role == Qt::FontRole) { + QFont f; + f.setBold(true); + return f; + } + + if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { + return headers.at(section); + } + + return QVariant(); +} + +QString BannedPeersTableModel::getAddress(int row) const { + return modeldata->at(row).address.trimmed(); +} + +QString BannedPeersTableModel::getSubnet(int row) const { + return modeldata->at(row).subnet; +} + +qint64 BannedPeersTableModel::getBannedUntil(int row) const { + return modeldata->at(row).banned_until; +} diff --git a/src/bannedpeerstablemodel.h b/src/bannedpeerstablemodel.h new file mode 100644 index 0000000..a317469 --- /dev/null +++ b/src/bannedpeerstablemodel.h @@ -0,0 +1,36 @@ +// Copyright 2019-2021 The Hush developers +// Released under the GPLv3 +#ifndef BANNEDPEERSTABLEMODEL_H +#define BANNEDPEERSTABLEMODEL_H + +#include "precompiled.h" + +struct BannedPeerItem; + +class BannedPeersTableModel: public QAbstractTableModel +{ +public: + BannedPeersTableModel(QObject* parent); + ~BannedPeersTableModel(); + + QString getSubnet(int row) const; + QString getAddress(int row) const; + qint64 getBannedUntil(int row) const; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + + void addData (const QList& data); + +private: + void updateAllData(); + QList* bannedPeers = nullptr; + QList* modeldata = nullptr; + + QList headers; +}; + + +#endif // BANNEDPEERSTABLEMODEL_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ee7b638..54f1513 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -22,7 +22,6 @@ #include "requestdialog.h" #include "websockets.h" - MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) @@ -1128,16 +1127,62 @@ void MainWindow::setupBalancesTab() { }); } +QString peer2ip(QString peer) { + QString ip = ""; + if(peer.contains("[")) { + // this is actually ipv6, grab it all except the port + auto parts = peer.split(":"); + parts[8]=""; // remove port + peer = parts.join(":"); + peer.chop(1); // remove trailing : + } else { + ip = peer.split(":")[0]; + } + return ip; +} + void MainWindow::setupPeersTab() { qDebug() << __FUNCTION__; // Set up context menu on transactions tab ui->peersTable->setContextMenuPolicy(Qt::CustomContextMenu); + ui->bannedPeersTable->setContextMenuPolicy(Qt::CustomContextMenu); + + // Table right click + QObject::connect(ui->bannedPeersTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) { + QModelIndex index = ui->peersTable->indexAt(pos); + if (index.row() < 0) return; + + QMenu menu(this); + + auto bannedPeerModel = dynamic_cast(ui->bannedPeersTable->model()); + QString addr = bannedPeerModel->getAddress(index.row()); + QString ip = peer2ip(addr); + QString subnet = bannedPeerModel->getSubnet(index.row()); + qint64 banned_until = bannedPeerModel->getBannedUntil(index.row()); + + if(!ip.isEmpty()) { + menu.addAction(tr("Copy banned peer IP"), [=] () { + QGuiApplication::clipboard()->setText(ip); + ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000); + }); + } + + // shodan only supports ipv4 addresses *and* we get ipv6 addresses + // in a different format, yay + if(!ip.isEmpty() && !ip.contains(":")) { + menu.addAction(tr("View banned host IP on shodan.io (3rd party service)"), [=] () { + QString url = "https://www.shodan.io/host/" + ip; + qDebug() << "opening " << url; + QDesktopServices::openUrl(QUrl(url)); + }); + } + + menu.exec(ui->bannedPeersTable->viewport()->mapToGlobal(pos)); + }); // Table right click QObject::connect(ui->peersTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) { QModelIndex index = ui->peersTable->indexAt(pos); - // we auto-sort by conntime - //ui->peersTable->setSortingEnabled(true); if (index.row() < 0) return; QMenu menu(this); @@ -1146,23 +1191,15 @@ void MainWindow::setupPeersTab() { QString addr = peerModel->getAddress(index.row()); QString cipher = peerModel->getTLSCipher(index.row()); qint64 asn = peerModel->getASN(index.row()); - QString ip = addr.split(":")[0]; + QString ip = peer2ip(addr); QString as = QString::number(asn); - if(ip.contains("[")) { - // this is actually ipv6, grab it all except the port - auto parts = addr.split(":"); - parts[8]=""; // remove port - ip = parts.join(":"); - ip.chop(1); // remove trailing : - } - menu.addAction(tr("Copy peer address+port"), [=] () { QGuiApplication::clipboard()->setText(addr); ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000); }); - //TODO: support Tor correctly + //TODO: support Tor correctly when v3 lands menu.addAction(tr("Copy peer address"), [=] () { QGuiApplication::clipboard()->setText(ip); ui->statusBar->showMessage(tr("Copied to clipboard"), 3 * 1000); @@ -1197,6 +1234,36 @@ void MainWindow::setupPeersTab() { menu.exec(ui->peersTable->viewport()->mapToGlobal(pos)); }); + + /* + //grep 'BAN THRESHOLD EXCEEDED' ~/.komodo/HUSH3/debug.log + //grep Disconnected ... + QFile debuglog = ""; + +#ifdef Q_OS_LINUX + debuglog = "~/.komodo/HUSH3/debug.log"; +#elif defined(Q_OS_DARWIN) + debuglog = "~/Library/Application Support/Komodo/HUSH3/debug.log"; +#elif defined(Q_OS_WIN64) + // "C:/Users//AppData/Roaming/", + // TODO: get current username + debuglog = "C:/Users//AppData/Roaming/Komodo/HUSH3/debug.log"; +#else + // Bless Your Heart, You Like Danger! + // There are open bounties to port HUSH softtware to OpenBSD and friends: + // git.hush.is/hush/tasks + debuglog = "~/.komodo/HUSH3/debug.log"; +#endif // Q_OS_LINUX + + if(debuglog.exists()) { + qDebug() << __func__ << ": Found debuglog at " << debuglog; + } else { + qDebug() << __func__ << ": No debug.log found"; + } + */ + + //ui->recentlyBannedPeers = "Could not open " + debuglog; + } void MainWindow::setupHushTab() { diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 0cd0e80..9c901f7 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -941,6 +941,17 @@ + + + + + + Current Peers + + + + + @@ -950,18 +961,31 @@ QAbstractItemView::SelectRows - + - + - Looking For Recently Banned Peers ... + Banned Peers + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + + + + diff --git a/src/peerstablemodel.cpp b/src/peerstablemodel.cpp index ec7183a..0be6076 100644 --- a/src/peerstablemodel.cpp +++ b/src/peerstablemodel.cpp @@ -20,34 +20,6 @@ void PeersTableModel::addData(const QList& data) { updateAllData(); } -bool PeersTableModel::exportToCsv(QString fileName) const { - if (!modeldata) - return false; - - QFile file(fileName); - if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) - return false; - - QTextStream out(&file); // we will serialize the data into the file - - // Write headers - for (int i = 0; i < headers.length(); i++) { - out << "\"" << headers[i] << "\","; - } - out << "\"Memo\""; - out << endl; - - // Write out each row - for (int row = 0; row < modeldata->length(); row++) { - for (int col = 0; col < headers.length(); col++) { - out << "\"" << data(index(row, col), Qt::DisplayRole).toString() << "\","; - } - } - - file.close(); - return true; -} - void PeersTableModel::updateAllData() { auto newmodeldata = new QList(); diff --git a/src/peerstablemodel.h b/src/peerstablemodel.h index 1c995ac..08984f5 100644 --- a/src/peerstablemodel.h +++ b/src/peerstablemodel.h @@ -25,8 +25,6 @@ public: unsigned int getBanscore(int row) const; unsigned int getProtocolVersion(int row) const; - bool exportToCsv(QString fileName) const; - int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; diff --git a/src/rpc.cpp b/src/rpc.cpp index 915476a..0322c5e 100644 --- a/src/rpc.cpp +++ b/src/rpc.cpp @@ -27,8 +27,14 @@ RPC::RPC(MainWindow* main) { peersTableModel = new PeersTableModel(ui->peersTable); main->ui->peersTable->setModel(peersTableModel); - // tls cipher is wide - main->ui->peersTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + // tls ciphersuite is wide + main->ui->peersTable->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + + bannedPeersTableModel = new BannedPeersTableModel(ui->bannedPeersTable); + main->ui->bannedPeersTable->setModel(bannedPeersTableModel); + main->ui->bannedPeersTable->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); + + qDebug() << __func__ << "Done settings up TableModels"; // Set up timer to refresh Price priceTimer = new QTimer(main); @@ -36,6 +42,7 @@ RPC::RPC(MainWindow* main) { refreshPrice(); }); priceTimer->start(Settings::priceRefreshSpeed); + qDebug() << __func__ << ": started price refresh at speed=" << Settings::priceRefreshSpeed; // Set up a timer to refresh the UI every few seconds timer = new QTimer(main); @@ -53,6 +60,7 @@ RPC::RPC(MainWindow* main) { }); txTimer->start(Settings::updateSpeed); + qDebug() << __func__ << "Done settings up all timers"; usedAddresses = new QMap(); } @@ -64,6 +72,7 @@ RPC::~RPC() { delete transactionsTableModel; delete balancesTableModel; delete peersTableModel; + delete bannedPeersTableModel; delete utxos; delete allBalances; @@ -246,6 +255,11 @@ void RPC::getPeerInfo(const std::function& cb) { conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); } +void RPC::listBanned(const std::function& cb) { + QString method = "listbanned"; + conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); +} + void RPC::getTransactions(const std::function& cb) { QString method = "listtransactions"; conn->doRPCWithDefaultErrorHandling(makePayload(method), cb); @@ -628,7 +642,7 @@ void RPC::getInfoThenRefresh(bool force) { qint64 solrate = reply.toInt(); //TODO: format decimal - ui->solrate->setText(QString::number(solrate / 1000000) % " MegaSol/s"); + ui->solrate->setText(QString::number((double)solrate / 1000000) % " MegaSol/s"); }); // Get network info @@ -861,6 +875,34 @@ void RPC::refreshPeers() { if (conn == nullptr) return noConnection(); +/* +[ + { + "address": "199.247.28.148/255.255.255.255", + "banned_until": 1612869516 + }, + { + "address": "2001:19f0:5001:d26:5400:3ff:fe18:f6c2/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", + "banned_until": 1612870431 + } +] +*/ + + listBanned([=] (QJsonValue reply) { + QList peerdata; + for (const auto& it : reply.toArray()) { + auto addr = it.toObject()["address"].toString(); + auto bantime = (qint64)it.toObject()["banned_until"].toInt(); + auto parts = addr.split("/"); + auto ip = parts[0]; + auto subnet = parts[1]; + BannedPeerItem peer { ip, subnet, bantime }; + qDebug() << "Adding banned peer with address=" << addr; + peerdata.push_back(peer); + } + bannedPeersTableModel->addData(peerdata); + }); + getPeerInfo([=] (QJsonValue reply) { QList peerdata; @@ -893,7 +935,6 @@ void RPC::refreshPeers() { } //qDebug() << peerdata; - // Update model data, which updates the table view peersTableModel->addData(peerdata); }); diff --git a/src/rpc.h b/src/rpc.h index 88dc4aa..7c13bd5 100644 --- a/src/rpc.h +++ b/src/rpc.h @@ -7,6 +7,7 @@ #include "balancestablemodel.h" #include "txtablemodel.h" #include "peerstablemodel.h" +#include "bannedpeerstablemodel.h" #include "ui_mainwindow.h" #include "mainwindow.h" #include "connection.h" @@ -22,6 +23,12 @@ struct TransactionItem { QString memo; }; +struct BannedPeerItem { + QString address; + QString subnet; + qint64 banned_until; +}; + struct PeerItem { qint64 peerid; QString type; @@ -125,6 +132,7 @@ private: void getTransparentUnspent (const std::function& cb); void getZUnspent (const std::function& cb); void getTransactions (const std::function& cb); + void listBanned (const std::function& cb); void getPeerInfo (const std::function& cb); void getZAddresses (const std::function& cb); void getTAddresses (const std::function& cb); @@ -141,6 +149,7 @@ private: TxTableModel* transactionsTableModel = nullptr; PeersTableModel* peersTableModel = nullptr; + BannedPeersTableModel* bannedPeersTableModel = nullptr; BalancesTableModel* balancesTableModel = nullptr; QTimer* timer;