1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the plugins of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qnlaengine.h"
41 #include "../qnetworksession_impl.h"
42 
43 #include <QtNetwork/private/qnetworkconfiguration_p.h>
44 
45 #include <QtCore/qthread.h>
46 #include <QtCore/qmutex.h>
47 #include <QtCore/qcoreapplication.h>
48 #include <QtCore/qstringlist.h>
49 
50 #include <QtCore/qdebug.h>
51 
52 #include "../platformdefs_win.h"
53 
54 QT_BEGIN_NAMESPACE
55 
QWindowsSockInit2()56 QWindowsSockInit2::QWindowsSockInit2()
57 :   version(0)
58 {
59     //### should we try for 2.2 on all platforms ??
60     WSAData wsadata;
61 
62     // IPv6 requires Winsock v2.0 or better.
63     if (WSAStartup(MAKEWORD(2,0), &wsadata) != 0) {
64         qWarning("QBearerManagementAPI: WinSock v2.0 initialization failed.");
65     } else {
66         version = 0x20;
67     }
68 }
69 
~QWindowsSockInit2()70 QWindowsSockInit2::~QWindowsSockInit2()
71 {
72     WSACleanup();
73 }
74 
75 #ifdef BEARER_MANAGEMENT_DEBUG
printBlob(NLA_BLOB * blob)76 static void printBlob(NLA_BLOB *blob)
77 {
78     qDebug() << "==== BEGIN NLA_BLOB ====" << Qt::endl
79 
80              << "type:" << blob->header.type << Qt::endl
81              << "size:" << blob->header.dwSize << Qt::endl
82              << "next offset:" << blob->header.nextOffset;
83 
84     switch (blob->header.type) {
85     case NLA_RAW_DATA:
86         qDebug() << "Raw Data" << Qt::endl
87                  << '\t' << blob->data.rawData;
88         break;
89     case NLA_INTERFACE:
90         qDebug() << "Interface" << Qt::endl
91                  << "\ttype:" << blob->data.interfaceData.dwType << Qt::endl
92                  << "\tspeed:" << blob->data.interfaceData.dwSpeed  << Qt::endl
93                  << "\tadapter:" << blob->data.interfaceData.adapterName;
94         break;
95     case NLA_802_1X_LOCATION:
96         qDebug() << "802.1x Location" << Qt::endl
97                  << '\t' << blob->data.locationData.information;
98         break;
99     case NLA_CONNECTIVITY:
100         qDebug() << "Connectivity" << Qt::endl
101                  << "\ttype:" << blob->data.connectivity.type << Qt::endl
102                  << "\tinternet:" << blob->data.connectivity.internet;
103         break;
104     case NLA_ICS:
105         qDebug() << "ICS" << Qt::endl
106                  << "\tspeed:" << blob->data.ICS.remote.speed << Qt::endl
107                  << "\ttype:" << blob->data.ICS.remote.type << Qt::endl
108                  << "\tstate:" << blob->data.ICS.remote.state << Qt::endl
109                  << "\tmachine name:" << blob->data.ICS.remote.machineName << Qt::endl
110                  << "\tshared adapter name:" << blob->data.ICS.remote.sharedAdapterName;
111         break;
112     default:
113         qDebug("UNKNOWN BLOB TYPE");
114     }
115 
116     qDebug("===== END NLA_BLOB =====");
117 }
118 #endif
119 
qGetInterfaceType(const QString & interface)120 static QNetworkConfiguration::BearerType qGetInterfaceType(const QString &interface)
121 {
122     unsigned long oid;
123     DWORD bytesWritten;
124 
125     NDIS_MEDIUM medium;
126     NDIS_PHYSICAL_MEDIUM physicalMedium;
127 
128     HANDLE handle = CreateFile((TCHAR *)QString::fromLatin1("\\\\.\\%1").arg(interface).utf16(), 0,
129                                FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0);
130     if (handle == INVALID_HANDLE_VALUE)
131         return QNetworkConfiguration::BearerUnknown;
132 
133     oid = OID_GEN_MEDIA_SUPPORTED;
134     bytesWritten = 0;
135     bool result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid),
136                                   &medium, sizeof(medium), &bytesWritten, 0);
137     if (!result) {
138         CloseHandle(handle);
139         return QNetworkConfiguration::BearerUnknown;
140     }
141 
142     oid = OID_GEN_PHYSICAL_MEDIUM;
143     bytesWritten = 0;
144     result = DeviceIoControl(handle, IOCTL_NDIS_QUERY_GLOBAL_STATS, &oid, sizeof(oid),
145                              &physicalMedium, sizeof(physicalMedium), &bytesWritten, 0);
146     if (!result) {
147         CloseHandle(handle);
148 
149         if (medium == NdisMedium802_3)
150             return QNetworkConfiguration::BearerEthernet;
151         else
152             return QNetworkConfiguration::BearerUnknown;
153     }
154 
155     CloseHandle(handle);
156 
157     if (medium == NdisMedium802_3) {
158         switch (physicalMedium) {
159         case NdisPhysicalMediumWirelessLan:
160             return QNetworkConfiguration::BearerWLAN;
161         case NdisPhysicalMediumBluetooth:
162             return QNetworkConfiguration::BearerBluetooth;
163         case NdisPhysicalMediumWiMax:
164             return QNetworkConfiguration::BearerWiMAX;
165         default:
166 #ifdef BEARER_MANAGEMENT_DEBUG
167             qDebug() << "Physical Medium" << physicalMedium;
168 #endif
169             return QNetworkConfiguration::BearerEthernet;
170         }
171     }
172 
173 #ifdef BEARER_MANAGEMENT_DEBUG
174     qDebug() << medium << physicalMedium;
175 #endif
176 
177     return QNetworkConfiguration::BearerUnknown;
178 }
179 
180 class QNlaThread : public QThread
181 {
182     Q_OBJECT
183 
184 public:
185     QNlaThread(QNlaEngine *parent = 0);
186     ~QNlaThread();
187 
188     QList<QNetworkConfigurationPrivate *> getConfigurations();
189 
190     void forceUpdate();
191 
192 protected:
193     virtual void run();
194 
195 private:
196     void updateConfigurations(QList<QNetworkConfigurationPrivate *> &configs);
197     DWORD parseBlob(NLA_BLOB *blob, QNetworkConfigurationPrivate *cpPriv) const;
198     QNetworkConfigurationPrivate *parseQuerySet(const WSAQUERYSET *querySet) const;
199     void fetchConfigurations();
200 
201 signals:
202     void networksChanged();
203 
204 private:
205     QMutex mutex;
206     HANDLE handle;
207     bool done;
208     QList<QNetworkConfigurationPrivate *> fetchedConfigurations;
209 };
210 
QNlaThread(QNlaEngine * parent)211 QNlaThread::QNlaThread(QNlaEngine *parent)
212 :   QThread(parent), handle(0), done(false)
213 {
214 }
215 
~QNlaThread()216 QNlaThread::~QNlaThread()
217 {
218     mutex.lock();
219 
220     done = true;
221 
222     if (handle) {
223         /* cancel completion event */
224         if (WSALookupServiceEnd(handle) == SOCKET_ERROR) {
225 #ifdef BEARER_MANAGEMENT_DEBUG
226             qDebug("WSALookupServiceEnd error %d", WSAGetLastError());
227 #endif
228         }
229     }
230     mutex.unlock();
231 
232     wait();
233 }
234 
getConfigurations()235 QList<QNetworkConfigurationPrivate *> QNlaThread::getConfigurations()
236 {
237     QMutexLocker locker(&mutex);
238 
239     QList<QNetworkConfigurationPrivate *> foundConfigurations = fetchedConfigurations;
240     fetchedConfigurations.clear();
241 
242     return foundConfigurations;
243 }
244 
forceUpdate()245 void QNlaThread::forceUpdate()
246 {
247     mutex.lock();
248 
249     if (handle) {
250         /* cancel completion event */
251         if (WSALookupServiceEnd(handle) == SOCKET_ERROR) {
252 #ifdef BEARER_MANAGEMENT_DEBUG
253             qDebug("WSALookupServiceEnd error %d", WSAGetLastError());
254 #endif
255         }
256         handle = 0;
257     }
258     mutex.unlock();
259 }
260 
run()261 void QNlaThread::run()
262 {
263     WSAEVENT changeEvent = WSACreateEvent();
264     if (changeEvent == WSA_INVALID_EVENT)
265         return;
266 
267     while (true) {
268         fetchConfigurations();
269 
270         WSAQUERYSET qsRestrictions;
271 
272         memset(&qsRestrictions, 0, sizeof(qsRestrictions));
273         qsRestrictions.dwSize = sizeof(qsRestrictions);
274         qsRestrictions.dwNameSpace = NS_NLA;
275 
276         mutex.lock();
277         if (done) {
278             mutex.unlock();
279             break;
280         }
281         int result = WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL, &handle);
282         mutex.unlock();
283 
284         if (result == SOCKET_ERROR)
285             break;
286 
287         WSACOMPLETION completion;
288         WSAOVERLAPPED overlapped;
289 
290         memset(&overlapped, 0, sizeof(overlapped));
291         overlapped.hEvent = changeEvent;
292 
293         memset(&completion, 0, sizeof(completion));
294         completion.Type = NSP_NOTIFY_EVENT;
295         completion.Parameters.Event.lpOverlapped = &overlapped;
296 
297         DWORD bytesReturned = 0;
298         result = WSANSPIoctl(handle, SIO_NSP_NOTIFY_CHANGE, 0, 0, 0, 0,
299                              &bytesReturned, &completion);
300         if (result == SOCKET_ERROR) {
301             if (WSAGetLastError() != WSA_IO_PENDING)
302                 break;
303         }
304 
305         // Not interested in unrelated IO completion events
306         // although we also don't want to block them
307         while (WaitForSingleObjectEx(changeEvent, WSA_INFINITE, true) != WAIT_IO_COMPLETION &&
308                handle)
309         {
310         }
311 
312         mutex.lock();
313         if (handle) {
314             result = WSALookupServiceEnd(handle);
315             if (result == SOCKET_ERROR) {
316                 mutex.unlock();
317                 break;
318             }
319             handle = 0;
320         }
321         mutex.unlock();
322     }
323 
324     WSACloseEvent(changeEvent);
325 }
326 
updateConfigurations(QList<QNetworkConfigurationPrivate * > & configs)327 void QNlaThread::updateConfigurations(QList<QNetworkConfigurationPrivate *> &configs)
328 {
329     mutex.lock();
330 
331     while (!fetchedConfigurations.isEmpty())
332         delete fetchedConfigurations.takeFirst();
333 
334     fetchedConfigurations = configs;
335 
336     mutex.unlock();
337 
338     emit networksChanged();
339 }
340 
parseBlob(NLA_BLOB * blob,QNetworkConfigurationPrivate * cpPriv) const341 DWORD QNlaThread::parseBlob(NLA_BLOB *blob, QNetworkConfigurationPrivate *cpPriv) const
342 {
343 #ifdef BEARER_MANAGEMENT_DEBUG
344     printBlob(blob);
345 #endif
346 
347     switch (blob->header.type) {
348     case NLA_RAW_DATA:
349 #ifdef BEARER_MANAGEMENT_DEBUG
350         qDebug("%s: unhandled header type NLA_RAW_DATA", __FUNCTION__);
351 #endif
352         break;
353     case NLA_INTERFACE:
354         cpPriv->state = QNetworkConfiguration::Active;
355         if (QNlaEngine *engine = qobject_cast<QNlaEngine *>(parent())) {
356             engine->configurationInterface[cpPriv->id.toUInt()] =
357                 QString::fromLatin1(blob->data.interfaceData.adapterName);
358         }
359         break;
360     case NLA_802_1X_LOCATION:
361 #ifdef BEARER_MANAGEMENT_DEBUG
362         qDebug("%s: unhandled header type NLA_802_1X_LOCATION", __FUNCTION__);
363 #endif
364         break;
365     case NLA_CONNECTIVITY:
366 #ifdef BEARER_MANAGEMENT_DEBUG
367         qDebug("%s: unhandled header type NLA_CONNECTIVITY", __FUNCTION__);
368 #endif
369         break;
370     case NLA_ICS:
371 #ifdef BEARER_MANAGEMENT_DEBUG
372         qDebug("%s: unhandled header type NLA_ICS", __FUNCTION__);
373 #endif
374         break;
375     default:
376 #ifdef BEARER_MANAGEMENT_DEBUG
377         qDebug("%s: unhandled header type %d", __FUNCTION__, blob->header.type);
378 #endif
379         ;
380     }
381 
382     return blob->header.nextOffset;
383 }
384 
parseQuerySet(const WSAQUERYSET * querySet) const385 QNetworkConfigurationPrivate *QNlaThread::parseQuerySet(const WSAQUERYSET *querySet) const
386 {
387     QNetworkConfigurationPrivate *cpPriv = new QNetworkConfigurationPrivate;
388 
389     cpPriv->name = QString::fromWCharArray(querySet->lpszServiceInstanceName);
390     cpPriv->isValid = true;
391     cpPriv->id = QString::number(qHash(QLatin1String("NLA:") + cpPriv->name));
392     cpPriv->state = QNetworkConfiguration::Defined;
393     cpPriv->type = QNetworkConfiguration::InternetAccessPoint;
394 
395 #ifdef BEARER_MANAGEMENT_DEBUG
396     qDebug() << "size:" << querySet->dwSize;
397     qDebug() << "service instance name:" << QString::fromUtf16(querySet->lpszServiceInstanceName);
398     qDebug() << "service class id:" << querySet->lpServiceClassId;
399     qDebug() << "version:" << querySet->lpVersion;
400     qDebug() << "comment:" << QString::fromUtf16(querySet->lpszComment);
401     qDebug() << "namespace:" << querySet->dwNameSpace;
402     qDebug() << "namespace provider id:" << querySet->lpNSProviderId;
403     qDebug() << "context:" << QString::fromUtf16(querySet->lpszContext);
404     qDebug() << "number of protocols:" << querySet->dwNumberOfProtocols;
405     qDebug() << "protocols:" << querySet->lpafpProtocols;
406     qDebug() << "query string:" << QString::fromUtf16(querySet->lpszQueryString);
407     qDebug() << "number of cs addresses:" << querySet->dwNumberOfCsAddrs;
408     qDebug() << "cs addresses:" << querySet->lpcsaBuffer;
409     qDebug() << "output flags:" << querySet->dwOutputFlags;
410 #endif
411 
412     if (querySet->lpBlob) {
413 #ifdef BEARER_MANAGEMENT_DEBUG
414         qDebug() << "blob size:" << querySet->lpBlob->cbSize;
415         qDebug() << "blob data:" << querySet->lpBlob->pBlobData;
416 #endif
417 
418         DWORD offset = 0;
419         do {
420             NLA_BLOB *blob = reinterpret_cast<NLA_BLOB *>(querySet->lpBlob->pBlobData + offset);
421             DWORD nextOffset = parseBlob(blob, cpPriv);
422             if (nextOffset == offset)
423                 break;
424             else
425                 offset = nextOffset;
426         } while (offset != 0 && offset < querySet->lpBlob->cbSize);
427     }
428 
429     if (QNlaEngine *engine = qobject_cast<QNlaEngine *>(parent())) {
430         const QString interface = engine->getInterfaceFromId(cpPriv->id);
431         cpPriv->bearerType = qGetInterfaceType(interface);
432     }
433 
434     return cpPriv;
435 }
436 
fetchConfigurations()437 void QNlaThread::fetchConfigurations()
438 {
439     QList<QNetworkConfigurationPrivate *> foundConfigurations;
440 
441     WSAQUERYSET qsRestrictions;
442     HANDLE hLookup = 0;
443 
444     memset(&qsRestrictions, 0, sizeof(qsRestrictions));
445     qsRestrictions.dwSize = sizeof(qsRestrictions);
446     qsRestrictions.dwNameSpace = NS_NLA;
447 
448     int result = WSALookupServiceBegin(&qsRestrictions, LUP_RETURN_ALL | LUP_DEEP, &hLookup);
449     if (result == SOCKET_ERROR) {
450         mutex.lock();
451         fetchedConfigurations.clear();
452         mutex.unlock();
453     }
454 
455     char buffer[0x10000];
456     while (result == 0) {
457         DWORD bufferLength = sizeof(buffer);
458         result = WSALookupServiceNext(hLookup, LUP_RETURN_ALL,
459                                       &bufferLength, reinterpret_cast<WSAQUERYSET *>(buffer));
460 
461         if (result == SOCKET_ERROR)
462             break;
463 
464         QNetworkConfigurationPrivate *cpPriv =
465             parseQuerySet(reinterpret_cast<WSAQUERYSET *>(buffer));
466 
467         foundConfigurations.append(cpPriv);
468     }
469 
470     if (hLookup) {
471         result = WSALookupServiceEnd(hLookup);
472         if (result == SOCKET_ERROR) {
473 #ifdef BEARER_MANAGEMENT_DEBUG
474             qDebug("WSALookupServiceEnd error %d", WSAGetLastError());
475 #endif
476         }
477     }
478 
479     updateConfigurations(foundConfigurations);
480 }
481 
QNlaEngine(QObject * parent)482 QNlaEngine::QNlaEngine(QObject *parent)
483 :   QBearerEngineImpl(parent), nlaThread(0)
484 {
485     nlaThread = new QNlaThread(this);
486     connect(nlaThread, SIGNAL(networksChanged()),
487             this, SLOT(networksChanged()));
488     nlaThread->start();
489 
490     qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
491 }
492 
~QNlaEngine()493 QNlaEngine::~QNlaEngine()
494 {
495     delete nlaThread;
496 }
497 
networksChanged()498 void QNlaEngine::networksChanged()
499 {
500     QMutexLocker locker(&mutex);
501 
502     QStringList previous = accessPointConfigurations.keys();
503 
504     QList<QNetworkConfigurationPrivate *> foundConfigurations = nlaThread->getConfigurations();
505     while (!foundConfigurations.isEmpty()) {
506         QNetworkConfigurationPrivate *cpPriv = foundConfigurations.takeFirst();
507 
508         previous.removeAll(cpPriv->id);
509 
510         if (accessPointConfigurations.contains(cpPriv->id)) {
511             QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(cpPriv->id);
512 
513             bool changed = false;
514 
515             ptr->mutex.lock();
516 
517             if (ptr->isValid != cpPriv->isValid) {
518                 ptr->isValid = cpPriv->isValid;
519                 changed = true;
520             }
521 
522             if (ptr->name != cpPriv->name) {
523                 ptr->name = cpPriv->name;
524                 changed = true;
525             }
526 
527             if (ptr->state != cpPriv->state) {
528                 ptr->state = cpPriv->state;
529                 changed = true;
530             }
531 
532             ptr->mutex.unlock();
533 
534             if (changed) {
535                 locker.unlock();
536                 emit configurationChanged(ptr);
537                 locker.relock();
538             }
539 
540             delete cpPriv;
541         } else {
542             QNetworkConfigurationPrivatePointer ptr(cpPriv);
543 
544             accessPointConfigurations.insert(ptr->id, ptr);
545 
546             locker.unlock();
547             emit configurationAdded(ptr);
548             locker.relock();
549         }
550     }
551 
552     while (!previous.isEmpty()) {
553         QNetworkConfigurationPrivatePointer ptr =
554             accessPointConfigurations.take(previous.takeFirst());
555 
556         locker.unlock();
557         emit configurationRemoved(ptr);
558         locker.relock();
559     }
560 
561     locker.unlock();
562     emit updateCompleted();
563 }
564 
getInterfaceFromId(const QString & id)565 QString QNlaEngine::getInterfaceFromId(const QString &id)
566 {
567     QMutexLocker locker(&mutex);
568 
569     return configurationInterface.value(id.toUInt());
570 }
571 
hasIdentifier(const QString & id)572 bool QNlaEngine::hasIdentifier(const QString &id)
573 {
574     QMutexLocker locker(&mutex);
575 
576     return configurationInterface.contains(id.toUInt());
577 }
578 
connectToId(const QString & id)579 void QNlaEngine::connectToId(const QString &id)
580 {
581     emit connectionError(id, OperationNotSupported);
582 }
583 
disconnectFromId(const QString & id)584 void QNlaEngine::disconnectFromId(const QString &id)
585 {
586     emit connectionError(id, OperationNotSupported);
587 }
588 
requestUpdate()589 void QNlaEngine::requestUpdate()
590 {
591     QMutexLocker locker(&mutex);
592 
593     nlaThread->forceUpdate();
594 }
595 
sessionStateForId(const QString & id)596 QNetworkSession::State QNlaEngine::sessionStateForId(const QString &id)
597 {
598     QMutexLocker locker(&mutex);
599 
600     QNetworkConfigurationPrivatePointer ptr = accessPointConfigurations.value(id);
601 
602     if (!ptr)
603         return QNetworkSession::Invalid;
604 
605     if (!ptr->isValid) {
606         return QNetworkSession::Invalid;
607     } else if ((ptr->state & QNetworkConfiguration::Active) == QNetworkConfiguration::Active) {
608         return QNetworkSession::Connected;
609     } else if ((ptr->state & QNetworkConfiguration::Discovered) ==
610                 QNetworkConfiguration::Discovered) {
611         return QNetworkSession::Disconnected;
612     } else if ((ptr->state & QNetworkConfiguration::Defined) == QNetworkConfiguration::Defined) {
613         return QNetworkSession::NotAvailable;
614     } else if ((ptr->state & QNetworkConfiguration::Undefined) ==
615                 QNetworkConfiguration::Undefined) {
616         return QNetworkSession::NotAvailable;
617     }
618 
619     return QNetworkSession::Invalid;
620 }
621 
capabilities() const622 QNetworkConfigurationManager::Capabilities QNlaEngine::capabilities() const
623 {
624     return QNetworkConfigurationManager::ForcedRoaming;
625 }
626 
createSessionBackend()627 QNetworkSessionPrivate *QNlaEngine::createSessionBackend()
628 {
629     return new QNetworkSessionPrivateImpl;
630 }
631 
defaultConfiguration()632 QNetworkConfigurationPrivatePointer QNlaEngine::defaultConfiguration()
633 {
634     return QNetworkConfigurationPrivatePointer();
635 }
636 
637 #include "qnlaengine.moc"
638 QT_END_NAMESPACE
639 
640