File indexing completed on 2024-05-12 17:07:35

0001 // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0002 // SPDX-FileCopyrightText: 2018 Alexis Lopes Zubeta <contact@azubieta.net>
0003 // SPDX-FileCopyrightText: 2020 Tomaz Canabrava <tcanabrava@kde.org>
0004 
0005 #include "netstathelper.h"
0006 
0007 #include <KLocalizedString>
0008 
0009 #include <QDebug>
0010 #include <QStandardPaths>
0011 #include <QStringList>
0012 #include <QTimer>
0013 
0014 Q_LOGGING_CATEGORY(NetstatHelperDebug, "netstat.helper")
0015 
0016 NetstatHelper::NetstatHelper()
0017 {
0018 }
0019 
0020 void NetstatHelper::query()
0021 {
0022     m_executableProcess = new QProcess();
0023     m_processKillerTimer = new QTimer();
0024     m_processKillerTimer->setSingleShot(true);
0025 
0026     /* parameters passed to ss
0027      *  -r, --resolve       resolve host names
0028      *  -a, --all           display all sockets
0029      *  -p, --processes     show process using socket
0030      *  -u, --udp           display only UDP sockets
0031      *  -t, --tcp           display only TCP sockets
0032      */
0033 
0034     const QStringList netstatArgs(m_hasTimeoutError ? QStringList({"-tuap"}) : QStringList({"-tuapr"}));
0035     const QString executable = QStringLiteral("ss");
0036 
0037     connect(m_executableProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, &NetstatHelper::stepExecuteFinished);
0038 
0039     connect(m_processKillerTimer, &QTimer::timeout, this, &NetstatHelper::stopProcess);
0040 
0041     m_executableProcess->start(executable, netstatArgs, QIODevice::ReadOnly);
0042 
0043     // We wait 10 seconds before killing the process.
0044     m_processKillerTimer->start(10000);
0045     qDebug() << "Running process";
0046 }
0047 
0048 void NetstatHelper::stopProcess()
0049 {
0050     qDebug() << "Timing out!";
0051     m_hasTimeoutError = true;
0052 
0053     m_processKillerTimer->stop();
0054     m_processKillerTimer->deleteLater();
0055     m_processKillerTimer = nullptr;
0056 
0057     m_executableProcess->disconnect();
0058     m_executableProcess->kill();
0059     m_executableProcess->deleteLater();
0060     m_executableProcess = nullptr;
0061 }
0062 
0063 void NetstatHelper::stepExecuteFinished(int exitCode)
0064 {
0065     // No need to kill anything - we had success executing the process.
0066     if (!m_processKillerTimer) {
0067         return;
0068     }
0069 
0070     if (m_processKillerTimer != nullptr) {
0071         m_processKillerTimer->stop();
0072         m_processKillerTimer->deleteLater();
0073         m_processKillerTimer = nullptr;
0074     }
0075 
0076     m_hasError = false;
0077 
0078     if (0 != exitCode) {
0079         m_hasError = true;
0080         m_errorString = m_executableProcess->readAllStandardError();
0081     } else {
0082         QVector<QStringList> result = parseSSOutput(m_executableProcess->readAllStandardOutput());
0083         Q_EMIT queryFinished(result);
0084         ;
0085     }
0086 
0087     m_executableProcess->deleteLater();
0088     m_executableProcess = nullptr;
0089 }
0090 
0091 bool NetstatHelper::hasError() const
0092 {
0093     return m_hasError;
0094 }
0095 
0096 QString NetstatHelper::errorString() const
0097 {
0098     return m_errorString;
0099 }
0100 
0101 QVector<QStringList> NetstatHelper::parseSSOutput(const QByteArray &netstatOutput)
0102 {
0103     QString rawOutput = netstatOutput;
0104     QStringList outputLines = rawOutput.split(QStringLiteral("\n"));
0105 
0106     QVector<QStringList> connections;
0107 
0108     // discard lines.
0109     while (outputLines.size()) {
0110         if (outputLines.first().indexOf(QLatin1String("Recv-Q"))) {
0111             outputLines.removeFirst();
0112             break;
0113         }
0114         outputLines.removeFirst();
0115     }
0116 
0117     // can't easily parse because of the spaces in Local and Peer AddressPort.
0118     QStringList headerLines = {
0119         QStringLiteral("Netid"),
0120         QStringLiteral("State"),
0121         QStringLiteral("Recv-Q"),
0122         QStringLiteral("Send-Q"),
0123         QStringLiteral("Local Address:Port"),
0124         QStringLiteral("Peer Address:Port"),
0125         QStringLiteral("Process"),
0126     };
0127 
0128     // Extract Information
0129     for (const auto &line : qAsConst(outputLines)) {
0130         QStringList values = line.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0131         if (values.isEmpty()) {
0132             continue;
0133         }
0134 
0135         // Some lines lack one or two values.
0136         while (values.size() < headerLines.size()) {
0137             values.append(QString());
0138         }
0139 
0140         QString appName;
0141         QString pid;
0142         if (values[6].size()) {
0143             values[6].remove(0, QStringLiteral("users:((").size());
0144             values[6].chop(QStringLiteral("))").size());
0145 
0146             QStringList substrings = values[6].split(',');
0147             appName = substrings[0].remove(QStringLiteral("\""));
0148             pid = substrings[1].split('=')[1];
0149         }
0150 
0151         /* Insertion order needs to match the Model Columns:
0152             ProtocolRole = Qt::UserRole + 1,
0153             LocalAddressRole,
0154             ForeignAddressRole,
0155             StatusRole,
0156             PidRole,
0157             ProgramRole
0158         */
0159         QStringList connection{
0160             values[0], // NetId
0161             values[4], // Local Address
0162             values[5], // Peer Address,
0163             values[1], // State
0164             pid,
0165             appName,
0166         };
0167 
0168         connections.append(connection);
0169     }
0170 
0171     return connections;
0172 }
0173 
0174 QString NetstatHelper::extractAndStrip(const QString &src, int index, int size)
0175 {
0176     QString str = src.mid(index, size);
0177     str.remove(QLatin1String(" "));
0178     return str;
0179 }