File indexing completed on 2025-10-26 05:13:45

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 void NetstatHelper::query()
0017 {
0018     if (m_executableProcess) {
0019         // If we get queried again before the process finished, we'll forcefully terminate the process and start from scratch
0020         // without host name resolution to hopefully speed things up.
0021         stopProcess();
0022     }
0023 
0024     m_executableProcess = new QProcess(this);
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     m_executableProcess->start(executable, netstatArgs, QIODevice::ReadOnly);
0039 
0040     qDebug() << "Running process";
0041 }
0042 
0043 void NetstatHelper::stopProcess()
0044 {
0045     qDebug() << "Timing out!";
0046     m_hasTimeoutError = true;
0047 
0048     m_executableProcess->disconnect();
0049     m_executableProcess->kill();
0050     m_executableProcess->deleteLater();
0051     m_executableProcess = nullptr;
0052 }
0053 
0054 void NetstatHelper::stepExecuteFinished(int exitCode)
0055 {
0056     m_hasError = false;
0057 
0058     if (0 != exitCode) {
0059         m_hasError = true;
0060         m_errorString = m_executableProcess->readAllStandardError();
0061     } else {
0062         QList<QStringList> result = parseSSOutput(m_executableProcess->readAllStandardOutput());
0063         Q_EMIT queryFinished(result);
0064     }
0065 
0066     m_executableProcess->deleteLater();
0067     m_executableProcess = nullptr;
0068 }
0069 
0070 bool NetstatHelper::hasError() const
0071 {
0072     return m_hasError;
0073 }
0074 
0075 QString NetstatHelper::errorString() const
0076 {
0077     return m_errorString;
0078 }
0079 
0080 QList<QStringList> NetstatHelper::parseSSOutput(const QByteArray &netstatOutput)
0081 {
0082     QString rawOutput = netstatOutput;
0083     QStringList outputLines = rawOutput.split(QStringLiteral("\n"));
0084 
0085     QList<QStringList> connections;
0086 
0087     // discard lines.
0088     while (outputLines.size()) {
0089         if (outputLines.first().indexOf(QLatin1String("Recv-Q"))) {
0090             outputLines.removeFirst();
0091             break;
0092         }
0093         outputLines.removeFirst();
0094     }
0095 
0096     // can't easily parse because of the spaces in Local and Peer AddressPort.
0097     QStringList headerLines = {
0098         QStringLiteral("Netid"),
0099         QStringLiteral("State"),
0100         QStringLiteral("Recv-Q"),
0101         QStringLiteral("Send-Q"),
0102         QStringLiteral("Local Address:Port"),
0103         QStringLiteral("Peer Address:Port"),
0104         QStringLiteral("Process"),
0105     };
0106 
0107     // Extract Information
0108     for (const auto &line : std::as_const(outputLines)) {
0109         QStringList values = line.split(QLatin1Char(' '), Qt::SkipEmptyParts);
0110         if (values.isEmpty()) {
0111             continue;
0112         }
0113 
0114         // Some lines lack one or two values.
0115         while (values.size() < headerLines.size()) {
0116             values.append(QString());
0117         }
0118 
0119         QString appName;
0120         QString pid;
0121         if (values[6].size()) {
0122             values[6].remove(0, QStringLiteral("users:((").size());
0123             values[6].chop(QStringLiteral("))").size());
0124 
0125             QStringList substrings = values[6].split(',');
0126             appName = substrings[0].remove(QStringLiteral("\""));
0127             pid = substrings[1].split('=')[1];
0128         }
0129 
0130         /* Insertion order needs to match the Model Columns:
0131             ProtocolRole = Qt::UserRole + 1,
0132             LocalAddressRole,
0133             ForeignAddressRole,
0134             StatusRole,
0135             PidRole,
0136             ProgramRole
0137         */
0138         QStringList connection{
0139             values[0], // NetId
0140             values[4], // Local Address
0141             values[5], // Peer Address,
0142             values[1], // State
0143             pid,
0144             appName,
0145         };
0146 
0147         connections.append(connection);
0148     }
0149 
0150     return connections;
0151 }
0152 
0153 QString NetstatHelper::extractAndStrip(const QString &src, int index, int size)
0154 {
0155     QString str = src.mid(index, size);
0156     str.remove(QLatin1String(" "));
0157     return str;
0158 }