Commit be88b542 authored by David Redondo's avatar David Redondo 🏎

Add a network backend that uses rtnetlink

Queries information about current network devices using the rtnetlink kernel
interface. This exposed all the info except a pretty name for the current
connection and wireless strength. We can use this as a fallback on Linux if
NetworkManager is not available.
Because this is our fallback on Linux libnl is now required (from recommended).
BUG:425992
parent 6bc094b2
......@@ -68,6 +68,15 @@ set_package_properties(Sensors PROPERTIES
TYPE OPTIONAL
PURPOSE "Allows to show sensor information")
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
find_package(NL)
set_package_properties(NL PROPERTIES
TYPE REQUIRED
PURPOSE "Used for gathering socket info via the sock_diag netlink subsystem and for the network
plugin when NetworkManagerQt is not available."
)
endif()
find_package(libpcap)
set_package_properties(
libpcap PROPERTIES
......@@ -75,12 +84,7 @@ set_package_properties(
PURPOSE "libpcap is used for per-application network usage."
)
find_package(NL)
set_package_properties(NL PROPERTIES
TYPE RECOMMENDED
PURPOSE "Used for gathering socket info via the sock_diag netlink subsystem."
)
if(libpcap_FOUND AND NL_FOUND)
if(libpcap_FOUND )
set(BUILD_NETWORK_PLUGIN TRUE)
endif()
......
......@@ -23,7 +23,7 @@ else()
)
find_package(PkgConfig)
pkg_check_modules(NL3 libnl-3.0)
pkg_check_modules(NL3 libnl-3.0 libnl-route-3.0)
if(NOT NL3_FOUND)
pkg_search_module(NL2 libnl-2.0)
endif()
......@@ -55,10 +55,21 @@ else()
PATHS
$(SEARCHPATHS)
)
find_library(NLROUTE_LIBRARY
NAMES
nl-route-3 nl-route
PATH_SUFFIXES
lib64 lib
HINTS
"${NL3_libnl-route-3.0_LIBDIR}"
"${NL2_LIBDIR}"
PATHS
$(SEARCHPATHS)
)
#
# If we don't have all of those libraries, we can't use libnl.
#
if(NL3_LIBRARY)
if(NL3_LIBRARY AND NLROUTE_LIBRARY)
set(NL_LIBRARY ${NL3_LIBRARY})
if(NL3_INCLUDE_DIR)
# NL2 and NL3 are similar and just affect how the version is reported in
......@@ -110,7 +121,7 @@ INCLUDE(FindPackageHandleStandardArgs)
FIND_PACKAGE_HANDLE_STANDARD_ARGS(NL DEFAULT_MSG NL_LIBRARY NL_INCLUDE_DIR)
IF(NL_FOUND)
set(NL_LIBRARIES ${NL_LIBRARY})
set(NL_LIBRARIES ${NL_LIBRARY} ${NLROUTE_LIBRARY})
set(NL_INCLUDE_DIRS ${NL_INCLUDE_DIR})
set(HAVE_LIBNL 1)
else()
......
......@@ -11,10 +11,15 @@ if (KF5NetworkManagerQt_FOUND)
endif()
add_library(ksysguard_globalplugin_network MODULE ${KSYSGUARD_NETWORK_PLUGIN_SOURCES})
target_link_libraries(ksysguard_globalplugin_network Qt5::Core Qt5::Gui Qt5::DBus KSysGuard::StatsBackend KF5::CoreAddons KF5::I18n)
target_link_libraries(ksysguard_globalplugin_network PRIVATE Qt5::Core Qt5::Gui Qt5::DBus KSysGuard::StatsBackend KF5::CoreAddons KF5::I18n)
if (KF5NetworkManagerQt_FOUND)
target_link_libraries(ksysguard_globalplugin_network KF5::NetworkManagerQt)
target_link_libraries(ksysguard_globalplugin_network PRIVATE KF5::NetworkManagerQt)
endif()
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
target_sources(ksysguard_globalplugin_network PRIVATE RtNetlinkBackend.cpp)
target_link_libraries(ksysguard_globalplugin_network PRIVATE ${NL_LIBRARIES})
target_include_directories(ksysguard_globalplugin_network PRIVATE ${NL_INCLUDE_DIRS})
endif()
install(TARGETS ksysguard_globalplugin_network DESTINATION ${KDE_INSTALL_PLUGINDIR}/ksysguard)
......@@ -22,6 +22,7 @@ public:
virtual bool isSupported() = 0;
virtual void start() = 0;
virtual void stop() = 0;
virtual void update() {};
Q_SIGNAL void deviceAdded(NetworkDevice *device);
Q_SIGNAL void deviceRemoved(NetworkDevice *device);
......
......@@ -16,8 +16,6 @@ public:
NetworkDevice(const QString& id, const QString& name);
~NetworkDevice() override = default;
virtual void update() = 0;
protected:
SensorProperty *m_networkSensor;
SensorProperty *m_signalSensor;
......
......@@ -27,7 +27,7 @@ public:
NetworkManagerDevice(const QString &id, QSharedPointer<NetworkManager::Device> device);
~NetworkManagerDevice() override;
void update() override;
void update();
private:
void updateWifi(NetworkManager::WirelessDevice *device);
......
......@@ -33,6 +33,9 @@
#ifdef NETWORKMANAGER_FOUND
#include "NetworkManagerBackend.h"
#endif
#ifdef Q_OS_LINUX
#include "RtNetlinkBackend.h"
#endif
class NetworkPrivate
{
......@@ -58,6 +61,9 @@ NetworkPlugin::NetworkPlugin(QObject *parent, const QVariantList &args)
std::vector<creationFunction> backendFunctions;
#ifdef NETWORKMANAGER_FOUND
backendFunctions.emplace_back([](NetworkPlugin *parent) -> NetworkBackend* {return new NetworkManagerBackend(parent);});
#endif
#ifdef Q_OS_LINUX
backendFunctions.emplace_back([](NetworkPlugin *parent) -> NetworkBackend* {return new RtNetlinkBackend(parent);});
#endif
for (auto func : backendFunctions) {
auto backend = func(this);
......@@ -88,6 +94,13 @@ void NetworkPlugin::onDeviceRemoved(NetworkDevice *device)
d->container->removeObject(device);
}
void NetworkPlugin::update()
{
if (d->backend) {
d->backend->update();
}
}
NetworkPlugin::~NetworkPlugin() = default;
K_PLUGIN_CLASS_WITH_JSON(NetworkPlugin, "metadata.json")
......
......@@ -26,6 +26,8 @@ public:
void onDeviceAdded(NetworkDevice *device);
void onDeviceRemoved(NetworkDevice *device);
void update() override;
private:
std::unique_ptr<NetworkPrivate> d;
};
/*
* SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
* SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#include "RtNetlinkBackend.h"
#include <SysFsSensor.h>
#include <QDir>
#include <QFile>
#include <netlink/netlink.h>
#include <netlink/route/addr.h>
#include <netlink/route/link.h>
#include <arpa/inet.h>
#include <linux/if_arp.h>
#include <linux/if_link.h>
#include <linux/rtnetlink.h>
static const QString devicesFolder = QStringLiteral("/sys/class/net");
RtNetlinkDevice::RtNetlinkDevice(const QString &id)
: NetworkDevice(id, id)
{
// Even though we have no sensor, we need to have a name for the grouped text on the front page
// of plasma-systemmonitor
m_networkSensor->setValue(id);
std::array<SensorProperty*, 4> statisticSensors {m_downloadSensor, m_totalDownloadSensor, m_uploadSensor, m_totalUploadSensor};
auto resetStatistics = [this, statisticSensors]() {
if (std::none_of(statisticSensors.begin(), statisticSensors.end(), [](auto property) {return property->isSubscribed();})) {
m_totalDownloadSensor->setValue(0);
m_totalUploadSensor->setValue(0);
}
};
for (auto property : statisticSensors) {
connect(property, &SensorProperty::subscribedChanged, this, resetStatistics);
}
connect(this, &RtNetlinkDevice::disconnected, this, resetStatistics);
}
void RtNetlinkDevice::update(rtnl_link *link, nl_cache *address_cache, qint64 elapsedTime)
{
const bool isConnected = rtnl_link_get_operstate(link) == IF_OPER_UP;
if (isConnected && !m_connected) {
m_connected = isConnected;
Q_EMIT connected();
} else if (!isConnected && m_connected) {
m_connected = isConnected;
Q_EMIT disconnected();
}
if (!m_connected || !isSubscribed()) {
return;
}
const qulonglong downloadedBytes = rtnl_link_get_stat(link, RTNL_LINK_RX_BYTES);
const qulonglong previousDownloadedBytes = m_totalDownloadSensor->value().toULongLong();
if (previousDownloadedBytes != 0) {
m_downloadSensor->setValue((downloadedBytes - previousDownloadedBytes) * 1000 / elapsedTime);
}
m_totalDownloadSensor->setValue(downloadedBytes);
const qulonglong uploadedBytes = rtnl_link_get_stat(link, RTNL_LINK_TX_BYTES);
const qulonglong previousUploadedBytes = m_totalUploadSensor->value().toULongLong();
if (previousUploadedBytes != 0) {
m_uploadSensor->setValue((uploadedBytes - previousUploadedBytes) * 1000 / elapsedTime);
}
m_totalUploadSensor->setValue(uploadedBytes);
m_ipv4Sensor->setValue(QString());
m_ipv6Sensor->setValue(QString());
auto filterAddress = rtnl_addr_alloc();
rtnl_addr_set_ifindex(filterAddress, rtnl_link_get_ifindex(link));
nl_cache_foreach_filter(address_cache, reinterpret_cast<nl_object*>(filterAddress), [] (nl_object *object, void *arg) {
auto self = static_cast<RtNetlinkDevice *>(arg);
rtnl_addr *address = reinterpret_cast<rtnl_addr *>(object);
if (rtnl_addr_get_family(address) == AF_INET && self->m_ipv4Sensor->value().toString().isEmpty()) {
char buffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET, nl_addr_get_binary_addr(rtnl_addr_get_local(address)), buffer, INET_ADDRSTRLEN);
self->m_ipv4Sensor->setValue(QString::fromLatin1(buffer));
} else if (rtnl_addr_get_family(address) == AF_INET6 && self->m_ipv6Sensor->value().toString().isEmpty()) {
char buffer[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, nl_addr_get_binary_addr(rtnl_addr_get_local(address)), buffer, INET6_ADDRSTRLEN);
self->m_ipv6Sensor->setValue(QString::fromLatin1(buffer));
}
}, this);
rtnl_addr_put(filterAddress);
}
RtNetlinkBackend::RtNetlinkBackend(QObject *parent)
: NetworkBackend(parent)
, m_socket(nl_socket_alloc(), nl_socket_free)
{
nl_connect(m_socket.get(), NETLINK_ROUTE);
}
RtNetlinkBackend::~RtNetlinkBackend()
{
qDeleteAll(m_devices);
}
bool RtNetlinkBackend::isSupported()
{
return bool(m_socket);
}
void RtNetlinkBackend::start()
{
if (!m_socket) {
return;
}
update();
}
void RtNetlinkBackend::stop()
{
}
void RtNetlinkBackend::update()
{
const qint64 elapsedTime = m_updateTimer.restart();
nl_cache *link_cache, *address_cache;
int error = rtnl_link_alloc_cache(m_socket.get(), AF_UNSPEC, &link_cache);
if (error != 0) {
qWarning() << nl_geterror(error);
return;
}
error = rtnl_addr_alloc_cache(m_socket.get(), &address_cache);
if (error != 0) {
qWarning() << nl_geterror(error);
return;
}
for (nl_object *object = nl_cache_get_first(link_cache); object != nullptr; object = nl_cache_get_next(object)) {
auto link = reinterpret_cast<rtnl_link *>(object);
if (rtnl_link_get_arptype(link) != ARPHRD_ETHER) {
// FIXME Maybe this is to aggresive? On my machines wifi is also ether
continue;
}
// Hardware devices do have an empty type
if (qstrlen(rtnl_link_get_type(link)) != 0) {
continue;
}
const auto name = QByteArray(rtnl_link_get_name(link));
if (!m_devices.contains(name)) {
auto device = new RtNetlinkDevice(name);
m_devices.insert(name, device);
connect(device, &RtNetlinkDevice::connected, this, [device, this] { Q_EMIT deviceAdded(device); });
connect(device, &RtNetlinkDevice::disconnected, this, [device, this] { Q_EMIT deviceRemoved(device); });
}
m_devices[name]->update(link, address_cache, elapsedTime);
}
nl_cache_free(link_cache);
nl_cache_free(address_cache);
}
/*
* SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
*
* SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
*/
#pragma once
#include "NetworkBackend.h"
#include "NetworkDevice.h"
#include <QElapsedTimer>
#include <netlink/cache.h>
#include <netlink/socket.h>
struct rtnl_addr;
struct rtnl_link;
class RtNetlinkDevice : public NetworkDevice
{
Q_OBJECT
public:
RtNetlinkDevice(const QString &id);
void update(rtnl_link *link, nl_cache *address_cache, qint64 elapsedTime);
Q_SIGNALS:
void connected();
void disconnected();
private:
bool m_connected = false;
};
class RtNetlinkBackend : public NetworkBackend
{
public:
RtNetlinkBackend(QObject *parent);
~RtNetlinkBackend() override;
bool isSupported() override;
void start() override;
void stop() override;
void update() override;
private:
QHash<QByteArray, RtNetlinkDevice *> m_devices;
std::unique_ptr<nl_sock, decltype(&nl_socket_free)> m_socket;
QElapsedTimer m_updateTimer;
};
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment