diff options
Diffstat (limited to 'src/network')
29 files changed, 1156 insertions, 273 deletions
diff --git a/src/network/.prev_CMakeLists.txt b/src/network/.prev_CMakeLists.txt index e5756e291d..6d43386d74 100644 --- a/src/network/.prev_CMakeLists.txt +++ b/src/network/.prev_CMakeLists.txt @@ -108,9 +108,7 @@ extend_target(Network CONDITION QT_FEATURE_settings access/qhstsstore.cpp access/qhstsstore_p.h ) -extend_target(Network CONDITION APPLE_OSX - SOURCES - kernel/qnetworkproxy_mac.cpp +extend_target(Network CONDITION APPLE LIBRARIES ${FWCoreFoundation} ${FWSecurity} @@ -219,7 +217,7 @@ extend_target(Network CONDITION QT_FEATURE_dnslookup AND WINRT kernel/qdnslookup_winrt.cpp ) -extend_target(Network CONDITION APPLE_OSX AND NOT APPLE_UIKIT +extend_target(Network CONDITION APPLE AND NOT APPLE_UIKIT LIBRARIES ${FWCoreServices} ${FWSystemConfiguration} @@ -232,7 +230,12 @@ extend_target(Network CONDITION APPLE_IOS OR APPLE_OSX ${FWSystemConfiguration} ) -extend_target(Network CONDITION NOT APPLE_IOS AND NOT APPLE_OSX +extend_target(Network CONDITION QT_FEATURE_netlistmgr AND NOT APPLE_IOS AND NOT APPLE_OSX + SOURCES + kernel/qnetconmonitor_win.cpp +) + +extend_target(Network CONDITION NOT APPLE_IOS AND NOT APPLE_OSX AND NOT QT_FEATURE_netlistmgr SOURCES kernel/qnetconmonitor_stub.cpp ) @@ -247,6 +250,11 @@ extend_target(Network CONDITION APPLE_UIKIT kernel/qnetworkinterface_uikit_p.h ) +extend_target(Network CONDITION APPLE_OSX + SOURCES + kernel/qnetworkproxy_mac.cpp +) + extend_target(Network CONDITION QT_FEATURE_libproxy AND NOT APPLE_OSX AND (UNIX OR WINRT) SOURCES kernel/qnetworkproxy_libproxy.cpp diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt index 3148c00704..85ee368df5 100644 --- a/src/network/CMakeLists.txt +++ b/src/network/CMakeLists.txt @@ -108,9 +108,7 @@ extend_target(Network CONDITION QT_FEATURE_settings access/qhstsstore.cpp access/qhstsstore_p.h ) -extend_target(Network CONDITION APPLE_OSX - SOURCES - kernel/qnetworkproxy_mac.cpp +extend_target(Network CONDITION APPLE LIBRARIES ${FWCoreFoundation} ${FWSecurity} @@ -220,7 +218,7 @@ extend_target(Network CONDITION QT_FEATURE_dnslookup AND WINRT kernel/qdnslookup_winrt.cpp ) -extend_target(Network CONDITION APPLE_OSX AND NOT APPLE_UIKIT +extend_target(Network CONDITION APPLE AND NOT APPLE_UIKIT LIBRARIES ${FWCoreServices} ${FWSystemConfiguration} @@ -233,7 +231,12 @@ extend_target(Network CONDITION APPLE_IOS OR APPLE_OSX ${FWSystemConfiguration} ) -extend_target(Network CONDITION NOT APPLE_IOS AND NOT APPLE_OSX +extend_target(Network CONDITION QT_FEATURE_netlistmgr AND NOT APPLE_IOS AND NOT APPLE_OSX + SOURCES + kernel/qnetconmonitor_win.cpp +) + +extend_target(Network CONDITION NOT APPLE_IOS AND NOT APPLE_OSX AND NOT QT_FEATURE_netlistmgr SOURCES kernel/qnetconmonitor_stub.cpp ) @@ -248,6 +251,11 @@ extend_target(Network CONDITION APPLE_UIKIT kernel/qnetworkinterface_uikit_p.h ) +extend_target(Network CONDITION APPLE_OSX + SOURCES + kernel/qnetworkproxy_mac.cpp +) + extend_target(Network CONDITION QT_FEATURE_libproxy AND NOT APPLE_OSX AND (UNIX OR WINRT) SOURCES kernel/qnetworkproxy_libproxy.cpp diff --git a/src/network/access/qhttp2protocolhandler.cpp b/src/network/access/qhttp2protocolhandler.cpp index 93afcf0ee1..0ece5b7179 100644 --- a/src/network/access/qhttp2protocolhandler.cpp +++ b/src/network/access/qhttp2protocolhandler.cpp @@ -170,7 +170,6 @@ QHttp2ProtocolHandler::QHttp2ProtocolHandler(QHttpNetworkConnectionChannel *chan encoder(HPack::FieldLookupTable::DefaultSize, true) { Q_ASSERT(channel && m_connection); - continuedFrames.reserve(20); const ProtocolParameters params(m_connection->http2Parameters()); @@ -328,10 +327,32 @@ bool QHttp2ProtocolHandler::sendRequest() return false; } + // Process 'fake' (created by QNetworkAccessManager::connectToHostEncrypted()) + // requests first: + auto &requests = m_channel->spdyRequestsToSend; + for (auto it = requests.begin(), endIt = requests.end(); it != endIt;) { + const auto &pair = *it; + const QString scheme(pair.first.url().scheme()); + if (scheme == QLatin1String("preconnect-http") + || scheme == QLatin1String("preconnect-https")) { + m_connection->preConnectFinished(); + emit pair.second->finished(); + it = requests.erase(it); + if (!requests.size()) { + // Normally, after a connection was established and H2 + // was negotiated, we send a client preface. connectToHostEncrypted + // though is not meant to send any data, it's just a 'preconnect'. + // Thus we return early: + return true; + } + } else { + ++it; + } + } + if (!prefaceSent && !sendClientPreface()) return false; - auto &requests = m_channel->spdyRequestsToSend; if (!requests.size()) return true; diff --git a/src/network/access/qhttpthreaddelegate.cpp b/src/network/access/qhttpthreaddelegate.cpp index 6fb4710d77..acc551a7c9 100644 --- a/src/network/access/qhttpthreaddelegate.cpp +++ b/src/network/access/qhttpthreaddelegate.cpp @@ -128,7 +128,8 @@ static QByteArray makeCacheKey(QUrl &url, QNetworkProxy *proxy, const QString &p QString result; QUrl copy = url; QString scheme = copy.scheme(); - bool isEncrypted = scheme == QLatin1String("https"); + bool isEncrypted = scheme == QLatin1String("https") + || scheme == QLatin1String("preconnect-https"); copy.setPort(copy.port(isEncrypted ? 443 : 80)); if (scheme == QLatin1String("preconnect-http")) { copy.setScheme(QLatin1String("http")); @@ -296,17 +297,29 @@ void QHttpThreadDelegate::startRequest() connectionType = QHttpNetworkConnection::ConnectionTypeHTTP2Direct; } + const bool isH2 = httpRequest.isHTTP2Allowed() || httpRequest.isHTTP2Direct(); + if (isH2) { +#if QT_CONFIG(ssl) + if (ssl) { + if (!httpRequest.isHTTP2Direct()) { + QList<QByteArray> protocols; + protocols << QSslConfiguration::ALPNProtocolHTTP2 + << QSslConfiguration::NextProtocolHttp1_1; + incomingSslConfiguration->setAllowedNextProtocols(protocols); + } + urlCopy.setScheme(QStringLiteral("h2s")); + } else +#endif // QT_CONFIG(ssl) + { + urlCopy.setScheme(QStringLiteral("h2")); + } + } + #ifndef QT_NO_SSL if (ssl && !incomingSslConfiguration.data()) incomingSslConfiguration.reset(new QSslConfiguration); - if (httpRequest.isHTTP2Allowed() && ssl) { - // With HTTP2Direct we do not try any protocol negotiation. - QList<QByteArray> protocols; - protocols << QSslConfiguration::ALPNProtocolHTTP2 - << QSslConfiguration::NextProtocolHttp1_1; - incomingSslConfiguration->setAllowedNextProtocols(protocols); - } else if (httpRequest.isSPDYAllowed() && ssl) { + if (!isH2 && httpRequest.isSPDYAllowed() && ssl) { connectionType = QHttpNetworkConnection::ConnectionTypeSPDY; urlCopy.setScheme(QStringLiteral("spdy")); // to differentiate SPDY requests from HTTPS requests QList<QByteArray> nextProtocols; @@ -323,12 +336,11 @@ void QHttpThreadDelegate::startRequest() cacheKey = makeCacheKey(urlCopy, &cacheProxy, httpRequest.peerVerifyName()); else #endif - cacheKey = makeCacheKey(urlCopy, 0, httpRequest.peerVerifyName()); - + cacheKey = makeCacheKey(urlCopy, nullptr, httpRequest.peerVerifyName()); // the http object is actually a QHttpNetworkConnection httpConnection = static_cast<QNetworkAccessCachedHttpConnection *>(connections.localData()->requestEntryNow(cacheKey)); - if (httpConnection == 0) { + if (!httpConnection) { // no entry in cache; create an object // the http object is actually a QHttpNetworkConnection #ifdef QT_NO_BEARERMANAGEMENT @@ -358,7 +370,7 @@ void QHttpThreadDelegate::startRequest() connections.localData()->addEntry(cacheKey, httpConnection); } else { if (httpRequest.withCredentials()) { - QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), 0); + QNetworkAuthenticationCredential credential = authenticationManager->fetchCachedCredentials(httpRequest.url(), nullptr); if (!credential.user.isEmpty() && !credential.password.isEmpty()) { QAuthenticator auth; auth.setUser(credential.user); diff --git a/src/network/access/qnetworkaccessmanager.cpp b/src/network/access/qnetworkaccessmanager.cpp index a2996e3533..29192ae7b0 100644 --- a/src/network/access/qnetworkaccessmanager.cpp +++ b/src/network/access/qnetworkaccessmanager.cpp @@ -493,9 +493,9 @@ QNetworkAccessManager::QNetworkAccessManager(QObject *parent) connect(&d->statusMonitor, SIGNAL(onlineStateChanged(bool)), SLOT(_q_onlineStateChanged(bool))); #ifdef QT_NO_BEARERMANAGEMENT - d->networkAccessible = d->statusMonitor.isNetworkAccesible(); + d->networkAccessible = d->statusMonitor.isNetworkAccessible(); #else - d->networkAccessible = d->statusMonitor.isNetworkAccesible() ? Accessible : NotAccessible; + d->networkAccessible = d->statusMonitor.isNetworkAccessible() ? Accessible : NotAccessible; } else { // if a session is required, we track online state through // the QNetworkSession's signals if a request is already made. @@ -1236,10 +1236,11 @@ void QNetworkAccessManager::connectToHostEncrypted(const QString &hostName, quin if (sslConfiguration != QSslConfiguration::defaultConfiguration()) request.setSslConfiguration(sslConfiguration); - // There is no way to enable SPDY via a request, so we need to check - // the ssl configuration whether SPDY is allowed here. - if (sslConfiguration.allowedNextProtocols().contains( - QSslConfiguration::NextProtocolSpdy3_0)) + // There is no way to enable SPDY/HTTP2 via a request, so we need to check + // the ssl configuration whether SPDY/HTTP2 is allowed here. + if (sslConfiguration.allowedNextProtocols().contains(QSslConfiguration::ALPNProtocolHTTP2)) + request.setAttribute(QNetworkRequest::HTTP2AllowedAttribute, true); + else if (sslConfiguration.allowedNextProtocols().contains(QSslConfiguration::NextProtocolSpdy3_0)) request.setAttribute(QNetworkRequest::SpdyAllowedAttribute, true); request.setPeerVerifyName(peerName); @@ -1565,8 +1566,8 @@ QNetworkReply *QNetworkAccessManager::createRequest(QNetworkAccessManager::Opera QNetworkReplyImpl *reply = new QNetworkReplyImpl(this); #ifndef QT_NO_BEARERMANAGEMENT // NETMONTODO: network reply impl must be augmented to use the same monitoring - // capabilities as http network reply impl does. - if (!isLocalFile && !d->statusMonitor.isEnabled()) { + // capabilities as http network reply impl does. Once it does: uncomment the condition below + if (!isLocalFile /*&& !d->statusMonitor.isEnabled()*/) { connect(this, SIGNAL(networkSessionConnected()), reply, SLOT(_q_networkSessionConnected())); } diff --git a/src/network/access/qnetworkreplywasmimpl.cpp b/src/network/access/qnetworkreplywasmimpl.cpp index ee91dc20b3..53784407d8 100644 --- a/src/network/access/qnetworkreplywasmimpl.cpp +++ b/src/network/access/qnetworkreplywasmimpl.cpp @@ -135,8 +135,10 @@ static void q_loadCallback(val event) if (readyState == 4) { // done reply->setReplyAttributes(xhr["data-handler"].as<quintptr>(), status, statusText); - if (!responseString.isEmpty()) - reply->dataReceived(responseString.toUtf8(), responseString.size()); + if (!responseString.isEmpty()) { + QByteArray responseStringArray = responseString.toUtf8(); + reply->dataReceived(responseStringArray, responseStringArray.size()); + } } } if (status >= 400 && !statusText.isEmpty()) diff --git a/src/network/access/qnetworkrequest.cpp b/src/network/access/qnetworkrequest.cpp index ba36c75419..37f64c3f52 100644 --- a/src/network/access/qnetworkrequest.cpp +++ b/src/network/access/qnetworkrequest.cpp @@ -468,13 +468,24 @@ public: }; /*! + Constructs a QNetworkRequest object with no URL to be requested. + Use setUrl() to set one. + + \sa url(), setUrl() +*/ +QNetworkRequest::QNetworkRequest() + : d(new QNetworkRequestPrivate) +{ +} + +/*! Constructs a QNetworkRequest object with \a url as the URL to be requested. \sa url(), setUrl() */ QNetworkRequest::QNetworkRequest(const QUrl &url) - : d(new QNetworkRequestPrivate) + : QNetworkRequest() { d->url = url; } @@ -1344,7 +1355,7 @@ QDateTime QNetworkHeadersPrivate::fromHttpDate(const QByteArray &value) QByteArray QNetworkHeadersPrivate::toHttpDate(const QDateTime &dt) { - return QLocale::c().toString(dt, QStringViewLiteral("ddd, dd MMM yyyy hh:mm:ss 'GMT'")) + return QLocale::c().toString(dt, u"ddd, dd MMM yyyy hh:mm:ss 'GMT'") .toLatin1(); } diff --git a/src/network/access/qnetworkrequest.h b/src/network/access/qnetworkrequest.h index 846ead1592..8ad4ab41c0 100644 --- a/src/network/access/qnetworkrequest.h +++ b/src/network/access/qnetworkrequest.h @@ -128,7 +128,8 @@ public: }; - explicit QNetworkRequest(const QUrl &url = QUrl()); + QNetworkRequest(); + explicit QNetworkRequest(const QUrl &url); QNetworkRequest(const QNetworkRequest &other); ~QNetworkRequest(); QNetworkRequest &operator=(QNetworkRequest &&other) noexcept { swap(other); return *this; } diff --git a/src/network/access/qspdyprotocolhandler.cpp b/src/network/access/qspdyprotocolhandler.cpp index 403c01e974..f845235bf7 100644 --- a/src/network/access/qspdyprotocolhandler.cpp +++ b/src/network/access/qspdyprotocolhandler.cpp @@ -305,7 +305,7 @@ bool QSpdyProtocolHandler::sendRequest() currentReply->setSpdyWasUsed(true); qint32 streamID = generateNextStreamID(); - currentReply->setProperty("SPDYStreamID", streamID); + m_streamIDs.insert(currentReply, streamID); currentReply->setRequest(currentRequest); currentReply->d_func()->connection = m_connection; @@ -322,7 +322,7 @@ bool QSpdyProtocolHandler::sendRequest() void QSpdyProtocolHandler::_q_replyDestroyed(QObject* reply) { - qint32 streamID = reply->property("SPDYStreamID").toInt(); + qint32 streamID = m_streamIDs.take(reply); if (m_inFlightStreams.remove(streamID)) sendRST_STREAM(streamID, RST_STREAM_CANCEL); } @@ -624,10 +624,12 @@ void QSpdyProtocolHandler::sendSYN_STREAM(const HttpMessagePair &messagePair, // hack: set the stream ID on the device directly, so when we get // the signal for uploading we know which stream we are sending on - request.uploadByteDevice()->setProperty("SPDYStreamID", streamID); + m_streamIDs.insert(request.uploadByteDevice(), streamID); QObject::connect(request.uploadByteDevice(), SIGNAL(readyRead()), this, SLOT(_q_uploadDataReadyRead()), Qt::QueuedConnection); + QObject::connect(request.uploadByteDevice(), SIGNAL(destroyed(QObject*)), this, + SLOT(_q_uploadDataDestroyed(QObject *))); } QByteArray namesAndValues = composeHeader(request); @@ -663,6 +665,11 @@ void QSpdyProtocolHandler::sendSYN_STREAM(const HttpMessagePair &messagePair, uploadData(streamID); } +void QSpdyProtocolHandler::_q_uploadDataDestroyed(QObject *uploadData) +{ + m_streamIDs.remove(uploadData); +} + void QSpdyProtocolHandler::sendRST_STREAM(qint32 streamID, RST_STREAM_STATUS_CODE statusCode) { char wireData[8]; @@ -756,7 +763,7 @@ void QSpdyProtocolHandler::_q_uploadDataReadyRead() { QNonContiguousByteDevice *device = qobject_cast<QNonContiguousByteDevice *>(sender()); Q_ASSERT(device); - qint32 streamID = device->property("SPDYStreamID").toInt(); + qint32 streamID = m_streamIDs.value(device); Q_ASSERT(streamID > 0); uploadData(streamID); } diff --git a/src/network/access/qspdyprotocolhandler_p.h b/src/network/access/qspdyprotocolhandler_p.h index dd93a9aba2..14e2ff388a 100644 --- a/src/network/access/qspdyprotocolhandler_p.h +++ b/src/network/access/qspdyprotocolhandler_p.h @@ -110,6 +110,7 @@ public: private slots: void _q_uploadDataReadyRead(); void _q_replyDestroyed(QObject*); + void _q_uploadDataDestroyed(QObject *); private: @@ -216,6 +217,7 @@ private: bool m_waitingForCompleteStream; z_stream m_deflateStream; z_stream m_inflateStream; + QHash<QObject *, qint32> m_streamIDs; }; Q_DECLARE_OPERATORS_FOR_FLAGS(QSpdyProtocolHandler::DataFrameFlags) diff --git a/src/network/bearer/qsharednetworksession.cpp b/src/network/bearer/qsharednetworksession.cpp index af543f77e3..b3e9892f4b 100644 --- a/src/network/bearer/qsharednetworksession.cpp +++ b/src/network/bearer/qsharednetworksession.cpp @@ -64,9 +64,23 @@ struct DeleteLater { } }; +template <typename Container> +static void maybe_prune_expired(Container &c) +{ + if (c.size() > 16) { + for (auto it = c.cbegin(), end = c.cend(); it != end; /*erasing*/) { + if (!it->second.lock()) + it = c.erase(it); + else + ++it; + } + } +} + QSharedPointer<QNetworkSession> QSharedNetworkSessionManager::getSession(const QNetworkConfiguration &config) { QSharedNetworkSessionManager *m = sharedNetworkSessionManager(); + maybe_prune_expired(m->sessions); auto &entry = m->sessions[config]; //if already have a session, return it if (auto p = entry.toStrongRef()) diff --git a/src/network/configure.cmake b/src/network/configure.cmake index c7d300acb0..2dd5460b9d 100644 --- a/src/network/configure.cmake +++ b/src/network/configure.cmake @@ -182,6 +182,29 @@ gss_ctx_id_t ctx; "# FIXME: qmake: LIBS += -lgssapi_krb5 ) +# netlistmgr +qt_config_compile_test(netlistmgr + LABEL "Network List Manager" +" +#include <netlistmgr.h> +#include <wrl/client.h> + +int main(int argc, char **argv) +{ + (void)argc; (void)argv; + /* BEGIN TEST: */ +using namespace Microsoft::WRL; +ComPtr<INetworkListManager> networkListManager; +ComPtr<IConnectionPoint> connectionPoint; +ComPtr<IConnectionPointContainer> connectionPointContainer; +networkListManager.As(&connectionPointContainer); +connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, &connectionPoint); + /* END TEST: */ + return 0; +} +"# FIXME: qmake: LIBS += -lOle32 +) + #### Features @@ -352,3 +375,9 @@ qt_feature("sspi" PUBLIC CONDITION WIN32 AND NOT WINRT ) qt_feature_definition("sspi" "QT_NO_SSPI" NEGATE VALUE "1") +qt_feature("netlistmgr" PRIVATE + SECTION "Networking" + LABEL "Network List Manager" + PURPOSE "Use Network List Manager to keep track of network connectivity" + CONDITION WIN32 AND TEST_netlistmgr +) diff --git a/src/network/configure.json b/src/network/configure.json index 7225a6251d..f80f38f143 100644 --- a/src/network/configure.json +++ b/src/network/configure.json @@ -208,6 +208,22 @@ "main": ["gss_ctx_id_t ctx;"], "qmake": "LIBS += -lgssapi_krb5" } + }, + "netlistmgr": { + "label": "Network List Manager", + "type": "compile", + "test": { + "include": [ "netlistmgr.h", "wrl/client.h" ], + "main": [ + "using namespace Microsoft::WRL;", + "ComPtr<INetworkListManager> networkListManager;", + "ComPtr<IConnectionPoint> connectionPoint;", + "ComPtr<IConnectionPointContainer> connectionPointContainer;", + "networkListManager.As(&connectionPointContainer);", + "connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, &connectionPoint);" + ], + "qmake": "LIBS += -lOle32" + } } }, @@ -397,6 +413,13 @@ "section": "Networking", "condition": "config.win32 && !config.winrt", "output": [ "publicFeature", "feature" ] + }, + "netlistmgr": { + "label": "Network List Manager", + "purpose": "Use Network List Manager to keep track of network connectivity", + "section": "Networking", + "condition": "config.win32 && tests.netlistmgr", + "output": [ "privateFeature" ] } }, diff --git a/src/network/kernel/kernel.pri b/src/network/kernel/kernel.pri index 0e4cef5e74..a55648dbc7 100644 --- a/src/network/kernel/kernel.pri +++ b/src/network/kernel/kernel.pri @@ -77,6 +77,8 @@ macos | ios { kernel/qnetconmonitor_darwin.mm LIBS_PRIVATE += -framework SystemConfiguration +} else:qtConfig(netlistmgr) { + SOURCES += kernel/qnetconmonitor_win.cpp } else { SOURCES += kernel/qnetconmonitor_stub.cpp } diff --git a/src/network/kernel/qhostinfo.cpp b/src/network/kernel/qhostinfo.cpp index 25ff873307..487cac6d90 100644 --- a/src/network/kernel/qhostinfo.cpp +++ b/src/network/kernel/qhostinfo.cpp @@ -47,6 +47,7 @@ #include <qabstracteventdispatcher.h> #include <qcoreapplication.h> #include <qmetaobject.h> +#include <qscopeguard.h> #include <qstringlist.h> #include <qthread.h> #include <qurl.h> @@ -85,13 +86,6 @@ private: QString m_toBeLookedUp; }; -// ### C++11: remove once we can use std::any_of() -template<class InputIt, class UnaryPredicate> -bool any_of(InputIt first, InputIt last, UnaryPredicate p) -{ - return std::find_if(first, last, p) != last; -} - template <typename InputIt, typename OutputIt1, typename OutputIt2, typename UnaryPredicate> std::pair<OutputIt1, OutputIt2> separate_if(InputIt first, InputIt last, OutputIt1 dest1, OutputIt2 dest2, UnaryPredicate p) { @@ -118,11 +112,39 @@ int get_signal_index() return signal_index + QMetaObjectPrivate::signalOffset(senderMetaObject); } -void emit_results_ready(const QHostInfo &hostInfo, const QObject *receiver, - QtPrivate::QSlotObjectBase *slotObj) +} + +/* + The calling thread is likely the one that executes the lookup via + QHostInfoRunnable. Unless we operate with a queued connection already, + posts the QHostInfo to a dedicated QHostInfoResult object that lives in + the same thread as the user-provided receiver, or (if there is none) in + the thread that made the call to lookupHost. That QHostInfoResult object + then calls the user code in the correct thread. + + The 'result' object deletes itself (via deleteLater) when the metacall + event is received. +*/ +void QHostInfoResult::postResultsReady(const QHostInfo &info) { + // queued connection will take care of dispatching to right thread + if (!slotObj) { + emitResultsReady(info); + return; + } static const int signal_index = get_signal_index(); + + // we used to have a context object, but it's already destroyed + if (withContextObject && !receiver) + return; + + /* QHostInfoResult c'tor moves the result object to the thread of receiver. + If we don't have a receiver, then the result object will not live in a + thread that runs an event loop - so move it to this' thread, which is the thread + that initiated the lookup, and required to have a running event loop. */ auto result = new QHostInfoResult(receiver, slotObj); + if (!receiver) + result->moveToThread(thread()); Q_CHECK_PTR(result); const int nargs = 2; auto types = reinterpret_cast<int *>(malloc(nargs * sizeof(int))); @@ -132,15 +154,13 @@ void emit_results_ready(const QHostInfo &hostInfo, const QObject *receiver, auto args = reinterpret_cast<void **>(malloc(nargs * sizeof(void *))); Q_CHECK_PTR(args); args[0] = 0; - args[1] = QMetaType::create(types[1], &hostInfo); + args[1] = QMetaType::create(types[1], &info); Q_CHECK_PTR(args[1]); auto metaCallEvent = new QMetaCallEvent(slotObj, nullptr, signal_index, nargs, types, args); Q_CHECK_PTR(metaCallEvent); qApp->postEvent(result, metaCallEvent); } -} - /*! \class QHostInfo \brief The QHostInfo class provides static functions for host name lookups. @@ -328,6 +348,10 @@ int QHostInfo::lookupHost(const QString &name, QObject *receiver, ready, the \a functor is called with a QHostInfo argument. The QHostInfo object can then be inspected to get the results of the lookup. + + The \a functor will be run in the thread that makes the call to lookupHost; + that thread must have a running Qt event loop. + \note There is no guarantee on the order the signals will be emitted if you start multiple requests with lookupHost(). @@ -398,7 +422,7 @@ QHostInfo QHostInfo::fromName(const QString &name) #endif QHostInfo hostInfo = QHostInfoAgent::fromName(name); - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); manager->cache.put(name, hostInfo); return hostInfo; } @@ -411,7 +435,7 @@ QHostInfo QHostInfoPrivate::fromName(const QString &name, QSharedPointer<QNetwor #endif QHostInfo hostInfo = QHostInfoAgent::fromName(name, session); - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); manager->cache.put(name, hostInfo); return hostInfo; } @@ -600,8 +624,9 @@ QHostInfo QHostInfoAgent::lookup(const QString &hostName) \sa lookupId() */ QHostInfo::QHostInfo(int id) - : d(new QHostInfoPrivate) + : d_ptr(new QHostInfoPrivate) { + Q_D(QHostInfo); d->lookupId = id; } @@ -609,17 +634,30 @@ QHostInfo::QHostInfo(int id) Constructs a copy of \a other. */ QHostInfo::QHostInfo(const QHostInfo &other) - : d(new QHostInfoPrivate(*other.d.data())) + : d_ptr(new QHostInfoPrivate(*other.d_ptr)) { } /*! + Move-constucts a new QHostInfo from \a other. + + \note The moved-from object \a other is placed in a + partially-formed state, in which the only valid operations are + destruction and assignment of a new value. + + \since 5.14 +*/ + +/*! Assigns the data of the \a other object to this host info object, and returns a reference to it. */ QHostInfo &QHostInfo::operator=(const QHostInfo &other) { - *d.data() = *other.d.data(); + if (d_ptr) + *d_ptr = *other.d_ptr; + else + d_ptr = new QHostInfoPrivate(*other.d_ptr); return *this; } @@ -628,6 +666,7 @@ QHostInfo &QHostInfo::operator=(const QHostInfo &other) */ QHostInfo::~QHostInfo() { + delete d_ptr; } /*! @@ -642,6 +681,7 @@ QHostInfo::~QHostInfo() */ QList<QHostAddress> QHostInfo::addresses() const { + Q_D(const QHostInfo); return d->addrs; } @@ -652,6 +692,7 @@ QList<QHostAddress> QHostInfo::addresses() const */ void QHostInfo::setAddresses(const QList<QHostAddress> &addresses) { + Q_D(QHostInfo); d->addrs = addresses; } @@ -662,6 +703,7 @@ void QHostInfo::setAddresses(const QList<QHostAddress> &addresses) */ QString QHostInfo::hostName() const { + Q_D(const QHostInfo); return d->hostName; } @@ -672,6 +714,7 @@ QString QHostInfo::hostName() const */ void QHostInfo::setHostName(const QString &hostName) { + Q_D(QHostInfo); d->hostName = hostName; } @@ -683,6 +726,7 @@ void QHostInfo::setHostName(const QString &hostName) */ QHostInfo::HostInfoError QHostInfo::error() const { + Q_D(const QHostInfo); return d->err; } @@ -693,6 +737,7 @@ QHostInfo::HostInfoError QHostInfo::error() const */ void QHostInfo::setError(HostInfoError error) { + Q_D(QHostInfo); d->err = error; } @@ -703,6 +748,7 @@ void QHostInfo::setError(HostInfoError error) */ int QHostInfo::lookupId() const { + Q_D(const QHostInfo); return d->lookupId; } @@ -713,6 +759,7 @@ int QHostInfo::lookupId() const */ void QHostInfo::setLookupId(int id) { + Q_D(QHostInfo); d->lookupId = id; } @@ -724,6 +771,7 @@ void QHostInfo::setLookupId(int id) */ QString QHostInfo::errorString() const { + Q_D(const QHostInfo); return d->errorStr; } @@ -735,6 +783,7 @@ QString QHostInfo::errorString() const */ void QHostInfo::setErrorString(const QString &str) { + Q_D(QHostInfo); d->errorStr = str; } @@ -791,7 +840,8 @@ int QHostInfo::lookupHostImpl(const QString &name, QHostInfo hostInfo(id); hostInfo.setError(QHostInfo::HostNotFound); hostInfo.setErrorString(QCoreApplication::translate("QHostInfo", "No host name given")); - emit_results_ready(hostInfo, receiver, slotObj); + QHostInfoResult result(receiver, slotObj); + result.postResultsReady(hostInfo); return id; } @@ -805,7 +855,8 @@ int QHostInfo::lookupHostImpl(const QString &name, QHostInfo info = manager->cache.get(name, &valid); if (valid) { info.setLookupId(id); - emit_results_ready(info, receiver, slotObj); + QHostInfoResult result(receiver, slotObj); + result.postResultsReady(info); return id; } } @@ -833,11 +884,10 @@ QHostInfoRunnable::QHostInfoRunnable(const QString &hn, int i, const QObject *re void QHostInfoRunnable::run() { QHostInfoLookupManager *manager = theHostInfoLookupManager(); + const auto sg = qScopeGuard([&] { manager->lookupFinished(this); }); // check aborted - if (manager->wasAborted(id)) { - manager->lookupFinished(this); + if (manager->wasAborted(id)) return; - } QHostInfo hostInfo; @@ -859,14 +909,12 @@ void QHostInfoRunnable::run() } // check aborted again - if (manager->wasAborted(id)) { - manager->lookupFinished(this); + if (manager->wasAborted(id)) return; - } // signal emission hostInfo.setLookupId(id); - resultEmitter.emitResultsReady(hostInfo); + resultEmitter.postResultsReady(hostInfo); #if QT_CONFIG(thread) // now also iterate through the postponed ones @@ -879,30 +927,31 @@ void QHostInfoRunnable::run() QHostInfoRunnable* postponed = *it; // we can now emit hostInfo.setLookupId(postponed->id); - postponed->resultEmitter.emitResultsReady(hostInfo); + postponed->resultEmitter.postResultsReady(hostInfo); delete postponed; } manager->postponedLookups.erase(partitionBegin, partitionEnd); } #endif - manager->lookupFinished(this); - // thread goes back to QThreadPool } QHostInfoLookupManager::QHostInfoLookupManager() : wasDeleted(false) { - moveToThread(QCoreApplicationPrivate::mainThread()); #if QT_CONFIG(thread) - connect(QCoreApplication::instance(), SIGNAL(destroyed()), SLOT(waitForThreadPoolDone()), Qt::DirectConnection); + QObject::connect(QCoreApplication::instance(), &QObject::destroyed, + &threadPool, [&](QObject *) { threadPool.waitForDone(); }, + Qt::DirectConnection); threadPool.setMaxThreadCount(20); // do up to 20 DNS lookups in parallel #endif } QHostInfoLookupManager::~QHostInfoLookupManager() { + QMutexLocker locker(&mutex); wasDeleted = true; + locker.unlock(); // don't qDeleteAll currentLookups, the QThreadPool has ownership clear(); @@ -928,7 +977,8 @@ void QHostInfoLookupManager::clear() cache.clear(); } -void QHostInfoLookupManager::work() +// assumes mutex is locked by caller +void QHostInfoLookupManager::rescheduleWithMutexHeld() { if (wasDeleted) return; @@ -937,8 +987,6 @@ void QHostInfoLookupManager::work() // - launch new lookups via the thread pool // - make sure only one lookup per host/IP is in progress - QMutexLocker locker(&mutex); - if (!finishedLookups.isEmpty()) { // remove ID from aborted if it is in there for (int i = 0; i < finishedLookups.length(); i++) { @@ -950,7 +998,7 @@ void QHostInfoLookupManager::work() #if QT_CONFIG(thread) auto isAlreadyRunning = [this](QHostInfoRunnable *lookup) { - return any_of(currentLookups.cbegin(), currentLookups.cend(), ToBeLookedUpEquals(lookup->toBeLookedUp)); + return std::any_of(currentLookups.cbegin(), currentLookups.cend(), ToBeLookedUpEquals(lookup->toBeLookedUp)); }; // Transfer any postponed lookups that aren't currently running to the scheduled list, keeping already-running lookups: @@ -990,22 +1038,23 @@ void QHostInfoLookupManager::work() // called by QHostInfo void QHostInfoLookupManager::scheduleLookup(QHostInfoRunnable *r) { + QMutexLocker locker(&this->mutex); + if (wasDeleted) return; - QMutexLocker locker(&this->mutex); scheduledLookups.enqueue(r); - work(); + rescheduleWithMutexHeld(); } // called by QHostInfo void QHostInfoLookupManager::abortLookup(int id) { + QMutexLocker locker(&this->mutex); + if (wasDeleted) return; - QMutexLocker locker(&this->mutex); - #if QT_CONFIG(thread) // is postponed? delete and return for (int i = 0; i < postponedLookups.length(); i++) { @@ -1031,25 +1080,27 @@ void QHostInfoLookupManager::abortLookup(int id) // called from QHostInfoRunnable bool QHostInfoLookupManager::wasAborted(int id) { + QMutexLocker locker(&this->mutex); + if (wasDeleted) return true; - QMutexLocker locker(&this->mutex); return abortedLookups.contains(id); } // called from QHostInfoRunnable void QHostInfoLookupManager::lookupFinished(QHostInfoRunnable *r) { + QMutexLocker locker(&this->mutex); + if (wasDeleted) return; - QMutexLocker locker(&this->mutex); #if QT_CONFIG(thread) currentLookups.removeOne(r); #endif finishedLookups.append(r); - work(); + rescheduleWithMutexHeld(); } // This function returns immediately when we had a result in the cache, else it will later emit a signal @@ -1059,7 +1110,7 @@ QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char *id = -1; // check cache - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); if (manager && manager->cache.isEnabled()) { QHostInfo info = manager->cache.get(name, valid); if (*valid) { @@ -1076,7 +1127,7 @@ QHostInfo qt_qhostinfo_lookup(const QString &name, QObject *receiver, const char void qt_qhostinfo_clear_cache() { - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); if (manager) { manager->clear(); } @@ -1085,7 +1136,7 @@ void qt_qhostinfo_clear_cache() #ifdef QT_BUILD_INTERNAL void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e) { - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); if (manager) { manager->cache.setEnabled(e); } @@ -1093,7 +1144,7 @@ void Q_AUTOTEST_EXPORT qt_qhostinfo_enable_cache(bool e) void qt_qhostinfo_cache_inject(const QString &hostname, const QHostInfo &resolution) { - QAbstractHostInfoLookupManager* manager = theHostInfoLookupManager(); + QHostInfoLookupManager* manager = theHostInfoLookupManager(); if (!manager || !manager->cache.isEnabled()) return; @@ -1106,23 +1157,10 @@ void qt_qhostinfo_cache_inject(const QString &hostname, const QHostInfo &resolut QHostInfoCache::QHostInfoCache() : max_age(60), enabled(true), cache(128) { #ifdef QT_QHOSTINFO_CACHE_DISABLED_BY_DEFAULT - enabled = false; + enabled.store(false, std::memory_order_relaxed); #endif } -bool QHostInfoCache::isEnabled() -{ - return enabled; -} - -// this function is currently only used for the auto tests -// and not usable by public API -void QHostInfoCache::setEnabled(bool e) -{ - enabled = e; -} - - QHostInfo QHostInfoCache::get(const QString &name, bool *valid) { QMutexLocker locker(&this->mutex); @@ -1162,9 +1200,4 @@ void QHostInfoCache::clear() cache.clear(); } -QAbstractHostInfoLookupManager* QAbstractHostInfoLookupManager::globalInstance() -{ - return theHostInfoLookupManager(); -} - QT_END_NAMESPACE diff --git a/src/network/kernel/qhostinfo.h b/src/network/kernel/qhostinfo.h index dc31cc08e4..cda286b423 100644 --- a/src/network/kernel/qhostinfo.h +++ b/src/network/kernel/qhostinfo.h @@ -62,11 +62,12 @@ public: explicit QHostInfo(int lookupId = -1); QHostInfo(const QHostInfo &d); + QHostInfo(QHostInfo &&other) noexcept : d_ptr(qExchange(other.d_ptr, nullptr)) {} QHostInfo &operator=(const QHostInfo &d); QHostInfo &operator=(QHostInfo &&other) noexcept { swap(other); return *this; } ~QHostInfo(); - void swap(QHostInfo &other) noexcept { qSwap(d, other.d); } + void swap(QHostInfo &other) noexcept { qSwap(d_ptr, other.d_ptr); } QString hostName() const; void setHostName(const QString &name); @@ -147,7 +148,8 @@ public: #endif // Q_QDOC private: - QScopedPointer<QHostInfoPrivate> d; + QHostInfoPrivate *d_ptr; + Q_DECLARE_PRIVATE(QHostInfo) static int lookupHostImpl(const QString &name, const QObject *receiver, diff --git a/src/network/kernel/qhostinfo_p.h b/src/network/kernel/qhostinfo_p.h index 7df3f5414c..9a4657234e 100644 --- a/src/network/kernel/qhostinfo_p.h +++ b/src/network/kernel/qhostinfo_p.h @@ -73,6 +73,7 @@ #include <QNetworkSession> #include <QSharedPointer> +#include <atomic> QT_BEGIN_NAMESPACE @@ -83,12 +84,14 @@ class QHostInfoResult : public QObject QPointer<const QObject> receiver = nullptr; QtPrivate::QSlotObjectBase *slotObj = nullptr; + const bool withContextObject = false; public: QHostInfoResult() = default; QHostInfoResult(const QObject *receiver, QtPrivate::QSlotObjectBase *slotObj) : receiver(receiver), - slotObj(slotObj) + slotObj(slotObj), + withContextObject(slotObj && receiver) { connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, &QObject::deleteLater); @@ -96,10 +99,15 @@ public: moveToThread(receiver->thread()); } + void postResultsReady(const QHostInfo &info); + public Q_SLOTS: inline void emitResultsReady(const QHostInfo &info) { if (slotObj) { + // we used to have a context object, but it's already destroyed + if (withContextObject && !receiver) + return; QHostInfo copy = info; void *args[2] = { nullptr, reinterpret_cast<void *>(©) }; slotObj->call(const_cast<QObject*>(receiver.data()), args); @@ -177,10 +185,12 @@ public: void put(const QString &name, const QHostInfo &info); void clear(); - bool isEnabled(); - void setEnabled(bool e); + bool isEnabled() { return enabled.load(std::memory_order_relaxed); } + // this function is currently only used for the auto tests + // and not usable by public API + void setEnabled(bool e) { enabled.store(e, std::memory_order_relaxed); } private: - bool enabled; + std::atomic<bool> enabled; struct QHostInfoCacheElement { QHostInfo info; QElapsedTimer age; @@ -205,31 +215,13 @@ public: }; -class QAbstractHostInfoLookupManager : public QObject -{ - Q_OBJECT - -public: - ~QAbstractHostInfoLookupManager() {} - virtual void clear() = 0; - - QHostInfoCache cache; - -protected: - QAbstractHostInfoLookupManager() {} - static QAbstractHostInfoLookupManager* globalInstance(); - -}; - -class QHostInfoLookupManager : public QAbstractHostInfoLookupManager +class QHostInfoLookupManager { - Q_OBJECT public: QHostInfoLookupManager(); ~QHostInfoLookupManager(); - void clear() override; - void work(); + void clear(); // called from QHostInfo void scheduleLookup(QHostInfoRunnable *r); @@ -239,6 +231,8 @@ public: void lookupFinished(QHostInfoRunnable *r); bool wasAborted(int id); + QHostInfoCache cache; + friend class QHostInfoRunnable; protected: #if QT_CONFIG(thread) @@ -252,14 +246,12 @@ protected: #if QT_CONFIG(thread) QThreadPool threadPool; #endif - QRecursiveMutex mutex; + QMutex mutex; bool wasDeleted; -private slots: -#if QT_CONFIG(thread) - void waitForThreadPoolDone() { threadPool.waitForDone(); } -#endif +private: + void rescheduleWithMutexHeld(); }; QT_END_NAMESPACE diff --git a/src/network/kernel/qnetconmonitor_darwin.mm b/src/network/kernel/qnetconmonitor_darwin.mm index a64cd6e530..f6daf9ed50 100644 --- a/src/network/kernel/qnetconmonitor_darwin.mm +++ b/src/network/kernel/qnetconmonitor_darwin.mm @@ -376,7 +376,7 @@ bool QNetworkStatusMonitor::isMonitoring() const return d->ipv4Probe.isMonitoring() || d->ipv6Probe.isMonitoring(); } -bool QNetworkStatusMonitor::isNetworkAccesible() +bool QNetworkStatusMonitor::isNetworkAccessible() { // This function is to be executed on the thread that created // and uses 'this'. diff --git a/src/network/kernel/qnetconmonitor_p.h b/src/network/kernel/qnetconmonitor_p.h index 74ee56d422..282bac5081 100644 --- a/src/network/kernel/qnetconmonitor_p.h +++ b/src/network/kernel/qnetconmonitor_p.h @@ -61,7 +61,7 @@ QT_BEGIN_NAMESPACE class QNetworkConnectionMonitorPrivate; -class QNetworkConnectionMonitor : public QObject +class Q_AUTOTEST_EXPORT QNetworkConnectionMonitor : public QObject { Q_OBJECT @@ -91,7 +91,7 @@ private: }; class QNetworkStatusMonitorPrivate; -class QNetworkStatusMonitor : public QObject +class Q_AUTOTEST_EXPORT QNetworkStatusMonitor : public QObject { Q_OBJECT @@ -99,7 +99,7 @@ public: QNetworkStatusMonitor(); ~QNetworkStatusMonitor(); - bool isNetworkAccesible(); + bool isNetworkAccessible(); bool start(); void stop(); diff --git a/src/network/kernel/qnetconmonitor_stub.cpp b/src/network/kernel/qnetconmonitor_stub.cpp index 7f3a0c44c6..1ad4e9ba5a 100644 --- a/src/network/kernel/qnetconmonitor_stub.cpp +++ b/src/network/kernel/qnetconmonitor_stub.cpp @@ -123,7 +123,7 @@ bool QNetworkStatusMonitor::isMonitoring() const return false; } -bool QNetworkStatusMonitor::isNetworkAccesible() +bool QNetworkStatusMonitor::isNetworkAccessible() { return false; } diff --git a/src/network/kernel/qnetconmonitor_win.cpp b/src/network/kernel/qnetconmonitor_win.cpp new file mode 100644 index 0000000000..b543508169 --- /dev/null +++ b/src/network/kernel/qnetconmonitor_win.cpp @@ -0,0 +1,710 @@ +/**************************************************************************** +** +** Copyright (C) 2019 The Qt Company Ltd. +** 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 "qnetconmonitor_p.h" + +#include "private/qobject_p.h" + +#include <QtCore/quuid.h> +#include <QtCore/qmetaobject.h> + +#include <QtNetwork/qnetworkinterface.h> + +#include <objbase.h> +#include <netlistmgr.h> +#include <wrl/client.h> +#include <wrl/wrappers/corewrappers.h> +#include <comdef.h> +#include <iphlpapi.h> + +#include <algorithm> + +using namespace Microsoft::WRL; + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(lcNetMon, "qt.network.monitor"); + +namespace { +QString errorStringFromHResult(HRESULT hr) +{ + _com_error error(hr); + return QString::fromWCharArray(error.ErrorMessage()); +} + +template<typename T> +bool QueryInterfaceImpl(IUnknown *from, REFIID riid, void **ppvObject) +{ + if (riid == __uuidof(T)) { + *ppvObject = static_cast<T *>(from); + from->AddRef(); + return true; + } + return false; +} + +QNetworkInterface getInterfaceFromHostAddress(const QHostAddress &local) +{ + QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); + auto it = std::find_if( + interfaces.cbegin(), interfaces.cend(), [&local](const QNetworkInterface &iface) { + const auto &entries = iface.addressEntries(); + return std::any_of(entries.cbegin(), entries.cend(), + [&local](const QNetworkAddressEntry &entry) { + return entry.ip().isEqual(local, + QHostAddress::TolerantConversion); + }); + }); + if (it == interfaces.cend()) { + qCWarning(lcNetMon, "Could not find the interface for the local address."); + return {}; + } + return *it; +} +} // anonymous namespace + +class QNetworkConnectionEvents final : public INetworkConnectionEvents +{ +public: + QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor); + ~QNetworkConnectionEvents(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; } + ULONG STDMETHODCALLTYPE Release() override + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE + NetworkConnectionConnectivityChanged(GUID connectionId, NLM_CONNECTIVITY connectivity) override; + HRESULT STDMETHODCALLTYPE NetworkConnectionPropertyChanged( + GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) override; + + Q_REQUIRED_RESULT + bool setTarget(const QNetworkInterface &iface); + Q_REQUIRED_RESULT + bool startMonitoring(); + Q_REQUIRED_RESULT + bool stopMonitoring(); + +private: + ComPtr<INetworkConnection> getNetworkConnectionFromAdapterGuid(QUuid guid); + + QUuid currentConnectionId{}; + + ComPtr<INetworkListManager> networkListManager; + ComPtr<IConnectionPoint> connectionPoint; + + QNetworkConnectionMonitorPrivate *monitor = nullptr; + + QAtomicInteger<ULONG> ref = 1; // start at 1 for our own initial reference + DWORD cookie = 0; +}; + +class QNetworkConnectionMonitorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QNetworkConnectionMonitor); + +public: + QNetworkConnectionMonitorPrivate(); + ~QNetworkConnectionMonitorPrivate(); + + Q_REQUIRED_RESULT + bool setTargets(const QHostAddress &local, const QHostAddress &remote); + Q_REQUIRED_RESULT + bool startMonitoring(); + void stopMonitoring(); + + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + +private: + ComPtr<QNetworkConnectionEvents> connectionEvents; + // We can assume we have access to internet/subnet when this class is created because + // connection has already been established to the peer: + NLM_CONNECTIVITY connectivity = + NLM_CONNECTIVITY(NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET + | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET); + + bool sameSubnet = false; + bool monitoring = false; + bool comInitFailed = false; + bool remoteIsIPv6 = false; +}; + +QNetworkConnectionEvents::QNetworkConnectionEvents(QNetworkConnectionMonitorPrivate *monitor) + : monitor(monitor) +{ + auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER, + IID_INetworkListManager, &networkListManager); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:" + << errorStringFromHResult(hr); + return; + } + + ComPtr<IConnectionPointContainer> connectionPointContainer; + hr = networkListManager.As(&connectionPointContainer); + if (SUCCEEDED(hr)) { + hr = connectionPointContainer->FindConnectionPoint(IID_INetworkConnectionEvents, + &connectionPoint); + } + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get connection point for network events:" + << errorStringFromHResult(hr); + } +} + +QNetworkConnectionEvents::~QNetworkConnectionEvents() +{ + Q_ASSERT(ref == 0); +} + +ComPtr<INetworkConnection> QNetworkConnectionEvents::getNetworkConnectionFromAdapterGuid(QUuid guid) +{ + ComPtr<IEnumNetworkConnections> connections; + auto hr = networkListManager->GetNetworkConnections(connections.GetAddressOf()); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to enumerate network connections:" + << errorStringFromHResult(hr); + return nullptr; + } + ComPtr<INetworkConnection> connection = nullptr; + do { + hr = connections->Next(1, connection.GetAddressOf(), nullptr); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get next network connection in enumeration:" + << errorStringFromHResult(hr); + break; + } + if (connection) { + GUID adapterId; + hr = connection->GetAdapterId(&adapterId); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get adapter ID from network connection:" + << errorStringFromHResult(hr); + continue; + } + if (guid == adapterId) + return connection; + } + } while (connection); + return nullptr; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject) + || QueryInterfaceImpl<INetworkConnectionEvents>(this, riid, ppvObject) + ? S_OK + : E_NOINTERFACE; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionConnectivityChanged( + GUID connectionId, NLM_CONNECTIVITY newConnectivity) +{ + // This function is run on a different thread than 'monitor' is created on, so we need to run + // it on that thread + QMetaObject::invokeMethod(monitor->q_ptr, + [this, connectionId, newConnectivity, monitor = this->monitor]() { + if (connectionId == currentConnectionId) + monitor->setConnectivity(newConnectivity); + }, + Qt::QueuedConnection); + return S_OK; +} + +HRESULT STDMETHODCALLTYPE QNetworkConnectionEvents::NetworkConnectionPropertyChanged( + GUID connectionId, NLM_CONNECTION_PROPERTY_CHANGE flags) +{ + Q_UNUSED(connectionId); + Q_UNUSED(flags); + return E_NOTIMPL; +} + +bool QNetworkConnectionEvents::setTarget(const QNetworkInterface &iface) +{ + // Unset this in case it's already set to something + currentConnectionId = QUuid{}; + + NET_LUID luid; + if (ConvertInterfaceIndexToLuid(iface.index(), &luid) != NO_ERROR) { + qCWarning(lcNetMon, "Could not get the LUID for the interface."); + return false; + } + GUID guid; + if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) { + qCWarning(lcNetMon, "Could not get the GUID for the interface."); + return false; + } + ComPtr<INetworkConnection> connection = getNetworkConnectionFromAdapterGuid(guid); + if (!connection) { + qCWarning(lcNetMon, "Could not get the INetworkConnection instance for the adapter GUID."); + return false; + } + auto hr = connection->GetConnectionId(&guid); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get the connection's GUID:" << errorStringFromHResult(hr); + return false; + } + currentConnectionId = guid; + + return true; +} + +bool QNetworkConnectionEvents::startMonitoring() +{ + if (currentConnectionId.isNull()) { + qCWarning(lcNetMon, "Can not start monitoring, set targets first"); + return false; + } + if (!connectionPoint) { + qCWarning(lcNetMon, + "We don't have the connection point, cannot start listening to events!"); + return false; + } + + auto hr = connectionPoint->Advise(this, &cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + return true; +} + +bool QNetworkConnectionEvents::stopMonitoring() +{ + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to unsubscribe from network connection events:" + << errorStringFromHResult(hr); + return false; + } + cookie = 0; + currentConnectionId = QUuid{}; + return true; +} + +QNetworkConnectionMonitorPrivate::QNetworkConnectionMonitorPrivate() +{ + auto hr = CoInitialize(nullptr); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return; + } + + connectionEvents = new QNetworkConnectionEvents(this); +} + +QNetworkConnectionMonitorPrivate::~QNetworkConnectionMonitorPrivate() +{ + if (comInitFailed) + return; + connectionEvents.Reset(); + CoUninitialize(); +} + +bool QNetworkConnectionMonitorPrivate::setTargets(const QHostAddress &local, + const QHostAddress &remote) +{ + if (comInitFailed) + return false; + + QNetworkInterface iface = getInterfaceFromHostAddress(local); + if (!iface.isValid()) + return false; + const auto &addressEntries = iface.addressEntries(); + auto it = std::find_if( + addressEntries.cbegin(), addressEntries.cend(), + [&local](const QNetworkAddressEntry &entry) { return entry.ip() == local; }); + if (Q_UNLIKELY(it == addressEntries.cend())) { + qCWarning(lcNetMon, "The address entry we were working with disappeared"); + return false; + } + sameSubnet = remote.isInSubnet(local, it->prefixLength()); + remoteIsIPv6 = remote.protocol() == QAbstractSocket::IPv6Protocol; + + return connectionEvents->setTarget(iface); +} + +void QNetworkConnectionMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + Q_Q(QNetworkConnectionMonitor); + const bool reachable = q->isReachable(); + connectivity = newConnectivity; + const bool newReachable = q->isReachable(); + if (reachable != newReachable) + emit q->reachabilityChanged(newReachable); +} + +bool QNetworkConnectionMonitorPrivate::startMonitoring() +{ + Q_ASSERT(connectionEvents); + Q_ASSERT(!monitoring); + if (connectionEvents->startMonitoring()) + monitoring = true; + return monitoring; +} + +void QNetworkConnectionMonitorPrivate::stopMonitoring() +{ + Q_ASSERT(connectionEvents); + Q_ASSERT(monitoring); + if (connectionEvents->stopMonitoring()) + monitoring = false; +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor() + : QObject(*new QNetworkConnectionMonitorPrivate) +{ +} + +QNetworkConnectionMonitor::QNetworkConnectionMonitor(const QHostAddress &local, + const QHostAddress &remote) + : QObject(*new QNetworkConnectionMonitorPrivate) +{ + setTargets(local, remote); +} + +QNetworkConnectionMonitor::~QNetworkConnectionMonitor() = default; + +bool QNetworkConnectionMonitor::setTargets(const QHostAddress &local, const QHostAddress &remote) +{ + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + if (local.isNull()) { + qCWarning(lcNetMon, "Invalid (null) local address, cannot create a reachability target"); + return false; + } + // Silently return false for loopback addresses instead of printing warnings later + if (remote.isLoopback()) + return false; + + return d_func()->setTargets(local, remote); +} + +bool QNetworkConnectionMonitor::startMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + return d->startMonitoring(); +} + +bool QNetworkConnectionMonitor::isMonitoring() const +{ + return d_func()->monitoring; +} + +void QNetworkConnectionMonitor::stopMonitoring() +{ + Q_D(QNetworkConnectionMonitor); + if (!isMonitoring()) { + qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!"); + return; + } + d->stopMonitoring(); +} + +bool QNetworkConnectionMonitor::isReachable() +{ + Q_D(QNetworkConnectionMonitor); + NLM_CONNECTIVITY required = d->sameSubnet + ? (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_SUBNET : NLM_CONNECTIVITY_IPV4_SUBNET) + : (d->remoteIsIPv6 ? NLM_CONNECTIVITY_IPV6_INTERNET : NLM_CONNECTIVITY_IPV4_INTERNET); + return d_func()->connectivity & required; +} + +class QNetworkListManagerEvents final : public INetworkListManagerEvents +{ +public: + QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor); + ~QNetworkListManagerEvents(); + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject) override; + + ULONG STDMETHODCALLTYPE AddRef() override { return ++ref; } + ULONG STDMETHODCALLTYPE Release() override + { + if (--ref == 0) { + delete this; + return 0; + } + return ref; + } + + HRESULT STDMETHODCALLTYPE ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) override; + + Q_REQUIRED_RESULT + bool start(); + Q_REQUIRED_RESULT + bool stop(); + +private: + ComPtr<INetworkListManager> networkListManager = nullptr; + ComPtr<IConnectionPoint> connectionPoint = nullptr; + + QNetworkStatusMonitorPrivate *monitor = nullptr; + + QAtomicInteger<ULONG> ref = 1; // start at 1 for our own initial reference + DWORD cookie = 0; +}; + +class QNetworkStatusMonitorPrivate : public QObjectPrivate +{ + Q_DECLARE_PUBLIC(QNetworkStatusMonitor); + +public: + QNetworkStatusMonitorPrivate(); + ~QNetworkStatusMonitorPrivate(); + + Q_REQUIRED_RESULT + bool start(); + void stop(); + + void setConnectivity(NLM_CONNECTIVITY newConnectivity); + +private: + friend class QNetworkListManagerEvents; + + ComPtr<QNetworkListManagerEvents> managerEvents; + NLM_CONNECTIVITY connectivity = NLM_CONNECTIVITY_DISCONNECTED; + + bool monitoring = false; + bool comInitFailed = false; +}; + +QNetworkListManagerEvents::QNetworkListManagerEvents(QNetworkStatusMonitorPrivate *monitor) + : monitor(monitor) +{ + auto hr = CoCreateInstance(CLSID_NetworkListManager, nullptr, CLSCTX_INPROC_SERVER, + IID_INetworkListManager, &networkListManager); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Could not get a NetworkListManager instance:" + << errorStringFromHResult(hr); + return; + } + + // Set initial connectivity + hr = networkListManager->GetConnectivity(&monitor->connectivity); + if (FAILED(hr)) + qCWarning(lcNetMon) << "Could not get connectivity:" << errorStringFromHResult(hr); + + ComPtr<IConnectionPointContainer> connectionPointContainer; + hr = networkListManager.As(&connectionPointContainer); + if (SUCCEEDED(hr)) { + hr = connectionPointContainer->FindConnectionPoint(IID_INetworkListManagerEvents, + &connectionPoint); + } + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to get connection point for network list manager events:" + << errorStringFromHResult(hr); + } +} + +QNetworkListManagerEvents::~QNetworkListManagerEvents() +{ + Q_ASSERT(ref == 0); +} + +HRESULT STDMETHODCALLTYPE QNetworkListManagerEvents::QueryInterface(REFIID riid, void **ppvObject) +{ + if (!ppvObject) + return E_INVALIDARG; + + return QueryInterfaceImpl<IUnknown>(this, riid, ppvObject) + || QueryInterfaceImpl<INetworkListManagerEvents>(this, riid, ppvObject) + ? S_OK + : E_NOINTERFACE; +} + +HRESULT STDMETHODCALLTYPE +QNetworkListManagerEvents::ConnectivityChanged(NLM_CONNECTIVITY newConnectivity) +{ + // This function is run on a different thread than 'monitor' is created on, so we need to run + // it on that thread + QMetaObject::invokeMethod(monitor->q_ptr, + [newConnectivity, monitor = this->monitor]() { + monitor->setConnectivity(newConnectivity); + }, + Qt::QueuedConnection); + return S_OK; +} + +bool QNetworkListManagerEvents::start() +{ + if (!connectionPoint) { + qCWarning(lcNetMon, "Initialization failed, can't start!"); + return false; + } + auto hr = connectionPoint->Advise(this, &cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to subscribe to network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + return true; +} + +bool QNetworkListManagerEvents::stop() +{ + Q_ASSERT(connectionPoint); + auto hr = connectionPoint->Unadvise(cookie); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to unsubscribe from network connectivity events:" + << errorStringFromHResult(hr); + return false; + } + cookie = 0; + return true; +} + +QNetworkStatusMonitorPrivate::QNetworkStatusMonitorPrivate() +{ + auto hr = CoInitialize(nullptr); + if (FAILED(hr)) { + qCWarning(lcNetMon) << "Failed to initialize COM:" << errorStringFromHResult(hr); + comInitFailed = true; + return; + } + managerEvents = new QNetworkListManagerEvents(this); +} + +QNetworkStatusMonitorPrivate::~QNetworkStatusMonitorPrivate() +{ + if (comInitFailed) + return; + if (monitoring) + stop(); + managerEvents.Reset(); + CoUninitialize(); +} + +void QNetworkStatusMonitorPrivate::setConnectivity(NLM_CONNECTIVITY newConnectivity) +{ + Q_Q(QNetworkStatusMonitor); + + const bool oldAccessibility = q->isNetworkAccessible(); + connectivity = newConnectivity; + const bool accessibility = q->isNetworkAccessible(); + if (oldAccessibility != accessibility) + emit q->onlineStateChanged(accessibility); +} + +bool QNetworkStatusMonitorPrivate::start() +{ + if (comInitFailed) + return false; + Q_ASSERT(managerEvents); + Q_ASSERT(!monitoring); + if (managerEvents->start()) + monitoring = true; + return monitoring; +} + +void QNetworkStatusMonitorPrivate::stop() +{ + Q_ASSERT(managerEvents); + Q_ASSERT(monitoring); + if (managerEvents->stop()) + monitoring = false; +} + +QNetworkStatusMonitor::QNetworkStatusMonitor() : QObject(*new QNetworkStatusMonitorPrivate) {} + +QNetworkStatusMonitor::~QNetworkStatusMonitor() {} + +bool QNetworkStatusMonitor::start() +{ + if (isMonitoring()) { + qCWarning(lcNetMon, "Monitor is already active, call stopMonitoring() first"); + return false; + } + + return d_func()->start(); +} + +void QNetworkStatusMonitor::stop() +{ + if (!isMonitoring()) { + qCWarning(lcNetMon, "stopMonitoring was called when not monitoring!"); + return; + } + + d_func()->stop(); +} + +bool QNetworkStatusMonitor::isMonitoring() const +{ + return d_func()->monitoring; +} + +bool QNetworkStatusMonitor::isNetworkAccessible() +{ + return d_func()->connectivity + & (NLM_CONNECTIVITY_IPV4_INTERNET | NLM_CONNECTIVITY_IPV6_INTERNET + | NLM_CONNECTIVITY_IPV4_SUBNET | NLM_CONNECTIVITY_IPV6_SUBNET); +} + +bool QNetworkStatusMonitor::isEnabled() +{ + return true; +} + +void QNetworkStatusMonitor::reachabilityChanged(bool online) +{ + Q_UNUSED(online); + Q_UNREACHABLE(); +} + +QT_END_NAMESPACE diff --git a/src/network/socket/qhttpsocketengine.cpp b/src/network/socket/qhttpsocketengine.cpp index 6be1e9443e..2244955c55 100644 --- a/src/network/socket/qhttpsocketengine.cpp +++ b/src/network/socket/qhttpsocketengine.cpp @@ -627,7 +627,7 @@ void QHttpSocketEngine::slotSocketReadNotification() // from http spec is also allowed. if (proxyConnectionHeader.isEmpty()) proxyConnectionHeader = d->reply->headerField("Connection"); - if (proxyConnectionHeader.compare("close", Qt::CaseSensitive) == 0) { + if (proxyConnectionHeader.compare("close", Qt::CaseInsensitive) == 0) { willClose = true; } else if (proxyConnectionHeader.compare("keep-alive", Qt::CaseInsensitive) == 0) { willClose = false; diff --git a/src/network/socket/qnativesocketengine_unix.cpp b/src/network/socket/qnativesocketengine_unix.cpp index 24c17124dc..3ca586e247 100644 --- a/src/network/socket/qnativesocketengine_unix.cpp +++ b/src/network/socket/qnativesocketengine_unix.cpp @@ -1000,8 +1000,11 @@ qint64 QNativeSocketEnginePrivate::nativeReceiveDatagram(char *data, qint64 maxS // parse the ancillary data struct cmsghdr *cmsgptr; + QT_WARNING_PUSH + QT_WARNING_DISABLE_CLANG("-Wsign-compare") for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) { + QT_WARNING_POP if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO && cmsgptr->cmsg_len >= CMSG_LEN(sizeof(in6_pktinfo))) { in6_pktinfo *info = reinterpret_cast<in6_pktinfo *>(CMSG_DATA(cmsgptr)); diff --git a/src/network/socket/qnativesocketengine_win.cpp b/src/network/socket/qnativesocketengine_win.cpp index b429b3c365..9edabd7822 100644 --- a/src/network/socket/qnativesocketengine_win.cpp +++ b/src/network/socket/qnativesocketengine_win.cpp @@ -623,6 +623,53 @@ bool QNativeSocketEnginePrivate::fetchConnectionParameters() } +static void setErrorFromWSAError(int error, QNativeSocketEnginePrivate *d) +{ + Q_ASSERT(d); + switch (error) { + case WSAEISCONN: + d->socketState = QAbstractSocket::ConnectedState; + break; + case WSAEHOSTUNREACH: + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::HostUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEADDRNOTAVAIL: + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::AddressNotAvailableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEINPROGRESS: + d->setError(QAbstractSocket::UnfinishedSocketOperationError, QNativeSocketEnginePrivate::InvalidSocketErrorString); + d->socketState = QAbstractSocket::ConnectingState; + break; + case WSAEADDRINUSE: + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::AddressInuseErrorString); + break; + case WSAECONNREFUSED: + d->setError(QAbstractSocket::ConnectionRefusedError, QNativeSocketEnginePrivate::ConnectionRefusedErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAETIMEDOUT: + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::ConnectionTimeOutErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEACCES: + d->setError(QAbstractSocket::SocketAccessError, QNativeSocketEnginePrivate::AccessErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAENETUNREACH: + d->setError(QAbstractSocket::NetworkError, QNativeSocketEnginePrivate::NetworkUnreachableErrorString); + d->socketState = QAbstractSocket::UnconnectedState; + break; + case WSAEINVAL: + case WSAEALREADY: + d->setError(QAbstractSocket::UnfinishedSocketOperationError, QNativeSocketEnginePrivate::InvalidSocketErrorString); + break; + default: + break; + } +} + bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quint16 port) { @@ -651,9 +698,6 @@ bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quin case WSANOTINITIALISED: //### break; - case WSAEISCONN: - socketState = QAbstractSocket::ConnectedState; - break; case WSAEWOULDBLOCK: { // If WSAConnect returns WSAEWOULDBLOCK on the second // connection attempt, we have to check SO_ERROR's @@ -668,82 +712,33 @@ bool QNativeSocketEnginePrivate::nativeConnect(const QHostAddress &address, quin do { if (::getsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, (char *) &value, &valueSize) == 0) { if (value != NOERROR) { + WS_ERROR_DEBUG(value); + errorDetected = true; // MSDN says getsockopt with SO_ERROR clears the error, but it's not actually cleared // and this can affect all subsequent WSAConnect attempts, so clear it now. const int val = NO_ERROR; ::setsockopt(socketDescriptor, SOL_SOCKET, SO_ERROR, reinterpret_cast<const char*>(&val), sizeof val); - } - - if (value == WSAECONNREFUSED) { - setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); - socketState = QAbstractSocket::UnconnectedState; - errorDetected = true; - break; - } - if (value == WSAETIMEDOUT) { - setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); - socketState = QAbstractSocket::UnconnectedState; - errorDetected = true; - break; - } - if (value == WSAEHOSTUNREACH) { - setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); - socketState = QAbstractSocket::UnconnectedState; - errorDetected = true; - break; - } - if (value == WSAEADDRNOTAVAIL) { - setError(QAbstractSocket::NetworkError, AddressNotAvailableErrorString); - socketState = QAbstractSocket::UnconnectedState; - errorDetected = true; - break; - } - if (value == NOERROR) { + } else { // When we get WSAEWOULDBLOCK the outcome was not known, so a // NOERROR might indicate that the result of the operation // is still unknown. We try again to increase the chance that we did // get the correct result. tryAgain = !tryAgain; } + setErrorFromWSAError(value, this); } tries++; } while (tryAgain && (tries < 2)); if (errorDetected) break; + // fall through to unfinished operation error handling + err = WSAEINPROGRESS; Q_FALLTHROUGH(); } - case WSAEINPROGRESS: - setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); - socketState = QAbstractSocket::ConnectingState; - break; - case WSAEADDRINUSE: - setError(QAbstractSocket::NetworkError, AddressInuseErrorString); - break; - case WSAECONNREFUSED: - setError(QAbstractSocket::ConnectionRefusedError, ConnectionRefusedErrorString); - socketState = QAbstractSocket::UnconnectedState; - break; - case WSAETIMEDOUT: - setError(QAbstractSocket::NetworkError, ConnectionTimeOutErrorString); - break; - case WSAEACCES: - setError(QAbstractSocket::SocketAccessError, AccessErrorString); - socketState = QAbstractSocket::UnconnectedState; - break; - case WSAEHOSTUNREACH: - setError(QAbstractSocket::NetworkError, HostUnreachableErrorString); - socketState = QAbstractSocket::UnconnectedState; - break; - case WSAENETUNREACH: - setError(QAbstractSocket::NetworkError, NetworkUnreachableErrorString); - socketState = QAbstractSocket::UnconnectedState; - break; - case WSAEINVAL: - case WSAEALREADY: - setError(QAbstractSocket::UnfinishedSocketOperationError, InvalidSocketErrorString); - break; + default: + setErrorFromWSAError(err, this); break; } if (socketState != QAbstractSocket::ConnectedState) { diff --git a/src/network/ssl/qsslcertificate_openssl.cpp b/src/network/ssl/qsslcertificate_openssl.cpp index 806c6426e4..6f1fb26add 100644 --- a/src/network/ssl/qsslcertificate_openssl.cpp +++ b/src/network/ssl/qsslcertificate_openssl.cpp @@ -45,12 +45,19 @@ #include "qsslcertificateextension_p.h" #include <QtCore/qendian.h> +#include <QtCore/qmutex.h> -#if QT_CONFIG(thread) -#include <QtCore/private/qmutexpool_p.h> -#endif QT_BEGIN_NAMESPACE +Q_CONSTEXPR int MutexPoolSize = 17; +static QBasicMutex mutexPool[MutexPoolSize]; +namespace QMutexPool { + static QBasicMutex *globalInstanceGet(const void *addr) + { + return mutexPool + (quintptr(addr) % MutexPoolSize); + } +} + // forward declaration static QMultiMap<QByteArray, QString> _q_mapFromX509Name(X509_NAME *name); diff --git a/src/network/ssl/qsslsocket_mac.cpp b/src/network/ssl/qsslsocket_mac.cpp index 487e975db6..1725937bc2 100644 --- a/src/network/ssl/qsslsocket_mac.cpp +++ b/src/network/ssl/qsslsocket_mac.cpp @@ -215,7 +215,7 @@ void QSecureTransportContext::reset(SSLContextRef newContext) context = newContext; } -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_securetransport_mutex, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, qt_securetransport_mutex) //#define QSSLSOCKET_DEBUG diff --git a/src/network/ssl/qsslsocket_openssl11.cpp b/src/network/ssl/qsslsocket_openssl11.cpp index cc2d6ea2d9..28be4f2e79 100644 --- a/src/network/ssl/qsslsocket_openssl11.cpp +++ b/src/network/ssl/qsslsocket_openssl11.cpp @@ -72,7 +72,7 @@ QT_BEGIN_NAMESPACE -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_opensslInitMutex, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, qt_opensslInitMutex) void QSslSocketPrivate::deinitialize() { diff --git a/src/network/ssl/qsslsocket_openssl_symbols.cpp b/src/network/ssl/qsslsocket_openssl_symbols.cpp index 2eb9763e90..1fcfdf9f16 100644 --- a/src/network/ssl/qsslsocket_openssl_symbols.cpp +++ b/src/network/ssl/qsslsocket_openssl_symbols.cpp @@ -63,13 +63,11 @@ # include <QtCore/qlibrary.h> #endif #include <QtCore/qmutex.h> -#if QT_CONFIG(thread) -#include <private/qmutexpool_p.h> -#endif #include <QtCore/qdatetime.h> #if defined(Q_OS_UNIX) #include <QtCore/qdir.h> #endif +#include <QtCore/private/qmemory_p.h> #if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) #include <link.h> #endif @@ -599,8 +597,8 @@ DEFINEFUNC2(PKCS12 *, d2i_PKCS12_bio, BIO *bio, bio, PKCS12 **pkcs12, pkcs12, re DEFINEFUNC(void, PKCS12_free, PKCS12 *pkcs12, pkcs12, return, DUMMYARG) #define RESOLVEFUNC(func) \ - if (!(_q_##func = _q_PTR_##func(libs.first->resolve(#func))) \ - && !(_q_##func = _q_PTR_##func(libs.second->resolve(#func)))) \ + if (!(_q_##func = _q_PTR_##func(libs.ssl->resolve(#func))) \ + && !(_q_##func = _q_PTR_##func(libs.crypto->resolve(#func)))) \ qsslSocketCannotResolveSymbolWarning(#func); #if !defined QT_LINKED_OPENSSL @@ -736,34 +734,31 @@ static QStringList findAllLibCrypto() # endif #ifdef Q_OS_WIN -static bool tryToLoadOpenSslWin32Library(QLatin1String ssleay32LibName, QLatin1String libeay32LibName, QPair<QSystemLibrary*, QSystemLibrary*> &pair) -{ - pair.first = 0; - pair.second = 0; - QSystemLibrary *ssleay32 = new QSystemLibrary(ssleay32LibName); +struct LoadedOpenSsl { + std::unique_ptr<QSystemLibrary> ssl, crypto; +}; + +static bool tryToLoadOpenSslWin32Library(QLatin1String ssleay32LibName, QLatin1String libeay32LibName, LoadedOpenSsl &result) +{ + auto ssleay32 = qt_make_unique<QSystemLibrary>(ssleay32LibName); if (!ssleay32->load(false)) { - delete ssleay32; return FALSE; } - QSystemLibrary *libeay32 = new QSystemLibrary(libeay32LibName); + auto libeay32 = qt_make_unique<QSystemLibrary>(libeay32LibName); if (!libeay32->load(false)) { - delete ssleay32; - delete libeay32; return FALSE; } - pair.first = ssleay32; - pair.second = libeay32; + result.ssl = std::move(ssleay32); + result.crypto = std::move(libeay32); return TRUE; } -static QPair<QSystemLibrary*, QSystemLibrary*> loadOpenSslWin32() +static LoadedOpenSsl loadOpenSsl() { - QPair<QSystemLibrary*,QSystemLibrary*> pair; - pair.first = 0; - pair.second = 0; + LoadedOpenSsl result; #if QT_CONFIG(opensslv11) // With OpenSSL 1.1 the names have changed to libssl-1_1(-x64) and libcrypto-1_1(-x64), for builds using @@ -776,7 +771,7 @@ static QPair<QSystemLibrary*, QSystemLibrary*> loadOpenSslWin32() #endif // !Q_PROCESSOR_x86_64 tryToLoadOpenSslWin32Library(QLatin1String("libssl-1_1" QT_SSL_SUFFIX), - QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), pair); + QLatin1String("libcrypto-1_1" QT_SSL_SUFFIX), result); #undef QT_SSL_SUFFIX @@ -785,28 +780,30 @@ static QPair<QSystemLibrary*, QSystemLibrary*> loadOpenSslWin32() // When OpenSSL is built using MSVC then the libraries are named 'ssleay32.dll' and 'libeay32'dll'. // When OpenSSL is built using GCC then different library names are used (depending on the OpenSSL version) // The oldest version of a GCC-based OpenSSL which can be detected by the code below is 0.9.8g (released in 2007) - if (!tryToLoadOpenSslWin32Library(QLatin1String("ssleay32"), QLatin1String("libeay32"), pair)) { - if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-10"), QLatin1String("libcrypto-10"), pair)) { - if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-8"), QLatin1String("libcrypto-8"), pair)) { - tryToLoadOpenSslWin32Library(QLatin1String("libssl-7"), QLatin1String("libcrypto-7"), pair); + if (!tryToLoadOpenSslWin32Library(QLatin1String("ssleay32"), QLatin1String("libeay32"), result)) { + if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-10"), QLatin1String("libcrypto-10"), result)) { + if (!tryToLoadOpenSslWin32Library(QLatin1String("libssl-8"), QLatin1String("libcrypto-8"), result)) { + tryToLoadOpenSslWin32Library(QLatin1String("libssl-7"), QLatin1String("libcrypto-7"), result); } } } #endif // !QT_CONFIG(opensslv11) - return pair; + return result; } #else -static QPair<QLibrary*, QLibrary*> loadOpenSsl() +struct LoadedOpenSsl { + std::unique_ptr<QLibrary> ssl, crypto; +}; + +static LoadedOpenSsl loadOpenSsl() { - QPair<QLibrary*,QLibrary*> pair; + LoadedOpenSsl result = {qt_make_unique<QLibrary>(), qt_make_unique<QLibrary>()}; # if defined(Q_OS_UNIX) - QLibrary *&libssl = pair.first; - QLibrary *&libcrypto = pair.second; - libssl = new QLibrary; - libcrypto = new QLibrary; + QLibrary * const libssl = result.ssl.get(); + QLibrary * const libcrypto = result.crypto.get(); // Try to find the libssl library on the system. // @@ -850,7 +847,7 @@ static QPair<QLibrary*, QLibrary*> loadOpenSsl() libcrypto->setFileNameAndVersion(QLatin1String("crypto"), QLatin1String(SHLIB_VERSION_NUMBER)); if (libcrypto->load() && libssl->load()) { // libssl.so.<SHLIB_VERSION_NUMBER> and libcrypto.so.<SHLIB_VERSION_NUMBER> found - return pair; + return result; } else { libssl->unload(); libcrypto->unload(); @@ -869,7 +866,7 @@ static QPair<QLibrary*, QLibrary*> loadOpenSsl() libssl->setFileNameAndVersion(QLatin1String("ssl"), fallbackSoname); libcrypto->setFileNameAndVersion(QLatin1String("crypto"), fallbackSoname); if (libcrypto->load() && libssl->load()) { - return pair; + return result; } else { libssl->unload(); libcrypto->unload(); @@ -885,11 +882,28 @@ static QPair<QLibrary*, QLibrary*> loadOpenSsl() // macOS's /usr/lib/libssl.dylib, /usr/lib/libcrypto.dylib will be picked up in the third // attempt, _after_ <bundle>/Contents/Frameworks has been searched. // iOS does not ship a system libssl.dylib, libcrypto.dylib in the first place. +# if defined(Q_OS_ANDROID) + // OpenSSL 1.1.x must be suffixed otherwise it will use the system libcrypto.so libssl.so which on API-21 are OpenSSL 1.0 not 1.1 + auto openSSLSuffix = [](const QByteArray &defaultSuffix = {}) { + auto suffix = qgetenv("ANDROID_OPENSSL_SUFFIX"); + if (suffix.isEmpty()) + return defaultSuffix; + return suffix; + }; +# if QT_CONFIG(opensslv11) + static QString suffix = QString::fromLatin1(openSSLSuffix("_1_1")); +# else + static QString suffix = QString::fromLatin1(openSSLSuffix()); +# endif + libssl->setFileNameAndVersion(QLatin1String("ssl") + suffix, -1); + libcrypto->setFileNameAndVersion(QLatin1String("crypto") + suffix, -1); +# else libssl->setFileNameAndVersion(QLatin1String("ssl"), -1); libcrypto->setFileNameAndVersion(QLatin1String("crypto"), -1); +# endif if (libcrypto->load() && libssl->load()) { // libssl.so.0 and libcrypto.so.0 found - return pair; + return result; } else { libssl->unload(); libcrypto->unload(); @@ -914,7 +928,7 @@ static QPair<QLibrary*, QLibrary*> loadOpenSsl() if (libssl->load()) { // libssl.so.x and libcrypto.so.x found - return pair; + return result; } else { libssl->unload(); } @@ -924,41 +938,33 @@ static QPair<QLibrary*, QLibrary*> loadOpenSsl() } // failed to load anything - delete libssl; - delete libcrypto; - libssl = libcrypto = 0; - return pair; + result = {}; + return result; # else // not implemented for this platform yet - return pair; + return result; # endif } #endif +static QBasicMutex symbolResolveMutex; +static QBasicAtomicInt symbolsResolved = Q_BASIC_ATOMIC_INITIALIZER(false); +static bool triedToResolveSymbols = false; + bool q_resolveOpenSslSymbols() { - static bool symbolsResolved = false; - static bool triedToResolveSymbols = false; -#if QT_CONFIG(thread) -#if QT_CONFIG(opensslv11) - QMutexLocker locker(QMutexPool::globalInstanceGet((void *)&q_OPENSSL_init_ssl)); -#else - QMutexLocker locker(QMutexPool::globalInstanceGet((void *)&q_SSL_library_init)); -#endif -#endif - if (symbolsResolved) + if (symbolsResolved.loadAcquire()) + return true; + QMutexLocker locker(&symbolResolveMutex); + if (symbolsResolved.loadRelaxed()) return true; if (triedToResolveSymbols) return false; triedToResolveSymbols = true; -#ifdef Q_OS_WIN - QPair<QSystemLibrary *, QSystemLibrary *> libs = loadOpenSslWin32(); -#else - QPair<QLibrary *, QLibrary *> libs = loadOpenSsl(); -#endif - if (!libs.first || !libs.second) + LoadedOpenSsl libs = loadOpenSsl(); + if (!libs.ssl || !libs.crypto) // failed to load them return false; @@ -1007,8 +1013,6 @@ bool q_resolveOpenSslSymbols() if (!_q_OpenSSL_version) { // Apparently, we were built with OpenSSL 1.1 enabled but are now using // a wrong library. - delete libs.first; - delete libs.second; qCWarning(lcSsl, "Incompatible version of OpenSSL"); return false; } @@ -1130,8 +1134,6 @@ bool q_resolveOpenSslSymbols() // OpenSSL 1.1 has deprecated and removed SSLeay. We consider a failure to // resolve this symbol as a failure to resolve symbols. // The right operand of '||' above is ... a bit of paranoia. - delete libs.first; - delete libs.second; qCWarning(lcSsl, "Incompatible version of OpenSSL"); return false; } @@ -1402,9 +1404,7 @@ bool q_resolveOpenSslSymbols() RESOLVEFUNC(d2i_PKCS12_bio) RESOLVEFUNC(PKCS12_free) - symbolsResolved = true; - delete libs.first; - delete libs.second; + symbolsResolved.storeRelease(true); return true; } #endif // QT_CONFIG(library) diff --git a/src/network/ssl/qsslsocket_schannel.cpp b/src/network/ssl/qsslsocket_schannel.cpp index 1314b432a4..c254659a33 100644 --- a/src/network/ssl/qsslsocket_schannel.cpp +++ b/src/network/ssl/qsslsocket_schannel.cpp @@ -431,7 +431,7 @@ QByteArray createAlpnString(const QByteArrayList &nextAllowedProtocols) bool QSslSocketPrivate::s_loadRootCertsOnDemand = true; bool QSslSocketPrivate::s_loadedCiphersAndCerts = false; -Q_GLOBAL_STATIC_WITH_ARGS(QMutex, qt_schannel_mutex, (QMutex::Recursive)) +Q_GLOBAL_STATIC(QRecursiveMutex, qt_schannel_mutex) void QSslSocketPrivate::ensureInitialized() { |