/**************************************************************************** ** ** 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 #include #include #include #include #include #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 getConfigurations(); void forceUpdate(); protected: virtual void run(); private: void updateConfigurations(QList &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 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 QNlaThread::getConfigurations() { QMutexLocker locker(&mutex); QList 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 &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(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(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(parent())) { const QString interface = engine->getInterfaceFromId(cpPriv->id); cpPriv->bearerType = qGetInterfaceType(interface); } return cpPriv; } void QNlaThread::fetchConfigurations() { QList 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(buffer)); if (result == SOCKET_ERROR) break; QNetworkConfigurationPrivate *cpPriv = parseQuerySet(reinterpret_cast(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 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