| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the plugins 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 "qnlaengine.h" |
| #include "../qnetworksession_impl.h" |
| |
| #include <QtNetwork/private/qnetworkconfiguration_p.h> |
| |
| #include <QtCore/qthread.h> |
| #include <QtCore/qmutex.h> |
| #include <QtCore/qcoreapplication.h> |
| #include <QtCore/qstringlist.h> |
| |
| #include <QtCore/qdebug.h> |
| |
| #include "../platformdefs_win.h" |
| |
| QT_BEGIN_NAMESPACE |
| |
| QWindowsSockInit2::QWindowsSockInit2() |
| : version(0) |
| { |
| //### should we try for 2.2 on all platforms ?? |
| WSAData wsadata; |
| |
| // IPv6 requires Winsock v2.0 or better. |
| if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) { |
| qWarning("QBearerManagementAPI: WinSock v2.0 initialization failed."); |
| } else { |
| version = 0x20; |
| } |
| } |
| |
| QWindowsSockInit2::~QWindowsSockInit2() |
| { |
| WSACleanup(); |
| } |
| |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| static void printBlob(NLA_BLOB *blob) |
| { |
| qDebug() << "==== BEGIN NLA_BLOB ====" << Qt::endl |
| |
| << "type:" << blob->header.type << Qt::endl |
| << "size:" << blob->header.dwSize << Qt::endl |
| << "next offset:" << blob->header.nextOffset; |
| |
| switch (blob->header.type) { |
| case NLA_RAW_DATA: |
| qDebug() << "Raw Data" << Qt::endl |
| << '\t' << blob->data.rawData; |
| break; |
| case NLA_INTERFACE: |
| qDebug() << "Interface" << Qt::endl |
| << "\ttype:" << blob->data.interfaceData.dwType << Qt::endl |
| << "\tspeed:" << blob->data.interfaceData.dwSpeed << Qt::endl |
| << "\tadapter:" << blob->data.interfaceData.adapterName; |
| break; |
| case NLA_802_1X_LOCATION: |
| qDebug() << "802.1x Location" << Qt::endl |
| << '\t' << blob->data.locationData.information; |
| break; |
| case NLA_CONNECTIVITY: |
| qDebug() << "Connectivity" << Qt::endl |
| << "\ttype:" << blob->data.connectivity.type << Qt::endl |
| << "\tinternet:" << blob->data.connectivity.internet; |
| break; |
| case NLA_ICS: |
| qDebug() << "ICS" << Qt::endl |
| << "\tspeed:" << blob->data.ICS.remote.speed << Qt::endl |
| << "\ttype:" << blob->data.ICS.remote.type << Qt::endl |
| << "\tstate:" << blob->data.ICS.remote.state << Qt::endl |
| << "\tmachine name:" << blob->data.ICS.remote.machineName << Qt::endl |
| << "\tshared adapter name:" << blob->data.ICS.remote.sharedAdapterName; |
| break; |
| default: |
| qDebug("UNKNOWN BLOB TYPE"); |
| } |
| |
| qDebug("===== END NLA_BLOB ====="); |
| } |
| #endif |
| |
| static QNetworkConfiguration::BearerType qGetInterfaceType(const QString &interface) |
| { |
| unsigned long oid; |
| DWORD bytesWritten; |
| |
| NDIS_MEDIUM medium; |
| NDIS_PHYSICAL_MEDIUM physicalMedium; |
| |
| HANDLE handle = CreateFile((TCHAR *)QString::fromLatin1("\\\\.\\%1").arg(interface).utf16(), 0, |
| FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); |
| if (handle == INVALID_HANDLE_VALUE) |
| return QNetworkConfiguration::BearerUnknown; |
| |
| oid = OID_GEN_MEDIA_SUPPORTED; |
| bytesWritten = 0; |
| bool result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), |
| &medium, sizeof(medium), &bytesWritten, 0); |
| if (!result) { |
| CloseHandle(handle); |
| return QNetworkConfiguration::BearerUnknown; |
| } |
| |
| oid = OID_GEN_PHYSICAL_MEDIUM; |
| bytesWritten = 0; |
| result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid), |
| &physicalMedium, sizeof(physicalMedium), &bytesWritten, 0); |
| if (!result) { |
| CloseHandle(handle); |
| |
| if (medium == NdisMedium802_3) |
| return QNetworkConfiguration::BearerEthernet; |
| else |
| return QNetworkConfiguration::BearerUnknown; |
| } |
| |
| CloseHandle(handle); |
| |
| if (medium == NdisMedium802_3) { |
| switch (physicalMedium) { |
| case NdisPhysicalMediumWirelessLan: |
| return QNetworkConfiguration::BearerWLAN; |
| case NdisPhysicalMediumBluetooth: |
| return QNetworkConfiguration::BearerBluetooth; |
| case NdisPhysicalMediumWiMax: |
| return QNetworkConfiguration::BearerWiMAX; |
| default: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug() << "Physical Medium" << physicalMedium; |
| #endif |
| return QNetworkConfiguration::BearerEthernet; |
| } |
| } |
| |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug() << medium << physicalMedium; |
| #endif |
| |
| return QNetworkConfiguration::BearerUnknown; |
| } |
| |
| class QNlaThread : public QThread |
| { |
| Q_OBJECT |
| |
| public: |
| QNlaThread(QNlaEngine *parent = 0); |
| ~QNlaThread(); |
| |
| QList<QNetworkConfigurationPrivate *> getConfigurations(); |
| |
| void forceUpdate(); |
| |
| protected: |
| virtual void run(); |
| |
| private: |
| void updateConfigurations(QList<QNetworkConfigurationPrivate *> &configs); |
| DWORD parseBlob(NLA_BLOB *blob, QNetworkConfigurationPrivate *cpPriv) const; |
| QNetworkConfigurationPrivate *parseQuerySet(const WSAQUERYSET *querySet) const; |
| void fetchConfigurations(); |
| |
| signals: |
| void networksChanged(); |
| |
| private: |
| QMutex mutex; |
| HANDLE handle; |
| bool done; |
| QList<QNetworkConfigurationPrivate *> fetchedConfigurations; |
| }; |
| |
| QNlaThread::QNlaThread(QNlaEngine *parent) |
| : QThread(parent), handle(0), done(false) |
| { |
| } |
| |
| QNlaThread::~QNlaThread() |
| { |
| mutex.lock(); |
| |
| done = true; |
| |
| if (handle) { |
| /* cancel completion event */ |
| if (WSALookupServiceEnd(handle) == SOCKET_ERROR) { |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("WSALookupServiceEnd error %d", WSAGetLastError()); |
| #endif |
| } |
| } |
| mutex.unlock(); |
| |
| wait(); |
| } |
| |
| QList<QNetworkConfigurationPrivate *> QNlaThread::getConfigurations() |
| { |
| QMutexLocker locker(&mutex); |
| |
| QList<QNetworkConfigurationPrivate *> foundConfigurations = fetchedConfigurations; |
| fetchedConfigurations.clear(); |
| |
| return foundConfigurations; |
| } |
| |
| void QNlaThread::forceUpdate() |
| { |
| mutex.lock(); |
| |
| if (handle) { |
| /* cancel completion event */ |
| if (WSALookupServiceEnd(handle) == SOCKET_ERROR) { |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("WSALookupServiceEnd error %d", WSAGetLastError()); |
| #endif |
| } |
| handle = 0; |
| } |
| mutex.unlock(); |
| } |
| |
| void QNlaThread::run() |
| { |
| WSAEVENT changeEvent = WSACreateEvent(); |
| if (changeEvent == WSA_INVALID_EVENT) |
| return; |
| |
| while (true) { |
| fetchConfigurations(); |
| |
| WSAQUERYSET qsRestrictions; |
| |
| memset(&qsRestrictions, 0, sizeof(qsRestrictions)); |
| qsRestrictions.dwSize = sizeof(qsRestrictions); |
| qsRestrictions.dwNameSpace = NS_NLA; |
| |
| mutex.lock(); |
| if (done) { |
| mutex.unlock(); |
| break; |
| } |
| int result = WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL, &handle); |
| mutex.unlock(); |
| |
| if (result == SOCKET_ERROR) |
| break; |
| |
| WSACOMPLETION completion; |
| WSAOVERLAPPED overlapped; |
| |
| memset(&overlapped, 0, sizeof(overlapped)); |
| overlapped.hEvent = changeEvent; |
| |
| memset(&completion, 0, sizeof(completion)); |
| completion.Type = NSP_NOTIFY_EVENT; |
| completion.Parameters.Event.lpOverlapped = &overlapped; |
| |
| DWORD bytesReturned = 0; |
| result = WSANSPIoctl(handle, SIO_NSP_NOTIFY_CHANGE, 0, 0, 0, 0, |
| &bytesReturned, &completion); |
| if (result == SOCKET_ERROR) { |
| if (WSAGetLastError() != WSA_IO_PENDING) |
| break; |
| } |
| |
| // Not interested in unrelated IO completion events |
| // although we also don't want to block them |
| while (WaitForSingleObjectEx(changeEvent, WSA_INFINITE, true) != WAIT_IO_COMPLETION && |
| handle) |
| { |
| } |
| |
| mutex.lock(); |
| if (handle) { |
| result = WSALookupServiceEnd(handle); |
| if (result == SOCKET_ERROR) { |
| mutex.unlock(); |
| break; |
| } |
| handle = 0; |
| } |
| mutex.unlock(); |
| } |
| |
| WSACloseEvent(changeEvent); |
| } |
| |
| void QNlaThread::updateConfigurations(QList<QNetworkConfigurationPrivate *> &configs) |
| { |
| mutex.lock(); |
| |
| while (!fetchedConfigurations.isEmpty()) |
| delete fetchedConfigurations.takeFirst(); |
| |
| fetchedConfigurations = configs; |
| |
| mutex.unlock(); |
| |
| emit networksChanged(); |
| } |
| |
| DWORD QNlaThread::parseBlob(NLA_BLOB *blob, QNetworkConfigurationPrivate *cpPriv) const |
| { |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| printBlob(blob); |
| #endif |
| |
| switch (blob->header.type) { |
| case NLA_RAW_DATA: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("%s: unhandled header type NLA_RAW_DATA", __FUNCTION__); |
| #endif |
| break; |
| case NLA_INTERFACE: |
| cpPriv->state = QNetworkConfiguration::Active; |
| if (QNlaEngine *engine = qobject_cast<QNlaEngine *>(parent())) { |
| engine->configurationInterface[cpPriv->id.toUInt()] = |
| QString::fromLatin1(blob->data.interfaceData.adapterName); |
| } |
| break; |
| case NLA_802_1X_LOCATION: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("%s: unhandled header type NLA_802_1X_LOCATION", __FUNCTION__); |
| #endif |
| break; |
| case NLA_CONNECTIVITY: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("%s: unhandled header type NLA_CONNECTIVITY", __FUNCTION__); |
| #endif |
| break; |
| case NLA_ICS: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("%s: unhandled header type NLA_ICS", __FUNCTION__); |
| #endif |
| break; |
| default: |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("%s: unhandled header type %d", __FUNCTION__, blob->header.type); |
| #endif |
| ; |
| } |
| |
| return blob->header.nextOffset; |
| } |
| |
| QNetworkConfigurationPrivate *QNlaThread::parseQuerySet(const WSAQUERYSET *querySet) const |
| { |
| QNetworkConfigurationPrivate *cpPriv = new QNetworkConfigurationPrivate; |
| |
| cpPriv->name = QString::fromWCharArray(querySet->lpszServiceInstanceName); |
| cpPriv->isValid = true; |
| cpPriv->id = QString::number(qHash(QLatin1String("NLA:") + cpPriv->name)); |
| cpPriv->state = QNetworkConfiguration::Defined; |
| cpPriv->type = QNetworkConfiguration::InternetAccessPoint; |
| |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug() << "size:" << querySet->dwSize; |
| qDebug() << "service instance name:" << QString::fromUtf16(querySet->lpszServiceInstanceName); |
| qDebug() << "service class id:" << querySet->lpServiceClassId; |
| qDebug() << "version:" << querySet->lpVersion; |
| qDebug() << "comment:" << QString::fromUtf16(querySet->lpszComment); |
| qDebug() << "namespace:" << querySet->dwNameSpace; |
| qDebug() << "namespace provider id:" << querySet->lpNSProviderId; |
| qDebug() << "context:" << QString::fromUtf16(querySet->lpszContext); |
| qDebug() << "number of protocols:" << querySet->dwNumberOfProtocols; |
| qDebug() << "protocols:" << querySet->lpafpProtocols; |
| qDebug() << "query string:" << QString::fromUtf16(querySet->lpszQueryString); |
| qDebug() << "number of cs addresses:" << querySet->dwNumberOfCsAddrs; |
| qDebug() << "cs addresses:" << querySet->lpcsaBuffer; |
| qDebug() << "output flags:" << querySet->dwOutputFlags; |
| #endif |
| |
| if (querySet->lpBlob) { |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug() << "blob size:" << querySet->lpBlob->cbSize; |
| qDebug() << "blob data:" << querySet->lpBlob->pBlobData; |
| #endif |
| |
| DWORD offset = 0; |
| do { |
| NLA_BLOB *blob = reinterpret_cast<NLA_BLOB *>(querySet->lpBlob->pBlobData + offset); |
| DWORD nextOffset = parseBlob(blob, cpPriv); |
| if (nextOffset == offset) |
| break; |
| else |
| offset = nextOffset; |
| } while (offset != 0 && offset < querySet->lpBlob->cbSize); |
| } |
| |
| if (QNlaEngine *engine = qobject_cast<QNlaEngine *>(parent())) { |
| const QString interface = engine->getInterfaceFromId(cpPriv->id); |
| cpPriv->bearerType = qGetInterfaceType(interface); |
| } |
| |
| return cpPriv; |
| } |
| |
| void QNlaThread::fetchConfigurations() |
| { |
| QList<QNetworkConfigurationPrivate *> foundConfigurations; |
| |
| WSAQUERYSET qsRestrictions; |
| HANDLE hLookup = 0; |
| |
| memset(&qsRestrictions, 0, sizeof(qsRestrictions)); |
| qsRestrictions.dwSize = sizeof(qsRestrictions); |
| qsRestrictions.dwNameSpace = NS_NLA; |
| |
| int result = WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL | LUP_DEEP, &hLookup); |
| if (result == SOCKET_ERROR) { |
| mutex.lock(); |
| fetchedConfigurations.clear(); |
| mutex.unlock(); |
| } |
| |
| char buffer[0x10000]; |
| while (result == 0) { |
| DWORD bufferLength = sizeof(buffer); |
| result = WSALookupServiceNext(hLookup, LUP_RETURN_ALL, |
| &bufferLength, reinterpret_cast<WSAQUERYSET *>(buffer)); |
| |
| if (result == SOCKET_ERROR) |
| break; |
| |
| QNetworkConfigurationPrivate *cpPriv = |
| parseQuerySet(reinterpret_cast<WSAQUERYSET *>(buffer)); |
| |
| foundConfigurations.append(cpPriv); |
| } |
| |
| if (hLookup) { |
| result = WSALookupServiceEnd(hLookup); |
| if (result == SOCKET_ERROR) { |
| #ifdef BEARER_MANAGEMENT_DEBUG |
| qDebug("WSALookupServiceEnd error %d", WSAGetLastError()); |
| #endif |
| } |
| } |
| |
| updateConfigurations(foundConfigurations); |
| } |
| |
| QNlaEngine::QNlaEngine(QObject *parent) |
| : QBearerEngineImpl(parent), nlaThread(0) |
| { |
| nlaThread = new QNlaThread(this); |
| connect(nlaThread, SIGNAL(networksChanged()), |
| this, SLOT(networksChanged())); |
| nlaThread->start(); |
| |
| qApp->processEvents(QEventLoop::ExcludeUserInputEvents); |
| } |
| |
| QNlaEngine::~QNlaEngine() |
| { |
| delete nlaThread; |
| } |
| |
| void QNlaEngine::networksChanged() |
| { |
| QMutexLocker locker(&mutex); |
| |
| QStringList previous = accessPointConfigurations.keys(); |
| |
| QList<QNetworkConfigurationPrivate *> foundConfigurations = nlaThread->getConfigurations(); |
| while (!foundConfigurations.isEmpty()) { |
| QNetworkConfigurationPrivate *cpPriv = foundConfigurations.takeFirst(); |
| |
| previous.removeAll(cpPriv->id); |
| |
| if (accessPointConfigurations.contains(cpPriv->id)) { |
| QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(cpPriv->id); |
| |
| bool changed = false; |
| |
| ptr->mutex.lock(); |
| |
| if (ptr->isValid != cpPriv->isValid) { |
| ptr->isValid = cpPriv->isValid; |
| changed = true; |
| } |
| |
| if (ptr->name != cpPriv->name) { |
| ptr->name = cpPriv->name; |
| changed = true; |
| } |
| |
| if (ptr->state != cpPriv->state) { |
| ptr->state = cpPriv->state; |
| changed = true; |
| } |
| |
| ptr->mutex.unlock(); |
| |
| if (changed) { |
| locker.unlock(); |
| emit configurationChanged(ptr); |
| locker.relock(); |
| } |
| |
| delete cpPriv; |
| } else { |
| QNetworkConfigurationPrivatePointer ptr(cpPriv); |
| |
| accessPointConfigurations.insert(ptr->id, ptr); |
| |
| locker.unlock(); |
| emit configurationAdded(ptr); |
| locker.relock(); |
| } |
| } |
| |
| while (!previous.isEmpty()) { |
| QNetworkConfigurationPrivatePointer ptr = |
| accessPointConfigurations.take(previous.takeFirst()); |
| |
| locker.unlock(); |
| emit configurationRemoved(ptr); |
| locker.relock(); |
| } |
| |
| locker.unlock(); |
| emit updateCompleted(); |
| } |
| |
| QString QNlaEngine::getInterfaceFromId(const QString &id) |
| { |
| QMutexLocker locker(&mutex); |
| |
| return configurationInterface.value(id.toUInt()); |
| } |
| |
| bool QNlaEngine::hasIdentifier(const QString &id) |
| { |
| QMutexLocker locker(&mutex); |
| |
| return configurationInterface.contains(id.toUInt()); |
| } |
| |
| void QNlaEngine::connectToId(const QString &id) |
| { |
| emit connectionError(id, OperationNotSupported); |
| } |
| |
| void QNlaEngine::disconnectFromId(const QString &id) |
| { |
| emit connectionError(id, OperationNotSupported); |
| } |
| |
| void QNlaEngine::requestUpdate() |
| { |
| QMutexLocker locker(&mutex); |
| |
| nlaThread->forceUpdate(); |
| } |
| |
| QNetworkSession::State QNlaEngine::sessionStateForId(const QString &id) |
| { |
| QMutexLocker locker(&mutex); |
| |
| QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id); |
| |
| if (!ptr) |
| return QNetworkSession::Invalid; |
| |
| if (!ptr->isValid) { |
| return QNetworkSession::Invalid; |
| } else if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) { |
| return QNetworkSession::Connected; |
| } else if ((ptr->state & QNetworkConfiguration::Discovered) == |
| QNetworkConfiguration::Discovered) { |
| return QNetworkSession::Disconnected; |
| } else if ((ptr->state & QNetworkConfiguration::Defined) == QNetworkConfiguration::Defined) { |
| return QNetworkSession::NotAvailable; |
| } else if ((ptr->state & QNetworkConfiguration::Undefined) == |
| QNetworkConfiguration::Undefined) { |
| return QNetworkSession::NotAvailable; |
| } |
| |
| return QNetworkSession::Invalid; |
| } |
| |
| QNetworkConfigurationManager::Capabilities QNlaEngine::capabilities() const |
| { |
| return QNetworkConfigurationManager::ForcedRoaming; |
| } |
| |
| QNetworkSessionPrivate *QNlaEngine::createSessionBackend() |
| { |
| return new QNetworkSessionPrivateImpl; |
| } |
| |
| QNetworkConfigurationPrivatePointer QNlaEngine::defaultConfiguration() |
| { |
| return QNetworkConfigurationPrivatePointer(); |
| } |
| |
| #include "qnlaengine.moc" |
| QT_END_NAMESPACE |
| |