File indexing completed on 2024-05-19 05:30:20

0001 /*
0002  * SPDX-FileCopyrightText: 2020 Arjen Hiemstra <ahiemstra@heimr.nl>
0003  * SPDX-FileCopyrightText: 2020 David Redondo <kde@david-redondo.de>
0004  * SPDX-FileCopyrightText: 2021 Alessio Bonfiglio <alessio.bonfiglio@mail.polimi.it>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007  */
0008 
0009 #include "RtNetlinkBackend.h"
0010 
0011 #include <systemstats/SysFsSensor.h>
0012 
0013 #include <QNetworkAddressEntry>
0014 #include <QHostAddress>
0015 #include <QString>
0016 #include <array>
0017 
0018 #include <netlink/netlink.h>
0019 #include <netlink/route/addr.h>
0020 #include <netlink/route/route.h>
0021 #include <netlink/route/link.h>
0022 
0023 #include <arpa/inet.h>
0024 #include <linux/if_arp.h>
0025 #include <linux/if_link.h>
0026 #include <linux/rtnetlink.h>
0027 
0028 static const QString devicesFolder = QStringLiteral("/sys/class/net");
0029 
0030 RtNetlinkDevice::RtNetlinkDevice(const QString &id)
0031     : NetworkDevice(id, id)
0032 {
0033     // Even though we have no sensor, we need to have a name for the grouped text on the front page
0034     // of plasma-systemmonitor
0035     m_networkSensor->setValue(id);
0036 
0037     std::array<KSysGuard::SensorProperty*, 6> statisticSensors {m_downloadSensor, m_downloadBitsSensor, m_totalDownloadSensor, m_uploadSensor, m_uploadBitsSensor, m_totalUploadSensor};
0038     auto resetStatistics = [this, statisticSensors]() {
0039         if (std::none_of(statisticSensors.begin(), statisticSensors.end(), [](auto property) {return property->isSubscribed();})) {
0040             m_totalDownloadSensor->setValue(0);
0041             m_totalUploadSensor->setValue(0);
0042         }
0043     };
0044     for (auto property : statisticSensors) {
0045         connect(property, &KSysGuard::SensorProperty::subscribedChanged, this, resetStatistics);
0046     }
0047     connect(this, &RtNetlinkDevice::disconnected, this, resetStatistics);
0048 
0049     // FIXME: find the currently used dns servers
0050     m_ipv4DNSSensor->setValue(QString{});
0051     m_ipv6DNSSensor->setValue(QString{});
0052 }
0053 
0054 void RtNetlinkDevice::update(rtnl_link *link, nl_cache *address_cache, nl_cache *route_cache, qint64 elapsedTime)
0055 {
0056     const bool isConnected = rtnl_link_get_operstate(link) == IF_OPER_UP;
0057     if (isConnected && !m_connected) {
0058         m_connected = isConnected;
0059         Q_EMIT connected();
0060     } else if (!isConnected && m_connected) {
0061         m_connected = isConnected;
0062         Q_EMIT disconnected();
0063     }
0064 
0065     if (!m_connected || !isSubscribed()) {
0066         return;
0067     }
0068 
0069     const qulonglong downloadedBytes = rtnl_link_get_stat(link, RTNL_LINK_RX_BYTES);
0070     const qulonglong previousDownloadedBytes = m_totalDownloadSensor->value().toULongLong();
0071     if (previousDownloadedBytes != 0) {
0072         m_downloadSensor->setValue((downloadedBytes - previousDownloadedBytes) * 1000 / elapsedTime);
0073         m_downloadBitsSensor->setValue((downloadedBytes - previousDownloadedBytes) * 1000 / elapsedTime * 8);
0074     }
0075     m_totalDownloadSensor->setValue(downloadedBytes);
0076 
0077     const qulonglong uploadedBytes = rtnl_link_get_stat(link, RTNL_LINK_TX_BYTES);
0078     const qulonglong previousUploadedBytes = m_totalUploadSensor->value().toULongLong();
0079     if (previousUploadedBytes != 0) {
0080         m_uploadSensor->setValue((uploadedBytes - previousUploadedBytes) * 1000 / elapsedTime);
0081         m_uploadBitsSensor->setValue((uploadedBytes - previousUploadedBytes) * 1000 / elapsedTime * 8);
0082     }
0083     m_totalUploadSensor->setValue(uploadedBytes);
0084 
0085     m_ipv4Sensor->setValue(QString());
0086     m_ipv4SubnetMaskSensor->setValue(QString{});
0087     m_ipv4WithPrefixLengthSensor->setValue(QString{});
0088     m_ipv6Sensor->setValue(QString());
0089     m_ipv6SubnetMaskSensor->setValue(QString{});
0090     m_ipv6WithPrefixLengthSensor->setValue(QString{});
0091     auto filterAddress = rtnl_addr_alloc();
0092     rtnl_addr_set_ifindex(filterAddress, rtnl_link_get_ifindex(link));
0093     nl_cache_foreach_filter(address_cache, reinterpret_cast<nl_object*>(filterAddress), [] (nl_object *object, void *arg) {
0094         auto self = static_cast<RtNetlinkDevice *>(arg);
0095         rtnl_addr *address = reinterpret_cast<rtnl_addr *>(object);
0096         int prefixLen = rtnl_addr_get_prefixlen(address);
0097         auto dummyAddress = QNetworkAddressEntry(); // conveniently used to compute the subnet mask
0098         if (rtnl_addr_get_family(address) == AF_INET) {
0099             if(self->m_ipv4Sensor->value().toString().isEmpty()) {
0100                 char buffer[INET6_ADDRSTRLEN];
0101                 inet_ntop(AF_INET, nl_addr_get_binary_addr(rtnl_addr_get_local(address)), buffer, INET_ADDRSTRLEN);
0102                 auto ipv4 = QString::fromLatin1(buffer);
0103                 self->m_ipv4Sensor->setValue(ipv4);
0104                 if(self->m_ipv4WithPrefixLengthSensor->value().toString().isEmpty()) {
0105                     self->m_ipv4WithPrefixLengthSensor->setValue(static_cast<QString>(ipv4 + '/' + QString::number(prefixLen)));
0106                 }
0107             }
0108             if(self->m_ipv4SubnetMaskSensor->value().toString().isEmpty()) {
0109                 dummyAddress.setIp(QHostAddress::AnyIPv4);
0110                 dummyAddress.setPrefixLength(prefixLen);
0111                 self->m_ipv4SubnetMaskSensor->setValue(dummyAddress.netmask().toString());
0112             }
0113         } else if (rtnl_addr_get_family(address) == AF_INET6) {
0114             if(self->m_ipv6Sensor->value().toString().isEmpty()) {
0115                 char buffer[INET6_ADDRSTRLEN];
0116                 inet_ntop(AF_INET6, nl_addr_get_binary_addr(rtnl_addr_get_local(address)), buffer, INET6_ADDRSTRLEN);
0117                 auto ipv6 = QString::fromLatin1(buffer);
0118                 self->m_ipv6Sensor->setValue(ipv6);
0119                 if(self->m_ipv6WithPrefixLengthSensor->value().toString().isEmpty()) {
0120                     self->m_ipv6WithPrefixLengthSensor->setValue(static_cast<QString>(ipv6 + '/' + QString::number(prefixLen)));
0121                 }
0122             }
0123             if(self->m_ipv6SubnetMaskSensor->value().toString().isEmpty()) {
0124                 dummyAddress.setIp(QHostAddress::AnyIPv6);
0125                 dummyAddress.setPrefixLength(prefixLen);
0126                 self->m_ipv6SubnetMaskSensor->setValue(dummyAddress.netmask().toString());
0127             }
0128         }
0129     }, this);
0130 
0131     m_ipv4GatewaySensor->setValue(QString());
0132     m_ipv6GatewaySensor->setValue(QString());
0133     // The gateway is found using a filter on the destination address with size = 0
0134     auto dst = nl_addr_build(AF_INET, NULL, 0);
0135     auto routeFilter = rtnl_route_alloc();
0136     rtnl_route_set_iif(routeFilter, rtnl_link_get_ifindex(link));
0137     rtnl_route_set_dst(routeFilter, dst);
0138 
0139     nl_cache_foreach_filter(route_cache, reinterpret_cast<nl_object*>(routeFilter), [] (nl_object *object, void *arg) {
0140         auto self = static_cast<RtNetlinkDevice *>(arg);
0141         auto route = reinterpret_cast<rtnl_route *>(object);
0142         if (rtnl_route_get_family(route) == AF_INET && self->m_ipv4GatewaySensor->value().toString().isEmpty()) {
0143             char buffer[INET6_ADDRSTRLEN];
0144             inet_ntop(AF_INET, nl_addr_get_binary_addr(rtnl_route_nh_get_gateway(rtnl_route_nexthop_n(route,0))), buffer, INET_ADDRSTRLEN);
0145             self->m_ipv4GatewaySensor->setValue(QString::fromLatin1(buffer));
0146         } else if (rtnl_route_get_family(route) == AF_INET6 && self->m_ipv6GatewaySensor->value().toString().isEmpty()) {
0147             char buffer[INET6_ADDRSTRLEN];
0148             inet_ntop(AF_INET6, nl_addr_get_binary_addr(rtnl_route_nh_get_gateway(rtnl_route_nexthop_n(route,0))), buffer, INET6_ADDRSTRLEN);
0149             self->m_ipv6GatewaySensor->setValue(QString::fromLatin1(buffer));
0150         }
0151     }, this);
0152 
0153     rtnl_addr_put(filterAddress);
0154     nl_addr_put(dst);
0155     rtnl_route_put(routeFilter);
0156 }
0157 
0158 RtNetlinkBackend::RtNetlinkBackend(QObject *parent)
0159     : NetworkBackend(parent)
0160     , m_socket(nl_socket_alloc(), nl_socket_free)
0161 {
0162     nl_connect(m_socket.get(), NETLINK_ROUTE);
0163 }
0164 
0165 RtNetlinkBackend::~RtNetlinkBackend()
0166 {
0167     qDeleteAll(m_devices);
0168 }
0169 
0170 bool RtNetlinkBackend::isSupported()
0171 {
0172     return bool(m_socket);
0173 }
0174 
0175 void RtNetlinkBackend::start()
0176 {
0177     if (!m_socket) {
0178         return;
0179     }
0180     update();
0181 }
0182 
0183 void RtNetlinkBackend::stop()
0184 {
0185 }
0186 
0187 void RtNetlinkBackend::update()
0188 {
0189     const qint64 elapsedTime = m_updateTimer.restart();
0190     nl_cache *link_cache, *address_cache, *route_cache;
0191     int error = rtnl_link_alloc_cache(m_socket.get(), AF_UNSPEC, &link_cache);
0192     if (error != 0) {
0193         qWarning() << nl_geterror(error);
0194         return;
0195     }
0196     error = rtnl_addr_alloc_cache(m_socket.get(), &address_cache);
0197     if (error != 0) {
0198         qWarning() << nl_geterror(error);
0199         return;
0200     }
0201     error = rtnl_route_alloc_cache(m_socket.get(), AF_UNSPEC, 0, &route_cache);
0202     if (error != 0) {
0203         qWarning() << nl_geterror(error);
0204         return;
0205     }
0206 
0207     for (nl_object *object = nl_cache_get_first(link_cache); object != nullptr; object = nl_cache_get_next(object)) {
0208         auto link = reinterpret_cast<rtnl_link *>(object);
0209         if (rtnl_link_get_arptype(link) != ARPHRD_ETHER) {
0210             // FIXME Maybe this is to aggresive? On my machines wifi is also ether
0211             continue;
0212         }
0213         // Hardware devices do have an empty type
0214         if (qstrlen(rtnl_link_get_type(link)) != 0) {
0215             continue;
0216         }
0217         const auto name = QByteArray(rtnl_link_get_name(link));
0218         if (!m_devices.contains(name)) {
0219             auto device = new RtNetlinkDevice(name);
0220             m_devices.insert(name, device);
0221             connect(device, &RtNetlinkDevice::connected, this, [device, this] { Q_EMIT deviceAdded(device); });
0222             connect(device, &RtNetlinkDevice::disconnected, this, [device, this] { Q_EMIT deviceRemoved(device); });
0223         }
0224         m_devices[name]->update(link, address_cache, route_cache, elapsedTime);
0225     }
0226     nl_cache_free(link_cache);
0227     nl_cache_free(address_cache);
0228     nl_cache_free(route_cache);
0229 }
0230 
0231 #include "moc_RtNetlinkBackend.cpp"