diff options
Diffstat (limited to 'src/network/kernel/qnetworkinterface_linux.cpp')
-rw-r--r-- | src/network/kernel/qnetworkinterface_linux.cpp | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/src/network/kernel/qnetworkinterface_linux.cpp b/src/network/kernel/qnetworkinterface_linux.cpp new file mode 100644 index 0000000000..8d77d288cd --- /dev/null +++ b/src/network/kernel/qnetworkinterface_linux.cpp @@ -0,0 +1,359 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Intel Corporation. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtNetwork module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qnetworkinterface.h" +#include "qnetworkinterface_p.h" +#include "qnetworkinterface_unix_p.h" + +#include <qendian.h> +#include <qobjectdefs.h> +#include <qvarlengtharray.h> + +// accordding to rtnetlink(7) +#include <asm/types.h> +#include <linux/if.h> +#include <linux/netlink.h> +#include <linux/rtnetlink.h> +#include <sys/socket.h> + +QT_BEGIN_NAMESPACE + +enum { + BufferSize = 8192 +}; + +namespace { +struct NetlinkSocket +{ + int sock; + NetlinkSocket(int bufferSize) + { + sock = qt_safe_socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE); + if (Q_UNLIKELY(sock == -1)) + qErrnoWarning("Could not create AF_NETLINK socket"); + + // set buffer length + socklen_t len = sizeof(bufferSize); + setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &bufferSize, len); + } + + ~NetlinkSocket() + { + if (sock != -1) + qt_safe_close(sock); + } + + operator int() const { return sock; } +}; + +template <typename Lambda> struct ProcessNetlinkRequest +{ + using FunctionTraits = QtPrivate::FunctionPointer<decltype(&Lambda::operator())>; + using FirstArgument = typename FunctionTraits::Arguments::Car; + + static int expectedTypeForRequest(int rtype) + { + Q_STATIC_ASSERT(RTM_NEWADDR == RTM_GETADDR - 2); + Q_STATIC_ASSERT(RTM_NEWLINK == RTM_GETLINK - 2); + Q_ASSERT(rtype == RTM_GETADDR || rtype == RTM_GETLINK); + return rtype - 2; + } + + void operator()(int sock, nlmsghdr *hdr, char *buf, size_t bufsize, Lambda &&func) + { + // send the request + if (send(sock, hdr, hdr->nlmsg_len, 0) != hdr->nlmsg_len) + return; + + // receive and parse the request + int expectedType = expectedTypeForRequest(hdr->nlmsg_type); + const bool isDump = hdr->nlmsg_flags & NLM_F_DUMP; + forever { + qssize_t len = recv(sock, buf, bufsize, 0); + hdr = reinterpret_cast<struct nlmsghdr *>(buf); + if (!NLMSG_OK(hdr, len)) + return; + + auto arg = reinterpret_cast<FirstArgument>(NLMSG_DATA(hdr)); + size_t payloadLen = NLMSG_PAYLOAD(hdr, 0); + + // is this a multipart message? + Q_ASSERT(isDump == !!(hdr->nlmsg_flags & NLM_F_MULTI)); + if (!isDump) { + // no, single message + if (hdr->nlmsg_type == expectedType && payloadLen >= sizeof(FirstArgument)) + return void(func(arg, payloadLen)); + } else { + // multipart, parse until done + do { + if (hdr->nlmsg_type == NLMSG_DONE) + return; + if (hdr->nlmsg_type != expectedType || payloadLen < sizeof(FirstArgument)) + break; + func(arg, payloadLen); + + // NLMSG_NEXT also updates the len variable + hdr = NLMSG_NEXT(hdr, len); + arg = reinterpret_cast<FirstArgument>(NLMSG_DATA(hdr)); + payloadLen = NLMSG_PAYLOAD(hdr, 0); + } while (NLMSG_OK(hdr, len)); + + if (len == 0) + continue; // get new datagram + } + +#ifndef QT_NO_DEBUG + if (NLMSG_OK(hdr, len)) + qWarning("QNetworkInterface/AF_NETLINK: received unknown packet type (%d) or too short (%u)", + hdr->nlmsg_type, hdr->nlmsg_len); + else + qWarning("QNetworkInterface/AF_NETLINK: received invalid packet with size %d", int(len)); +#endif + return; + } + } +}; + +template <typename Lambda> +void processNetlinkRequest(int sock, struct nlmsghdr *hdr, char *buf, size_t bufsize, Lambda &&l) +{ + ProcessNetlinkRequest<Lambda>()(sock, hdr, buf, bufsize, std::forward<Lambda>(l)); +} +} + +uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name) +{ + uint index = 0; + if (name.length() >= IFNAMSIZ) + return index; + + int socket = qt_safe_socket(AF_INET, SOCK_DGRAM, 0); + if (socket >= 0) { + struct ifreq req; + req.ifr_ifindex = 0; + strcpy(req.ifr_name, name.toLatin1().constData()); + + if (qt_safe_ioctl(socket, SIOCGIFINDEX, &req) >= 0) + index = req.ifr_ifindex; + qt_safe_close(socket); + } + return index; +} + +QString QNetworkInterfaceManager::interfaceNameFromIndex(uint index) +{ + int socket = qt_safe_socket(AF_INET, SOCK_DGRAM, 0); + if (socket >= 0) { + struct ifreq req; + req.ifr_ifindex = index; + + if (qt_safe_ioctl(socket, SIOCGIFNAME, &req) >= 0) { + qt_safe_close(socket); + return QString::fromLatin1(req.ifr_name); + } + qt_safe_close(socket); + } + return QString(); +} + +static QList<QNetworkInterfacePrivate *> getInterfaces(int sock, char *buf) +{ + QList<QNetworkInterfacePrivate *> result; + + // request all links + struct { + struct nlmsghdr req; + struct ifinfomsg ifi; + } ifi_req; + memset(&ifi_req, 0, sizeof(ifi_req)); + + ifi_req.req.nlmsg_len = sizeof(ifi_req); + ifi_req.req.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + ifi_req.req.nlmsg_type = RTM_GETLINK; + + // parse the interfaces + processNetlinkRequest(sock, &ifi_req.req, buf, BufferSize, [&](ifinfomsg *ifi, size_t len) { + auto iface = new QNetworkInterfacePrivate; + iface->index = ifi->ifi_index; + iface->flags = convertFlags(ifi->ifi_flags); + + // read attributes + auto rta = reinterpret_cast<struct rtattr *>(ifi + 1); + len -= sizeof(*ifi); + for ( ; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + int payloadLen = RTA_PAYLOAD(rta); + auto payloadPtr = reinterpret_cast<char *>(RTA_DATA(rta)); + + switch (rta->rta_type) { + case IFLA_ADDRESS: // link-level address + iface->hardwareAddress = + iface->makeHwAddress(payloadLen, reinterpret_cast<uchar *>(payloadPtr)); + break; + + case IFLA_IFNAME: // interface name + iface->name = QString::fromLatin1(payloadPtr, payloadLen - 1); + break; + + case IFLA_OPERSTATE: // operational state + if (*payloadPtr != IF_OPER_UNKNOWN) { + // override the flag + iface->flags &= ~QNetworkInterface::IsUp; + if (*payloadPtr == IF_OPER_UP) + iface->flags |= QNetworkInterface::IsUp; + } + break; + } + } + + if (Q_UNLIKELY(iface->name.isEmpty())) { + qWarning("QNetworkInterface: found interface %d with no name", iface->index); + delete iface; + } else { + result.append(iface); + } + }); + return result; +} + +static void getAddresses(int sock, char *buf, QList<QNetworkInterfacePrivate *> &result) +{ + // request all addresses + struct { + struct nlmsghdr req; + struct ifaddrmsg ifa; + } ifa_req; + memset(&ifa_req, 0, sizeof(ifa_req)); + + ifa_req.req.nlmsg_len = sizeof(ifa_req); + ifa_req.req.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + ifa_req.req.nlmsg_type = RTM_GETADDR; + ifa_req.req.nlmsg_seq = 1; + + // parse the addresses + processNetlinkRequest(sock, &ifa_req.req, buf, BufferSize, [&](ifaddrmsg *ifa, size_t len) { + if (Q_UNLIKELY(ifa->ifa_family != AF_INET && ifa->ifa_family != AF_INET6)) { + // unknown address types + return; + } + + // find the interface this is relevant to + QNetworkInterfacePrivate *iface = nullptr; + for (auto candidate : qAsConst(result)) { + if (candidate->index != int(ifa->ifa_index)) + continue; + iface = candidate; + break; + } + + if (Q_UNLIKELY(!iface)) { + qWarning("QNetworkInterface/AF_NETLINK: found unknown interface with index %d", ifa->ifa_index); + return; + } + + QNetworkAddressEntry entry; + quint32 flags = ifa->ifa_flags; // may be overwritten by IFA_FLAGS + + auto makeAddress = [=](uchar *ptr, int len) { + QHostAddress addr; + if (ifa->ifa_family == AF_INET) { + Q_ASSERT(len == 4); + addr.setAddress(qFromBigEndian<quint32>(ptr)); + } else { + Q_ASSERT(len == 16); + addr.setAddress(ptr); + + // do we need a scope ID? + if (addr.isLinkLocal()) + addr.setScopeId(iface->name); + } + return addr; + }; + + // read attributes + auto rta = reinterpret_cast<struct rtattr *>(ifa + 1); + len -= sizeof(*ifa); + for ( ; RTA_OK(rta, len); rta = RTA_NEXT(rta, len)) { + int payloadLen = RTA_PAYLOAD(rta); + auto payloadPtr = reinterpret_cast<uchar *>(RTA_DATA(rta)); + + switch (rta->rta_type) { + case IFA_ADDRESS: // address + entry.setIp(makeAddress(payloadPtr, payloadLen)); + break; + + case IFA_BROADCAST: + Q_ASSERT(ifa->ifa_family == AF_INET); + entry.setBroadcast(makeAddress(payloadPtr, payloadLen)); + break; + + case IFA_FLAGS: + Q_ASSERT(payloadLen == 4); + flags = qFromUnaligned<quint32>(payloadPtr); + break; + } + } + + // now handle flags + Q_UNUSED(flags); + + if (!entry.ip().isNull()) { + entry.setPrefixLength(ifa->ifa_prefixlen); + iface->addressEntries.append(entry); + } + }); +} + +QList<QNetworkInterfacePrivate *> QNetworkInterfaceManager::scan() +{ + // open netlink socket + QList<QNetworkInterfacePrivate *> result; + NetlinkSocket sock(BufferSize); + if (Q_UNLIKELY(sock == -1)) + return result; + + QByteArray buffer(BufferSize, Qt::Uninitialized); + char *buf = buffer.data(); + + result = getInterfaces(sock, buf); + getAddresses(sock, buf, result); + + return result; +} + +QT_END_NAMESPACE |