diff options
-rw-r--r-- | src/network/configure.json | 28 | ||||
-rw-r--r-- | src/network/kernel/kernel.pri | 6 | ||||
-rw-r--r-- | src/network/kernel/qnetworkinterface_linux.cpp | 359 | ||||
-rw-r--r-- | src/network/kernel/qnetworkinterface_unix.cpp | 38 | ||||
-rw-r--r-- | src/network/kernel/qnetworkinterface_unix_p.h | 102 |
5 files changed, 495 insertions, 38 deletions
diff --git a/src/network/configure.json b/src/network/configure.json index d46fbfc101..9d99605df0 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -116,6 +116,24 @@ }, "use": "network" }, + "linux-netlink": { + "label": "Linux AF_NETLINK sockets", + "type": "compile", + "test": { + "include": [ "asm/types.h", "linux/netlink.h", "linux/rtnetlink.h", "sys/socket.h" ], + "main": [ + "struct rtattr rta = { };", + "struct ifinfomsg ifi = {};", + "struct ifaddrmsg ifa = {};", + "struct ifa_cacheinfo ci;", + "ci.ifa_prefered = ci.ifa_valid = 0;", + "(void)RTM_NEWLINK; (void)RTM_NEWADDR;", + "(void)IFLA_ADDRESS; (void)IFLA_IFNAME;", + "(void)IFA_ADDRESS; (void)IFA_LABEL; (void)IFA_CACHEINFO;", + "(void)(IFA_F_SECONDARY | IFA_F_DEPRECATED | IFA_F_PERMANENT | IFA_F_MANAGETEMPADDR);" + ] + } + }, "sctp": { "label": "SCTP support", "type": "compile", @@ -161,6 +179,11 @@ "condition": "libs.libproxy", "output": [ "privateFeature" ] }, + "linux-netlink": { + "label": "Linux AF_NETLINK", + "condition": "config.linux && tests.linux-netlink", + "output": [ "privateFeature" ] + }, "openssl": { "label": "OpenSSL", "enable": "input.openssl == 'yes' || input.openssl == 'linked' || input.openssl == 'runtime'", @@ -310,6 +333,11 @@ For example: "getifaddrs", "ipv6ifname", "libproxy", { "type": "feature", + "args": "linux-netlink", + "condition": "config.linux" + }, + { + "type": "feature", "args": "securetransport", "condition": "config.darwin" }, diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index 54b61f3ad3..806cdc85cf 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -17,6 +17,7 @@ HEADERS += kernel/qtnetworkglobal.h \ kernel/qnetworkdatagram_p.h \ kernel/qnetworkinterface.h \ kernel/qnetworkinterface_p.h \ + kernel/qnetworkinterface_unix_p.h \ kernel/qnetworkproxy.h SOURCES += kernel/qauthenticator.cpp \ @@ -34,7 +35,10 @@ qtConfig(ftp) { unix { !integrity: SOURCES += kernel/qdnslookup_unix.cpp - SOURCES += kernel/qhostinfo_unix.cpp kernel/qnetworkinterface_unix.cpp + SOURCES += kernel/qhostinfo_unix.cpp + + qtConfig(linux-netlink): SOURCES += kernel/qnetworkinterface_linux.cpp + else: SOURCES += kernel/qnetworkinterface_unix.cpp } android { 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 diff --git a/src/network/kernel/qnetworkinterface_unix.cpp b/src/network/kernel/qnetworkinterface_unix.cpp index f8a33c395e..0b361810e8 100644 --- a/src/network/kernel/qnetworkinterface_unix.cpp +++ b/src/network/kernel/qnetworkinterface_unix.cpp @@ -41,34 +41,15 @@ #include "qset.h" #include "qnetworkinterface.h" #include "qnetworkinterface_p.h" +#include "qnetworkinterface_unix_p.h" #include "qalgorithms.h" -#include "private/qnet_unix_p.h" #ifndef QT_NO_NETWORKINTERFACE -#define IP_MULTICAST // make AIX happy and define IFF_MULTICAST - -#include <sys/types.h> -#include <sys/socket.h> - -#ifdef Q_OS_SOLARIS -# include <sys/sockio.h> -#endif -#include <net/if.h> - -#ifndef QT_NO_IPV6IFNAME -#include <net/if.h> -#endif - #if defined(QT_LINUXBASE) # define QT_NO_GETIFADDRS #endif -#ifdef Q_OS_HAIKU -# include <sys/sockio.h> -# define IFF_RUNNING 0x0001 -#endif - #ifndef QT_NO_GETIFADDRS # include <ifaddrs.h> #endif @@ -107,23 +88,6 @@ static QHostAddress addressFromSockaddr(sockaddr *sa, int ifindex = 0, const QSt } -static QNetworkInterface::InterfaceFlags convertFlags(uint rawFlags) -{ - QNetworkInterface::InterfaceFlags flags = 0; - flags |= (rawFlags & IFF_UP) ? QNetworkInterface::IsUp : QNetworkInterface::InterfaceFlag(0); - flags |= (rawFlags & IFF_RUNNING) ? QNetworkInterface::IsRunning : QNetworkInterface::InterfaceFlag(0); - flags |= (rawFlags & IFF_BROADCAST) ? QNetworkInterface::CanBroadcast : QNetworkInterface::InterfaceFlag(0); - flags |= (rawFlags & IFF_LOOPBACK) ? QNetworkInterface::IsLoopBack : QNetworkInterface::InterfaceFlag(0); -#ifdef IFF_POINTOPOINT //cygwin doesn't define IFF_POINTOPOINT - flags |= (rawFlags & IFF_POINTOPOINT) ? QNetworkInterface::IsPointToPoint : QNetworkInterface::InterfaceFlag(0); -#endif - -#ifdef IFF_MULTICAST - flags |= (rawFlags & IFF_MULTICAST) ? QNetworkInterface::CanMulticast : QNetworkInterface::InterfaceFlag(0); -#endif - return flags; -} - uint QNetworkInterfaceManager::interfaceIndexFromName(const QString &name) { #ifndef QT_NO_IPV6IFNAME diff --git a/src/network/kernel/qnetworkinterface_unix_p.h b/src/network/kernel/qnetworkinterface_unix_p.h new file mode 100644 index 0000000000..c085194e3c --- /dev/null +++ b/src/network/kernel/qnetworkinterface_unix_p.h @@ -0,0 +1,102 @@ +/**************************************************************************** +** +** Copyright (C) 2017 The Qt Company Ltd. +** 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$ +** +****************************************************************************/ + +#ifndef QNETWORKINTERFACE_UNIX_P_H +#define QNETWORKINTERFACE_UNIX_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qnetworkinterface_p.h" +#include "private/qnet_unix_p.h" + +#ifndef QT_NO_NETWORKINTERFACE + +#define IP_MULTICAST // make AIX happy and define IFF_MULTICAST + +#include <sys/types.h> +#include <sys/socket.h> +#ifdef Q_OS_SOLARIS +# include <sys/sockio.h> +#endif +#ifdef Q_OS_HAIKU +# include <sys/sockio.h> +# define IFF_RUNNING 0x0001 +#endif +#if QT_CONFIG(linux_netlink) +// Same as net/if.h but contains other things we need in +// qnetworkinterface_linux.cpp. +# include <linux/if.h> +#else +# include <net/if.h> +#endif + +QT_BEGIN_NAMESPACE + +static QNetworkInterface::InterfaceFlags convertFlags(uint rawFlags) +{ + QNetworkInterface::InterfaceFlags flags = 0; + flags |= (rawFlags & IFF_UP) ? QNetworkInterface::IsUp : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_RUNNING) ? QNetworkInterface::IsRunning : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_BROADCAST) ? QNetworkInterface::CanBroadcast : QNetworkInterface::InterfaceFlag(0); + flags |= (rawFlags & IFF_LOOPBACK) ? QNetworkInterface::IsLoopBack : QNetworkInterface::InterfaceFlag(0); +#ifdef IFF_POINTOPOINT //cygwin doesn't define IFF_POINTOPOINT + flags |= (rawFlags & IFF_POINTOPOINT) ? QNetworkInterface::IsPointToPoint : QNetworkInterface::InterfaceFlag(0); +#endif + +#ifdef IFF_MULTICAST + flags |= (rawFlags & IFF_MULTICAST) ? QNetworkInterface::CanMulticast : QNetworkInterface::InterfaceFlag(0); +#endif + return flags; +} + +QT_END_NAMESPACE + +#endif // QT_NO_NETWORKINTERFACE + +#endif // QNETWORKINTERFACE_UNIX_P_H |