diff --git a/.dictionary b/.dictionary index 7f70d53525..81cf3e8305 100644 --- a/.dictionary +++ b/.dictionary @@ -77,6 +77,7 @@ SHA Serde SettingsHolder Speedtest +Stenberg SwiftyPing TBD TODO diff --git a/.gitmodules b/.gitmodules index 58f8563851..0b15a9312f 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ url = https://github.com/mozilla-l10n/mozilla-vpn-client-l10n.git branch = main shallow = true +[submodule "3rdparty/c-ares"] + path = 3rdparty/c-ares + url = https://github.com/c-ares/c-ares diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt new file mode 100644 index 0000000000..d9d59260ff --- /dev/null +++ b/3rdparty/CMakeLists.txt @@ -0,0 +1,28 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Nothing we need to do here rn. +if(CMAKE_CROSSCOMPILING) + return() +endif() + + +# This Policy will make Cmake respect SET +# over the OPTIONS of sub +cmake_policy(SET CMP0077 NEW) + +include(FetchContent) + +FetchContent_Declare(libcares SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/c-ares") + +# Set options before calling FetchContent_MakeAvailable +set(CARES_STATIC ON CACHE BOOL "Build static c-ares" FORCE) +set(CARES_SHARED OFF CACHE BOOL "Disable shared c-ares" FORCE) +set(CARES_BUILD_TESTS OFF CACHE BOOL "Disable c-ares tests" FORCE) +set(CARES_BUILD_CONTAINER_TESTS OFF CACHE BOOL "Disable c-ares container tests" FORCE) +set(CARES_BUILD_TOOLS OFF CACHE BOOL "Disable c-ares tools" FORCE) +set(CARES_INSTALL OFF CACHE BOOL "Disable c-ares global install" FORCE) + + +FetchContent_MakeAvailable(libcares) diff --git a/3rdparty/c-ares b/3rdparty/c-ares new file mode 160000 index 0000000000..b82840329a --- /dev/null +++ b/3rdparty/c-ares @@ -0,0 +1 @@ +Subproject commit b82840329a4081a1f1b125e6e6b760d4e1237b52 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e163a2117..64c9aaa6ea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -116,6 +116,8 @@ if(MSVC) elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang$") include(src/cmake/clang.cmake) endif() +# Add External dependencies +add_subdirectory(3rdparty) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) diff --git a/LICENSE.md b/LICENSE.md index 824bec6d33..5b6cf02af5 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1527,3 +1527,17 @@ DATA FILES OR SOFTWARE. not be used in advertising or otherwise to promote the sale, use or other dealings in these Data Files or Software without prior written authorization of the copyright holder. + + + +The MIT License (MIT) - c-ares +===================================== + +Copyright (c) 1998 Massachusetts Institute of Technology +Copyright (c) 2007 - 2023 Daniel Stenberg with many contributors, see AUTHORS file. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next paragraph) shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/extension/socks5proxy/CMakeLists.txt b/extension/socks5proxy/CMakeLists.txt index a5f9fdf3fe..3fd7a47798 100644 --- a/extension/socks5proxy/CMakeLists.txt +++ b/extension/socks5proxy/CMakeLists.txt @@ -3,12 +3,15 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # No need to compile this for mobile/web -if(NOT CMAKE_CROSSCOMPILING) - add_subdirectory(src) - add_subdirectory(bin) - add_subdirectory(tests) - - target_compile_definitions(shared-sources INTERFACE MZ_PROXY_ENABLED) +if(CMAKE_CROSSCOMPILING) + return() endif() +add_subdirectory(src) +add_subdirectory(bin) +add_subdirectory(tests) + +target_compile_definitions(shared-sources INTERFACE MZ_PROXY_ENABLED) + + diff --git a/extension/socks5proxy/bin/CMakeLists.txt b/extension/socks5proxy/bin/CMakeLists.txt index 9189555833..1f6e5c5cf6 100644 --- a/extension/socks5proxy/bin/CMakeLists.txt +++ b/extension/socks5proxy/bin/CMakeLists.txt @@ -14,6 +14,10 @@ target_link_libraries(socksproxy PUBLIC libSocks5proxy ) +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + target_compile_definitions(socksproxy PRIVATE MZ_DEBUG) +endif() + if(WIN32) target_compile_definitions(socksproxy PRIVATE PROXY_OS_WIN) target_sources(socksproxy PRIVATE @@ -23,8 +27,7 @@ if(WIN32) winfwpolicy.h winsvcthread.cpp winsvcthread.h - winutils.cpp - winutils.h) + ) target_link_libraries(socksproxy PRIVATE Iphlpapi.lib) install(FILES diff --git a/extension/socks5proxy/bin/main.cpp b/extension/socks5proxy/bin/main.cpp index 3ceb61ad4f..43b2035323 100644 --- a/extension/socks5proxy/bin/main.cpp +++ b/extension/socks5proxy/bin/main.cpp @@ -189,5 +189,7 @@ int main(int argc, char** argv) { WinFwPolicy::create(socks5); #endif + QObject::connect(qApp, &QCoreApplication::aboutToQuit, + []() { qInfo() << "Shutting down"; }); return app.exec(); } diff --git a/extension/socks5proxy/bin/windowsbypass.cpp b/extension/socks5proxy/bin/windowsbypass.cpp index 5c6751b292..9cece392fc 100644 --- a/extension/socks5proxy/bin/windowsbypass.cpp +++ b/extension/socks5proxy/bin/windowsbypass.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -20,10 +21,6 @@ #include "socks5.h" #include "winutils.h" -// Fixed GUID of the Wireguard NT driver. -constexpr const QUuid WIREGUARD_NT_GUID(0xf64063ab, 0xbfee, 0x4881, 0xbf, 0x79, - 0x36, 0x6e, 0x4c, 0xc7, 0xba, 0x75); - // Called by the kernel on network interface changes. // Runs in some unknown thread, so invoke a Qt signal to do the real work. static void netChangeCallback(PVOID context, PMIB_IPINTERFACE_ROW row, @@ -147,18 +144,6 @@ void WindowsBypass::outgoingConnection(QAbstractSocket* s, } } -// static - -quint64 WindowsBypass::getVpnLuid() const { - // Get the LUID of the wireguard interface, if it's up. - NET_LUID luid; - GUID vpnInterfaceGuid = WIREGUARD_NT_GUID; - if (ConvertInterfaceGuidToLuid(&vpnInterfaceGuid, &luid) != NO_ERROR) { - return 0; - } - return luid.Value; -} - void WindowsBypass::refreshAddresses() { // Get the unicast address table. MIB_UNICASTIPADDRESS_TABLE* table; @@ -172,7 +157,7 @@ void WindowsBypass::refreshAddresses() { // Populate entries. QHash data; - const quint64 vpnInterfaceLuid = getVpnLuid(); + const quint64 vpnInterfaceLuid = WinUtils::getVpnLuid(); for (ULONG i = 0; i < table->NumEntries; i++) { const MIB_UNICASTIPADDRESS_ROW* row = &table->Table[i]; if (row->SkipAsSource) { @@ -215,19 +200,57 @@ void WindowsBypass::refreshAddresses() { } } - // Fetch the interface metrics too. + // Fetch the interface metrics. for (auto i = data.begin(); i != data.end(); i++) { MIB_IPINTERFACE_ROW row = {0}; + row.Family = AF_INET; + row.InterfaceLuid.Value = i.key(); + if (GetIpInterfaceEntry(&row) == NO_ERROR) { + i->ipv4metric = row.Metric; + } else { + i->ipv4metric = ULONG_MAX; + } + + row.Family = AF_INET6; row.InterfaceLuid.Value = i.key(); if (GetIpInterfaceEntry(&row) == NO_ERROR) { - i->metric = row.Metric; + i->ipv6metric = row.Metric; } else { - i->metric = ULONG_MAX; + i->ipv6metric = ULONG_MAX; } } + // Fetch the DNS resolvers too. + QByteArray gaaBuffer(4096, 0); + ULONG gaaBufferSize = gaaBuffer.size(); + auto adapterAddrs = reinterpret_cast(gaaBuffer.data()); + result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, + adapterAddrs, &gaaBufferSize); + if (result == ERROR_BUFFER_OVERFLOW) { + gaaBuffer.resize(gaaBufferSize); + adapterAddrs = reinterpret_cast(gaaBuffer.data()); + result = GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_INCLUDE_PREFIX, nullptr, + adapterAddrs, &gaaBufferSize); + } + for (auto i = adapterAddrs; result == NO_ERROR && i != nullptr; i = i->Next) { + // Ignore DNS servers on interfaces without routable addresses. + if (!data.contains(i->Luid.Value)) { + continue; + } + if (i->FirstDnsServerAddress == nullptr) { + continue; + } + + // FIXME: Only using the first DNS server for now. + struct sockaddr* sa = i->FirstDnsServerAddress->Address.lpSockaddr; + data[i->Luid.Value].dnsAddr.setAddress(sa); + qDebug() << "Using" << data[i->Luid.Value].dnsAddr.toString() + << "for DNS server"; + } + // Swap the updated table into use. m_interfaceData.swap(data); + updateNameserver(); } void WindowsBypass::interfaceChanged(quint64 luid) { @@ -239,14 +262,52 @@ void WindowsBypass::interfaceChanged(quint64 luid) { return; } - // Update the interface metric. + // Update the interface metrics. MIB_IPINTERFACE_ROW row = {0}; + row.Family = AF_INET; row.InterfaceLuid.Value = luid; if (GetIpInterfaceEntry(&row) == NO_ERROR) { - i->metric = row.Metric; + i->ipv4metric = row.Metric; } else { - i->metric = ULONG_MAX; + i->ipv4metric = ULONG_MAX; + } + + row.Family = AF_INET6; + row.InterfaceLuid.Value = luid; + if (GetIpInterfaceEntry(&row) == NO_ERROR) { + i->ipv6metric = row.Metric; + } else { + i->ipv6metric = ULONG_MAX; + } + + updateNameserver(); +} + +void WindowsBypass::updateNameserver() { + // Update the preferred DNS server. + QHostAddress dnsNameserver; + ULONG dnsMetric = ULONG_MAX; + for (auto i = m_interfaceData.constBegin(); i != m_interfaceData.constEnd(); + i++) { + auto data = i.value(); + if (data.dnsAddr.isNull()) { + continue; + } + if (data.dnsAddr.protocol() == QAbstractSocket::IPv6Protocol) { + if (data.ipv6metric >= dnsMetric) { + continue; + } + dnsMetric = data.ipv6metric; + } else { + if (data.ipv4metric >= dnsMetric) { + continue; + } + dnsMetric = data.ipv4metric; + } + dnsNameserver = data.dnsAddr; } + qDebug() << "Setting nameserver:" << dnsNameserver.toString(); + DNSResolver::instance()->setNameserver(dnsNameserver); } // In this function, we basically try our best to re-implement the Windows @@ -295,18 +356,20 @@ const MIB_IPFORWARD_ROW2* WindowsBypass::lookupRoute( // Ensure this route has a valid source address. auto ifData = m_interfaceData.value(row.InterfaceLuid.Value); + ULONG rowMetric = row.Metric; if (family == AF_INET) { if (ifData.ipv4addr.isNull()) { continue; } + rowMetric += ifData.ipv4metric; } else { if (ifData.ipv6addr.isNull()) { continue; } + rowMetric += ifData.ipv6metric; } // Choose the route with the best metric in case of a tie. - ULONG rowMetric = row.Metric + ifData.metric; if (Q_UNLIKELY(rowMetric < row.Metric)) { rowMetric = ULONG_MAX; // check for saturation arithmetic. } @@ -341,7 +404,7 @@ void WindowsBypass::updateTable(QVector& table, auto mibGuard = qScopeGuard([mib] { FreeMibTable(mib); }); // First pass: iterate over the table and estimate the size to allocate. - const quint64 vpnInterfaceLuid = getVpnLuid(); + const quint64 vpnInterfaceLuid = WinUtils::getVpnLuid(); ULONG tableSize = 0; for (ULONG i = 0; i < mib->NumEntries; i++) { if (mib->Table[i].InterfaceLuid.Value != vpnInterfaceLuid) { diff --git a/extension/socks5proxy/bin/windowsbypass.h b/extension/socks5proxy/bin/windowsbypass.h index bd9b3bca5d..99a38d5f6c 100644 --- a/extension/socks5proxy/bin/windowsbypass.h +++ b/extension/socks5proxy/bin/windowsbypass.h @@ -26,6 +26,7 @@ class WindowsBypass final : public QObject { private: quint64 getVpnLuid() const; void updateTable(QVector& table, int family); + void updateNameserver(); const struct _MIB_IPFORWARD_ROW2* lookupRoute(const QHostAddress& dest) const; private slots: @@ -40,9 +41,11 @@ class WindowsBypass final : public QObject { void* m_routeChangeHandle = nullptr; struct InterfaceData { - unsigned long metric; + unsigned long ipv4metric; + unsigned long ipv6metric; QHostAddress ipv4addr; QHostAddress ipv6addr; + QHostAddress dnsAddr; }; QHash m_interfaceData; diff --git a/extension/socks5proxy/bin/winfwpolicy.cpp b/extension/socks5proxy/bin/winfwpolicy.cpp index d7d344bf0d..a3731bfdc4 100644 --- a/extension/socks5proxy/bin/winfwpolicy.cpp +++ b/extension/socks5proxy/bin/winfwpolicy.cpp @@ -184,6 +184,9 @@ void WinFwPolicy::fwpmSublayerChanged(uint changeType, } void WinFwPolicy::restrictProxyPort(quint16 port) { +#ifdef MZ_DEBUG + return; +#endif // Start a transaction so that the firewall changes can be made atomically. FwpmTransactionBegin0(m_fwEngineHandle, 0); auto txnGuard = diff --git a/extension/socks5proxy/src/CMakeLists.txt b/extension/socks5proxy/src/CMakeLists.txt index ccf230910e..d2dd22220f 100644 --- a/extension/socks5proxy/src/CMakeLists.txt +++ b/extension/socks5proxy/src/CMakeLists.txt @@ -4,17 +4,30 @@ qt_add_library(libSocks5proxy STATIC) set_target_properties(libSocks5proxy PROPERTIES FOLDER "Libs") -target_link_libraries(libSocks5proxy PUBLIC Qt6::Core Qt6::Network) +target_link_libraries(libSocks5proxy PUBLIC + Qt6::Core + Qt6::Network) + +add_dependencies(libSocks5proxy c-ares) +target_link_libraries(libSocks5proxy PRIVATE c-ares) + +target_compile_definitions(libSocks5proxy PRIVATE CARES_STATICLIB) target_sources(libSocks5proxy PRIVATE socks5.h socks5.cpp socks5connection.cpp socks5connection.h + dnsresolver.h + dnsresolver.cpp ) if(WIN32) - target_sources(libSocks5proxy PRIVATE socks5local_windows.cpp) + target_sources(libSocks5proxy PRIVATE + socks5local_windows.cpp + winutils.cpp + winutils.h + ) elseif(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") target_sources(libSocks5proxy PRIVATE socks5local_linux.cpp) else() diff --git a/extension/socks5proxy/src/dnsresolver.cpp b/extension/socks5proxy/src/dnsresolver.cpp new file mode 100644 index 0000000000..10dc9f4369 --- /dev/null +++ b/extension/socks5proxy/src/dnsresolver.cpp @@ -0,0 +1,100 @@ + +#include "dnsresolver.h" + +#include + +#include +#include + +#include "socks5connection.h" + +Q_GLOBAL_STATIC(DNSResolver, dnsResolver); +DNSResolver* DNSResolver::instance() { return dnsResolver; } + +DNSResolver::DNSResolver() { + static int s_ares_init = false; + if (!s_ares_init) { + ares_library_init(ARES_LIB_INIT_ALL); + QObject::connect(qApp, &QCoreApplication::aboutToQuit, + []() { ares_library_cleanup(); }); + s_ares_init = true; + } + + struct ares_options options; + int optmask = 0; + memset(&options, 0, sizeof(options)); + optmask |= ARES_OPT_EVENT_THREAD; + options.evsys = ARES_EVSYS_DEFAULT; + if (ares_init_options(&mChannel, &options, optmask) != ARES_SUCCESS) { + printf("c-ares initialization issue\n"); + } +} + +DNSResolver::~DNSResolver() { ares_destroy(mChannel); } + +/* Callback that is called when DNS query is finished */ +void DNSResolver::addressInfoCallback(void* arg, int status, int timeouts, + struct ares_addrinfo* result) { + // This should be our Socks5Connection + auto ctx = static_cast(arg); + switch (status) { + case ARES_ENOTIMP: + qDebug() << "The ares library does not know how to find addresses of " + "type family. "; + QMetaObject::invokeMethod(ctx, "onHostnameNotFound", + Qt::QueuedConnection); + return; + case ARES_ENOTFOUND: + qDebug() << " The name was not found."; + QMetaObject::invokeMethod(ctx, "onHostnameNotFound", + Qt::QueuedConnection); + return; + case ARES_ENOMEM: + qDebug() << "Memory was exhausted."; + return; + case ARES_ESERVICE: + qDebug() << "The textual service name provided could not be dereferenced " + "into a port. "; + return; + case ARES_EDESTRUCTION: + qDebug() << "The name service channel channel is being destroyed; the " + "query will not be completed. "; + return; + } + + Q_ASSERT(status == ARES_SUCCESS); + + auto guard = qScopeGuard([&]() { ares_freeaddrinfo(result); }); + if (!result) { + return; + } + for (auto node = result->nodes; node != NULL; node = node->ai_next) { + if (node->ai_family != AF_INET && node->ai_family != AF_INET6) { + continue; + } + QHostAddress target{node->ai_addr}; + QMetaObject::invokeMethod(ctx, "onHostnameResolved", Qt::QueuedConnection, + Q_ARG(QHostAddress, target)); + } +} + +void DNSResolver::resolveAsync(const QString& hostname, + Socks5Connection* parent) { + if (!m_nameserver.isNull()) { + auto serverString = m_nameserver.toString().toLocal8Bit(); + int result = ares_set_servers_csv(mChannel, serverString.constData()); + Q_ASSERT(result == ARES_SUCCESS); + } + + auto name = hostname.toStdString(); + struct ares_addrinfo_hints hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_flags = ARES_AI_CANONNAME; + ares_getaddrinfo(mChannel, name.c_str(), NULL, &hints, &addressInfoCallback, + parent); +} + +void DNSResolver::setNameserver(const QHostAddress& addr) { + m_nameserver = addr; +} diff --git a/extension/socks5proxy/src/dnsresolver.h b/extension/socks5proxy/src/dnsresolver.h new file mode 100644 index 0000000000..5a780b10e6 --- /dev/null +++ b/extension/socks5proxy/src/dnsresolver.h @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include +#include +#include + +struct ares_channeldata; +class ares_addrinfo; +class Socks5Connection; + +class DNSResolver { + public: + DNSResolver(); + ~DNSResolver(); + + static DNSResolver* instance(); + + /** + * @brief Queues up a DNS Query to get Resolved. + * + * @param hostname - The requested Hostname + * @param parent - The Socks5Connection to notify. Will call + * Socks5Connection::onHostnameResolved(QHostAddress) when done. + */ + void resolveAsync(const QString& hostname, Socks5Connection* parent); + + void setNameserver(const QHostAddress& addr); + + private: + static void addressInfoCallback(void* arg, int status, int timeouts, + struct ares_addrinfo* result); + void shutdownAres(); + + ares_channeldata* mChannel = nullptr; + + QHostAddress m_nameserver; +}; diff --git a/extension/socks5proxy/src/socks5.h b/extension/socks5proxy/src/socks5.h index e2e79c5464..9070c08eeb 100644 --- a/extension/socks5proxy/src/socks5.h +++ b/extension/socks5proxy/src/socks5.h @@ -7,6 +7,7 @@ #include +#include "dnsresolver.h" #include "socks5connection.h" class QAbstractSocket; diff --git a/extension/socks5proxy/src/socks5connection.cpp b/extension/socks5proxy/src/socks5connection.cpp index 68242a7fe2..4fcb29d41f 100644 --- a/extension/socks5proxy/src/socks5connection.cpp +++ b/extension/socks5proxy/src/socks5connection.cpp @@ -4,6 +4,7 @@ #include "socks5connection.h" +#include "dnsresolver.h" #include "socks5.h" #ifdef Q_OS_WIN @@ -16,8 +17,6 @@ #include #include -constexpr const int MAX_DNS_LOOKUP_ATTEMPTS = 5; - constexpr const int MAX_CONNECTION_BUFFER = 16 * 1024; namespace { @@ -84,7 +83,6 @@ Socks5Connection::Socks5Connection(QIODevice* socket) connect(m_inSocket, &QIODevice::bytesWritten, this, &Socks5Connection::bytesWritten); - readyRead(); } @@ -281,17 +279,10 @@ void Socks5Connection::readyRead() { return; } - // Todo VPN-6510: Let's resolve the Host with the local device DNS QString hostname = QString::fromUtf8(buffer, length); m_hostLookupStack.append(hostname); - - // Start a DNS lookup to handle this request. - m_dnsLookupAttempts = MAX_DNS_LOOKUP_ATTEMPTS; - QDnsLookup* lookup = new QDnsLookup(QDnsLookup::ANY, hostname, this); - connect(lookup, &QDnsLookup::finished, this, - [this, port]() { dnsResolutionFinished(htons(port)); }); - - lookup->lookup(); + m_destPort = htons(port); + DNSResolver::instance()->resolveAsync(hostname, this); } else if (m_addressType == 0x04 /* Ipv6 */) { @@ -347,6 +338,16 @@ void Socks5Connection::bytesWritten(qint64 bytes) { proxy(m_outSocket, m_inSocket, m_recvHighWaterMark); } +void Socks5Connection::onHostnameResolved(QHostAddress resolved) { + if (m_outSocket != nullptr) { + // We might get multiple ip results. + return; + } + m_destAddress = resolved; + Q_ASSERT(!resolved.isNull()); + configureOutSocket(m_destPort); +} + void Socks5Connection::proxy(QIODevice* from, QIODevice* to, quint64& watermark) { Q_ASSERT(from && to); @@ -380,79 +381,6 @@ void Socks5Connection::proxy(QIODevice* from, QIODevice* to, } } -void Socks5Connection::dnsResolutionFinished(quint16 port) { - QDnsLookup* lookup = qobject_cast(QObject::sender()); - - // Garbage collect the lookup when we're finished. - m_dnsLookupAttempts--; - auto guard = qScopeGuard([lookup]() { - if (lookup->isFinished()) { - lookup->deleteLater(); - } - }); - - if (lookup->error() != QDnsLookup::NoError) { - setError(ErrorHostUnreachable, lookup->errorString()); - return; - } - - // If we get a hostname record. Then DNS resolution has succeeded. and - // we can proceed to the outbound socket setup. - auto hostRecords = lookup->hostAddressRecords(); - if (hostRecords.length() > 0) { - m_destAddress = hostRecords.first().value(); - configureOutSocket(port); - return; - } - - // If we have exhausted the DNS lookup attempts, then give up on DNS - // resolution and report the host as unreachable. This safeguards us from - // recursive DNS loops and other unresolvable situations. - if (m_dnsLookupAttempts <= 0) { - setError(ErrorHostUnreachable, "Maximum DNS attempts exhausted"); - return; - } - - // If we got a canonical name record, restart the lookup using the CNAME. - auto cnameRecords = lookup->canonicalNameRecords(); - if (cnameRecords.length() > 0) { - QString target = cnameRecords.first().value(); - m_hostLookupStack.append(target); - lookup->setName(target); - lookup->setType(QDnsLookup::ANY); - lookup->lookup(); - return; - } - - // Service records are not supported. - auto serviceRecords = lookup->serviceRecords(); - if (serviceRecords.length() > 0) { - // TODO: Not supported. - // - // In theory we can restart the DNS lookup with the target name, but - // the port may have changed too and that is stored somewhere in the - // signal binding. Service records aren't used a whole lot out in the - // wild either. - // - // We can also receive more than one service record and we are expected - // to load balance/fallback amongst them. - setError(ErrorHostUnreachable, "SRV records not supported"); - return; - } - - // If we get this far, the request didn't fail, but we also didn't get any - // records that we could make sense of. Fallback to an explicit IPv4 (A) query - // if this originated from an ANY query. - if (lookup->type() == QDnsLookup::ANY) { - lookup->setType(QDnsLookup::A); - lookup->lookup(); - return; - } - - // Otherwise, no such host was found. - setError(ErrorHostUnreachable, "DNS hostname not found"); -} - void Socks5Connection::configureOutSocket(quint16 port) { Q_ASSERT(!m_destAddress.isNull()); m_hostLookupStack.append(m_destAddress.toString()); @@ -494,6 +422,10 @@ void Socks5Connection::configureOutSocket(quint16 port) { }); } +void Socks5Connection::onHostnameNotFound() { + setError(ErrorHostUnreachable, "Failed to Resolve DNS Query"); +} + Socks5Connection::Socks5Replies Socks5Connection::socketErrorToSocks5Rep( QAbstractSocket::SocketError error) { switch (error) { diff --git a/extension/socks5proxy/src/socks5connection.h b/extension/socks5proxy/src/socks5connection.h index d49612feb7..3f6efb295f 100644 --- a/extension/socks5proxy/src/socks5connection.h +++ b/extension/socks5proxy/src/socks5connection.h @@ -76,6 +76,7 @@ class Socks5Connection final : public QObject { const QString& clientName() const { return m_clientName; } const QHostAddress& destAddress() const { return m_destAddress; } + const QStringList& hostLookupStack() const { return m_hostLookupStack; } const Socks5State& state() const { return m_state; } @@ -89,11 +90,14 @@ class Socks5Connection final : public QObject { void dataSentReceived(qint64 sent, qint64 received); void stateChanged(); + private slots: + void onHostnameResolved(QHostAddress addr); + void onHostnameNotFound(); + private: void setState(Socks5State state); void setError(Socks5Replies reply, const QString& errorString); void configureOutSocket(quint16 port); - void dnsResolutionFinished(quint16 port); void readyRead(); void bytesWritten(qint64 bytes); @@ -109,11 +113,10 @@ class Socks5Connection final : public QObject { QString m_clientName; uint16_t m_socksPort = 0; + uint16_t m_destPort = 0; uint8_t m_addressType = 0; QHostAddress m_destAddress; - - int m_dnsLookupAttempts = 0; QStringList m_hostLookupStack; quint64 m_sendHighWaterMark = 0; diff --git a/extension/socks5proxy/bin/winutils.cpp b/extension/socks5proxy/src/winutils.cpp similarity index 57% rename from extension/socks5proxy/bin/winutils.cpp rename to extension/socks5proxy/src/winutils.cpp index 7208f455f3..6526842631 100644 --- a/extension/socks5proxy/bin/winutils.cpp +++ b/extension/socks5proxy/src/winutils.cpp @@ -4,9 +4,18 @@ #include "winutils.h" +#include +#include +#include #include +#include #include +#include + +// Fixed GUID of the Wireguard NT driver. +constexpr const QUuid WIREGUARD_NT_GUID(0xf64063ab, 0xbfee, 0x4881, 0xbf, 0x79, + 0x36, 0x6e, 0x4c, 0xc7, 0xba, 0x75); QString WinUtils::win32strerror(unsigned long code) { LPWSTR buffer = nullptr; @@ -19,3 +28,13 @@ QString WinUtils::win32strerror(unsigned long code) { LocalFree(buffer); return result; } + +quint64 WinUtils::getVpnLuid() { + // Get the LUID of the wireguard interface, if it's up. + NET_LUID luid; + GUID vpnInterfaceGuid = WIREGUARD_NT_GUID; + if (ConvertInterfaceGuidToLuid(&vpnInterfaceGuid, &luid) != NO_ERROR) { + return 0; + } + return luid.Value; +} diff --git a/extension/socks5proxy/bin/winutils.h b/extension/socks5proxy/src/winutils.h similarity index 88% rename from extension/socks5proxy/bin/winutils.h rename to extension/socks5proxy/src/winutils.h index 9c0b39377d..b9af18de48 100644 --- a/extension/socks5proxy/bin/winutils.h +++ b/extension/socks5proxy/src/winutils.h @@ -9,6 +9,7 @@ namespace WinUtils { QString win32strerror(unsigned long code); -} +quint64 getVpnLuid(); +} // namespace WinUtils #endif // WINUTILS_H diff --git a/taskcluster/scripts/build/windows_clang_cl.ps1 b/taskcluster/scripts/build/windows_clang_cl.ps1 index 92ecac0ca1..0e27400d41 100644 --- a/taskcluster/scripts/build/windows_clang_cl.ps1 +++ b/taskcluster/scripts/build/windows_clang_cl.ps1 @@ -57,6 +57,7 @@ $BUILD_DIR =resolve-path "$TASK_WORKDIR/cmake_build" # Do the generic build cmake -S $REPO_ROOT_PATH -B $BUILD_DIR -GNinja ` -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_TOOLCHAIN_FILE="scripts/windows/conda-toolchain.cmake" ` -DWIREGUARD_FOLDER="$FETCHES_PATH\wireguard-nt" ` -DCMAKE_PREFIX_PATH="$QTPATH/lib/cmake" ` -DBUILD_TESTS=OFF