From 00330c4207ecfeda225602797a6ece972cce6d59 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 30 Oct 2024 14:05:45 +0700 Subject: [PATCH 01/13] cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream --- .../windows/daemon/windowssplittunnel.cpp | 170 ++++++++++++++---- .../windows/daemon/windowssplittunnel.h | 122 ++----------- 2 files changed, 147 insertions(+), 145 deletions(-) diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index c4e893b2b..17e6d002f 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -18,21 +18,138 @@ #include #include #include -#include + +#pragma region + +// Driver Configuration structures +using CONFIGURATION_ENTRY = struct { + // Offset into buffer region that follows all entries. + // The image name uses the device path. + SIZE_T ImageNameOffset; + // Length of the String + USHORT ImageNameLength; +}; + +using CONFIGURATION_HEADER = struct { + // Number of entries immediately following the header. + SIZE_T NumEntries; + + // Total byte length: header + entries + string buffer. + SIZE_T TotalLength; +}; + +// Used to Configure Which IP is network/vpn +using IP_ADDRESSES_CONFIG = struct { + IN_ADDR TunnelIpv4; + IN_ADDR InternetIpv4; + + IN6_ADDR TunnelIpv6; + IN6_ADDR InternetIpv6; +}; + +// Used to Define Which Processes are alive on activation +using PROCESS_DISCOVERY_HEADER = struct { + SIZE_T NumEntries; + SIZE_T TotalLength; +}; + +using PROCESS_DISCOVERY_ENTRY = struct { + HANDLE ProcessId; + HANDLE ParentProcessId; + + SIZE_T ImageNameOffset; + USHORT ImageNameLength; +}; + +using ProcessInfo = struct { + DWORD ProcessId; + DWORD ParentProcessId; + FILETIME CreationTime; + std::wstring DevicePath; +}; + +#ifndef CTL_CODE + +# define FILE_ANY_ACCESS 0x0000 + +# define METHOD_BUFFERED 0 +# define METHOD_IN_DIRECT 1 +# define METHOD_NEITHER 3 + +# define CTL_CODE(DeviceType, Function, Method, Access) \ + (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) +#endif + +// Known ControlCodes +#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS) + +#define IOCTL_DEQUEUE_EVENT \ + CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_REGISTER_PROCESSES \ + CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_REGISTER_IP_ADDRESSES \ + CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_GET_IP_ADDRESSES \ + CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_SET_CONFIGURATION \ + CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_GET_CONFIGURATION \ + CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_CLEAR_CONFIGURATION \ + CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS) + +#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_QUERY_PROCESS \ + CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS) + +#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS) + +constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL"; +constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys"; +constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel"; +constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN"; + +#pragma endregion namespace { Logger logger("WindowsSplitTunnel"); + +ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) { + ProcessInfo pi; + pi.ParentProcessId = processMeta.th32ParentProcessID; + pi.ProcessId = processMeta.th32ProcessID; + pi.CreationTime = {0, 0}; + pi.DevicePath = L""; + + FILETIME creationTime, null_time; + auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time, + &null_time); + if (ok) { + pi.CreationTime = creationTime; + } + wchar_t imagepath[MAX_PATH + 1]; + if (K32GetProcessImageFileNameW( + process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) { + pi.DevicePath = imagepath; + } + return pi; } +} // namespace + WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) { if (detectConflict()) { logger.error() << "Conflict detected, abort Split-Tunnel init."; uninstallDriver(); return; } - - m_tries = 0; - if (!isInstalled()) { logger.debug() << "Driver is not Installed, doing so"; auto handle = installDriver(); @@ -63,15 +180,20 @@ void WindowsSplitTunnel::initDriver() { m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); ; - if (m_driver == INVALID_HANDLE_VALUE && m_tries < 500) { + + if (m_driver == INVALID_HANDLE_VALUE) { WindowsUtils::windowsLog("Failed to open Driver: "); - m_tries++; - Sleep(100); + // If the handle is not present, try again after the serivce has started; - auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME); - QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted, - this, &WindowsSplitTunnel::initDriver); - driver_manager.startService(); + auto driver_manager = WindowsServiceManager::open( + QString::fromWCharArray(DRIVER_SERVICE_NAME)); + if (driver_manager == nullptr) { + return; + } + QObject::connect(driver_manager.get(), + &WindowsServiceManager::serviceStarted, this, + &WindowsSplitTunnel::initDriver); + driver_manager->startService(); return; } @@ -208,7 +330,7 @@ void WindowsSplitTunnel::reset() { logger.debug() << "Reset Split tunnel successfull"; } -DRIVER_STATE WindowsSplitTunnel::getState() { +WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() { if (m_driver == INVALID_HANDLE_VALUE) { logger.debug() << "Can't query State from non Opened Driver"; return STATE_UNKNOWN; @@ -225,7 +347,7 @@ DRIVER_STATE WindowsSplitTunnel::getState() { WindowsUtils::windowsLog("getState response is empty"); return STATE_UNKNOWN; } - return static_cast(outBuffer); + return static_cast(outBuffer); } std::vector WindowsSplitTunnel::generateAppConfiguration( @@ -416,28 +538,6 @@ void WindowsSplitTunnel::close() { m_driver = INVALID_HANDLE_VALUE; } -ProcessInfo WindowsSplitTunnel::getProcessInfo( - HANDLE process, const PROCESSENTRY32W& processMeta) { - ProcessInfo pi; - pi.ParentProcessId = processMeta.th32ParentProcessID; - pi.ProcessId = processMeta.th32ProcessID; - pi.CreationTime = {0, 0}; - pi.DevicePath = L""; - - FILETIME creationTime, null_time; - auto ok = GetProcessTimes(process, &creationTime, &null_time, &null_time, - &null_time); - if (ok) { - pi.CreationTime = creationTime; - } - wchar_t imagepath[MAX_PATH + 1]; - if (K32GetProcessImageFileNameW( - process, imagepath, sizeof(imagepath) / sizeof(*imagepath)) != 0) { - pi.DevicePath = imagepath; - } - return pi; -} - // static SC_HANDLE WindowsSplitTunnel::installDriver() { LPCWSTR displayName = L"Amnezia Split Tunnel Service"; diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index 466036d6c..d3f373a03 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -18,114 +18,12 @@ #include #include -// States for GetState -enum DRIVER_STATE { - STATE_UNKNOWN = -1, - STATE_NONE = 0, - STATE_STARTED = 1, - STATE_INITIALIZED = 2, - STATE_READY = 3, - STATE_RUNNING = 4, - STATE_ZOMBIE = 5, -}; - -#ifndef CTL_CODE - -# define FILE_ANY_ACCESS 0x0000 - -# define METHOD_BUFFERED 0 -# define METHOD_IN_DIRECT 1 -# define METHOD_NEITHER 3 - -# define CTL_CODE(DeviceType, Function, Method, Access) \ - (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)) -#endif - -// Known ControlCodes -#define IOCTL_INITIALIZE CTL_CODE(0x8000, 1, METHOD_NEITHER, FILE_ANY_ACCESS) - -#define IOCTL_DEQUEUE_EVENT \ - CTL_CODE(0x8000, 2, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_REGISTER_PROCESSES \ - CTL_CODE(0x8000, 3, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_REGISTER_IP_ADDRESSES \ - CTL_CODE(0x8000, 4, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_GET_IP_ADDRESSES \ - CTL_CODE(0x8000, 5, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_SET_CONFIGURATION \ - CTL_CODE(0x8000, 6, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_GET_CONFIGURATION \ - CTL_CODE(0x8000, 7, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_CLEAR_CONFIGURATION \ - CTL_CODE(0x8000, 8, METHOD_NEITHER, FILE_ANY_ACCESS) - -#define IOCTL_GET_STATE CTL_CODE(0x8000, 9, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_QUERY_PROCESS \ - CTL_CODE(0x8000, 10, METHOD_BUFFERED, FILE_ANY_ACCESS) - -#define IOCTL_ST_RESET CTL_CODE(0x8000, 11, METHOD_NEITHER, FILE_ANY_ACCESS) - -// Driver Configuration structures - -typedef struct { - // Offset into buffer region that follows all entries. - // The image name uses the device path. - SIZE_T ImageNameOffset; - // Length of the String - USHORT ImageNameLength; -} CONFIGURATION_ENTRY; - -typedef struct { - // Number of entries immediately following the header. - SIZE_T NumEntries; - - // Total byte length: header + entries + string buffer. - SIZE_T TotalLength; -} CONFIGURATION_HEADER; - -// Used to Configure Which IP is network/vpn -typedef struct { - IN_ADDR TunnelIpv4; - IN_ADDR InternetIpv4; - - IN6_ADDR TunnelIpv6; - IN6_ADDR InternetIpv6; -} IP_ADDRESSES_CONFIG; - -// Used to Define Which Processes are alive on activation -typedef struct { - SIZE_T NumEntries; - SIZE_T TotalLength; -} PROCESS_DISCOVERY_HEADER; - -typedef struct { - HANDLE ProcessId; - HANDLE ParentProcessId; - - SIZE_T ImageNameOffset; - USHORT ImageNameLength; -} PROCESS_DISCOVERY_ENTRY; - -typedef struct { - DWORD ProcessId; - DWORD ParentProcessId; - FILETIME CreationTime; - std::wstring DevicePath; -} ProcessInfo; - class WindowsSplitTunnel final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(WindowsSplitTunnel) public: explicit WindowsSplitTunnel(QObject* parent); - ~WindowsSplitTunnel(); + ~WindowsSplitTunnel() override; // void excludeApps(const QStringList& paths); // Excludes an Application from the VPN @@ -147,18 +45,24 @@ class WindowsSplitTunnel final : public QObject { static bool isInstalled(); static bool detectConflict(); + // States for GetState + enum DRIVER_STATE { + STATE_UNKNOWN = -1, + STATE_NONE = 0, + STATE_STARTED = 1, + STATE_INITIALIZED = 2, + STATE_READY = 3, + STATE_RUNNING = 4, + STATE_ZOMBIE = 5, + }; + private slots: void initDriver(); private: HANDLE m_driver = INVALID_HANDLE_VALUE; - constexpr static const auto DRIVER_SYMLINK = L"\\\\.\\MULLVADSPLITTUNNEL"; - constexpr static const auto DRIVER_FILENAME = "mullvad-split-tunnel.sys"; - constexpr static const auto DRIVER_SERVICE_NAME = L"AmneziaVPNSplitTunnel"; - constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN"; DRIVER_STATE getState(); - int m_tries; // Initializes the WFP Sublayer bool initSublayer(); @@ -170,8 +74,6 @@ class WindowsSplitTunnel final : public QObject { void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6); // Collects info about an Opened Process - ProcessInfo getProcessInfo(HANDLE process, - const PROCESSENTRY32W& processMeta); // Converts a path to a Dos Path: // e.g C:/a.exe -> /harddisk0/a.exe From 3265c9d47acf13ae0fbc55c7e4b79737e391f262 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 5 Nov 2024 15:44:16 +0700 Subject: [PATCH 02/13] cherry-pick a95fa8c088b9edaff2de18751336942c2d145a9a from mozilla --- client/daemon/daemon.cpp | 20 +- client/daemon/wireguardutils.h | 4 +- .../macos/daemon/macosroutemonitor.cpp | 14 +- .../macos/daemon/macosroutemonitor.h | 6 +- .../macos/daemon/wireguardutilsmacos.cpp | 77 +++-- .../macos/daemon/wireguardutilsmacos.h | 3 + .../windows/daemon/windowsfirewall.cpp | 148 ++++---- .../windows/daemon/windowsfirewall.h | 10 +- .../windows/daemon/windowsroutemonitor.cpp | 316 ++++++++++++++---- .../windows/daemon/windowsroutemonitor.h | 31 +- .../windows/daemon/wireguardutilswindows.cpp | 54 ++- .../windows/daemon/wireguardutilswindows.h | 7 +- 12 files changed, 493 insertions(+), 197 deletions(-) diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index a234860b0..081a7a90a 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -114,12 +114,23 @@ bool Daemon::activate(const InterfaceConfig& config) { // Bring up the wireguard interface if not already done. if (!wgutils()->interfaceExists()) { + // Create the interface. if (!wgutils()->addInterface(config)) { logger.error() << "Interface creation failed."; return false; } } + // Bring the interface up. + if (supportIPUtils()) { + if (!iputils()->addInterfaceIPs(config)) { + return false; + } + if (!iputils()->setMTUAndUp(config)) { + return false; + } + } + // Configure routing for excluded addresses. for (const QString& i : config.m_excludedAddresses) { addExclusionRoute(IPAddress(i)); @@ -135,15 +146,6 @@ bool Daemon::activate(const InterfaceConfig& config) { return false; } - if (supportIPUtils()) { - if (!iputils()->addInterfaceIPs(config)) { - return false; - } - if (!iputils()->setMTUAndUp(config)) { - return false; - } - } - // set routing for (const IPAddress& ip : config.m_allowedIPAddressRanges) { if (!wgutils()->updateRoutePrefix(ip)) { diff --git a/client/daemon/wireguardutils.h b/client/daemon/wireguardutils.h index cdee40ef7..b600a923a 100644 --- a/client/daemon/wireguardutils.h +++ b/client/daemon/wireguardutils.h @@ -45,9 +45,11 @@ class WireguardUtils : public QObject { virtual bool updateRoutePrefix(const IPAddress& prefix) = 0; virtual bool deleteRoutePrefix(const IPAddress& prefix) = 0; - + virtual bool addExclusionRoute(const IPAddress& prefix) = 0; virtual bool deleteExclusionRoute(const IPAddress& prefix) = 0; + + virtual bool excludeLocalNetworks(const QList& addresses) = 0; }; #endif // WIREGUARDUTILS_H diff --git a/client/platforms/macos/daemon/macosroutemonitor.cpp b/client/platforms/macos/daemon/macosroutemonitor.cpp index 395f008af..bd991c011 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.cpp +++ b/client/platforms/macos/daemon/macosroutemonitor.cpp @@ -358,8 +358,8 @@ void MacosRouteMonitor::rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, } bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix, - unsigned int ifindex, - const void* gateway) { + unsigned int ifindex, const void* gateway, + int flags) { constexpr size_t rtm_max_size = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in6) * 2 + sizeof(struct sockaddr_storage); @@ -370,7 +370,7 @@ bool MacosRouteMonitor::rtmSendRoute(int action, const IPAddress& prefix, rtm->rtm_version = RTM_VERSION; rtm->rtm_type = action; rtm->rtm_index = ifindex; - rtm->rtm_flags = RTF_STATIC | RTF_UP; + rtm->rtm_flags = flags | RTF_STATIC | RTF_UP; rtm->rtm_addrs = 0; rtm->rtm_pid = 0; rtm->rtm_seq = m_rtseq++; @@ -490,7 +490,7 @@ bool MacosRouteMonitor::rtmFetchRoutes(int family) { return false; } -bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) { +bool MacosRouteMonitor::insertRoute(const IPAddress& prefix, int flags) { struct sockaddr_dl datalink; memset(&datalink, 0, sizeof(datalink)); datalink.sdl_family = AF_LINK; @@ -502,11 +502,11 @@ bool MacosRouteMonitor::insertRoute(const IPAddress& prefix) { datalink.sdl_slen = 0; memcpy(&datalink.sdl_data, qPrintable(m_ifname), datalink.sdl_nlen); - return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink); + return rtmSendRoute(RTM_ADD, prefix, m_ifindex, &datalink, flags); } -bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix) { - return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr); +bool MacosRouteMonitor::deleteRoute(const IPAddress& prefix, int flags) { + return rtmSendRoute(RTM_DELETE, prefix, m_ifindex, nullptr, flags); } bool MacosRouteMonitor::addExclusionRoute(const IPAddress& prefix) { diff --git a/client/platforms/macos/daemon/macosroutemonitor.h b/client/platforms/macos/daemon/macosroutemonitor.h index b2483d760..783966901 100644 --- a/client/platforms/macos/daemon/macosroutemonitor.h +++ b/client/platforms/macos/daemon/macosroutemonitor.h @@ -24,8 +24,8 @@ class MacosRouteMonitor final : public QObject { MacosRouteMonitor(const QString& ifname, QObject* parent = nullptr); ~MacosRouteMonitor(); - bool insertRoute(const IPAddress& prefix); - bool deleteRoute(const IPAddress& prefix); + bool insertRoute(const IPAddress& prefix, int flags = 0); + bool deleteRoute(const IPAddress& prefix, int flags = 0); int interfaceFlags() { return m_ifflags; } bool addExclusionRoute(const IPAddress& prefix); @@ -37,7 +37,7 @@ class MacosRouteMonitor final : public QObject { void handleRtmUpdate(const struct rt_msghdr* msg, const QByteArray& payload); void handleIfaceInfo(const struct if_msghdr* msg, const QByteArray& payload); bool rtmSendRoute(int action, const IPAddress& prefix, unsigned int ifindex, - const void* gateway); + const void* gateway, int flags = 0); bool rtmFetchRoutes(int family); static void rtmAppendAddr(struct rt_msghdr* rtm, size_t maxlen, int rtaddr, const void* sa); diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index e2802ebc6..eae228373 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -5,6 +5,7 @@ #include "wireguardutilsmacos.h" #include +#include #include #include @@ -130,7 +131,6 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { } int err = uapiErrno(uapiCommand(message)); - if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); } else { @@ -211,7 +211,6 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) { logger.warning() << "Failed to create peer with no endpoints"; return false; } - out << config.m_serverPort << "\n"; out << "replace_allowed_ips=true\n"; @@ -323,10 +322,10 @@ bool WireguardUtilsMacos::deleteRoutePrefix(const IPAddress& prefix) { if (!m_rtmonitor) { return false; } + if (prefix.prefixLength() > 0) { - return m_rtmonitor->insertRoute(prefix); + return m_rtmonitor->deleteRoute(prefix); } - // Ensure that we do not replace the default route. if (prefix.type() == QAbstractSocket::IPv4Protocol) { return m_rtmonitor->deleteRoute(IPAddress("0.0.0.0/1")) && @@ -346,31 +345,6 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) { return m_rtmonitor->addExclusionRoute(prefix); } -void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params) -{ - // double-check + ensure our firewall is installed and enabled. This is necessary as - // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) - if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); - - MacOSFirewall::ensureRootAnchorPriority(); - MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll); - MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets); - MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets, - QStringLiteral("allownets"), params.allowAddrs); - - MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets); - MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets, - QStringLiteral("blocknets"), params.blockAddrs); - - MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); - MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers); -} - bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) { if (!m_rtmonitor) { return false; @@ -378,6 +352,26 @@ bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) { return m_rtmonitor->deleteExclusionRoute(prefix); } +bool WireguardUtilsMacos::excludeLocalNetworks(const QList& routes) { + if (!m_rtmonitor) { + return false; + } + + // Explicitly discard LAN traffic that makes its way into the tunnel. This + // doesn't really exclude the LAN traffic, we just don't take any action to + // overrule the routes of other interfaces. + bool result = true; + for (const auto& prefix : routes) { + logger.error() << "Attempting to exclude:" << prefix.toString(); + if (!m_rtmonitor->insertRoute(prefix, RTF_IFSCOPE | RTF_REJECT)) { + result = false; + } + } + + // TODO: A kill switch would be nice though :) + return result; +} + QString WireguardUtilsMacos::uapiCommand(const QString& command) { QLocalSocket socket; QTimer uapiTimeout; @@ -454,3 +448,28 @@ QString WireguardUtilsMacos::waitForTunnelName(const QString& filename) { return QString(); } + +void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params) +{ + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets, + QStringLiteral("allownets"), params.allowAddrs); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets, + QStringLiteral("blocknets"), params.blockAddrs); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers); +} diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.h b/client/platforms/macos/daemon/wireguardutilsmacos.h index 243f4b645..3f0d33910 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.h +++ b/client/platforms/macos/daemon/wireguardutilsmacos.h @@ -35,6 +35,9 @@ class WireguardUtilsMacos final : public WireguardUtils { bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; + + bool excludeLocalNetworks(const QList& lanAddressRanges) override; + void applyFirewallRules(FirewallParams& params); signals: diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index 3d45f228c..a3f32745e 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -9,9 +9,7 @@ #include #include #include -//#include -#include - +#include #include #include @@ -161,7 +159,7 @@ bool WindowsFirewall::init() { return true; } -bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) { +bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { // Checks if the FW_Rule was enabled succesfully, // disables the whole killswitch and returns false if not. #define FW_OK(rule) \ @@ -184,7 +182,7 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) { } \ } - logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex; + logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex; FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT, "Allow usage of VPN Adapter")); FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); @@ -200,6 +198,37 @@ bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) { #undef FW_OK } +// Allow unprotected traffic sent to the following local address ranges. +bool WindowsFirewall::enableLanBypass(const QList& ranges) { + // Start the firewall transaction + auto result = FwpmTransactionBegin(m_sessionHandle, NULL); + if (result != ERROR_SUCCESS) { + disableKillSwitch(); + return false; + } + auto cleanup = qScopeGuard([&] { + FwpmTransactionAbort0(m_sessionHandle); + disableKillSwitch(); + }); + + // Blocking unprotected traffic + logger.info() << "Blocking unprotected traffic"; + for (const IPAddress& prefix : ranges) { + if (!allowTrafficTo(prefix, MED_WEIGHT, "Allow LAN bypass traffic")) { + return false; + } + } + + result = FwpmTransactionCommit0(m_sessionHandle); + if (result != ERROR_SUCCESS) { + logger.error() << "FwpmTransactionCommit0 failed with error:" << result; + return false; + } + + cleanup.dismiss(); + return true; +} + bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { // Start the firewall transaction auto result = FwpmTransactionBegin(m_sessionHandle, NULL); @@ -240,7 +269,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { for (const QString& i : config.m_excludedAddresses) { logger.debug() << "range: " << i; - if (!allowTrafficToRange(i, HIGH_WEIGHT, + if (!allowTrafficTo(i, HIGH_WEIGHT, "Allow Ecxlude route", config.m_serverPublicKey)) { return false; } @@ -421,9 +450,59 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight, return true; } +bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight, + const QString& title, + const QString& peer) { + GUID layerKeyOut; + GUID layerKeyIn; + if (addr.type() == QAbstractSocket::IPv4Protocol) { + layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V4; + layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; + } else { + layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V6; + layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; + } + + // Match the IP address range. + FWPM_FILTER_CONDITION0 cond[1] = {}; + FWP_RANGE0 ipRange; + QByteArray lowIpV6Buffer; + QByteArray highIpV6Buffer; + + importAddress(addr.address(), ipRange.valueLow, &lowIpV6Buffer); + importAddress(addr.broadcastAddress(), ipRange.valueHigh, &highIpV6Buffer); + + cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS; + cond[0].matchType = FWP_MATCH_RANGE; + cond[0].conditionValue.type = FWP_RANGE_TYPE; + cond[0].conditionValue.rangeValue = &ipRange; + + // Assemble the Filter base + FWPM_FILTER0 filter; + memset(&filter, 0, sizeof(filter)); + filter.action.type = FWP_ACTION_PERMIT; + filter.weight.type = FWP_UINT8; + filter.weight.uint8 = weight; + filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; + filter.numFilterConditions = 1; + filter.filterCondition = cond; + + // Send the filters down to the firewall. + QString description = "Permit traffic %1 " + addr.toString(); + filter.layerKey = layerKeyOut; + if (!enableFilter(&filter, title, description.arg("to"), peer)) { + return false; + } + filter.layerKey = layerKeyIn; + if (!enableFilter(&filter, title, description.arg("from"), peer)) { + return false; + } + return true; +} + bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port, - int weight, const QString& title, - const QString& peer) { + int weight, const QString& title, + const QString& peer) { bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol; GUID layerOut = isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6; @@ -484,57 +563,6 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port, return true; } -bool WindowsFirewall::allowTrafficToRange(const IPAddress& addr, uint8_t weight, - const QString& title, - const QString& peer) { - QString description("Allow traffic %1 %2 "); - - auto lower = addr.address(); - auto upper = addr.broadcastAddress(); - - const bool isV4 = addr.type() == QAbstractSocket::IPv4Protocol; - const GUID layerKeyOut = - isV4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6; - const GUID layerKeyIn = isV4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4 - : FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; - - // Assemble the Filter base - FWPM_FILTER0 filter; - memset(&filter, 0, sizeof(filter)); - filter.action.type = FWP_ACTION_PERMIT; - filter.weight.type = FWP_UINT8; - filter.weight.uint8 = weight; - filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; - - FWPM_FILTER_CONDITION0 cond[1] = {0}; - FWP_RANGE0 ipRange; - QByteArray lowIpV6Buffer; - QByteArray highIpV6Buffer; - - importAddress(lower, ipRange.valueLow, &lowIpV6Buffer); - importAddress(upper, ipRange.valueHigh, &highIpV6Buffer); - - cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS; - cond[0].matchType = FWP_MATCH_RANGE; - cond[0].conditionValue.type = FWP_RANGE_TYPE; - cond[0].conditionValue.rangeValue = &ipRange; - - filter.numFilterConditions = 1; - filter.filterCondition = cond; - - filter.layerKey = layerKeyOut; - if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()), - peer)) { - return false; - } - filter.layerKey = layerKeyIn; - if (!enableFilter(&filter, title, - description.arg("from").arg(addr.toString()), peer)) { - return false; - } - return true; -} - bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) { // Allow outbound DHCPv4 { @@ -734,7 +762,7 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight, filter.weight.uint8 = weight; filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; - FWPM_FILTER_CONDITION0 cond[1] = {0}; + FWPM_FILTER_CONDITION0 cond[1] = {}; FWP_RANGE0 ipRange; QByteArray lowIpV6Buffer; QByteArray highIpV6Buffer; diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index e0d5ebe8a..674d2a9ef 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -18,7 +18,7 @@ #include #include -#include "../client/daemon/interfaceconfig.h" +#include "interfaceconfig.h" class IpAdressRange; struct FWP_VALUE0_; @@ -31,7 +31,8 @@ class WindowsFirewall final : public QObject { static WindowsFirewall* instance(); bool init(); - bool enableKillSwitch(int vpnAdapterIndex); + bool enableInterface(int vpnAdapterIndex); + bool enableLanBypass(const QList& ranges); bool enablePeerTraffic(const InterfaceConfig& config); bool disablePeerTraffic(const QString& pubkey); bool disableKillSwitch(); @@ -50,11 +51,10 @@ class WindowsFirewall final : public QObject { bool blockTrafficTo(const IPAddress& addr, uint8_t weight, const QString& title, const QString& peer = QString()); bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title); + bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title, + const QString& peer = QString()); bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight, const QString& title, const QString& peer = QString()); - bool allowTrafficToRange(const IPAddress& addr, uint8_t weight, - const QString& title, - const QString& peer); bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight, const QString& title); bool allowDHCPTraffic(uint8_t weight, const QString& title); diff --git a/client/platforms/windows/daemon/windowsroutemonitor.cpp b/client/platforms/windows/daemon/windowsroutemonitor.cpp index 69967526c..fb0fbf7ec 100644 --- a/client/platforms/windows/daemon/windowsroutemonitor.cpp +++ b/client/platforms/windows/daemon/windowsroutemonitor.cpp @@ -13,6 +13,12 @@ namespace { Logger logger("WindowsRouteMonitor"); }; // namespace +// Attempt to mark routing entries that we create with a relatively +// high metric. This ensures that we can skip over routes of our own +// creation when processing route changes, and ensures that we give +// way to other routing entries. +constexpr const ULONG EXCLUSION_ROUTE_METRIC = 0x5e72; + // Called by the kernel on route changes - perform some basic filtering and // invoke the routeChanged slot to do the real work. static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row, @@ -20,22 +26,17 @@ static void routeChangeCallback(PVOID context, PMIB_IPFORWARD_ROW2 row, WindowsRouteMonitor* monitor = (WindowsRouteMonitor*)context; Q_UNUSED(type); - // Ignore host route changes, and unsupported protocols. - if (row->DestinationPrefix.Prefix.si_family == AF_INET6) { - if (row->DestinationPrefix.PrefixLength >= 128) { - return; - } - } else if (row->DestinationPrefix.Prefix.si_family == AF_INET) { - if (row->DestinationPrefix.PrefixLength >= 32) { - return; - } - } else { + // Ignore route changes that we created. + if ((row->Protocol == MIB_IPPROTO_NETMGMT) && + (row->Metric == EXCLUSION_ROUTE_METRIC)) { return; } - - if (monitor->getLuid() != row->InterfaceLuid.Value) { - QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection); + if (monitor->getLuid() == row->InterfaceLuid.Value) { + return; } + + // Invoke the route changed signal to do the real work in Qt. + QMetaObject::invokeMethod(monitor, "routeChanged", Qt::QueuedConnection); } // Perform prefix matching comparison on IP addresses in host order. @@ -57,7 +58,8 @@ static int prefixcmp(const void* a, const void* b, size_t bits) { return 0; } -WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) { +WindowsRouteMonitor::WindowsRouteMonitor(quint64 luid, QObject* parent) + : QObject(parent), m_luid(luid) { MZ_COUNT_CTOR(WindowsRouteMonitor); logger.debug() << "WindowsRouteMonitor created."; @@ -67,11 +69,13 @@ WindowsRouteMonitor::WindowsRouteMonitor(QObject* parent) : QObject(parent) { WindowsRouteMonitor::~WindowsRouteMonitor() { MZ_COUNT_DTOR(WindowsRouteMonitor); CancelMibChangeNotify2(m_routeHandle); - flushExclusionRoutes(); + + flushRouteTable(m_exclusionRoutes); + flushRouteTable(m_clonedRoutes); logger.debug() << "WindowsRouteMonitor destroyed."; } -void WindowsRouteMonitor::updateValidInterfaces(int family) { +void WindowsRouteMonitor::updateInterfaceMetrics(int family) { PMIB_IPINTERFACE_TABLE table; DWORD result = GetIpInterfaceTable(family, &table); if (result != NO_ERROR) { @@ -82,10 +86,10 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) { // Flush the list of interfaces that are valid for routing. if ((family == AF_INET) || (family == AF_UNSPEC)) { - m_validInterfacesIpv4.clear(); + m_interfaceMetricsIpv4.clear(); } if ((family == AF_INET6) || (family == AF_UNSPEC)) { - m_validInterfacesIpv6.clear(); + m_interfaceMetricsIpv6.clear(); } // Rebuild the list of interfaces that are valid for routing. @@ -101,12 +105,12 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) { if (row->Family == AF_INET) { logger.debug() << "Interface" << row->InterfaceIndex << "is valid for IPv4 routing"; - m_validInterfacesIpv4.append(row->InterfaceLuid.Value); + m_interfaceMetricsIpv4[row->InterfaceLuid.Value] = row->Metric; } if (row->Family == AF_INET6) { logger.debug() << "Interface" << row->InterfaceIndex << "is valid for IPv6 routing"; - m_validInterfacesIpv6.append(row->InterfaceLuid.Value); + m_interfaceMetricsIpv6[row->InterfaceLuid.Value] = row->Metric; } } } @@ -126,72 +130,72 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data, if (row->InterfaceLuid.Value == m_luid) { continue; } - // Ignore host routes, and shorter potential matches. - if (row->DestinationPrefix.PrefixLength >= - data->DestinationPrefix.PrefixLength) { + if (row->DestinationPrefix.PrefixLength < bestMatch) { continue; } - if (row->DestinationPrefix.PrefixLength < bestMatch) { + // Ignore routes of our own creation. + if ((row->Protocol == data->Protocol) && (row->Metric == data->Metric)) { continue; } // Check if the routing table entry matches the destination. + if (!routeContainsDest(&row->DestinationPrefix, &data->DestinationPrefix)) { + continue; + } + + // Compute the combined interface and routing metric. + ULONG routeMetric = row->Metric; if (data->DestinationPrefix.Prefix.si_family == AF_INET6) { - if (row->DestinationPrefix.Prefix.Ipv6.sin6_family != AF_INET6) { - continue; - } - if (!m_validInterfacesIpv6.contains(row->InterfaceLuid.Value)) { - continue; - } - if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv6.sin6_addr, - &row->DestinationPrefix.Prefix.Ipv6.sin6_addr, - row->DestinationPrefix.PrefixLength) != 0) { + if (!m_interfaceMetricsIpv6.contains(row->InterfaceLuid.Value)) { continue; } + routeMetric += m_interfaceMetricsIpv6[row->InterfaceLuid.Value]; } else if (data->DestinationPrefix.Prefix.si_family == AF_INET) { - if (row->DestinationPrefix.Prefix.Ipv4.sin_family != AF_INET) { - continue; - } - if (!m_validInterfacesIpv4.contains(row->InterfaceLuid.Value)) { - continue; - } - if (prefixcmp(&data->DestinationPrefix.Prefix.Ipv4.sin_addr, - &row->DestinationPrefix.Prefix.Ipv4.sin_addr, - row->DestinationPrefix.PrefixLength) != 0) { + if (!m_interfaceMetricsIpv4.contains(row->InterfaceLuid.Value)) { continue; } + routeMetric += m_interfaceMetricsIpv4[row->InterfaceLuid.Value]; } else { // Unsupported destination address family. continue; } + if (routeMetric < row->Metric) { + routeMetric = ULONG_MAX; + } // Prefer routes with lower metric if we find multiple matches // with the same prefix length. if ((row->DestinationPrefix.PrefixLength == bestMatch) && - (row->Metric >= bestMetric)) { + (routeMetric >= bestMetric)) { continue; } // If we got here, then this is the longest prefix match so far. memcpy(&nexthop, &row->NextHop, sizeof(SOCKADDR_INET)); - bestLuid = row->InterfaceLuid.Value; bestMatch = row->DestinationPrefix.PrefixLength; - bestMetric = row->Metric; + bestMetric = routeMetric; + if (bestMatch == data->DestinationPrefix.PrefixLength) { + bestLuid = 0; // Don't write to the table if we find an exact match. + } else { + bestLuid = row->InterfaceLuid.Value; + } } // If neither the interface nor next-hop have changed, then do nothing. - if ((data->InterfaceLuid.Value) == bestLuid && + if (data->InterfaceLuid.Value == bestLuid && memcmp(&nexthop, &data->NextHop, sizeof(SOCKADDR_INET)) == 0) { return; } - // Update the routing table entry. + // Delete the previous routing table entry, if any. if (data->InterfaceLuid.Value != 0) { DWORD result = DeleteIpForwardEntry2(data); if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) { logger.error() << "Failed to delete route:" << result; } } + + // Update the routing table entry. data->InterfaceLuid.Value = bestLuid; memcpy(&data->NextHop, &nexthop, sizeof(SOCKADDR_INET)); if (data->InterfaceLuid.Value != 0) { @@ -202,10 +206,178 @@ void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data, } } +// static +bool WindowsRouteMonitor::routeContainsDest(const IP_ADDRESS_PREFIX* route, + const IP_ADDRESS_PREFIX* dest) { + if (route->Prefix.si_family != dest->Prefix.si_family) { + return false; + } + if (route->PrefixLength > dest->PrefixLength) { + return false; + } + if (route->Prefix.si_family == AF_INET) { + return prefixcmp(&route->Prefix.Ipv4.sin_addr, &dest->Prefix.Ipv4.sin_addr, + route->PrefixLength) == 0; + } else if (route->Prefix.si_family == AF_INET6) { + return prefixcmp(&route->Prefix.Ipv6.sin6_addr, + &dest->Prefix.Ipv6.sin6_addr, route->PrefixLength) == 0; + } else { + return false; + } +} + +// static +QHostAddress WindowsRouteMonitor::prefixToAddress( + const IP_ADDRESS_PREFIX* dest) { + if (dest->Prefix.si_family == AF_INET6) { + return QHostAddress(dest->Prefix.Ipv6.sin6_addr.s6_addr); + } else if (dest->Prefix.si_family == AF_INET) { + quint32 addr = htonl(dest->Prefix.Ipv4.sin_addr.s_addr); + return QHostAddress(addr); + } else { + return QHostAddress(); + } +} + +bool WindowsRouteMonitor::isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const { + auto i = m_exclusionRoutes.constBegin(); + while (i != m_exclusionRoutes.constEnd()) { + const MIB_IPFORWARD_ROW2* row = i.value(); + if (routeContainsDest(&row->DestinationPrefix, dest)) { + return true; + } + i++; + } + return false; +} + +void WindowsRouteMonitor::updateCapturedRoutes(int family) { + if (!m_defaultRouteCapture) { + return; + } + + PMIB_IPFORWARD_TABLE2 table; + DWORD error = GetIpForwardTable2(family, &table); + if (error != NO_ERROR) { + updateCapturedRoutes(family, table); + FreeMibTable(table); + } +} + +void WindowsRouteMonitor::updateCapturedRoutes(int family, void* ptable) { + PMIB_IPFORWARD_TABLE2 table = reinterpret_cast(ptable); + if (!m_defaultRouteCapture) { + return; + } + + for (ULONG i = 0; i < table->NumEntries; i++) { + MIB_IPFORWARD_ROW2* row = &table->Table[i]; + // Ignore routes into the VPN interface. + if (row->InterfaceLuid.Value == m_luid) { + continue; + } + // Ignore the default route + if (row->DestinationPrefix.PrefixLength == 0) { + continue; + } + // Ignore routes of our own creation. + if ((row->Protocol == MIB_IPPROTO_NETMGMT) && + (row->Metric == EXCLUSION_ROUTE_METRIC)) { + continue; + } + // Ignore routes which should be excluded. + if (isRouteExcluded(&row->DestinationPrefix)) { + continue; + } + QHostAddress destination = prefixToAddress(&row->DestinationPrefix); + if (destination.isLoopback() || destination.isBroadcast() || + destination.isLinkLocal() || destination.isMulticast()) { + continue; + } + + // If we get here, this route should be cloned. + IPAddress prefix(destination, row->DestinationPrefix.PrefixLength); + MIB_IPFORWARD_ROW2* data = m_clonedRoutes.value(prefix, nullptr); + if (data != nullptr) { + // Count the number of matching entries in the main table. + data->Age++; + continue; + } + logger.debug() << "Capturing route to" + << logger.sensitive(prefix.toString()); + + // Clone the route and direct it into the VPN tunnel. + data = new MIB_IPFORWARD_ROW2; + InitializeIpForwardEntry(data); + data->InterfaceLuid.Value = m_luid; + data->DestinationPrefix = row->DestinationPrefix; + data->NextHop.si_family = data->DestinationPrefix.Prefix.si_family; + + // Set the rest of the flags for a static route. + data->ValidLifetime = 0xffffffff; + data->PreferredLifetime = 0xffffffff; + data->Metric = 0; + data->Protocol = MIB_IPPROTO_NETMGMT; + data->Loopback = false; + data->AutoconfigureAddress = false; + data->Publish = false; + data->Immortal = false; + data->Age = 0; + + // Route this traffic into the VPN tunnel. + DWORD result = CreateIpForwardEntry2(data); + if (result != NO_ERROR) { + logger.error() << "Failed to update route:" << result; + delete data; + } else { + m_clonedRoutes.insert(prefix, data); + data->Age++; + } + } + + // Finally scan for any routes which were removed from the table. We do this + // by reusing the age field to count the number of matching entries in the + // main table. + auto i = m_clonedRoutes.begin(); + while (i != m_clonedRoutes.end()) { + MIB_IPFORWARD_ROW2* data = i.value(); + if (data->Age > 0) { + // Entry is in use, don't delete it. + data->Age = 0; + i++; + continue; + } + if ((family != AF_UNSPEC) && + (data->DestinationPrefix.Prefix.si_family != family)) { + // We are not processing updates to this address family. + i++; + continue; + } + + logger.debug() << "Removing route capture for" + << logger.sensitive(i.key().toString()); + + // Otherwise, this route is no longer in use. + DWORD result = DeleteIpForwardEntry2(data); + if ((result != NO_ERROR) && (result != ERROR_NOT_FOUND)) { + logger.error() << "Failed to delete route:" << result; + } + delete data; + i = m_clonedRoutes.erase(i); + } +} + bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) { logger.debug() << "Adding exclusion route for" << logger.sensitive(prefix.toString()); + // Silently ignore non-routeable addresses. + QHostAddress addr = prefix.address(); + if (addr.isLoopback() || addr.isBroadcast() || addr.isLinkLocal() || + addr.isMulticast()) { + return true; + } + if (m_exclusionRoutes.contains(prefix)) { logger.warning() << "Exclusion route already exists"; return false; @@ -232,7 +404,7 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) { // Set the rest of the flags for a static route. data->ValidLifetime = 0xffffffff; data->PreferredLifetime = 0xffffffff; - data->Metric = 0; + data->Metric = EXCLUSION_ROUTE_METRIC; data->Protocol = MIB_IPPROTO_NETMGMT; data->Loopback = false; data->AutoconfigureAddress = false; @@ -254,7 +426,8 @@ bool WindowsRouteMonitor::addExclusionRoute(const IPAddress& prefix) { delete data; return false; } - updateValidInterfaces(family); + updateInterfaceMetrics(family); + updateCapturedRoutes(family, table); updateExclusionRoute(data, table); FreeMibTable(table); @@ -266,26 +439,28 @@ bool WindowsRouteMonitor::deleteExclusionRoute(const IPAddress& prefix) { logger.debug() << "Deleting exclusion route for" << logger.sensitive(prefix.address().toString()); - for (;;) { - MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix); - if (data == nullptr) { - break; - } + MIB_IPFORWARD_ROW2* data = m_exclusionRoutes.take(prefix); + if (data == nullptr) { + return true; + } - DWORD result = DeleteIpForwardEntry2(data); - if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { - logger.error() << "Failed to delete route to" - << logger.sensitive(prefix.toString()) - << "result:" << result; - } - delete data; + DWORD result = DeleteIpForwardEntry2(data); + if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { + logger.error() << "Failed to delete route to" + << logger.sensitive(prefix.toString()) + << "result:" << result; } + // Captured routes might have changed. + updateCapturedRoutes(data->DestinationPrefix.Prefix.si_family); + + delete data; return true; } -void WindowsRouteMonitor::flushExclusionRoutes() { - for (auto i = m_exclusionRoutes.begin(); i != m_exclusionRoutes.end(); i++) { +void WindowsRouteMonitor::flushRouteTable( + QHash& table) { + for (auto i = table.begin(); i != table.end(); i++) { MIB_IPFORWARD_ROW2* data = i.value(); DWORD result = DeleteIpForwardEntry2(data); if ((result != ERROR_NOT_FOUND) && (result != NO_ERROR)) { @@ -295,7 +470,17 @@ void WindowsRouteMonitor::flushExclusionRoutes() { } delete data; } - m_exclusionRoutes.clear(); + table.clear(); +} + +void WindowsRouteMonitor::setDetaultRouteCapture(bool enable) { + m_defaultRouteCapture = enable; + + // Flush any captured routes when disabling the feature. + if (!m_defaultRouteCapture) { + flushRouteTable(m_clonedRoutes); + return; + } } void WindowsRouteMonitor::routeChanged() { @@ -308,7 +493,8 @@ void WindowsRouteMonitor::routeChanged() { return; } - updateValidInterfaces(AF_UNSPEC); + updateInterfaceMetrics(AF_UNSPEC); + updateCapturedRoutes(AF_UNSPEC, table); for (MIB_IPFORWARD_ROW2* data : m_exclusionRoutes) { updateExclusionRoute(data, table); } diff --git a/client/platforms/windows/daemon/windowsroutemonitor.h b/client/platforms/windows/daemon/windowsroutemonitor.h index 0ae9a8a29..fa04f646d 100644 --- a/client/platforms/windows/daemon/windowsroutemonitor.h +++ b/client/platforms/windows/daemon/windowsroutemonitor.h @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include "ipaddress.h" @@ -19,28 +21,41 @@ class WindowsRouteMonitor final : public QObject { Q_OBJECT public: - WindowsRouteMonitor(QObject* parent); + WindowsRouteMonitor(quint64 luid, QObject* parent); ~WindowsRouteMonitor(); + void setDetaultRouteCapture(bool enable); + bool addExclusionRoute(const IPAddress& prefix); bool deleteExclusionRoute(const IPAddress& prefix); - void flushExclusionRoutes(); + void flushExclusionRoutes() { return flushRouteTable(m_exclusionRoutes); }; - void setLuid(quint64 luid) { m_luid = luid; } - quint64 getLuid() { return m_luid; } + quint64 getLuid() const { return m_luid; } public slots: void routeChanged(); private: + bool isRouteExcluded(const IP_ADDRESS_PREFIX* dest) const; + static bool routeContainsDest(const IP_ADDRESS_PREFIX* route, + const IP_ADDRESS_PREFIX* dest); + static QHostAddress prefixToAddress(const IP_ADDRESS_PREFIX* dest); + + void flushRouteTable(QHash& table); void updateExclusionRoute(MIB_IPFORWARD_ROW2* data, void* table); - void updateValidInterfaces(int family); + void updateInterfaceMetrics(int family); + void updateCapturedRoutes(int family); + void updateCapturedRoutes(int family, void* table); QHash m_exclusionRoutes; - QList m_validInterfacesIpv4; - QList m_validInterfacesIpv6; + QMap m_interfaceMetricsIpv4; + QMap m_interfaceMetricsIpv6; + + // Default route cloning + bool m_defaultRouteCapture = false; + QHash m_clonedRoutes; - quint64 m_luid = 0; + const quint64 m_luid = 0; HANDLE m_routeHandle = INVALID_HANDLE_VALUE; }; diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index 1a220235f..5e93f2f48 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -25,7 +25,7 @@ Logger logger("WireguardUtilsWindows"); }; // namespace WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent) - : WireguardUtils(parent), m_tunnel(this), m_routeMonitor(this) { + : WireguardUtils(parent), m_tunnel(this) { MZ_COUNT_CTOR(WireguardUtilsWindows); logger.debug() << "WireguardUtilsWindows created."; @@ -114,13 +114,13 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) { return false; } m_luid = luid.Value; - m_routeMonitor.setLuid(luid.Value); + m_routeMonitor = new WindowsRouteMonitor(luid.Value, this); if (config.m_killSwitchEnabled) { // Enable the windows firewall NET_IFINDEX ifindex; ConvertInterfaceLuidToIndex(&luid, &ifindex); - WindowsFirewall::instance()->enableKillSwitch(ifindex); + WindowsFirewall::instance()->enableInterface(ifindex); } logger.debug() << "Registration completed"; @@ -128,6 +128,10 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) { } bool WireguardUtilsWindows::deleteInterface() { + if (m_routeMonitor) { + m_routeMonitor->deleteLater(); + } + WindowsFirewall::instance()->disableKillSwitch(); m_tunnel.stop(); return true; @@ -171,9 +175,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { } // Exclude the server address, except for multihop exit servers. - if (config.m_hopType != InterfaceConfig::MultiHopExit) { - m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - m_routeMonitor.addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); + if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) { + m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); + m_routeMonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); } QString reply = m_tunnel.uapiCommand(message); @@ -186,9 +190,9 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) { QByteArray::fromBase64(qPrintable(config.m_serverPublicKey)); // Clear exclustion routes for this peer. - if (config.m_hopType != InterfaceConfig::MultiHopExit) { - m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); - m_routeMonitor.deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); + if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) { + m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); + m_routeMonitor->deleteExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); } // Disable the windows firewall for this peer. @@ -238,6 +242,13 @@ void WireguardUtilsWindows::buildMibForwardRow(const IPAddress& prefix, } bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) { + if (m_routeMonitor && (prefix.prefixLength() == 0)) { + // If we are setting up a default route, instruct the route monitor to + // capture traffic to all non-excluded destinations + m_routeMonitor->setDetaultRouteCapture(true); + } + // Build the route + MIB_IPFORWARD_ROW2 entry; buildMibForwardRow(prefix, &entry); @@ -255,6 +266,12 @@ bool WireguardUtilsWindows::updateRoutePrefix(const IPAddress& prefix) { } bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) { + if (m_routeMonitor && (prefix.prefixLength() == 0)) { + // Deactivate the route capture feature. + m_routeMonitor->setDetaultRouteCapture(false); + } + // Build the route + MIB_IPFORWARD_ROW2 entry; buildMibForwardRow(prefix, &entry); @@ -278,3 +295,22 @@ bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) { bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) { return m_routeMonitor.deleteExclusionRoute(prefix); } + +bool WireguardUtilsWindows::excludeLocalNetworks( + const QList& addresses) { + // If the interface isn't up then something went horribly wrong. + Q_ASSERT(m_routeMonitor); + // For each destination - attempt to exclude it from the VPN tunnel. + bool result = true; + for (const IPAddress& prefix : addresses) { + if (!m_routeMonitor->addExclusionRoute(prefix)) { + result = false; + } + } + // Permit LAN traffic through the firewall. + if (!WindowsFirewall::instance()->enableLanBypass(addresses)) { + result = false; + } + + return result; +} diff --git a/client/platforms/windows/daemon/wireguardutilswindows.h b/client/platforms/windows/daemon/wireguardutilswindows.h index 4fd67fadc..fc1ac8720 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.h +++ b/client/platforms/windows/daemon/wireguardutilswindows.h @@ -9,11 +9,14 @@ #include #include +#include #include "daemon/wireguardutils.h" #include "windowsroutemonitor.h" #include "windowstunnelservice.h" +class WindowsRouteMonitor; + class WireguardUtilsWindows final : public WireguardUtils { Q_OBJECT @@ -39,6 +42,8 @@ class WireguardUtilsWindows final : public WireguardUtils { bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; + WireguardUtilsWindows::excludeLocalNetworks(const QList& addresses) override; + signals: void backendFailure(); @@ -47,7 +52,7 @@ class WireguardUtilsWindows final : public WireguardUtils { quint64 m_luid = 0; WindowsTunnelService m_tunnel; - WindowsRouteMonitor m_routeMonitor; + QPointer m_routeMonitor; }; #endif // WIREGUARDUTILSWINDOWS_H From bd4fd545fc857d37367ea45aa3e3ddbf66a39579 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 5 Nov 2024 15:45:13 +0700 Subject: [PATCH 03/13] cherry-pick commit 4fc1ebbad86a9abcafdc761725a7afd811c8d2d3 from mozilla --- client/platforms/windows/daemon/windowsfirewall.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index a3f32745e..f1883ae7f 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -212,9 +212,8 @@ bool WindowsFirewall::enableLanBypass(const QList& ranges) { }); // Blocking unprotected traffic - logger.info() << "Blocking unprotected traffic"; for (const IPAddress& prefix : ranges) { - if (!allowTrafficTo(prefix, MED_WEIGHT, "Allow LAN bypass traffic")) { + if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) { return false; } } From 51d4aea00cd81fe0e58908426d09e519deb5d10a Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 11:33:17 +0700 Subject: [PATCH 04/13] cherry-pick 4dfcad96506fb5b88c5bb27342b6d9413fc361c9 from mozilla upstream --- .../windows/daemon/windowsdaemon.cpp | 37 +++-- .../platforms/windows/daemon/windowsdaemon.h | 6 +- .../windows/daemon/windowsfirewall.cpp | 64 +++----- .../windows/daemon/windowsfirewall.h | 18 ++- .../windows/daemon/windowssplittunnel.cpp | 146 +++++++++++------- .../windows/daemon/windowssplittunnel.h | 51 +++--- .../windows/daemon/wireguardutilswindows.cpp | 26 +++- .../windows/daemon/wireguardutilswindows.h | 6 +- 8 files changed, 207 insertions(+), 147 deletions(-) diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp index 00435f0b9..d5aa005dc 100644 --- a/client/platforms/windows/daemon/windowsdaemon.cpp +++ b/client/platforms/windows/daemon/windowsdaemon.cpp @@ -5,6 +5,7 @@ #include "windowsdaemon.h" #include +#include #include #include @@ -18,25 +19,28 @@ #include "dnsutilswindows.h" #include "leakdetector.h" #include "logger.h" -#include "core/networkUtilities.h" +#include "platforms/windows/daemon/windowsfirewall.h" +#include "platforms/windows/daemon/windowssplittunnel.h" #include "platforms/windows/windowscommons.h" -#include "platforms/windows/windowsservicemanager.h" #include "windowsfirewall.h" namespace { Logger logger("WindowsDaemon"); } -WindowsDaemon::WindowsDaemon() : Daemon(nullptr), m_splitTunnelManager(this) { +WindowsDaemon::WindowsDaemon() : Daemon(nullptr) { MZ_COUNT_CTOR(WindowsDaemon); + m_firewallManager = WindowsFirewall::create(this); + Q_ASSERT(m_firewallManager != nullptr); - m_wgutils = new WireguardUtilsWindows(this); + m_wgutils = WireguardUtilsWindows::create(m_firewallManager, this); m_dnsutils = new DnsUtilsWindows(this); + m_splitTunnelManager = WindowsSplitTunnel::create(m_firewallManager); - connect(m_wgutils, &WireguardUtilsWindows::backendFailure, this, + connect(m_wgutils.get(), &WireguardUtilsWindows::backendFailure, this, &WindowsDaemon::monitorBackendFailure); connect(this, &WindowsDaemon::activationFailure, - []() { WindowsFirewall::instance()->disableKillSwitch(); }); + [this]() { m_firewallManager->disableKillSwitch(); }); } WindowsDaemon::~WindowsDaemon() { @@ -65,21 +69,20 @@ void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAd } bool WindowsDaemon::run(Op op, const InterfaceConfig& config) { - if (op == Down) { - m_splitTunnelManager.stop(); + if (!m_splitTunnelManager) { return true; } - if (op == Up) { - logger.debug() << "Tunnel UP, Starting SplitTunneling"; - if (!WindowsSplitTunnel::isInstalled()) { - logger.warning() << "Split Tunnel Driver not Installed yet, fixing this."; - WindowsSplitTunnel::installDriver(); - } + if (op == Down) { + m_splitTunnelManager->stop(); + return true; + } + if (config.m_vpnDisabledApps.length() > 0) { + m_splitTunnelManager->start(m_inetAdapterIndex); + m_splitTunnelManager->setRules(config.m_vpnDisabledApps); + } else { + m_splitTunnelManager->stop(); } - - activateSplitTunnel(config); - return true; } diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h index 7e38c41eb..69393d7f4 100644 --- a/client/platforms/windows/daemon/windowsdaemon.h +++ b/client/platforms/windows/daemon/windowsdaemon.h @@ -5,8 +5,11 @@ #ifndef WINDOWSDAEMON_H #define WINDOWSDAEMON_H +#include + #include "daemon/daemon.h" #include "dnsutilswindows.h" +#include "windowsfirewall.h" #include "windowssplittunnel.h" #include "windowstunnelservice.h" #include "wireguardutilswindows.h" @@ -41,7 +44,8 @@ class WindowsDaemon final : public Daemon { WireguardUtilsWindows* m_wgutils = nullptr; DnsUtilsWindows* m_dnsutils = nullptr; - WindowsSplitTunnel m_splitTunnelManager; + std::unique_ptr m_splitTunnelManager; + QPointer m_firewallManager; }; #endif // WINDOWSDAEMON_H diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index f1883ae7f..e06dafd25 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -47,18 +48,13 @@ constexpr uint8_t HIGH_WEIGHT = 13; constexpr uint8_t MAX_WEIGHT = 15; } // namespace -WindowsFirewall* WindowsFirewall::instance() { - if (s_instance == nullptr) { - s_instance = new WindowsFirewall(qApp); +WindowsFirewall* WindowsFirewall::create(QObject* parent) { + if (s_instance != nullptr) { + // Only one instance of the firewall is allowed + Q_ASSERT(false); + return s_instance; } - return s_instance; -} - -WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) { - MZ_COUNT_CTOR(WindowsFirewall); - Q_ASSERT(s_instance == nullptr); - - HANDLE engineHandle = NULL; + HANDLE engineHandle = nullptr; DWORD result = ERROR_SUCCESS; // Use dynamic sessions for efficiency and safety: // -> Filtering policy objects are deleted even when the application crashes/ @@ -69,15 +65,24 @@ WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) { logger.debug() << "Opening the filter engine."; - result = - FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle); + result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session, + &engineHandle); if (result != ERROR_SUCCESS) { WindowsUtils::windowsLog("FwpmEngineOpen0 failed"); - return; + return nullptr; } logger.debug() << "Filter engine opened successfully."; - m_sessionHandle = engineHandle; + if (!initSublayer()) { + return nullptr; + } + s_instance = new WindowsFirewall(engineHandle, parent); + return s_instance; +} + +WindowsFirewall::WindowsFirewall(HANDLE session, QObject* parent) + : QObject(parent), m_sessionHandle(session) { + MZ_COUNT_CTOR(WindowsFirewall); } WindowsFirewall::~WindowsFirewall() { @@ -87,15 +92,8 @@ WindowsFirewall::~WindowsFirewall() { } } -bool WindowsFirewall::init() { - if (m_init) { - logger.warning() << "Alread initialised FW_WFP layer"; - return true; - } - if (m_sessionHandle == INVALID_HANDLE_VALUE) { - logger.error() << "Cant Init Sublayer with invalid wfp handle"; - return false; - } +// static +bool WindowsFirewall::initSublayer() { // If we were not able to aquire a handle, this will fail anyway. // We need to open up another handle because of wfp rules: // If a wfp resource was created with SESSION_DYNAMIC, @@ -155,7 +153,6 @@ bool WindowsFirewall::init() { return false; } logger.debug() << "Initialised Sublayer"; - m_init = true; return true; } @@ -188,7 +185,7 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic")); FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT, - "Allow all for AmneziaVPN.exe")); + "Allow all for Mozilla VPN.exe")); FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS")); FW_OK( allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1")); @@ -242,7 +239,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { // Build the firewall rules for this peer. logger.info() << "Enabling traffic for peer" - << config.m_serverPublicKey; + << logger.keys(config.m_serverPublicKey); if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT, "Block Internet", config.m_serverPublicKey)) { return false; @@ -264,17 +261,6 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { } } - if (!config.m_excludedAddresses.empty()) { - for (const QString& i : config.m_excludedAddresses) { - logger.debug() << "range: " << i; - - if (!allowTrafficTo(i, HIGH_WEIGHT, - "Allow Ecxlude route", config.m_serverPublicKey)) { - return false; - } - } - } - result = FwpmTransactionCommit0(m_sessionHandle); if (result != ERROR_SUCCESS) { logger.error() << "FwpmTransactionCommit0 failed with error:" << result; @@ -298,7 +284,7 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) { return false; } - logger.info() << "Disabling traffic for peer" << pubkey; + logger.info() << "Disabling traffic for peer" << logger.keys(pubkey); for (const auto& filterID : m_peerRules.values(pubkey)) { FwpmFilterDeleteById0(m_sessionHandle, filterID); m_peerRules.remove(pubkey, filterID); diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index 674d2a9ef..14f503d13 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -26,10 +26,17 @@ struct FWP_CONDITION_VALUE0_; class WindowsFirewall final : public QObject { public: - ~WindowsFirewall(); - - static WindowsFirewall* instance(); - bool init(); + /** + * @brief Opens the Windows Filtering Platform, initializes the session, + * sublayer. Returns a WindowsFirewall object if successful, otherwise + * nullptr. If there is already a WindowsFirewall object, it will be returned. + * + * @param parent - parent QObject + * @return WindowsFirewall* - nullptr if failed to open the Windows Filtering + * Platform. + */ + static WindowsFirewall* create(QObject* parent); + ~WindowsFirewall() override; bool enableInterface(int vpnAdapterIndex); bool enableLanBypass(const QList& ranges); @@ -38,7 +45,8 @@ class WindowsFirewall final : public QObject { bool disableKillSwitch(); private: - WindowsFirewall(QObject* parent); + static bool initSublayer(); + WindowsFirewall(HANDLE session, QObject* parent); HANDLE m_sessionHandle; bool m_init = false; QList m_activeRules; diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 17e6d002f..5579ba10a 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -4,9 +4,15 @@ #include "windowssplittunnel.h" +#include + +#include + #include "../windowscommons.h" #include "../windowsservicemanager.h" #include "logger.h" +#include "platforms/windows/daemon/windowsfirewall.h" +#include "platforms/windows/daemon/windowssplittunnel.h" #include "platforms/windows/windowsutils.h" #include "windowsfirewall.h" @@ -144,96 +150,113 @@ ProcessInfo getProcessInfo(HANDLE process, const PROCESSENTRY32W& processMeta) { } // namespace -WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) { +std::unique_ptr WindowsSplitTunnel::create( + WindowsFirewall* fw) { + if (fw == nullptr) { + // Pre-Condition: + // Make sure the Windows Firewall has created the sublayer + // otherwise the driver will fail to initialize + logger.error() << "Failed to did not pass a WindowsFirewall obj" + << "The Driver cannot work with the sublayer not created"; + return nullptr; + } + // 00: Check if we conflict with mullvad, if so. if (detectConflict()) { logger.error() << "Conflict detected, abort Split-Tunnel init."; - uninstallDriver(); - return; + return nullptr; } + // 01: Check if the driver is installed, if not do so. if (!isInstalled()) { logger.debug() << "Driver is not Installed, doing so"; auto handle = installDriver(); if (handle == INVALID_HANDLE_VALUE) { WindowsUtils::windowsLog("Failed to install Driver"); - return; + return nullptr; } logger.debug() << "Driver installed"; CloseServiceHandle(handle); } else { - logger.debug() << "Driver is installed"; - } - initDriver(); -} - -WindowsSplitTunnel::~WindowsSplitTunnel() { - CloseHandle(m_driver); - uninstallDriver(); -} - -void WindowsSplitTunnel::initDriver() { - if (detectConflict()) { - logger.error() << "Conflict detected, abort Split-Tunnel init."; - return; - } - logger.debug() << "Try to open Split Tunnel Driver"; - // Open the Driver Symlink - m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0, - nullptr, OPEN_EXISTING, 0, nullptr); + logger.debug() << "Driver was installed"; + } + // 02: Now check if the service is running + auto driver_manager = + WindowsServiceManager::open(QString::fromWCharArray(DRIVER_SERVICE_NAME)); + if (Q_UNLIKELY(driver_manager == nullptr)) { + // Let's be fair if we end up here, + // after checking it exists and installing it, + // this is super unlikeley + Q_ASSERT(false); + logger.error() + << "WindowsServiceManager was unable fo find Split Tunnel service?"; + return nullptr; + } + if (!driver_manager->isRunning()) { + logger.debug() << "Driver is not running, starting it"; + // Start the service + if (!driver_manager->startService()) { + logger.error() << "Failed to start Split Tunnel Service"; + return nullptr; + }; + } + // 03: Open the Driver Symlink + auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0, + nullptr, OPEN_EXISTING, 0, nullptr); ; - - if (m_driver == INVALID_HANDLE_VALUE) { + if (driverFile == INVALID_HANDLE_VALUE) { WindowsUtils::windowsLog("Failed to open Driver: "); - - // If the handle is not present, try again after the serivce has started; - auto driver_manager = WindowsServiceManager::open( - QString::fromWCharArray(DRIVER_SERVICE_NAME)); - if (driver_manager == nullptr) { - return; - } - QObject::connect(driver_manager.get(), - &WindowsServiceManager::serviceStarted, this, - &WindowsSplitTunnel::initDriver); - driver_manager->startService(); - return; + return nullptr; } - - logger.debug() << "Connected to the Driver"; - // Reset Driver as it has wfp handles probably >:( - - if (!WindowsFirewall::instance()->init()) { - logger.error() << "Init WFP-Sublayer failed, driver won't be functional"; - return; + if (!initDriver(driverFile)) { + logger.error() << "Failed to init driver"; + return nullptr; } + // We're ready to talk to the driver, it's alive and setup. + return std::make_unique(driverFile); +} +bool WindowsSplitTunnel::initDriver(HANDLE driverIO) { // We need to now check the state and init it, if required - - auto state = getState(); + auto state = getState(driverIO); if (state == STATE_UNKNOWN) { logger.debug() << "Cannot check if driver is initialized"; + return false; } if (state >= STATE_INITIALIZED) { logger.debug() << "Driver already initialized: " << state; - reset(); + // Reset Driver as it has wfp handles probably >:( + resetDriver(driverIO); - auto newState = getState(); + auto newState = getState(driverIO); logger.debug() << "New state after reset:" << newState; if (newState >= STATE_INITIALIZED) { logger.debug() << "Reset unsuccesfull"; - return; + return false; } } DWORD bytesReturned; - auto ok = DeviceIoControl(m_driver, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0, + auto ok = DeviceIoControl(driverIO, IOCTL_INITIALIZE, nullptr, 0, nullptr, 0, &bytesReturned, nullptr); if (!ok) { auto err = GetLastError(); logger.error() << "Driver init failed err -" << err; - logger.error() << "State:" << getState(); + logger.error() << "State:" << getState(driverIO); - return; + return false; } - logger.debug() << "Driver initialized" << getState(); + logger.debug() << "Driver initialized" << getState(driverIO); + return true; +} + +WindowsSplitTunnel::WindowsSplitTunnel(HANDLE driverIO) : m_driver(driverIO) { + logger.debug() << "Connected to the Driver"; + + Q_ASSERT(getState() == STATE_INITIALIZED); +} + +WindowsSplitTunnel::~WindowsSplitTunnel() { + CloseHandle(m_driver); + uninstallDriver(); } void WindowsSplitTunnel::setRules(const QStringList& appPaths) { @@ -319,25 +342,27 @@ void WindowsSplitTunnel::stop() { logger.debug() << "Stopping Split tunnel successfull"; } -void WindowsSplitTunnel::reset() { +bool WindowsSplitTunnel::resetDriver(HANDLE driverIO) { DWORD bytesReturned; - auto ok = DeviceIoControl(m_driver, IOCTL_ST_RESET, nullptr, 0, nullptr, 0, + auto ok = DeviceIoControl(driverIO, IOCTL_ST_RESET, nullptr, 0, nullptr, 0, &bytesReturned, nullptr); if (!ok) { logger.error() << "Reset Split tunnel not successfull"; - return; + return false; } logger.debug() << "Reset Split tunnel successfull"; + return true; } -WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() { - if (m_driver == INVALID_HANDLE_VALUE) { +// static +WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState(HANDLE driverIO) { + if (driverIO == INVALID_HANDLE_VALUE) { logger.debug() << "Can't query State from non Opened Driver"; return STATE_UNKNOWN; } DWORD bytesReturned; SIZE_T outBuffer; - bool ok = DeviceIoControl(m_driver, IOCTL_GET_STATE, nullptr, 0, &outBuffer, + bool ok = DeviceIoControl(driverIO, IOCTL_GET_STATE, nullptr, 0, &outBuffer, sizeof(outBuffer), &bytesReturned, nullptr); if (!ok) { WindowsUtils::windowsLog("getState response failure"); @@ -349,6 +374,9 @@ WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() { } return static_cast(outBuffer); } +WindowsSplitTunnel::DRIVER_STATE WindowsSplitTunnel::getState() { + return getState(m_driver); +} std::vector WindowsSplitTunnel::generateAppConfiguration( const QStringList& appPaths) { diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index d3f373a03..f91ff236f 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -8,6 +8,7 @@ #include #include #include +#include // Note: the ws2tcpip.h import must come before the others. // clang-format off @@ -18,12 +19,30 @@ #include #include -class WindowsSplitTunnel final : public QObject { - Q_OBJECT - Q_DISABLE_COPY_MOVE(WindowsSplitTunnel) +class WindowsFirewall; + +class WindowsSplitTunnel final { public: - explicit WindowsSplitTunnel(QObject* parent); - ~WindowsSplitTunnel() override; + /** + * @brief Installs and Initializes the Split Tunnel Driver. + * + * @param fw - + * @return std::unique_ptr - Is null on failure. + */ + static std::unique_ptr create(WindowsFirewall* fw); + + /** + * @brief Construct a new Windows Split Tunnel object + * + * @param driverIO - The Handle to the Driver's IO file, it assumes the driver + * is in STATE_INITIALIZED and the Firewall has been setup. + * Prefer using create() to get to this state. + */ + WindowsSplitTunnel(HANDLE driverIO); + /** + * @brief Destroy the Windows Split Tunnel object and uninstalls the Driver. + */ + ~WindowsSplitTunnel(); // void excludeApps(const QStringList& paths); // Excludes an Application from the VPN @@ -33,16 +52,10 @@ class WindowsSplitTunnel final : public QObject { void start(int inetAdapterIndex, int vpnAdapterIndex = 0); // Deletes Rules and puts the driver into passive mode void stop(); - // Resets the Whole Driver - void reset(); // Just close connection, leave state as is void close(); - - // Installes the Kernel Driver as Driver Service - static SC_HANDLE installDriver(); - static bool uninstallDriver(); - static bool isInstalled(); + void reset(); static bool detectConflict(); // States for GetState @@ -56,16 +69,18 @@ class WindowsSplitTunnel final : public QObject { STATE_ZOMBIE = 5, }; - private slots: - void initDriver(); - private: + // Installes the Kernel Driver as Driver Service + static SC_HANDLE installDriver(); + static bool uninstallDriver(); + static bool isInstalled(); + static bool initDriver(HANDLE driverIO); + static DRIVER_STATE getState(HANDLE driverIO); + static bool resetDriver(HANDLE driverIO); + HANDLE m_driver = INVALID_HANDLE_VALUE; DRIVER_STATE getState(); - // Initializes the WFP Sublayer - bool initSublayer(); - // Generates a Configuration for Each APP std::vector generateAppConfiguration(const QStringList& appPaths); // Generates a Configuration which IP's are VPN and which network diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index 5e93f2f48..c538bfbf3 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -24,8 +24,20 @@ namespace { Logger logger("WireguardUtilsWindows"); }; // namespace -WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent) - : WireguardUtils(parent), m_tunnel(this) { +std::unique_ptr WireguardUtilsWindows::create( + WindowsFirewall* fw, QObject* parent) { + if (!fw) { + logger.error() << "WireguardUtilsWindows::create: no wfp handle"; + return {}; + } + + // Can't use make_unique here as the Constructor is private :( + auto utils = new WireguardUtilsWindows(parent, fw); + return std::unique_ptr(utils); +} + +WireguardUtilsWindows::WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw) + : WireguardUtils(parent), m_tunnel(this), m_firewall(fw) { MZ_COUNT_CTOR(WireguardUtilsWindows); logger.debug() << "WireguardUtilsWindows created."; @@ -120,7 +132,7 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) { // Enable the windows firewall NET_IFINDEX ifindex; ConvertInterfaceLuidToIndex(&luid, &ifindex); - WindowsFirewall::instance()->enableInterface(ifindex); + m_firewall->enableInterface(ifindex); } logger.debug() << "Registration completed"; @@ -132,7 +144,7 @@ bool WireguardUtilsWindows::deleteInterface() { m_routeMonitor->deleteLater(); } - WindowsFirewall::instance()->disableKillSwitch(); + m_firewall->disableKillSwitch(); m_tunnel.stop(); return true; } @@ -145,7 +157,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) { if (config.m_killSwitchEnabled) { // Enable the windows firewall for this peer. - WindowsFirewall::instance()->enablePeerTraffic(config); + m_firewall->enablePeerTraffic(config); } logger.debug() << "Configuring peer" << publicKey.toHex() << "via" << config.m_serverIpv4AddrIn; @@ -196,7 +208,7 @@ bool WireguardUtilsWindows::deletePeer(const InterfaceConfig& config) { } // Disable the windows firewall for this peer. - WindowsFirewall::instance()->disablePeerTraffic(config.m_serverPublicKey); + m_firewall->disablePeerTraffic(config.m_serverPublicKey); QString message; QTextStream out(&message); @@ -308,7 +320,7 @@ bool WireguardUtilsWindows::excludeLocalNetworks( } } // Permit LAN traffic through the firewall. - if (!WindowsFirewall::instance()->enableLanBypass(addresses)) { + if (!m_firewall->enableLanBypass(addresses)) { result = false; } diff --git a/client/platforms/windows/daemon/wireguardutilswindows.h b/client/platforms/windows/daemon/wireguardutilswindows.h index fc1ac8720..083f27889 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.h +++ b/client/platforms/windows/daemon/wireguardutilswindows.h @@ -15,13 +15,15 @@ #include "windowsroutemonitor.h" #include "windowstunnelservice.h" +class WindowsFirewall; class WindowsRouteMonitor; class WireguardUtilsWindows final : public WireguardUtils { Q_OBJECT public: - WireguardUtilsWindows(QObject* parent); + static std::unique_ptr create(WindowsFirewall* fw, + QObject* parent); ~WireguardUtilsWindows(); bool interfaceExists() override { return m_tunnel.isRunning(); } @@ -48,11 +50,13 @@ class WireguardUtilsWindows final : public WireguardUtils { void backendFailure(); private: + WireguardUtilsWindows(QObject* parent, WindowsFirewall* fw); void buildMibForwardRow(const IPAddress& prefix, void* row); quint64 m_luid = 0; WindowsTunnelService m_tunnel; QPointer m_routeMonitor; + QPointer m_firewall; }; #endif // WIREGUARDUTILSWINDOWS_H From 912daebb162ddf2d86f25a03e86a5a43b1bfac08 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 11:59:36 +0700 Subject: [PATCH 05/13] cherry-pick 22de4fcbd454c64ff496c3380eeaeeb6afff4d64 from mozilla upstream --- .../windows/daemon/windowssplittunnel.cpp | 76 +++++++++---------- .../windows/daemon/windowssplittunnel.h | 5 +- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 5579ba10a..ea902b765 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -423,58 +423,56 @@ std::vector WindowsSplitTunnel::generateAppConfiguration( return outBuffer; } -std::vector WindowsSplitTunnel::generateIPConfiguration( +std::vector WindowsSplitTunnel::generateIPConfiguration( int inetAdapterIndex, int vpnAdapterIndex) { - std::vector out(sizeof(IP_ADDRESSES_CONFIG)); + std::vector out(sizeof(IP_ADDRESSES_CONFIG)); auto config = reinterpret_cast(&out[0]); auto ifaces = QNetworkInterface::allInterfaces(); - if (vpnAdapterIndex == 0) { + if (vpnAdapterIndex == 0) { vpnAdapterIndex = WindowsCommons::VPNAdapterIndex(); } - // Always the VPN - getAddress(vpnAdapterIndex, &config->TunnelIpv4, - &config->TunnelIpv6); - // 2nd best route - getAddress(inetAdapterIndex, &config->InternetIpv4, &config->InternetIpv6); + if (!getAddress(vpnAdapterIndex, &config->TunnelIpv4, + &config->TunnelIpv6)) { + return {}; + } + // 2nd best route is usually the internet adapter + if (!getAddress(inetAdapterIndex, &config->InternetIpv4, + &config->InternetIpv6)) { + return {}; + }; return out; } -void WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4, +bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6) { QNetworkInterface target = QNetworkInterface::interfaceFromIndex(adapterIndex); logger.debug() << "Getting adapter info for:" << target.humanReadableName(); - // take the first v4/v6 Adress and convert to in_addr - for (auto address : target.addressEntries()) { - if (address.ip().protocol() == QAbstractSocket::IPv4Protocol) { - auto adrr = address.ip().toString(); - std::wstring wstr = adrr.toStdWString(); - logger.debug() << "IpV4" << logger.sensitive(adrr); - PCWSTR w_str_ip = wstr.c_str(); - auto ok = InetPtonW(AF_INET, w_str_ip, out_ipv4); - if (ok != 1) { - logger.debug() << "Ipv4 Conversation error" << WSAGetLastError(); + auto get = [&target](QAbstractSocket::NetworkLayerProtocol protocol) { + for (auto address : target.addressEntries()) { + if (address.ip().protocol() != protocol) { + continue; } - break; + return address.ip().toString().toStdWString(); } + return std::wstring{}; + }; + auto ipv4 = get(QAbstractSocket::IPv4Protocol); + auto ipv6 = get(QAbstractSocket::IPv6Protocol); + + if (InetPtonW(AF_INET, ipv4.c_str(), out_ipv4) != 1) { + logger.debug() << "Ipv4 Conversation error" << WSAGetLastError(); + return false; } - for (auto address : target.addressEntries()) { - if (address.ip().protocol() == QAbstractSocket::IPv6Protocol) { - auto adrr = address.ip().toString(); - std::wstring wstr = adrr.toStdWString(); - logger.debug() << "IpV6" << logger.sensitive(adrr); - PCWSTR w_str_ip = wstr.c_str(); - auto ok = InetPtonW(AF_INET6, w_str_ip, out_ipv6); - if (ok != 1) { - logger.error() << "Ipv6 Conversation error" << WSAGetLastError(); - } - break; - } + if (InetPtonW(AF_INET6, ipv6.c_str(), out_ipv6) != 1) { + logger.debug() << "Ipv6 Conversation error" << WSAGetLastError(); + return false; } + return true; } std::vector WindowsSplitTunnel::generateProcessBlob() { @@ -576,15 +574,15 @@ SC_HANDLE WindowsSplitTunnel::installDriver() { return (SC_HANDLE)INVALID_HANDLE_VALUE; } auto path = driver.absolutePath() + "/" + DRIVER_FILENAME; - LPCWSTR binPath = (const wchar_t*)path.utf16(); + auto binPath = (const wchar_t*)path.utf16(); auto scm_rights = SC_MANAGER_ALL_ACCESS; - auto serviceManager = OpenSCManager(NULL, // local computer - NULL, // servicesActive database + auto serviceManager = OpenSCManager(nullptr, // local computer + nullptr, // servicesActive database scm_rights); - auto service = CreateService(serviceManager, DRIVER_SERVICE_NAME, displayName, - SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, - SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, - binPath, nullptr, 0, nullptr, nullptr, nullptr); + auto service = CreateService( + serviceManager, DRIVER_SERVICE_NAME, displayName, SERVICE_ALL_ACCESS, + SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, binPath, + nullptr, nullptr, nullptr, nullptr, nullptr); CloseServiceHandle(serviceManager); return service; } diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index f91ff236f..122fcef32 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -84,10 +84,11 @@ class WindowsSplitTunnel final { // Generates a Configuration for Each APP std::vector generateAppConfiguration(const QStringList& appPaths); // Generates a Configuration which IP's are VPN and which network - std::vector generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0); + std::vector generateIPConfiguration(int inetAdapterIndex, int vpnAdapterIndex = 0); std::vector generateProcessBlob(); - void getAddress(int adapterIndex, IN_ADDR* out_ipv4, IN6_ADDR* out_ipv6); + [[nodiscard]] bool getAddress(int adapterIndex, IN_ADDR* out_ipv4, + IN6_ADDR* out_ipv6); // Collects info about an Opened Process // Converts a path to a Dos Path: From 3ab1635c3780270d7bbc714f45670eda20c6b05d Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 12:01:00 +0700 Subject: [PATCH 06/13] cherry-pick 649673be561b66c96367adf379da1545f8838763 from mozilla upstream --- client/platforms/windows/daemon/windowssplittunnel.cpp | 5 ----- client/platforms/windows/daemon/windowssplittunnel.h | 3 --- 2 files changed, 8 deletions(-) diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index ea902b765..82c3fc243 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -559,11 +559,6 @@ std::vector WindowsSplitTunnel::generateProcessBlob() { return out; } -void WindowsSplitTunnel::close() { - CloseHandle(m_driver); - m_driver = INVALID_HANDLE_VALUE; -} - // static SC_HANDLE WindowsSplitTunnel::installDriver() { LPCWSTR displayName = L"Amnezia Split Tunnel Service"; diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index 122fcef32..f5f6e6636 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -53,9 +53,6 @@ class WindowsSplitTunnel final { // Deletes Rules and puts the driver into passive mode void stop(); - // Just close connection, leave state as is - void close(); - void reset(); static bool detectConflict(); // States for GetState From 0c4224ae83b68fa63623bcb4ec2d15584de5fb83 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 13:00:26 +0700 Subject: [PATCH 07/13] cherry-pick 41bdad34517d0ddaef32139482e5505d92e4b533 from mozilla upstream --- client/daemon/daemon.h | 4 +- client/daemon/daemonerrors.h | 17 ++++++++ client/daemon/daemonlocalserverconnection.cpp | 3 +- client/daemon/daemonlocalserverconnection.h | 4 +- client/mozilla/localsocketcontroller.cpp | 41 +++++++++++++++++-- .../windows/daemon/windowsdaemon.cpp | 20 +++++++-- .../windows/daemon/windowssplittunnel.cpp | 16 ++++---- .../windows/daemon/windowssplittunnel.h | 4 +- 8 files changed, 90 insertions(+), 19 deletions(-) create mode 100644 client/daemon/daemonerrors.h diff --git a/client/daemon/daemon.h b/client/daemon/daemon.h index 3d418d706..757c9ff01 100644 --- a/client/daemon/daemon.h +++ b/client/daemon/daemon.h @@ -8,6 +8,8 @@ #include #include +#include "daemon/daemonerrors.h" +#include "daemonerrors.h" #include "dnsutils.h" #include "interfaceconfig.h" #include "iputils.h" @@ -51,7 +53,7 @@ class Daemon : public QObject { */ void activationFailure(); void disconnected(); - void backendFailure(); + void backendFailure(DaemonError reason = DaemonError::ERROR_FATAL); private: bool maybeUpdateResolvers(const InterfaceConfig& config); diff --git a/client/daemon/daemonerrors.h b/client/daemon/daemonerrors.h new file mode 100644 index 000000000..3d271244c --- /dev/null +++ b/client/daemon/daemonerrors.h @@ -0,0 +1,17 @@ +/* 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 + +enum class DaemonError : uint8_t { + ERROR_NONE = 0u, + ERROR_FATAL = 1u, + ERROR_SPLIT_TUNNEL_INIT_FAILURE = 2u, + ERROR_SPLIT_TUNNEL_START_FAILURE = 3u, + ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE = 4u, + + DAEMON_ERROR_MAX = 5u, +}; diff --git a/client/daemon/daemonlocalserverconnection.cpp b/client/daemon/daemonlocalserverconnection.cpp index edbc4c9b7..bc57c71fb 100644 --- a/client/daemon/daemonlocalserverconnection.cpp +++ b/client/daemon/daemonlocalserverconnection.cpp @@ -159,9 +159,10 @@ void DaemonLocalServerConnection::disconnected() { write(obj); } -void DaemonLocalServerConnection::backendFailure() { +void DaemonLocalServerConnection::backendFailure(DaemonError err) { QJsonObject obj; obj.insert("type", "backendFailure"); + obj.insert("errorCode", static_cast(err)); write(obj); } diff --git a/client/daemon/daemonlocalserverconnection.h b/client/daemon/daemonlocalserverconnection.h index ec32df756..34170cb31 100644 --- a/client/daemon/daemonlocalserverconnection.h +++ b/client/daemon/daemonlocalserverconnection.h @@ -7,6 +7,8 @@ #include +#include "daemonerrors.h" + class QLocalSocket; class DaemonLocalServerConnection final : public QObject { @@ -23,7 +25,7 @@ class DaemonLocalServerConnection final : public QObject { void connected(const QString& pubkey); void disconnected(); - void backendFailure(); + void backendFailure(DaemonError err); void write(const QJsonObject& obj); diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 5e9f0f97c..6b7120a34 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -1,9 +1,10 @@ /* 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/. */ -#include "protocols/protocols_defs.h" #include "localsocketcontroller.h" +#include + #include #include #include @@ -17,6 +18,9 @@ #include "leakdetector.h" #include "logger.h" #include "models/server.h" +#include "daemon/daemonerrors.h" + +#include "protocols/protocols_defs.h" // How many times do we try to reconnect. constexpr int MAX_CONNECTION_RETRY = 10; @@ -451,8 +455,39 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } if (type == "backendFailure") { - qCritical() << "backendFailure"; - return; + if (!obj.contains("errorCode")) { + // report a generic error if we dont know what it is. + logger.error() << "generic backend failure error" + // REPORTERROR(ErrorHandler::ControllerError, "controller"); + return; + } + auto errorCode = static_cast(obj["errorCode"].toInt()); + if (errorCode >= (uint8_t)DaemonError::DAEMON_ERROR_MAX) { + // Also report a generic error if the code is invalid. + logger.error() << "invalid backend failure error code" + // REPORTERROR(ErrorHandler::ControllerError, "controller"); + return; + } + switch (static_cast(errorCode)) { + case DaemonError::ERROR_NONE: + [[fallthrough]]; + case DaemonError::ERROR_FATAL: + logger.error() << "generic backend failure error (fatal or error none)" + // REPORTERROR(ErrorHandler::ControllerError, "controller"); + break; + case DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE: + [[fallthrough]]; + case DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE: + [[fallthrough]]; + case DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE: + logger.error() << "split tunnel backend failure error" + //REPORTERROR(ErrorHandler::SplitTunnelError, "controller"); + break; + case DaemonError::DAEMON_ERROR_MAX: + // We should not get here. + Q_ASSERT(false); + break; + } } if (type == "logs") { diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp index d5aa005dc..d7e76f3e0 100644 --- a/client/platforms/windows/daemon/windowsdaemon.cpp +++ b/client/platforms/windows/daemon/windowsdaemon.cpp @@ -16,6 +16,7 @@ #include #include +#include "daemon/daemonerrors.h" #include "dnsutilswindows.h" #include "leakdetector.h" #include "logger.h" @@ -70,6 +71,12 @@ void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAd bool WindowsDaemon::run(Op op, const InterfaceConfig& config) { if (!m_splitTunnelManager) { + if (config.m_vpnDisabledApps.length() > 0) { + // The Client has sent us a list of disabled apps, but we failed + // to init the the split tunnel driver. + // So let the client know this was not possible + emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE); + } return true; } @@ -78,11 +85,16 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) { return true; } if (config.m_vpnDisabledApps.length() > 0) { - m_splitTunnelManager->start(m_inetAdapterIndex); - m_splitTunnelManager->setRules(config.m_vpnDisabledApps); - } else { - m_splitTunnelManager->stop(); + if (!m_splitTunnelManager->start(m_inetAdapterIndex)) { + emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE); + }; + if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) { + emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE); + }; + return true; } + m_splitTunnelManager->stop(); + return true; } diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 82c3fc243..845ab9afa 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -259,12 +259,12 @@ WindowsSplitTunnel::~WindowsSplitTunnel() { uninstallDriver(); } -void WindowsSplitTunnel::setRules(const QStringList& appPaths) { +bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) { auto state = getState(); if (state != STATE_READY && state != STATE_RUNNING) { logger.warning() << "Driver is not in the right State to set Rules" << state; - return; + return false; } logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state; @@ -278,9 +278,10 @@ void WindowsSplitTunnel::setRules(const QStringList& appPaths) { auto err = GetLastError(); WindowsUtils::windowsLog("Set Config Failed:"); logger.error() << "Failed to set Config err code " << err; - return; + return false; } logger.debug() << "New Configuration applied: " << getState(); + return true; } void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { @@ -296,7 +297,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { 0, &bytesReturned, nullptr); if (!ok) { logger.error() << "Driver init failed"; - return; + return false; } } @@ -309,14 +310,14 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { nullptr); if (!ok) { logger.error() << "Failed to set Process Config"; - return; + return false; } logger.debug() << "Set Process Config ok || new State:" << getState(); } if (getState() == STATE_INITIALIZED) { logger.warning() << "Driver is still not ready after process list send"; - return; + return false; } logger.debug() << "Driver is ready || new State:" << getState(); @@ -326,9 +327,10 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { nullptr); if (!ok) { logger.error() << "Failed to set Network Config"; - return; + return false; } logger.debug() << "New Network Config Applied || new State:" << getState(); + return true; } void WindowsSplitTunnel::stop() { diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index f5f6e6636..9cc641de7 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -46,10 +46,10 @@ class WindowsSplitTunnel final { // void excludeApps(const QStringList& paths); // Excludes an Application from the VPN - void setRules(const QStringList& appPaths); + bool excludeApps(const QStringList& appPaths); // Fetches and Pushed needed info to move to engaged mode - void start(int inetAdapterIndex, int vpnAdapterIndex = 0); + bool start(int inetAdapterIndex, int vpnAdapterIndex = 0); // Deletes Rules and puts the driver into passive mode void stop(); From c893629074dae263e80081b5ee64909b19706b8e Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 13:10:52 +0700 Subject: [PATCH 08/13] cherry-pick f6e49a85538eaa230d3a8634fa7600966132ccab from mozilla upstream --- .../windows/daemon/windowsdaemon.cpp | 6 ++- .../windows/daemon/windowssplittunnel.cpp | 49 +++++++++++++++++-- .../windows/daemon/windowssplittunnel.h | 4 ++ 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp index d7e76f3e0..815a7f001 100644 --- a/client/platforms/windows/daemon/windowsdaemon.cpp +++ b/client/platforms/windows/daemon/windowsdaemon.cpp @@ -91,10 +91,14 @@ bool WindowsDaemon::run(Op op, const InterfaceConfig& config) { if (!m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps)) { emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE); }; + // Now the driver should be running (State == 4) + if (!m_splitTunnelManager->isRunning()) { + emit backendFailure(DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE); + } return true; } m_splitTunnelManager->stop(); - + return true; } diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 845ab9afa..167a757f9 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -204,7 +204,25 @@ std::unique_ptr WindowsSplitTunnel::create( ; if (driverFile == INVALID_HANDLE_VALUE) { WindowsUtils::windowsLog("Failed to open Driver: "); - return nullptr; + // Only once, if the opening did not work. Try to reboot it. # + logger.info() + << "Failed to open driver, attempting only once to reboot driver"; + if (!driver_manager->stopService()) { + logger.error() << "Unable stop driver"; + return nullptr; + }; + logger.info() << "Stopped driver, starting it again."; + if (!driver_manager->startService()) { + logger.error() << "Unable start driver"; + return nullptr; + }; + logger.info() << "Opening again."; + driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0, + nullptr, OPEN_EXISTING, 0, nullptr); + if (driverFile == INVALID_HANDLE_VALUE) { + logger.error() << "Opening Failed again, sorry!"; + return nullptr; + } } if (!initDriver(driverFile)) { logger.error() << "Failed to init driver"; @@ -280,7 +298,7 @@ bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) { logger.error() << "Failed to set Config err code " << err; return false; } - logger.debug() << "New Configuration applied: " << getState(); + logger.debug() << "New Configuration applied: " << stateString(); return true; } @@ -312,14 +330,14 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { logger.error() << "Failed to set Process Config"; return false; } - logger.debug() << "Set Process Config ok || new State:" << getState(); + logger.debug() << "Set Process Config ok || new State:" << stateString(); } if (getState() == STATE_INITIALIZED) { logger.warning() << "Driver is still not ready after process list send"; return false; } - logger.debug() << "Driver is ready || new State:" << getState(); + logger.debug() << "Driver is ready || new State:" << stateString(); auto config = generateIPConfiguration(inetAdapterIndex, vpnAdapterIndex); auto ok = DeviceIoControl(m_driver, IOCTL_REGISTER_IP_ADDRESSES, &config[0], @@ -329,7 +347,7 @@ void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { logger.error() << "Failed to set Network Config"; return false; } - logger.debug() << "New Network Config Applied || new State:" << getState(); + logger.debug() << "New Network Config Applied || new State:" << stateString(); return true; } @@ -677,3 +695,24 @@ bool WindowsSplitTunnel::detectConflict() { CloseServiceHandle(servicehandle); return err == ERROR_SERVICE_DOES_NOT_EXIST; } + +bool WindowsSplitTunnel::isRunning() { return getState() == STATE_RUNNING; } +QString WindowsSplitTunnel::stateString() { + switch (getState()) { + case STATE_UNKNOWN: + return "STATE_UNKNOWN"; + case STATE_NONE: + return "STATE_NONE"; + case STATE_STARTED: + return "STATE_STARTED"; + case STATE_INITIALIZED: + return "STATE_INITIALIZED"; + case STATE_READY: + return "STATE_READY"; + case STATE_RUNNING: + return "STATE_RUNNING"; + case STATE_ZOMBIE: + return "STATE_ZOMBIE"; + break; + } +} diff --git a/client/platforms/windows/daemon/windowssplittunnel.h b/client/platforms/windows/daemon/windowssplittunnel.h index 9cc641de7..85c827f6b 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.h +++ b/client/platforms/windows/daemon/windowssplittunnel.h @@ -53,6 +53,9 @@ class WindowsSplitTunnel final { // Deletes Rules and puts the driver into passive mode void stop(); + // Returns true if the split-tunnel driver is now up and running. + bool isRunning(); + static bool detectConflict(); // States for GetState @@ -77,6 +80,7 @@ class WindowsSplitTunnel final { HANDLE m_driver = INVALID_HANDLE_VALUE; DRIVER_STATE getState(); + QString stateString(); // Generates a Configuration for Each APP std::vector generateAppConfiguration(const QStringList& appPaths); From bf20eb79e7e7f71af9facad5f8ac21125dac4333 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 13:19:40 +0700 Subject: [PATCH 09/13] cherry-pick 86c585387efa0a09c7937dfe799a90a666404fcd from mozilla upstream --- client/platforms/windows/daemon/windowssplittunnel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 167a757f9..3c14add6b 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -488,6 +488,10 @@ bool WindowsSplitTunnel::getAddress(int adapterIndex, IN_ADDR* out_ipv4, logger.debug() << "Ipv4 Conversation error" << WSAGetLastError(); return false; } + if (ipv6.empty()) { + std::memset(out_ipv6, 0x00, sizeof(IN6_ADDR)); + return true; + } if (InetPtonW(AF_INET6, ipv6.c_str(), out_ipv6) != 1) { logger.debug() << "Ipv6 Conversation error" << WSAGetLastError(); return false; From d02c214609d229dff0b6b20157106aca6f92a363 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 13:21:11 +0700 Subject: [PATCH 10/13] cherry-pick a18c1fac740469ca3566751b74a16227518630c4 from mozilla upstream --- client/platforms/windows/daemon/windowssplittunnel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index 3c14add6b..f66db6d45 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -719,4 +719,5 @@ QString WindowsSplitTunnel::stateString() { return "STATE_ZOMBIE"; break; } + return {}; } From bcd58ed0ecf34cf7527ed7c7ff913c30482ed1bb Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 13:38:44 +0700 Subject: [PATCH 11/13] fixed missing ; --- client/mozilla/localsocketcontroller.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 6b7120a34..1081bcaef 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -457,14 +457,14 @@ void LocalSocketController::parseCommand(const QByteArray& command) { if (type == "backendFailure") { if (!obj.contains("errorCode")) { // report a generic error if we dont know what it is. - logger.error() << "generic backend failure error" + logger.error() << "generic backend failure error"; // REPORTERROR(ErrorHandler::ControllerError, "controller"); return; } auto errorCode = static_cast(obj["errorCode"].toInt()); if (errorCode >= (uint8_t)DaemonError::DAEMON_ERROR_MAX) { // Also report a generic error if the code is invalid. - logger.error() << "invalid backend failure error code" + logger.error() << "invalid backend failure error code"; // REPORTERROR(ErrorHandler::ControllerError, "controller"); return; } @@ -472,7 +472,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { case DaemonError::ERROR_NONE: [[fallthrough]]; case DaemonError::ERROR_FATAL: - logger.error() << "generic backend failure error (fatal or error none)" + logger.error() << "generic backend failure error (fatal or error none)"; // REPORTERROR(ErrorHandler::ControllerError, "controller"); break; case DaemonError::ERROR_SPLIT_TUNNEL_INIT_FAILURE: @@ -480,7 +480,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { case DaemonError::ERROR_SPLIT_TUNNEL_START_FAILURE: [[fallthrough]]; case DaemonError::ERROR_SPLIT_TUNNEL_EXCLUDE_FAILURE: - logger.error() << "split tunnel backend failure error" + logger.error() << "split tunnel backend failure error"; //REPORTERROR(ErrorHandler::SplitTunnelError, "controller"); break; case DaemonError::DAEMON_ERROR_MAX: From 5c9895d5dc044eb79b14f5976a815916246605b8 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 7 Nov 2024 14:22:49 +0700 Subject: [PATCH 12/13] added excludeLocalNetworks() for linux --- .../linux/daemon/wireguardutilslinux.cpp | 69 ++++++++++++------- .../linux/daemon/wireguardutilslinux.h | 3 + .../windows/daemon/windowsfirewall.h | 2 +- service/server/CMakeLists.txt | 1 + 4 files changed, 49 insertions(+), 26 deletions(-) diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 460a7fe13..8d530d68c 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -297,31 +297,6 @@ QList WireguardUtilsLinux::getPeerStatus() { return peerList; } - -void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params) -{ - // double-check + ensure our firewall is installed and enabled - if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); - - // Note: rule precedence is handled inside IpTablesFirewall - LinuxFirewall::ensureRootAnchorPriority(); - - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets); - LinuxFirewall::updateAllowNets(params.allowAddrs); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets); - LinuxFirewall::updateBlockNets(params.blockAddrs); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); - LinuxFirewall::updateDNSServers(params.dnsServers); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); -} - bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) { if (!m_rtmonitor) { return false; @@ -377,6 +352,26 @@ bool WireguardUtilsLinux::deleteExclusionRoute(const IPAddress& prefix) { return m_rtmonitor->deleteExclusionRoute(prefix); } +bool WireguardUtilsMacos::excludeLocalNetworks(const QList& routes) { + if (!m_rtmonitor) { + return false; + } + + // Explicitly discard LAN traffic that makes its way into the tunnel. This + // doesn't really exclude the LAN traffic, we just don't take any action to + // overrule the routes of other interfaces. + bool result = true; + for (const auto& prefix : routes) { + logger.error() << "Attempting to exclude:" << prefix.toString(); + if (!m_rtmonitor->insertRoute(prefix)) { + result = false; + } + } + + // TODO: A kill switch would be nice though :) + return result; +} + QString WireguardUtilsLinux::uapiCommand(const QString& command) { QLocalSocket socket; QTimer uapiTimeout; @@ -450,3 +445,27 @@ QString WireguardUtilsLinux::waitForTunnelName(const QString& filename) { return QString(); } + +void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params) +{ + // double-check + ensure our firewall is installed and enabled + if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); + + // Note: rule precedence is handled inside IpTablesFirewall + LinuxFirewall::ensureRootAnchorPriority(); + + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets); + LinuxFirewall::updateAllowNets(params.allowAddrs); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets); + LinuxFirewall::updateBlockNets(params.blockAddrs); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); + LinuxFirewall::updateDNSServers(params.dnsServers); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); +} diff --git a/client/platforms/linux/daemon/wireguardutilslinux.h b/client/platforms/linux/daemon/wireguardutilslinux.h index 9746ea4bb..c324111de 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.h +++ b/client/platforms/linux/daemon/wireguardutilslinux.h @@ -37,6 +37,9 @@ class WireguardUtilsLinux final : public WireguardUtils { bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; + + bool excludeLocalNetworks(const QList& lanAddressRanges) override; + void applyFirewallRules(FirewallParams& params); signals: void backendFailure(); diff --git a/client/platforms/windows/daemon/windowsfirewall.h b/client/platforms/windows/daemon/windowsfirewall.h index 14f503d13..55ee9417b 100644 --- a/client/platforms/windows/daemon/windowsfirewall.h +++ b/client/platforms/windows/daemon/windowsfirewall.h @@ -18,7 +18,7 @@ #include #include -#include "interfaceconfig.h" +#include "../client/daemon/interfaceconfig.h" class IpAdressRange; struct FWP_VALUE0_; diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 0f1010876..28174774d 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -127,6 +127,7 @@ if(WIN32) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsutils.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowspingsender.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/windows/windowsnetworkwatcher.h + ${CMAKE_CURRENT_LIST_DIR}/../../client/daemon/daemonerrors.h ) set(SOURCES ${SOURCES} From 1c6c8a67059d065f460dc73441b71463f6403111 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 12 Nov 2024 06:52:22 +0300 Subject: [PATCH 13/13] build fixes on windows after cherry-picks --- .../windows/daemon/windowsdaemon.cpp | 8 ++-- .../platforms/windows/daemon/windowsdaemon.h | 4 +- .../windows/daemon/windowsfirewall.cpp | 11 ++--- .../windows/daemon/windowssplittunnel.cpp | 2 +- .../windows/daemon/wireguardutilswindows.cpp | 4 +- .../windows/daemon/wireguardutilswindows.h | 2 +- .../windows/windowsservicemanager.cpp | 44 ++++++++++--------- .../platforms/windows/windowsservicemanager.h | 9 ++-- ipc/ipcserver.cpp | 16 ++++--- 9 files changed, 54 insertions(+), 46 deletions(-) diff --git a/client/platforms/windows/daemon/windowsdaemon.cpp b/client/platforms/windows/daemon/windowsdaemon.cpp index 815a7f001..620e0a1c6 100644 --- a/client/platforms/windows/daemon/windowsdaemon.cpp +++ b/client/platforms/windows/daemon/windowsdaemon.cpp @@ -25,6 +25,8 @@ #include "platforms/windows/windowscommons.h" #include "windowsfirewall.h" +#include "core/networkUtilities.h" + namespace { Logger logger("WindowsDaemon"); } @@ -62,10 +64,10 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) { if (config.m_vpnDisabledApps.length() > 0) { - m_splitTunnelManager.start(m_inetAdapterIndex, vpnAdapterIndex); - m_splitTunnelManager.setRules(config.m_vpnDisabledApps); + m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex); + m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps); } else { - m_splitTunnelManager.stop(); + m_splitTunnelManager->stop(); } } diff --git a/client/platforms/windows/daemon/windowsdaemon.h b/client/platforms/windows/daemon/windowsdaemon.h index 69393d7f4..b17dc811a 100644 --- a/client/platforms/windows/daemon/windowsdaemon.h +++ b/client/platforms/windows/daemon/windowsdaemon.h @@ -28,7 +28,7 @@ class WindowsDaemon final : public Daemon { protected: bool run(Op op, const InterfaceConfig& config) override; - WireguardUtils* wgutils() const override { return m_wgutils; } + WireguardUtils* wgutils() const override { return m_wgutils.get(); } DnsUtils* dnsutils() override { return m_dnsutils; } private: @@ -42,7 +42,7 @@ class WindowsDaemon final : public Daemon { int m_inetAdapterIndex = -1; - WireguardUtilsWindows* m_wgutils = nullptr; + std::unique_ptr m_wgutils; DnsUtilsWindows* m_dnsutils = nullptr; std::unique_ptr m_splitTunnelManager; QPointer m_firewallManager; diff --git a/client/platforms/windows/daemon/windowsfirewall.cpp b/client/platforms/windows/daemon/windowsfirewall.cpp index e06dafd25..6fbf02a94 100644 --- a/client/platforms/windows/daemon/windowsfirewall.cpp +++ b/client/platforms/windows/daemon/windowsfirewall.cpp @@ -13,6 +13,8 @@ #include #include #include +#include +#include "winsock.h" #include #include @@ -26,7 +28,6 @@ #include "leakdetector.h" #include "logger.h" #include "platforms/windows/windowsutils.h" -#include "winsock.h" #define IPV6_ADDRESS_SIZE 16 @@ -51,7 +52,7 @@ constexpr uint8_t MAX_WEIGHT = 15; WindowsFirewall* WindowsFirewall::create(QObject* parent) { if (s_instance != nullptr) { // Only one instance of the firewall is allowed - Q_ASSERT(false); +// Q_ASSERT(false); return s_instance; } HANDLE engineHandle = nullptr; @@ -185,7 +186,7 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) { FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic")); FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V Traffic")); FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT, - "Allow all for Mozilla VPN.exe")); + "Allow all for AmneziaVPN.exe")); FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS")); FW_OK( allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1")); @@ -239,7 +240,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) { // Build the firewall rules for this peer. logger.info() << "Enabling traffic for peer" - << logger.keys(config.m_serverPublicKey); + << config.m_serverPublicKey; if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT, "Block Internet", config.m_serverPublicKey)) { return false; @@ -284,7 +285,7 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) { return false; } - logger.info() << "Disabling traffic for peer" << logger.keys(pubkey); + logger.info() << "Disabling traffic for peer" << pubkey; for (const auto& filterID : m_peerRules.values(pubkey)) { FwpmFilterDeleteById0(m_sessionHandle, filterID); m_peerRules.remove(pubkey, filterID); diff --git a/client/platforms/windows/daemon/windowssplittunnel.cpp b/client/platforms/windows/daemon/windowssplittunnel.cpp index f66db6d45..b63c963e0 100644 --- a/client/platforms/windows/daemon/windowssplittunnel.cpp +++ b/client/platforms/windows/daemon/windowssplittunnel.cpp @@ -302,7 +302,7 @@ bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) { return true; } -void WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { +bool WindowsSplitTunnel::start(int inetAdapterIndex, int vpnAdapterIndex) { // To Start we need to send 2 things: // Network info (what is vpn what is network) logger.debug() << "Starting SplitTunnel"; diff --git a/client/platforms/windows/daemon/wireguardutilswindows.cpp b/client/platforms/windows/daemon/wireguardutilswindows.cpp index c538bfbf3..0823b9d7f 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.cpp +++ b/client/platforms/windows/daemon/wireguardutilswindows.cpp @@ -301,11 +301,11 @@ bool WireguardUtilsWindows::deleteRoutePrefix(const IPAddress& prefix) { } bool WireguardUtilsWindows::addExclusionRoute(const IPAddress& prefix) { - return m_routeMonitor.addExclusionRoute(prefix); + return m_routeMonitor->addExclusionRoute(prefix); } bool WireguardUtilsWindows::deleteExclusionRoute(const IPAddress& prefix) { - return m_routeMonitor.deleteExclusionRoute(prefix); + return m_routeMonitor->deleteExclusionRoute(prefix); } bool WireguardUtilsWindows::excludeLocalNetworks( diff --git a/client/platforms/windows/daemon/wireguardutilswindows.h b/client/platforms/windows/daemon/wireguardutilswindows.h index 083f27889..276966b40 100644 --- a/client/platforms/windows/daemon/wireguardutilswindows.h +++ b/client/platforms/windows/daemon/wireguardutilswindows.h @@ -44,7 +44,7 @@ class WireguardUtilsWindows final : public WireguardUtils { bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; - WireguardUtilsWindows::excludeLocalNetworks(const QList& addresses) override; + bool WireguardUtilsWindows::excludeLocalNetworks(const QList& addresses) override; signals: void backendFailure(); diff --git a/client/platforms/windows/windowsservicemanager.cpp b/client/platforms/windows/windowsservicemanager.cpp index 3a3342241..d5a21170c 100644 --- a/client/platforms/windows/windowsservicemanager.cpp +++ b/client/platforms/windows/windowsservicemanager.cpp @@ -4,6 +4,7 @@ #include "windowsservicemanager.h" +#include #include #include "Windows.h" @@ -16,35 +17,44 @@ namespace { Logger logger("WindowsServiceManager"); } -WindowsServiceManager::WindowsServiceManager(LPCWSTR serviceName) { +WindowsServiceManager::WindowsServiceManager(SC_HANDLE serviceManager, + SC_HANDLE service) + : QObject(qApp), m_serviceManager(serviceManager), m_service(service) { + m_timer.setSingleShot(false); +} + +std::unique_ptr WindowsServiceManager::open( + const QString serviceName) { + LPCWSTR service = (const wchar_t*)serviceName.utf16(); + DWORD err = NULL; auto scm_rights = SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE | SC_MANAGER_QUERY_LOCK_STATUS | STANDARD_RIGHTS_READ; - m_serviceManager = OpenSCManager(NULL, // local computer - NULL, // servicesActive database - scm_rights); + auto manager = OpenSCManager(NULL, // local computer + NULL, // servicesActive database + scm_rights); err = GetLastError(); if (err != NULL) { logger.error() << " OpenSCManager failed code: " << err; - return; + return {}; } logger.debug() << "OpenSCManager access given - " << err; - logger.debug() << "Opening Service - " - << QString::fromWCharArray(serviceName); + logger.debug() << "Opening Service - " << serviceName; // Try to get an elevated handle - m_service = OpenService(m_serviceManager, // SCM database - serviceName, // name of service - (GENERIC_READ | SERVICE_START | SERVICE_STOP)); + auto serviceHandle = + OpenService(manager, // SCM database + service, // name of service + (GENERIC_READ | SERVICE_START | SERVICE_STOP)); err = GetLastError(); if (err != NULL) { + CloseServiceHandle(manager); WindowsUtils::windowsLog("OpenService failed"); - return; + return {}; } - m_has_access = true; - m_timer.setSingleShot(false); logger.debug() << "Service manager execute access granted"; + return std::make_unique(manager, serviceHandle); } WindowsServiceManager::~WindowsServiceManager() { @@ -85,10 +95,6 @@ bool WindowsServiceManager::startPolling(DWORD goal_state, int max_wait_sec) { SERVICE_STATUS_PROCESS WindowsServiceManager::getStatus() { SERVICE_STATUS_PROCESS serviceStatus; - if (!m_has_access) { - logger.debug() << "Need read access to get service state"; - return serviceStatus; - } DWORD dwBytesNeeded; // Contains missing bytes if struct is too small? QueryServiceStatusEx(m_service, // handle to service SC_STATUS_PROCESS_INFO, // information level @@ -119,10 +125,6 @@ bool WindowsServiceManager::startService() { } bool WindowsServiceManager::stopService() { - if (!m_has_access) { - logger.error() << "Need execute access to stop services"; - return false; - } auto state = getStatus().dwCurrentState; if (state != SERVICE_RUNNING && state != SERVICE_START_PENDING) { logger.warning() << ("Service stop not possible, as its not running"); diff --git a/client/platforms/windows/windowsservicemanager.h b/client/platforms/windows/windowsservicemanager.h index 7638588f6..315212457 100644 --- a/client/platforms/windows/windowsservicemanager.h +++ b/client/platforms/windows/windowsservicemanager.h @@ -12,7 +12,7 @@ #include "Winsvc.h" /** - * @brief The WindowsServiceManager provides control over the MozillaVPNBroker + * @brief The WindowsServiceManager provides control over the a * service via SCM */ class WindowsServiceManager : public QObject { @@ -20,7 +20,10 @@ class WindowsServiceManager : public QObject { Q_DISABLE_COPY_MOVE(WindowsServiceManager) public: - WindowsServiceManager(LPCWSTR serviceName); + // Creates a WindowsServiceManager for the Named service. + // returns nullptr if + static std::unique_ptr open(const QString serviceName); + WindowsServiceManager(SC_HANDLE serviceManager, SC_HANDLE service); ~WindowsServiceManager(); // true if the Service is running @@ -45,8 +48,6 @@ class WindowsServiceManager : public QObject { // See // SERVICE_STOPPED,SERVICE_STOP_PENDING,SERVICE_START_PENDING,SERVICE_RUNNING SERVICE_STATUS_PROCESS getStatus(); - bool m_has_access = false; - LPWSTR m_serviceName; SC_HANDLE m_serviceManager; SC_HANDLE m_service; // Service handle with r/w priv. DWORD m_state_target; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index bb8a41826..76543e3dc 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -35,10 +35,6 @@ int IpcServer::createPrivilegedProcess() qDebug() << "IpcServer::createPrivilegedProcess"; #endif -#ifdef Q_OS_WIN - WindowsFirewall::instance()->init(); -#endif - m_localpid++; ProcessDescriptor pd(this); @@ -195,7 +191,9 @@ void IpcServer::setLogsEnabled(bool enabled) bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN - return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex); + auto firewallManager = WindowsFirewall::create(this); + Q_ASSERT(firewallManager != nullptr); + return firewallManager->enableInterface(vpnAdapterIndex); #endif #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) @@ -282,7 +280,9 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd bool IpcServer::disableKillSwitch() { #ifdef Q_OS_WIN - return WindowsFirewall::instance()->disableKillSwitch(); + auto firewallManager = WindowsFirewall::create(this); + Q_ASSERT(firewallManager != nullptr); + return firewallManager->disableKillSwitch(); #endif #ifdef Q_OS_LINUX @@ -347,7 +347,9 @@ bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) // killSwitch toggle if (QVariant(configStr.value(amnezia::config_key::killSwitchOption).toString()).toBool()) { - WindowsFirewall::instance()->enablePeerTraffic(config); + auto firewallManager = WindowsFirewall::create(this); + Q_ASSERT(firewallManager != nullptr); + firewallManager->enablePeerTraffic(config); } WindowsDaemon::instance()->prepareActivation(config, inetAdapterIndex);