From 858537df1f42e5ec999b64cd10c2172297d95688 Mon Sep 17 00:00:00 2001 From: Fedotov Anton Date: Wed, 2 Feb 2022 00:59:45 +0300 Subject: [PATCH 1/2] The couple objects were added from Mozilla VPN for Killswitch functionality --- service/killswitch/ipaddress.cpp | 290 +++++++++ service/killswitch/ipaddress.h | 53 ++ service/killswitch/killswitch.pri | 17 + service/killswitch/leakdetector.cpp | 75 +++ service/killswitch/leakdetector.h | 41 ++ service/killswitch/windowscommons.cpp | 182 ++++++ service/killswitch/windowscommons.h | 32 + service/killswitch/windowsfirewall.cpp | 857 +++++++++++++++++++++++++ service/killswitch/windowsfirewall.h | 69 ++ service/server/localserver.cpp | 4 + service/server/main.cpp | 27 +- service/server/router.cpp | 70 +- service/server/router_win.cpp | 155 +++-- service/server/router_win.h | 1 + service/server/server.pro | 2 + 15 files changed, 1794 insertions(+), 81 deletions(-) create mode 100644 service/killswitch/ipaddress.cpp create mode 100644 service/killswitch/ipaddress.h create mode 100644 service/killswitch/killswitch.pri create mode 100644 service/killswitch/leakdetector.cpp create mode 100644 service/killswitch/leakdetector.h create mode 100644 service/killswitch/windowscommons.cpp create mode 100644 service/killswitch/windowscommons.h create mode 100644 service/killswitch/windowsfirewall.cpp create mode 100644 service/killswitch/windowsfirewall.h diff --git a/service/killswitch/ipaddress.cpp b/service/killswitch/ipaddress.cpp new file mode 100644 index 00000000..3fe7f472 --- /dev/null +++ b/service/killswitch/ipaddress.cpp @@ -0,0 +1,290 @@ +/* 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 "ipaddress.h" +#include "leakdetector.h" +//#include "logger.h" + +#include + +//namespace { +//Logger logger(LOG_NETWORKING, "IPAddress"); +//} // namespace + +IPAddress::IPAddress() { MVPN_COUNT_CTOR(IPAddress); } + +IPAddress::IPAddress(const QString& ip) { + MVPN_COUNT_CTOR(IPAddress); + if (ip.contains("/")) { + QPair p = QHostAddress::parseSubnet(ip); + m_address = p.first; + m_prefixLength = p.second; + } else { + m_address = QHostAddress(ip); + m_prefixLength = 999999; + } + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength >= 32) { + m_prefixLength = 32; + } + } else if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + if (m_prefixLength >= 128) { + m_prefixLength = 128; + } + } else { + Q_ASSERT(false); + } +} + +IPAddress::IPAddress(const IPAddress& other) { + MVPN_COUNT_CTOR(IPAddress); + *this = other; +} + +IPAddress& IPAddress::operator=(const IPAddress& other) { + if (this == &other) return *this; + + m_address = other.m_address; + m_prefixLength = other.m_prefixLength; + + return *this; +} + +IPAddress::IPAddress(const QHostAddress& address) : m_address(address) { + MVPN_COUNT_CTOR(IPAddress); + + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + m_prefixLength = 32; + } else { + Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); + m_prefixLength = 128; + } +} + +IPAddress::IPAddress(const QHostAddress& address, int prefixLength) + : m_address(address), m_prefixLength(prefixLength) { + MVPN_COUNT_CTOR(IPAddress); + + if (address.protocol() == QAbstractSocket::IPv4Protocol) { + Q_ASSERT(prefixLength >= 0 && prefixLength <= 32); + } else { + Q_ASSERT(address.protocol() == QAbstractSocket::IPv6Protocol); + Q_ASSERT(prefixLength >= 0 && prefixLength <= 128); + } +} + +IPAddress::~IPAddress() { MVPN_COUNT_DTOR(IPAddress); } + +QAbstractSocket::NetworkLayerProtocol IPAddress::type() const { + return m_address.protocol(); +} + +QHostAddress IPAddress::netmask() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawNetmask = {0}; + Q_ASSERT(m_prefixLength <= 128); + memset(&rawNetmask, 0xff, m_prefixLength / 8); + if (m_prefixLength % 8) { + rawNetmask[m_prefixLength / 8] = 0xFF ^ (0xFF >> (m_prefixLength % 8)); + } + return QHostAddress(rawNetmask); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 rawNetmask = 0xffffffff; + Q_ASSERT(m_prefixLength <= 32); + if (m_prefixLength < 32) { + rawNetmask ^= (0xffffffff >> m_prefixLength); + } + return QHostAddress(rawNetmask); + } else { + return QHostAddress(); + } +} + +QHostAddress IPAddress::hostmask() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawHostmask = {0}; + int offset = (m_prefixLength + 7) / 8; + Q_ASSERT(m_prefixLength <= 128); + memset(&rawHostmask[offset], 0xff, sizeof(rawHostmask) - offset); + if (m_prefixLength % 8) { + rawHostmask[m_prefixLength / 8] = 0xFF >> (m_prefixLength % 8); + } + return QHostAddress(rawHostmask); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength < 32) { + return QHostAddress(0xffffffff >> m_prefixLength); + } else { + quint32 zero = 0; + return QHostAddress(zero); + } + } else { + return QHostAddress(); + } +} + +QHostAddress IPAddress::broadcastAddress() const { + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR rawAddress = m_address.toIPv6Address(); + int offset = (m_prefixLength + 7) / 8; + memset(&rawAddress[offset], 0xff, sizeof(rawAddress) - offset); + if (m_prefixLength % 8) { + rawAddress[m_prefixLength / 8] |= 0xFF >> (m_prefixLength % 8); + } + return QHostAddress(rawAddress); + } else if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 rawAddress = m_address.toIPv4Address(); + if (m_prefixLength < 32) { + rawAddress |= (0xffffffff >> m_prefixLength); + } + return QHostAddress(rawAddress); + } else { + return QHostAddress(); + } +} + +bool IPAddress::overlaps(const IPAddress& other) const { + if (m_prefixLength < other.m_prefixLength) { + return contains(other.m_address); + } else { + return other.contains(m_address); + } +} + +bool IPAddress::contains(const QHostAddress& address) const { + if (address.protocol() != m_address.protocol()) { + return false; + } + if (m_prefixLength == 0) { + return true; + } + + if (m_address.protocol() == QAbstractSocket::IPv6Protocol) { + Q_IPV6ADDR a = m_address.toIPv6Address(); + Q_IPV6ADDR b = address.toIPv6Address(); + int bytes = m_prefixLength / 8; + if (bytes > 0) { + if (memcmp(&a, &b, bytes) != 0) { + return false; + } + } + + if (m_prefixLength % 8) { + quint8 diff = (a[bytes] ^ b[bytes]) >> (8 - m_prefixLength % 8); + return (diff == 0); + } + + return true; + } + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + quint32 diff = m_address.toIPv4Address() ^ address.toIPv4Address(); + if (m_prefixLength < 32) { + diff >>= (32 - m_prefixLength); + } + return (diff == 0); + } + + return false; +} + +bool IPAddress::operator==(const IPAddress& other) const { + return m_address == other.m_address && m_prefixLength == other.m_prefixLength; +} + +bool IPAddress::subnetOf(const IPAddress& other) const { + if (other.m_address.protocol() != m_address.protocol()) { + return false; + } + if (m_prefixLength < other.m_prefixLength) { + return false; + } + + return other.contains(m_address); +} + +QList IPAddress::subnets() const { + QList list; + + if (m_address.protocol() == QAbstractSocket::IPv4Protocol) { + if (m_prefixLength >= 32) { + list.append(*this); + return list; + } + + quint32 rawAddress = m_address.toIPv4Address(); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + rawAddress |= (0x80000000 >> m_prefixLength); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + return list; + } + + Q_ASSERT(m_address.protocol() == QAbstractSocket::IPv6Protocol); + + if (m_prefixLength >= 128) { + list.append(*this); + return list; + } + + Q_IPV6ADDR rawAddress = m_address.toIPv6Address(); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + rawAddress[m_prefixLength / 8] |= (0x80 >> (m_prefixLength % 8)); + list.append(IPAddress(QHostAddress(rawAddress), m_prefixLength + 1)); + + return list; +} + +// static +QList IPAddress::excludeAddresses( + const QList& sourceList, const QList& excludeList) { + QList results = sourceList; + + for (const IPAddress& exclude : excludeList) { + QList newResults; + + for (const IPAddress& ip : results) { + if (ip.overlaps(exclude)) { + QList range = ip.excludeAddresses(exclude); + newResults.append(range); + } else { + newResults.append(ip); + } + } + + results = newResults; + } + + return results; +} + +QList IPAddress::excludeAddresses(const IPAddress& ip) const { + QList sn = subnets(); + Q_ASSERT(sn.length() >= 2); + + QList result; + while (sn[0] != ip && sn[1] != ip) { + if (ip.subnetOf(sn[0])) { + result.append(sn[1]); + sn = sn[0].subnets(); + } else if (ip.subnetOf(sn[1])) { + result.append(sn[0]); + sn = sn[1].subnets(); + } else { + Q_ASSERT(false); + } + } + + if (sn[0] == ip) { + result.append(sn[1]); + } else if (sn[1] == ip) { + result.append(sn[0]); + } else { + Q_ASSERT(false); + } + + return result; +} diff --git a/service/killswitch/ipaddress.h b/service/killswitch/ipaddress.h new file mode 100644 index 00000000..05b04de2 --- /dev/null +++ b/service/killswitch/ipaddress.h @@ -0,0 +1,53 @@ +/* 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/. */ + +#ifndef IPADDRESS_H +#define IPADDRESS_H + +#include + +class IPAddress final { + public: + static QList excludeAddresses(const QList& sourceList, + const QList& excludeList); + + IPAddress(); + IPAddress(const QString& ip); + IPAddress(const QHostAddress& address); + IPAddress(const QHostAddress& address, int prefixLength); + IPAddress(const IPAddress& other); + IPAddress& operator=(const IPAddress& other); + ~IPAddress(); + + QString toString() const { + return QString("%1/%2").arg(m_address.toString()).arg(m_prefixLength); + } + + const QHostAddress& address() const { return m_address; } + int prefixLength() const { return m_prefixLength; } + QHostAddress netmask() const; + QHostAddress hostmask() const; + QHostAddress broadcastAddress() const; + + bool overlaps(const IPAddress& other) const; + + bool contains(const QHostAddress& address) const; + + bool operator==(const IPAddress& other) const; + bool operator!=(const IPAddress& other) const { return !operator==(other); } + + bool subnetOf(const IPAddress& other) const; + + QList subnets() const; + + QList excludeAddresses(const IPAddress& ip) const; + + QAbstractSocket::NetworkLayerProtocol type() const; + + private: + QHostAddress m_address; + int m_prefixLength; +}; + +#endif // IPADDRESS_H diff --git a/service/killswitch/killswitch.pri b/service/killswitch/killswitch.pri new file mode 100644 index 00000000..4fba3a2c --- /dev/null +++ b/service/killswitch/killswitch.pri @@ -0,0 +1,17 @@ +INCLUDEPATH+= $$PWD +DEPENDPATH += $$PWD +QT += core network +HEADERS += \ + $$PWD/leakdetector.h \ + $$PWD/windowsfirewall.h \ + $$PWD/ipaddress.h \ + $$PWD/windowscommons.h + + SOURCES += \ + $$PWD/leakdetector.cpp \ + $$PWD/windowsfirewall.cpp \ + $$PWD/ipaddress.cpp \ + $$PWD/windowscommons.cpp + + # TARGET = libkillswitch + diff --git a/service/killswitch/leakdetector.cpp b/service/killswitch/leakdetector.cpp new file mode 100644 index 00000000..7a092a8a --- /dev/null +++ b/service/killswitch/leakdetector.cpp @@ -0,0 +1,75 @@ +/* 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 "leakdetector.h" + +#include +#include +#include +#include + +#ifdef MVPN_DEBUG +static QMutex s_leakDetector; + +QHash> s_leaks; +#endif + +LeakDetector::LeakDetector() { +#ifndef MVPN_DEBUG + qFatal("LeakDetector _must_ be created in debug builds only!"); +#endif +} + +LeakDetector::~LeakDetector() { +#ifdef MVPN_DEBUG + QTextStream out(stderr); + + out << "== Mozilla VPN - Leak report ===================" << Qt::endl; + + bool hasLeaks = false; + for (auto i = s_leaks.begin(); i != s_leaks.end(); ++i) { + QString className = i.key(); + + if (i->size() == 0) { + continue; + } + + hasLeaks = true; + out << className << Qt::endl; + + for (auto l = i->begin(); l != i->end(); ++l) { + out << " - ptr: " << l.key() << " size:" << l.value() << Qt::endl; + } + } + + if (!hasLeaks) { + out << "No leaks detected." << Qt::endl; + } +#endif +} + +#ifdef MVPN_DEBUG +void LeakDetector::logCtor(void* ptr, const char* typeName, uint32_t size) { + QMutexLocker lock(&s_leakDetector); + + QString type(typeName); + if (!s_leaks.contains(type)) { + s_leaks.insert(type, QHash()); + } + + s_leaks[type].insert(ptr, size); +} + +void LeakDetector::logDtor(void* ptr, const char* typeName, uint32_t size) { + QMutexLocker lock(&s_leakDetector); + + QString type(typeName); + Q_ASSERT(s_leaks.contains(type)); + + QHash& leak = s_leaks[type]; + Q_ASSERT(leak.contains(ptr)); + Q_ASSERT(leak[ptr] == size); + leak.remove(ptr); +} +#endif diff --git a/service/killswitch/leakdetector.h b/service/killswitch/leakdetector.h new file mode 100644 index 00000000..3016137c --- /dev/null +++ b/service/killswitch/leakdetector.h @@ -0,0 +1,41 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LEAKDETECTOR_H +#define LEAKDETECTOR_H + +#include + +#ifdef MVPN_DEBUG +# define MVPN_COUNT_CTOR(_type) \ + do { \ + static_assert(std::is_class<_type>(), \ + "Token '" #_type "' is not a class type."); \ + LeakDetector::logCtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +# define MVPN_COUNT_DTOR(_type) \ + do { \ + static_assert(std::is_class<_type>(), \ + "Token '" #_type "' is not a class type."); \ + LeakDetector::logDtor((void*)this, #_type, sizeof(*this)); \ + } while (0) + +#else +# define MVPN_COUNT_CTOR(_type) +# define MVPN_COUNT_DTOR(_type) +#endif + +class LeakDetector { + public: + LeakDetector(); + ~LeakDetector(); + +#ifdef MVPN_DEBUG + static void logCtor(void* ptr, const char* typeName, uint32_t size); + static void logDtor(void* ptr, const char* typeName, uint32_t size); +#endif +}; + +#endif // LEAKDETECTOR_H diff --git a/service/killswitch/windowscommons.cpp b/service/killswitch/windowscommons.cpp new file mode 100644 index 00000000..5809491f --- /dev/null +++ b/service/killswitch/windowscommons.cpp @@ -0,0 +1,182 @@ +/* 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 "windowscommons.h" +//#include "logger.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define TUNNEL_SERVICE_NAME L"WireGuardTunnel$mozvpn" + +constexpr const char* VPN_NAME = "MozillaVPN"; + +constexpr const int WINDOWS_11_BUILD = + 22000; // Build Number of the first release win 11 iso +//namespace { +//Logger logger(LOG_MAIN, "WindowsCommons"); +//} + +QString WindowsCommons::getErrorMessage() { + DWORD errorId = GetLastError(); + LPSTR messageBuffer = nullptr; + size_t size = FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, errorId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&messageBuffer, 0, nullptr); + + std::string message(messageBuffer, size); + QString result(message.c_str()); + LocalFree(messageBuffer); + return result; +} + +// A simple function to log windows error messages. +void WindowsCommons::windowsLog(const QString& msg) { + QString errmsg = getErrorMessage(); + qDebug() << msg << "-" << errmsg; +} + +QString WindowsCommons::tunnelConfigFile() { + QStringList paths = + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + for (const QString& path : paths) { + QDir dir(path); + if (!dir.exists()) { + continue; + } + + QDir vpnDir(dir.filePath(VPN_NAME)); + if (!vpnDir.exists()) { + continue; + } + + QString wireguardFile(vpnDir.filePath(QString("%1.conf").arg(VPN_NAME))); + if (!QFileInfo::exists(wireguardFile)) { + continue; + } + + qDebug() << "Found the current wireguard configuration:" + << wireguardFile; + return wireguardFile; + } + + for (const QString& path : paths) { + QDir dir(path); + + QDir vpnDir(dir.filePath(VPN_NAME)); + if (!vpnDir.exists() && !dir.mkdir(VPN_NAME)) { + qDebug() << "Failed to create path Mozilla under" << path; + continue; + } + + return vpnDir.filePath(QString("%1.conf").arg(VPN_NAME)); + } + + qDebug() << "Failed to create the right paths"; + return QString(); +} + +QString WindowsCommons::tunnelLogFile() { + QStringList paths = + QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); + + for (const QString& path : paths) { + QDir dir(path); + if (!dir.exists()) { + continue; + } + + QDir vpnDir(dir.filePath(VPN_NAME)); + if (!vpnDir.exists()) { + continue; + } + + return vpnDir.filePath("log.bin"); + } + + return QString(); +} + +// static +int WindowsCommons::AdapterIndexTo(const QHostAddress& dst) { + qDebug() << "Getting Current Internet Adapter that routes to" + << dst.toString(); + quint32_be ipBigEndian; + quint32 ip = dst.toIPv4Address(); + qToBigEndian(ip, &ipBigEndian); + _MIB_IPFORWARDROW routeInfo; + auto result = GetBestRoute(ipBigEndian, 0, &routeInfo); + if (result != NO_ERROR) { + return -1; + } + auto adapter = + QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex); + //logger.debug() << "Internet Adapter:" << adapter.name(); + qDebug()<< "Internet Adapter:" << adapter.name(); + return routeInfo.dwForwardIfIndex; +} + +// static +int WindowsCommons::VPNAdapterIndex() { + // For someReason QNetworkInterface::fromName(MozillaVPN) does not work >:( + auto adapterList = QNetworkInterface::allInterfaces(); + for (const auto& adapter : adapterList) { + if ( + adapter.humanReadableName().contains(IKEV2) || + adapter.humanReadableName().contains(IKEV2) + ) { + return adapter.index(); + } + } + return -1; +} + +// Static +QString WindowsCommons::getCurrentPath() { + QByteArray buffer(2048, 0xFF); + auto ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size()); + + if (ok == ERROR_INSUFFICIENT_BUFFER) { + buffer.resize(buffer.size() * 2); + ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size()); + } + if (ok == 0) { + WindowsCommons::windowsLog("Err fetching dos path"); + return ""; + } + return QString::fromLocal8Bit(buffer); +} + +// Static +QString WindowsCommons::WindowsVersion() { + /* The Tradegy of Getting a somewhat working windows version: + - GetVersion() -> deprecated and Reports win 8.1 for MozillaVPN... its tied + to some .exe flags + - NetWkstaGetInfo -> Reports Windows 10 on windows 11 + There is also the regirstry HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows + NT\CurrentVersion + -> CurrentMajorVersion reports 10 on win 11 + -> CurrentBuild seems to be correct, so lets infer it + */ + + QSettings regCurrentVersion( + "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion", + QSettings::NativeFormat); + + int buildNr = regCurrentVersion.value("CurrentBuild").toInt(); + if (buildNr >= WINDOWS_11_BUILD) { + return "11"; + } + return QSysInfo::productVersion(); +} diff --git a/service/killswitch/windowscommons.h b/service/killswitch/windowscommons.h new file mode 100644 index 00000000..432d1397 --- /dev/null +++ b/service/killswitch/windowscommons.h @@ -0,0 +1,32 @@ +/* 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/. */ + +#ifndef WINDOWSCOMMONS_H +#define WINDOWSCOMMONS_H + +#include + +constexpr char IKEV2[] {"AmneziaVPN IKEv2\0"}; + +class QHostAddress; + +class WindowsCommons final { + public: + static QString getErrorMessage(); + static void windowsLog(const QString& msg); + + static QString tunnelConfigFile(); + static QString tunnelLogFile(); + + // Returns the Interface Index of the VPN Adapter + static int VPNAdapterIndex(); + // Returns the Interface Index that could Route to dst + static int AdapterIndexTo(const QHostAddress& dst); + // Returns the Path of the Current process + static QString getCurrentPath(); + // Returns the major version of Windows + static QString WindowsVersion(); +}; + +#endif // WINDOWSCOMMONS_H diff --git a/service/killswitch/windowsfirewall.cpp b/service/killswitch/windowsfirewall.cpp new file mode 100644 index 00000000..9963ab12 --- /dev/null +++ b/service/killswitch/windowsfirewall.cpp @@ -0,0 +1,857 @@ +/* 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 "windowsfirewall.h" +//#include "logger.h" +#include "leakdetector.h" +//#include "windowscommons.h" +//#include "../../daemon/interfaceconfig.h" +//#include "../../ipaddress.h" + +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include "winsock.h" + +#include +#include +#include + +#define IPV6_ADDRESS_SIZE 16 + +// ID for the Firewall Sublayer +DEFINE_GUID(ST_FW_WINFW_BASELINE_SUBLAYER_KEY, 0xc78056ff, 0x2bc1, 0x4211, 0xaa, + 0xdd, 0x7f, 0x35, 0x8d, 0xef, 0x20, 0x2d); +// ID for the Mullvad Split-Tunnel Sublayer Provider +DEFINE_GUID(ST_FW_PROVIDER_KEY, 0xe2c114ee, 0xf32a, 0x4264, 0xa6, 0xcb, 0x3f, + 0xa7, 0x99, 0x63, 0x56, 0xd9); + +namespace { +//Logger logger(LOG_WINDOWS, "WindowsFirewall"); +WindowsFirewall* s_instance = nullptr; + +// Note Filter Weight may be between 0-15! +constexpr uint8_t LOW_WEIGHT = 0; +constexpr uint8_t MED_WEIGHT = 7; +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); + } + return s_instance; +} + +WindowsFirewall::WindowsFirewall(QObject* parent) : QObject(parent) { + MVPN_COUNT_CTOR(WindowsFirewall); + Q_ASSERT(s_instance == nullptr); + + HANDLE engineHandle = NULL; + DWORD result = ERROR_SUCCESS; + // Use dynamic sessions for efficiency and safety: + // -> Filtering policy objects are deleted even when the application crashes/ + // deamon goes down + FWPM_SESSION0 session; + memset(&session, 0, sizeof(session)); + session.flags = FWPM_SESSION_FLAG_DYNAMIC; + + qDebug() << "Opening the filter engine."; + + result = + FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &engineHandle); + + if (result != ERROR_SUCCESS) { + qDebug()<<"FwpmEngineOpen0 failed "< Hyper-V outbound. + filter.layerKey = FWPM_LAYER_OUTBOUND_MAC_FRAME_NATIVE; + if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound")) { + return false; + } + // #2 Permit Hyper-V => Hyper-V inbound. + filter.layerKey = FWPM_LAYER_INBOUND_MAC_FRAME_NATIVE; + if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound")) { + return false; + } + return true; +} + +bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight, + const QString& title, + const QString& peer) { + QString description("Block 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_BLOCK; + 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::blockTrafficTo(const QList& rangeList, + uint8_t weight, const QString& title, + const QString& peer) { + for (auto range : rangeList) { + if (!blockTrafficTo(range, weight, title, peer)) { + qDebug() << "Setting Range of" << range.toString() << "failed"; + return false; + } + } + return true; +} + +// Returns the Path of the Current Executable this runs in +QString WindowsFirewall::getCurrentPath() { + const unsigned char initValue = 0xff; + QByteArray buffer(2048, initValue); + auto ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size()); + + if (ok == ERROR_INSUFFICIENT_BUFFER) { + buffer.resize(buffer.size() * 2); + ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size()); + } + if (ok == 0) { + //WindowsCommons::windowsLog("Err fetching dos path"); + return ""; + } + + return QString::fromLocal8Bit(buffer); +} + +void WindowsFirewall::importAddress(const QHostAddress& addr, + OUT FWP_VALUE0_& value, + OUT QByteArray* v6DataBuffer) { + const bool isV4 = addr.protocol() == QAbstractSocket::IPv4Protocol; + if (isV4) { + value.type = FWP_UINT32; + value.uint32 = addr.toIPv4Address(); + return; + } + auto v6bytes = addr.toIPv6Address(); + v6DataBuffer->append((const char*)v6bytes.c, IPV6_ADDRESS_SIZE); + value.type = FWP_BYTE_ARRAY16_TYPE; + value.byteArray16 = (FWP_BYTE_ARRAY16*)v6DataBuffer->data(); +} + +void WindowsFirewall::importAddress(const QHostAddress& addr, + OUT FWP_CONDITION_VALUE0_& value, + OUT QByteArray* v6DataBuffer) { + const bool isV4 = addr.protocol() == QAbstractSocket::IPv4Protocol; + if (isV4) { + value.type = FWP_UINT32; + value.uint32 = addr.toIPv4Address(); + return; + } + auto v6bytes = addr.toIPv6Address(); + v6DataBuffer->append((const char*)v6bytes.c, IPV6_ADDRESS_SIZE); + value.type = FWP_BYTE_ARRAY16_TYPE; + value.byteArray16 = (FWP_BYTE_ARRAY16*)v6DataBuffer->data(); +} + +bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight, + const QString& title) { + // Allow Traffic to IP with PORT using any protocol + FWPM_FILTER_CONDITION0 conds[3]; + conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL; + conds[0].matchType = FWP_MATCH_EQUAL; + conds[0].conditionValue.type = FWP_UINT8; + conds[0].conditionValue.uint8 = (IPPROTO_UDP); + + conds[1].fieldKey = FWPM_CONDITION_IP_PROTOCOL; + conds[1].matchType = FWP_MATCH_EQUAL; + conds[1].conditionValue.type = FWP_UINT8; + conds[1].conditionValue.uint8 = (IPPROTO_TCP); + + conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT; + conds[2].matchType = FWP_MATCH_EQUAL; + conds[2].conditionValue.type = FWP_UINT16; + conds[2].conditionValue.uint16 = port; + + // Assemble the Filter base + FWPM_FILTER0 filter; + memset(&filter, 0, sizeof(filter)); + filter.filterCondition = conds; + filter.numFilterConditions = 3; + filter.action.type = FWP_ACTION_BLOCK; + filter.weight.type = FWP_UINT8; + filter.weight.uint8 = weight; + filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; + + QString description("Block %1 on Port %2"); + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6; + if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port))) { + return false; + } + filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4; + if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) { + return false; + } + + filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4; + if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) { + return false; + } + filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6; + if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) { + return false; + } + return true; +} + +bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title, + const QString& description, + const QString& peer) { + + uint64_t filterID = 0; + auto name = title.toStdWString(); + auto desc = description.toStdWString(); + filter->displayData.name = (PWSTR)name.c_str(); + filter->displayData.description = (PWSTR)desc.c_str(); + auto result = FwpmFilterAdd0(m_sessionHandle, filter, NULL, &filterID); + if (result != ERROR_SUCCESS) { + qDebug() << "Failed to enable filter: " << title << " " + << description<< "with result "< networkInterfaces = + QNetworkInterface::allInterfaces(); + for (const auto& iface : networkInterfaces) { + if (iface.type() != QNetworkInterface::Loopback) { + continue; + } + if (!allowTrafficOfAdapter(iface.index(), weight, + title.arg(iface.name()))) { + return false; + } + } + return true; +} diff --git a/service/killswitch/windowsfirewall.h b/service/killswitch/windowsfirewall.h new file mode 100644 index 00000000..4793df5d --- /dev/null +++ b/service/killswitch/windowsfirewall.h @@ -0,0 +1,69 @@ +/* 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/. */ + +#ifndef WINDOWSFIREWALL_H +#define WINDOWSFIREWALL_H + +#pragma comment(lib, "Fwpuclnt") + +//#include "../../daemon/interfaceconfig.h" + +#include "ipaddress.h" + +#include +#include +#include +#include +#include +#include +class IpAdressRange; +struct FWP_VALUE0_; +struct FWP_CONDITION_VALUE0_; + +class WindowsFirewall final : public QObject { + public: + ~WindowsFirewall(); + + static WindowsFirewall* instance(); + bool init(); + + bool enableKillSwitch(int vpnAdapterIndex); + //bool enablePeerTraffic(const InterfaceConfig& config); + bool disablePeerTraffic(const QString& pubkey); + bool disableKillSwitch(); + + private: + WindowsFirewall(QObject* parent); + HANDLE m_sessionHandle{INVALID_HANDLE_VALUE}; + bool m_init = false; + QList m_activeRules; + QMultiMap m_peerRules; + + bool allowTrafficForAppOnAll(const QString& exePath, int weight, + const QString& title); + bool blockTrafficTo(const QList& range, uint8_t weight, + const QString& title, const QString& peer = QString()); + 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 QHostAddress& targetIP, uint port, int weight, + const QString& title, const QString& peer = QString()); + bool allowTrafficOfAdapter(int networkAdapter, uint8_t weight, + const QString& title); + bool allowDHCPTraffic(uint8_t weight, const QString& title); + bool allowHyperVTraffic(uint8_t weight, const QString& title); + bool allowLoopbackTraffic(uint8_t weight, const QString& title); + + // Utils + QString getCurrentPath(); + void importAddress(const QHostAddress& addr, OUT FWP_VALUE0_& value, + OUT QByteArray* v6DataBuffer); + void importAddress(const QHostAddress& addr, OUT FWP_CONDITION_VALUE0_& value, + OUT QByteArray* v6DataBuffer); + bool enableFilter(FWPM_FILTER0* filter, const QString& title, + const QString& description, + const QString& peer = QString()); +}; + +#endif // WINDOWSFIREWALL_H diff --git a/service/server/localserver.cpp b/service/server/localserver.cpp index f94d2dca..2e25f2ef 100644 --- a/service/server/localserver.cpp +++ b/service/server/localserver.cpp @@ -10,6 +10,7 @@ #include "router.h" #ifdef Q_OS_WIN +#include "windowsfirewall.h" #include "tapcontroller_win.h" #endif @@ -38,6 +39,9 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent), LocalServer::~LocalServer() { + auto cf = WindowsFirewall::instance(); + cf->disableKillSwitch(); + qDebug()<<"KillSwitch deactivated"; qDebug() << "Local server stopped"; } diff --git a/service/server/main.cpp b/service/server/main.cpp index d0d7d82a..7fccbd6e 100644 --- a/service/server/main.cpp +++ b/service/server/main.cpp @@ -1,4 +1,5 @@ #include +#include #include "defines.h" #include "localserver.h" @@ -6,6 +7,25 @@ #include "systemservice.h" #include "utils.h" +#ifdef Q_OS_WIN +#include "windowsfirewall.h" +#include + +class KillSwitch final { +public: + explicit KillSwitch(){ + qDebug()<<__FUNCTION__; + auto cf = WindowsFirewall::instance(); + cf->init(); + //cf->disableKillSwitch(); + } + ~KillSwitch(){ + qDebug()<<__FUNCTION__; + auto cf = WindowsFirewall::instance(); + cf->disableKillSwitch(); + } +}; +#endif int runApplication(int argc, char** argv) { @@ -19,9 +39,12 @@ int runApplication(int argc, char** argv) int main(int argc, char **argv) { Utils::initializePath(Utils::systemLogPath()); - Log::initialize(); +#ifdef Q_OS_WIN + KillSwitch ks; +#endif + if (argc == 2) { qInfo() << "Started as console application"; return runApplication(argc, argv); @@ -32,7 +55,7 @@ int main(int argc, char **argv) SystemService systemService(argc, argv); return systemService.exec(); #else - return runApplication(argc, argv); + return runApplication(argc, argv); #endif } diff --git a/service/server/router.cpp b/service/server/router.cpp index 6b97e795..02157f07 100644 --- a/service/server/router.cpp +++ b/service/server/router.cpp @@ -8,11 +8,79 @@ #include "router_linux.h" #endif +#ifdef Q_OS_WIN +#include +#include "windowsfirewall.h" +//#include "netadpinfo.h" +#include + +namespace { + +constexpr char IKEV2[] {"AmneziaVPN IKEv2"}; +constexpr char WG[] {"AmneziaVPN.WireGuard0"}; +constexpr char OVPN[] {"TAP-Windows Adapter V9"}; + +bool get_eth_name(const QString &adp_name){ + //char buff[128]{}; + PIP_ADAPTER_INFO pAdapterInfo{}; + pAdapterInfo = (IP_ADAPTER_INFO *) malloc(sizeof(IP_ADAPTER_INFO)); + ULONG buflen = sizeof(IP_ADAPTER_INFO); + if(GetAdaptersInfo(pAdapterInfo, &buflen) == ERROR_BUFFER_OVERFLOW) + { + free(pAdapterInfo); + pAdapterInfo = (IP_ADAPTER_INFO *) + malloc(buflen); + } + if(GetAdaptersInfo(pAdapterInfo, &buflen) == NO_ERROR) { + PIP_ADAPTER_INFO pAdapter = pAdapterInfo; + while (pAdapter) { + qDebug()<<"Adapter Name: "<< pAdapter->AdapterName; + qDebug()<<"Adapter Desc: "<Description; + if (adp_name == pAdapter->Description) + return true; + pAdapter = pAdapter->Next; + } + } + return false; +} + +void enable_killswitch(){ + auto VPN_LIST = []()->int{ + auto adapterList = QNetworkInterface::allInterfaces(); + for (const auto& adapter : adapterList) { + bool finded{false}; + finded = get_eth_name(OVPN); + if ( adapter.humanReadableName().contains(IKEV2) || + adapter.humanReadableName().contains(WG) || + finded || + adapter.humanReadableName().contains(OVPN) + ) { + return adapter.index(); + } + } + return -1; + }; + const auto ¤t_vpn = VPN_LIST(); + if (current_vpn != -1){ + qInfo()<<"KillSwitch activated"; + auto cf = WindowsFirewall::instance(); + //cf->enableKillSwitch(current_vpn); + cf->disableKillSwitch(); + }else{ + qCritical().noquote() <<"No any adapters was found"; + } +} +}// end namespace +#endif + int Router::routeAddList(const QString &gw, const QStringList &ips) { #ifdef Q_OS_WIN - return RouterWin::Instance().routeAddList(gw, ips); + auto value = RouterWin::Instance().routeAddList(gw, ips); + enable_killswitch(); + return value; + //return RouterWin::Instance().routeAddList(gw, ips); #elif defined (Q_OS_MAC) return RouterMac::Instance().routeAddList(gw, ips); #elif defined Q_OS_LINUX diff --git a/service/server/router_win.cpp b/service/server/router_win.cpp index df431805..7eac324a 100644 --- a/service/server/router_win.cpp +++ b/service/server/router_win.cpp @@ -27,13 +27,12 @@ RouterWin &RouterWin::Instance() int RouterWin::routeAddList(const QString &gw, const QStringList &ips) { -// qDebug().noquote() << QString("ROUTE ADD List: IPs size:%1, GW: %2") -// .arg(ips.size()) -// .arg(gw); - -// qDebug().noquote() << QString("ROUTE ADD List: IPs:\n%1") -// .arg(ips.join("\n")); + // qDebug().noquote() << QString("ROUTE ADD List: IPs size:%1, GW: %2") + // .arg(ips.size()) + // .arg(gw); + // qDebug().noquote() << QString("ROUTE ADD List: IPs:\n%1") + // .arg(ips.join("\n")); if (!Utils::checkIPv4Format(gw)) { qCritical().noquote() << "Trying to add invalid route, gw: " << gw; @@ -204,12 +203,12 @@ bool RouterWin::clearSavedRoutes() int RouterWin::routeDeleteList(const QString &gw, const QStringList &ips) { -// qDebug().noquote() << QString("ROUTE DELETE List: IPs size:%1, GW: %2") -// .arg(ips.size()) -// .arg(gw); + // qDebug().noquote() << QString("ROUTE DELETE List: IPs size:%1, GW: %2") + // .arg(ips.size()) + // .arg(gw); -// qDebug().noquote() << QString("ROUTE DELETE List: IPs:\n%1") -// .arg(ips.join("\n")); + // qDebug().noquote() << QString("ROUTE DELETE List: IPs:\n%1") + // .arg(ips.join("\n")); PMIB_IPFORWARDTABLE pIpForwardTable = NULL; DWORD dwSize = 0; @@ -291,11 +290,11 @@ void RouterWin::flushDns() void RouterWin::resetIpStack() { -// { -// QProcess p; -// QString command = QString("ipconfig /release"); -// p.start(command); -// } + // { + // QProcess p; + // QString command = QString("ipconfig /release"); + // p.start(command); + // } { QProcess p; QString command = QString("netsh int ip reset"); @@ -346,90 +345,90 @@ DWORD RouterWin::GetServicePid(LPCWSTR serviceName) BOOL RouterWin::ListProcessThreads( DWORD dwOwnerPID ) { - HANDLE hThreadSnap = INVALID_HANDLE_VALUE; - THREADENTRY32 te32; + HANDLE hThreadSnap = INVALID_HANDLE_VALUE; + THREADENTRY32 te32; - // Take a snapshot of all running threads - hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); - if( hThreadSnap == INVALID_HANDLE_VALUE ) - return( FALSE ); + // Take a snapshot of all running threads + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( hThreadSnap == INVALID_HANDLE_VALUE ) + return( FALSE ); - // Fill in the size of the structure before using it. - te32.dwSize = sizeof(THREADENTRY32); + // Fill in the size of the structure before using it. + te32.dwSize = sizeof(THREADENTRY32); - // Retrieve information about the first thread, - // and exit if unsuccessful - if( !Thread32First( hThreadSnap, &te32 ) ) - { - //printError( TEXT("Thread32First") ); // show cause of failure - CloseHandle( hThreadSnap ); // clean the snapshot object - return( FALSE ); - } - - // Now walk the thread list of the system, - // and display information about each thread - // associated with the specified process - //HANDLE threadHandle; - do - { - if( te32.th32OwnerProcessID == dwOwnerPID ) + // Retrieve information about the first thread, + // and exit if unsuccessful + if( !Thread32First( hThreadSnap, &te32 ) ) { - HANDLE threadHandle = OpenThread (PROCESS_QUERY_INFORMATION, FALSE, te32.th32ThreadID); - qDebug() << "OpenThread GetLastError:"<< te32.th32ThreadID << GetLastError() << threadHandle; - ULONG64 cycles = 0; - BOOL ok = QueryThreadCycleTime(threadHandle, &cycles); - qDebug() << "QueryThreadCycleTime GetLastError:" << ok << GetLastError(); - - qDebug() << "Thread cycles:" << te32.th32ThreadID << cycles; -// _tprintf( TEXT("\n\n THREAD ID = 0x%08X"), te32.th32ThreadID ); -// _tprintf( TEXT("\n Base priority = %d"), te32.tpBasePri ); -// _tprintf( TEXT("\n Delta priority = %d"), te32.tpDeltaPri ); -// _tprintf( TEXT("\n")); - - CloseHandle(threadHandle); + //printError( TEXT("Thread32First") ); // show cause of failure + CloseHandle( hThreadSnap ); // clean the snapshot object + return( FALSE ); } - } while( Thread32Next(hThreadSnap, &te32 ) ); - CloseHandle( hThreadSnap ); - return( TRUE ); + // Now walk the thread list of the system, + // and display information about each thread + // associated with the specified process + //HANDLE threadHandle; + do + { + if( te32.th32OwnerProcessID == dwOwnerPID ) + { + HANDLE threadHandle = OpenThread (PROCESS_QUERY_INFORMATION, FALSE, te32.th32ThreadID); + qDebug() << "OpenThread GetLastError:"<< te32.th32ThreadID << GetLastError() << threadHandle; + ULONG64 cycles = 0; + BOOL ok = QueryThreadCycleTime(threadHandle, &cycles); + qDebug() << "QueryThreadCycleTime GetLastError:" << ok << GetLastError(); + + qDebug() << "Thread cycles:" << te32.th32ThreadID << cycles; + // _tprintf( TEXT("\n\n THREAD ID = 0x%08X"), te32.th32ThreadID ); + // _tprintf( TEXT("\n Base priority = %d"), te32.tpBasePri ); + // _tprintf( TEXT("\n Delta priority = %d"), te32.tpDeltaPri ); + // _tprintf( TEXT("\n")); + + CloseHandle(threadHandle); + } + } while( Thread32Next(hThreadSnap, &te32 ) ); + + CloseHandle( hThreadSnap ); + return( TRUE ); } BOOL RouterWin::EnableDebugPrivilege(VOID) { - HANDLE hToken = NULL; - TOKEN_PRIVILEGES priv; + HANDLE hToken = NULL; + TOKEN_PRIVILEGES priv; - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) - return FALSE; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) + return FALSE; - if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) - return FALSE; + if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) + return FALSE; - priv.PrivilegeCount = 1; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - return AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof(priv), NULL, NULL); + return AdjustTokenPrivileges(hToken, FALSE, &priv, sizeof(priv), NULL, NULL); } BOOL RouterWin::InitNtFunctions(VOID) { - HMODULE hModule; + HMODULE hModule; - hModule = GetModuleHandleW(L"ntdll.dll"); - if (hModule == NULL) - return FALSE; + hModule = GetModuleHandleW(L"ntdll.dll"); + if (hModule == NULL) + return FALSE; - //NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendThread"); - NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendProcess"); - if (NtSuspendProcess == NULL) - return FALSE; + //NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendThread"); + NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendProcess"); + if (NtSuspendProcess == NULL) + return FALSE; - //NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeThread"); - NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeProcess"); - if (NtResumeProcess == NULL) - return FALSE; + //NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeThread"); + NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeProcess"); + if (NtResumeProcess == NULL) + return FALSE; - return TRUE; + return TRUE; } BOOL RouterWin::SuspendProcess(BOOL fSuspend, DWORD dwProcessId) diff --git a/service/server/router_win.h b/service/server/router_win.h index bf23d208..3a9f1618 100644 --- a/service/server/router_win.h +++ b/service/server/router_win.h @@ -35,6 +35,7 @@ class RouterWin : public QObject public: static RouterWin& Instance(); + int routeAddList(const QString &gw, const QStringList &ips); bool clearSavedRoutes(); int routeDeleteList(const QString &gw, const QStringList &ips); diff --git a/service/server/server.pro b/service/server/server.pro index 2c7097ec..09f93d94 100644 --- a/service/server/server.pro +++ b/service/server/server.pro @@ -24,6 +24,8 @@ SOURCES = \ systemservice.cpp win32 { +include(../killswitch/killswitch.pri) + HEADERS += \ tapcontroller_win.h \ router_win.h From 7bc520819a4356a94725af10e9b7c76958601994 Mon Sep 17 00:00:00 2001 From: Fedotov Anton Date: Thu, 17 Feb 2022 19:31:45 +0300 Subject: [PATCH 2/2] The 'Kill Switch' option (used Mozilla VPN sources) is activated by default --- service/killswitch/windowsfirewall.cpp | 2 +- service/server/main.cpp | 6 +- service/server/router.cpp | 146 ++++++++++++++++++------- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/service/killswitch/windowsfirewall.cpp b/service/killswitch/windowsfirewall.cpp index 9963ab12..0f944829 100644 --- a/service/killswitch/windowsfirewall.cpp +++ b/service/killswitch/windowsfirewall.cpp @@ -140,7 +140,7 @@ bool WindowsFirewall::init() { FWPM_SUBLAYER0 subLayer; memset(&subLayer, 0, sizeof(subLayer)); subLayer.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY; - subLayer.displayData.name = (PWSTR)L"MozillaVPN-SplitTunnel-Sublayer"; + subLayer.displayData.name = (PWSTR)L"AmneziaVPN-SplitTunnel-Sublayer"; subLayer.displayData.description = (PWSTR)L"Filters that enforce a good baseline"; subLayer.weight = 0xFFFF; diff --git a/service/server/main.cpp b/service/server/main.cpp index 7fccbd6e..3a124243 100644 --- a/service/server/main.cpp +++ b/service/server/main.cpp @@ -17,7 +17,7 @@ public: qDebug()<<__FUNCTION__; auto cf = WindowsFirewall::instance(); cf->init(); - //cf->disableKillSwitch(); + cf->disableKillSwitch(); } ~KillSwitch(){ qDebug()<<__FUNCTION__; @@ -25,6 +25,7 @@ public: cf->disableKillSwitch(); } }; +std::unique_ptrks; #endif int runApplication(int argc, char** argv) @@ -35,14 +36,13 @@ int runApplication(int argc, char** argv) return app.exec(); } - int main(int argc, char **argv) { Utils::initializePath(Utils::systemLogPath()); Log::initialize(); #ifdef Q_OS_WIN - KillSwitch ks; + ks = std::make_unique(); #endif if (argc == 2) { diff --git a/service/server/router.cpp b/service/server/router.cpp index 02157f07..826dd7e6 100644 --- a/service/server/router.cpp +++ b/service/server/router.cpp @@ -2,7 +2,7 @@ #ifdef Q_OS_WIN #include "router_win.h" -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) #include "router_mac.h" #elif defined Q_OS_LINUX #include "router_linux.h" @@ -11,77 +11,148 @@ #ifdef Q_OS_WIN #include #include "windowsfirewall.h" -//#include "netadpinfo.h" #include -namespace { +namespace +{ +// TODO:FIXME try to get this constexpr variable from CORE code +constexpr char IKEV2[]{"AmneziaVPN IKEv2"}; +constexpr char WG[]{"AmneziaVPN.WireGuard0"}; +constexpr char OVPN[]{"TAP-Windows Adapter V9"}; -constexpr char IKEV2[] {"AmneziaVPN IKEv2"}; -constexpr char WG[] {"AmneziaVPN.WireGuard0"}; -constexpr char OVPN[] {"TAP-Windows Adapter V9"}; +bool is_eth_adapter_activated(char* ethName) +{ + auto convert_wide_to_ansi = [](const std::wstring& widestring)->std::string{ + auto nchars = WideCharToMultiByte( + CP_ACP, + 0, + widestring.c_str(), + static_cast(widestring.length() + 1), + nullptr, + 0, + nullptr, + nullptr); + std::string converted_string{}; + converted_string.resize(nchars); + WideCharToMultiByte(CP_ACP, + 0, + widestring.c_str(), + -1, + &converted_string[0], + static_cast(widestring.length()), + nullptr, + nullptr); + return converted_string; + }; -bool get_eth_name(const QString &adp_name){ - //char buff[128]{}; - PIP_ADAPTER_INFO pAdapterInfo{}; - pAdapterInfo = (IP_ADAPTER_INFO *) malloc(sizeof(IP_ADAPTER_INFO)); + constexpr ULONG MAX_BUFFER_SIZE = 15000; + ULONG bufferSize = MAX_BUFFER_SIZE; + IP_ADAPTER_ADDRESSES *pAdapterAddresses = (IP_ADAPTER_ADDRESSES *)HeapAlloc(GetProcessHeap(), 0, bufferSize); + ::GetAdaptersAddresses(AF_INET, 0, nullptr, pAdapterAddresses, &bufferSize); + do + { + const auto Descr = convert_wide_to_ansi(pAdapterAddresses->Description); + if (strcmp(ethName, Descr.data()) == 0) //the same + { + if (pAdapterAddresses->OperStatus == IfOperStatusUp){ + return true; + } + } + if (pAdapterAddresses->Next != 0) + { + pAdapterAddresses = pAdapterAddresses->Next; + } + } while (pAdapterAddresses->Next != 0); + //::HeapFree(GetProcessHeap(), 0, pAdapterAddresses); + return false; +} + +bool get_eth_name(const QString &adp_name) +{ + IP_ADAPTER_INFO *pAdapterInfo{nullptr}; + pAdapterInfo = (IP_ADAPTER_INFO *)malloc(sizeof(IP_ADAPTER_INFO)); ULONG buflen = sizeof(IP_ADAPTER_INFO); - if(GetAdaptersInfo(pAdapterInfo, &buflen) == ERROR_BUFFER_OVERFLOW) + if (GetAdaptersInfo(pAdapterInfo, &buflen) == ERROR_BUFFER_OVERFLOW) { free(pAdapterInfo); pAdapterInfo = (IP_ADAPTER_INFO *) malloc(buflen); } - if(GetAdaptersInfo(pAdapterInfo, &buflen) == NO_ERROR) { - PIP_ADAPTER_INFO pAdapter = pAdapterInfo; - while (pAdapter) { - qDebug()<<"Adapter Name: "<< pAdapter->AdapterName; - qDebug()<<"Adapter Desc: "<Description; - if (adp_name == pAdapter->Description) + if (GetAdaptersInfo(pAdapterInfo, &buflen) == NO_ERROR) + { + IP_ADAPTER_INFO *pAdapter{pAdapterInfo}; + if (pAdapter == nullptr) + return false; + while (pAdapter != nullptr) + { + const auto adp_acitvated = is_eth_adapter_activated(pAdapter->Description); + if (adp_name == pAdapter->Description && adp_acitvated){ + qDebug() << "We will work with:"; + qDebug() << "Adapter Name: " << pAdapter->AdapterName; + qDebug() << "Adapter Desc: " << pAdapter->Description; + qDebug() << "Adapter Index: " << pAdapter->Index; + qDebug() << "Adapter activated:"<Next; } } + free(pAdapterInfo); return false; } -void enable_killswitch(){ - auto VPN_LIST = []()->int{ - auto adapterList = QNetworkInterface::allInterfaces(); - for (const auto& adapter : adapterList) { +void enable_killswitch() +{ + auto VPN_LIST = []() -> int + { + const auto adapterList = QNetworkInterface::allInterfaces(); + //qAsConst + for (const auto &adapter : adapterList) + { bool finded{false}; finded = get_eth_name(OVPN); - if ( adapter.humanReadableName().contains(IKEV2) || + if (adapter.humanReadableName().contains(IKEV2) || adapter.humanReadableName().contains(WG) || finded || - adapter.humanReadableName().contains(OVPN) - ) { + adapter.humanReadableName().contains(OVPN)) + { + qDebug() << "Network adapter for 'kill switch' option finded: " << adapter.humanReadableName(); return adapter.index(); } } return -1; }; const auto ¤t_vpn = VPN_LIST(); - if (current_vpn != -1){ - qInfo()<<"KillSwitch activated"; + if (current_vpn != -1) + { + qInfo() << "KillSwitch option activated"; auto cf = WindowsFirewall::instance(); - //cf->enableKillSwitch(current_vpn); - cf->disableKillSwitch(); - }else{ - qCritical().noquote() <<"No any adapters was found"; + cf->enableKillSwitch(current_vpn); + } + else + { + // TODO::FIXME + qCritical().noquote() << "No any adapters was found, it's error"; } } -}// end namespace +} // end namespace #endif +// TODO::FIXME when wireguard is activated, the adapter will alaviable +// after a couple of seconds +#include int Router::routeAddList(const QString &gw, const QStringList &ips) { #ifdef Q_OS_WIN auto value = RouterWin::Instance().routeAddList(gw, ips); + // TODO::FIXME the next sleep is need only for wireguard + std::this_thread::sleep_for(std::chrono::seconds(5)); enable_killswitch(); return value; - //return RouterWin::Instance().routeAddList(gw, ips); -#elif defined (Q_OS_MAC) + // return RouterWin::Instance().routeAddList(gw, ips); +#elif defined(Q_OS_MAC) return RouterMac::Instance().routeAddList(gw, ips); #elif defined Q_OS_LINUX return RouterLinux::Instance().routeAddList(gw, ips); @@ -92,7 +163,7 @@ bool Router::clearSavedRoutes() { #ifdef Q_OS_WIN return RouterWin::Instance().clearSavedRoutes(); -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) return RouterMac::Instance().clearSavedRoutes(); #elif defined Q_OS_LINUX return RouterLinux::Instance().clearSavedRoutes(); @@ -103,7 +174,7 @@ int Router::routeDeleteList(const QString &gw, const QStringList &ips) { #ifdef Q_OS_WIN return RouterWin::Instance().routeDeleteList(gw, ips); -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) return RouterMac::Instance().routeDeleteList(gw, ips); #elif defined Q_OS_LINUX return RouterLinux::Instance().routeDeleteList(gw, ips); @@ -114,7 +185,7 @@ void Router::flushDns() { #ifdef Q_OS_WIN RouterWin::Instance().flushDns(); -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) RouterMac::Instance().flushDns(); #elif defined Q_OS_LINUX RouterLinux::Instance().flushDns(); @@ -125,10 +196,9 @@ void Router::resetIpStack() { #ifdef Q_OS_WIN RouterWin::Instance().resetIpStack(); -#elif defined (Q_OS_MAC) +#elif defined(Q_OS_MAC) // todo fixme #elif defined Q_OS_LINUX // todo fixme #endif } -