The couple objects were added from Mozilla VPN for Killswitch functionality

This commit is contained in:
Fedotov Anton 2022-02-02 00:59:45 +03:00
parent 8f23970ccc
commit 858537df1f
15 changed files with 1794 additions and 81 deletions

View file

@ -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 <QtMath>
//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<QHostAddress, int> 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> IPAddress::subnets() const {
QList<IPAddress> 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> IPAddress::excludeAddresses(
const QList<IPAddress>& sourceList, const QList<IPAddress>& excludeList) {
QList<IPAddress> results = sourceList;
for (const IPAddress& exclude : excludeList) {
QList<IPAddress> newResults;
for (const IPAddress& ip : results) {
if (ip.overlaps(exclude)) {
QList<IPAddress> range = ip.excludeAddresses(exclude);
newResults.append(range);
} else {
newResults.append(ip);
}
}
results = newResults;
}
return results;
}
QList<IPAddress> IPAddress::excludeAddresses(const IPAddress& ip) const {
QList<IPAddress> sn = subnets();
Q_ASSERT(sn.length() >= 2);
QList<IPAddress> 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;
}

View file

@ -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 <QHostAddress>
class IPAddress final {
public:
static QList<IPAddress> excludeAddresses(const QList<IPAddress>& sourceList,
const QList<IPAddress>& 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<IPAddress> subnets() const;
QList<IPAddress> excludeAddresses(const IPAddress& ip) const;
QAbstractSocket::NetworkLayerProtocol type() const;
private:
QHostAddress m_address;
int m_prefixLength;
};
#endif // IPADDRESS_H

View file

@ -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

View file

@ -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 <QHash>
#include <QMutex>
#include <QObject>
#include <QTextStream>
#ifdef MVPN_DEBUG
static QMutex s_leakDetector;
QHash<QString, QHash<void*, uint32_t>> 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<void*, uint32_t>());
}
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<void*, uint32_t>& leak = s_leaks[type];
Q_ASSERT(leak.contains(ptr));
Q_ASSERT(leak[ptr] == size);
leak.remove(ptr);
}
#endif

View file

@ -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 <QObject>
#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

View file

@ -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 <QDir>
#include <QtEndian>
#include <QHostAddress>
#include <QSettings>
#include <QStandardPaths>
#include <QSysInfo>
#include <QNetworkInterface>
#include <Windows.h>
#include <iphlpapi.h>
#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();
}

View file

@ -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 <QString>
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

View file

@ -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 <QApplication>
#include <QObject>
#include <QFileInfo>
#include <QNetworkInterface>
#include <QScopeGuard>
#include <QHostAddress>
#include <QtEndian>
#include <windows.h>
#include <fwpmu.h>
#include <stdio.h>
#include <comdef.h>
#include <netfw.h>
//#include "winsock.h"
#include <initguid.h>
#include <guiddef.h>
#include <qaccessible.h>
#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 "<<GetLastError();
//WindowsCommons::windowsLog("FwpmEngineOpen0 failed");
return;
}
qDebug() << "Filter engine opened successfully.";
m_sessionHandle = engineHandle;
//auto ret = WindowsCommons::VPNAdapterIndex();
}
WindowsFirewall::~WindowsFirewall() {
MVPN_COUNT_DTOR(WindowsFirewall);
if (m_sessionHandle != INVALID_HANDLE_VALUE) {
CloseHandle(m_sessionHandle);
}
}
bool WindowsFirewall::init() {
if (m_init) {
qDebug() << "Alread initialised FW_WFP layer";
return true;
}
if (m_sessionHandle == INVALID_HANDLE_VALUE) {
qDebug() << "Cant Init Sublayer with invalid wfp handle";
return false;
}
// 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,
// the session exlusively owns the resource, meaning the driver can't add
// filters to the sublayer. So let's have non dynamic session only for the
// sublayer creation. This means the Layer exists until the next Reboot.
DWORD result = ERROR_SUCCESS;
HANDLE wfp = INVALID_HANDLE_VALUE;
FWPM_SESSION0 session;
memset(&session, 0, sizeof(session));
qDebug()<< "Opening the filter engine";
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &wfp);
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmEngineOpen0 failed. Return value:.\n" << result<<" "<<GetLastError();
return false;
}
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
// Check if the Layer Already Exists
FWPM_SUBLAYER0* maybeLayer{nullptr};
result = FwpmSubLayerGetByKey0(wfp, &ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
&maybeLayer);
if (result == ERROR_SUCCESS) {
qDebug() << "The Sublayer Already Exists!";
FwpmFreeMemory0((void**)&maybeLayer);
return true;
}
// Step 1: Start Transaction
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
// Step 3: Add Sublayer
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.description =
(PWSTR)L"Filters that enforce a good baseline";
subLayer.weight = 0xFFFF;
result = FwpmSubLayerAdd0(wfp, &subLayer, NULL);
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result<<" "<<GetLastError();
return false;
}
// Step 4: Commit!
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
qDebug() << "Initialised Sublayer";
m_init = true;
return true;
}
bool WindowsFirewall::enableKillSwitch(int vpnAdapterIndex) {
// Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not.
#define FW_OK(rule) \
{ \
auto result = FwpmTransactionBegin(m_sessionHandle, NULL); \
if (result != ERROR_SUCCESS) { \
disableKillSwitch(); \
return false; \
} \
if (!rule) { \
FwpmTransactionAbort0(m_sessionHandle); \
disableKillSwitch(); \
return false; \
} \
result = FwpmTransactionCommit0(m_sessionHandle); \
if (result != ERROR_SUCCESS) { \
qDebug()<<"FwpmTransactionCommit0 failed. Return value:.\n" \
<< result; \
return false; \
} \
}
qDebug() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter"));
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-service.exe"));
// disable by 020221 //FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
FW_OK(allowLoopbackTraffic(MED_WEIGHT, "Allow Loopback traffic on device %1"));
qDebug() << "Killswitch on! Rules:" << m_activeRules.length();
return true;
#undef FW_OK
}
/*
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
disableKillSwitch();
return false;
}
auto cleanup = qScopeGuard([&] {
FwpmTransactionAbort0(m_sessionHandle);
disableKillSwitch();
});
// Build the firewall rules for this peer.
logger.info() << "Enabling traffic for peer" << config.m_serverPublicKey;
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
"Block Internet", config.m_serverPublicKey)) {
return false;
}
if (!config.m_dnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_dnsServer), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) {
return false;
}
// In some cases, we might configure a 2nd DNS server for IPv6, however
// this should probably be cleaned up by converting m_dnsServer into
// a QStringList instead.
if (config.m_dnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) {
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::disablePeerTraffic(const QString& pubkey) {
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
auto cleanup = qScopeGuard([&] {
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(m_sessionHandle);
}
});
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
qDebug() << "Disabling traffic for peer" << pubkey;
for (const auto& filterID : m_peerRules.values(pubkey)) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
m_peerRules.remove(pubkey, filterID);
}
// Commit!
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
qDebug()<< "FwpmTransactionCommit0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
return true;
}
bool WindowsFirewall::disableKillSwitch() {
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
auto cleanup = qScopeGuard([&] {
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(m_sessionHandle);
}
});
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
for (const auto& filterID : m_peerRules.values()) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
for (const auto& filterID : qAsConst(m_activeRules)) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
// Commit!
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
qDebug() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result<<" "<<GetLastError();
return false;
}
m_peerRules.clear();
m_activeRules.clear();
qDebug() << "Firewall Disabled!";
return true;
}
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
int weight,
const QString& title) {
DWORD result = ERROR_SUCCESS;
Q_ASSERT(weight <= 15);
// Get the AppID for the Executable;
QString appName = QFileInfo(exePath).baseName();
std::wstring wstr = exePath.toStdWString();
PCWSTR appPath = wstr.c_str();
FWP_BYTE_BLOB* appID = NULL;
result = FwpmGetAppIdFromFileName0(appPath, &appID);
if (result != ERROR_SUCCESS) {
qDebug()<<"FwpmGetAppIdFromFileName0 failure:"<< GetLastError();
// WindowsCommons::windowsLog("FwpmGetAppIdFromFileName0 failure");
return false;
}
// Condition: Request must come from the .exe
FWPM_FILTER_CONDITION0 conds;
conds.fieldKey = FWPM_CONDITION_ALE_APP_ID;
conds.matchType = FWP_MATCH_EQUAL;
conds.conditionValue.type = FWP_BYTE_BLOB_TYPE;
conds.conditionValue.byteBlob = appID;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = &conds;
filter.numFilterConditions = 1;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; // Make this decision
// only blockable by veto
// Build and add the Filters
// #1 Permit outbound IPv4 traffic.
{
QString desc("Permit (out) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, desc)) {
return false;
}
}
// #2 Permit inbound IPv4 traffic.
{
QString desc("Permit (in) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, desc)) {
return false;
}
}
return true;
}
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title) {
FWPM_FILTER_CONDITION0 conds;
memset(&conds, 0, sizeof(conds));
// Condition: Request must be targeting the TUN interface
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
conds.matchType = FWP_MATCH_EQUAL;
conds.conditionValue.type = FWP_UINT32;
conds.conditionValue.uint32 = networkAdapter;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = &conds;
filter.numFilterConditions = 1;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
QString description("Allow %0 traffic on Adapter %1");
// #1 Permit outbound IPv4 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) {
return false;
}
// #2 Permit inbound IPv4 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title,
description.arg("in").arg(networkAdapter))) {
return false;
}
// #3 Permit outbound IPv6 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) {
return false;
}
// #4 Permit inbound IPv6 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title,
description.arg("in").arg(networkAdapter))) {
return false;
}
return true;
}
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
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;
GUID layerIn = isIPv4 ? FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4
: FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
quint32_be ipBigEndian;
quint32 ip = targetIP.toIPv4Address();
qToBigEndian(ip, &ipBigEndian);
// Allow Traffic to IP with PORT using any protocol
FWPM_FILTER_CONDITION0 conds[4];
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.uint16 = (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;
conds[3].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
conds[3].matchType = FWP_MATCH_EQUAL;
QByteArray buffer;
// will hold v6 Addess bytes if present
importAddress(targetIP, conds[3].conditionValue, &buffer);
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = conds;
filter.numFilterConditions = 4;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.flags = FWPM_FILTER_FLAG_CLEAR_ACTION_RIGHT; // Hard Permit!
QString description("Permit traffic %1 %2 on port %3");
filter.layerKey = layerOut;
if (!enableFilter(&filter, title,
description.arg("to").arg(targetIP.toString()).arg(port),
peer)) {
return false;
}
filter.layerKey = layerIn;
if (!enableFilter(&filter, title,
description.arg("from").arg(targetIP.toString()).arg(port),
peer)) {
return false;
}
return true;
}
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
// Allow outbound DHCPv4
{
FWPM_FILTER_CONDITION0 conds[4];
// Condition: Request must be targeting the TUN interface
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_LOCAL_PORT;
conds[1].matchType = FWP_MATCH_EQUAL;
conds[1].conditionValue.type = FWP_UINT16;
conds[1].conditionValue.uint16 = (68);
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
conds[2].matchType = FWP_MATCH_EQUAL;
conds[2].conditionValue.type = FWP_UINT16;
conds[2].conditionValue.uint16 = 67;
conds[3].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
conds[3].matchType = FWP_MATCH_EQUAL;
conds[3].conditionValue.type = FWP_UINT32;
conds[3].conditionValue.uint32 = (0xffffffff);
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = conds;
filter.numFilterConditions = 4;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) {
return false;
}
}
// Allow inbound DHCPv4
{
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_LOCAL_PORT;
conds[1].matchType = FWP_MATCH_EQUAL;
conds[1].conditionValue.type = FWP_UINT16;
conds[1].conditionValue.uint16 = (68);
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
conds[2].matchType = FWP_MATCH_EQUAL;
conds[2].conditionValue.type = FWP_UINT16;
conds[2].conditionValue.uint16 = 67;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = conds;
filter.numFilterConditions = 3;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, "Allow inbound DHCP")) {
return false;
}
}
// Allow outbound DHCPv6
{
FWPM_FILTER_CONDITION0 conds[3];
// Condition: Request must be targeting the TUN interface
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_LOCAL_PORT;
conds[1].matchType = FWP_MATCH_EQUAL;
conds[1].conditionValue.type = FWP_UINT16;
conds[1].conditionValue.uint16 = (68);
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
conds[2].matchType = FWP_MATCH_EQUAL;
conds[2].conditionValue.type = FWP_UINT16;
conds[2].conditionValue.uint16 = 67;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = conds;
filter.numFilterConditions = 3;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) {
return false;
}
}
// Allow inbound DHCPv6
{
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_LOCAL_PORT;
conds[1].matchType = FWP_MATCH_EQUAL;
conds[1].conditionValue.type = FWP_UINT16;
conds[1].conditionValue.uint16 = (68);
conds[2].fieldKey = FWPM_CONDITION_IP_REMOTE_PORT;
conds[2].matchType = FWP_MATCH_EQUAL;
conds[2].conditionValue.type = FWP_UINT16;
conds[2].conditionValue.uint16 = 67;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = conds;
filter.numFilterConditions = 3;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title, "Allow inbound DHCPv6")) {
return false;
}
}
return true;
}
// Allows the internal Hyper-V Switches to work.
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
FWPM_FILTER_CONDITION0 cond;
// Condition: Request must be targeting the TUN interface
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
cond.matchType = FWP_MATCH_EQUAL;
cond.conditionValue.type = FWP_UINT32;
cond.conditionValue.uint32 = FWP_CONDITION_L2_IS_VM2VM;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.filterCondition = &cond;
filter.numFilterConditions = 1;
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
// #1 Permit Hyper-V => 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<IPAddress>& 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 "<<result<<" GetLastError:"<< GetLastError();
return false;
}
qDebug() << "Filter added: " << title << ":" << description;
if (peer.isEmpty()) {
m_activeRules.append(filterID);
} else {
m_peerRules.insert(peer, filterID);
}
return true;
}
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
const QString& title) {
QList<QNetworkInterface> 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;
}

View file

@ -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 <windows.h>
#include <fwpmu.h>
#include <QString>
#include <QObject>
#include <QHostAddress>
#include <QByteArray>
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<uint64_t> m_activeRules;
QMultiMap<QString, uint64_t> m_peerRules;
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
const QString& title);
bool blockTrafficTo(const QList<IPAddress>& 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

View file

@ -10,6 +10,7 @@
#include "router.h" #include "router.h"
#ifdef Q_OS_WIN #ifdef Q_OS_WIN
#include "windowsfirewall.h"
#include "tapcontroller_win.h" #include "tapcontroller_win.h"
#endif #endif
@ -38,6 +39,9 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
LocalServer::~LocalServer() LocalServer::~LocalServer()
{ {
auto cf = WindowsFirewall::instance();
cf->disableKillSwitch();
qDebug()<<"KillSwitch deactivated";
qDebug() << "Local server stopped"; qDebug() << "Local server stopped";
} }

View file

@ -1,4 +1,5 @@
#include <QDir> #include <QDir>
#include <QNetworkInterface>
#include "defines.h" #include "defines.h"
#include "localserver.h" #include "localserver.h"
@ -6,6 +7,25 @@
#include "systemservice.h" #include "systemservice.h"
#include "utils.h" #include "utils.h"
#ifdef Q_OS_WIN
#include "windowsfirewall.h"
#include <memory>
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) int runApplication(int argc, char** argv)
{ {
@ -19,9 +39,12 @@ int runApplication(int argc, char** argv)
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
Utils::initializePath(Utils::systemLogPath()); Utils::initializePath(Utils::systemLogPath());
Log::initialize(); Log::initialize();
#ifdef Q_OS_WIN
KillSwitch ks;
#endif
if (argc == 2) { if (argc == 2) {
qInfo() << "Started as console application"; qInfo() << "Started as console application";
return runApplication(argc, argv); return runApplication(argc, argv);
@ -32,7 +55,7 @@ int main(int argc, char **argv)
SystemService systemService(argc, argv); SystemService systemService(argc, argv);
return systemService.exec(); return systemService.exec();
#else #else
return runApplication(argc, argv); return runApplication(argc, argv);
#endif #endif
} }

View file

@ -8,11 +8,79 @@
#include "router_linux.h" #include "router_linux.h"
#endif #endif
#ifdef Q_OS_WIN
#include <QNetworkInterface>
#include "windowsfirewall.h"
//#include "netadpinfo.h"
#include <cstring>
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: "<<pAdapter->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 &current_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) int Router::routeAddList(const QString &gw, const QStringList &ips)
{ {
#ifdef Q_OS_WIN #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) #elif defined (Q_OS_MAC)
return RouterMac::Instance().routeAddList(gw, ips); return RouterMac::Instance().routeAddList(gw, ips);
#elif defined Q_OS_LINUX #elif defined Q_OS_LINUX

View file

@ -27,13 +27,12 @@ RouterWin &RouterWin::Instance()
int RouterWin::routeAddList(const QString &gw, const QStringList &ips) int RouterWin::routeAddList(const QString &gw, const QStringList &ips)
{ {
// qDebug().noquote() << QString("ROUTE ADD List: IPs size:%1, GW: %2") // qDebug().noquote() << QString("ROUTE ADD List: IPs size:%1, GW: %2")
// .arg(ips.size()) // .arg(ips.size())
// .arg(gw); // .arg(gw);
// qDebug().noquote() << QString("ROUTE ADD List: IPs:\n%1")
// .arg(ips.join("\n"));
// qDebug().noquote() << QString("ROUTE ADD List: IPs:\n%1")
// .arg(ips.join("\n"));
if (!Utils::checkIPv4Format(gw)) { if (!Utils::checkIPv4Format(gw)) {
qCritical().noquote() << "Trying to add invalid route, gw: " << 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) int RouterWin::routeDeleteList(const QString &gw, const QStringList &ips)
{ {
// qDebug().noquote() << QString("ROUTE DELETE List: IPs size:%1, GW: %2") // qDebug().noquote() << QString("ROUTE DELETE List: IPs size:%1, GW: %2")
// .arg(ips.size()) // .arg(ips.size())
// .arg(gw); // .arg(gw);
// qDebug().noquote() << QString("ROUTE DELETE List: IPs:\n%1") // qDebug().noquote() << QString("ROUTE DELETE List: IPs:\n%1")
// .arg(ips.join("\n")); // .arg(ips.join("\n"));
PMIB_IPFORWARDTABLE pIpForwardTable = NULL; PMIB_IPFORWARDTABLE pIpForwardTable = NULL;
DWORD dwSize = 0; DWORD dwSize = 0;
@ -291,11 +290,11 @@ void RouterWin::flushDns()
void RouterWin::resetIpStack() void RouterWin::resetIpStack()
{ {
// { // {
// QProcess p; // QProcess p;
// QString command = QString("ipconfig /release"); // QString command = QString("ipconfig /release");
// p.start(command); // p.start(command);
// } // }
{ {
QProcess p; QProcess p;
QString command = QString("netsh int ip reset"); QString command = QString("netsh int ip reset");
@ -346,90 +345,90 @@ DWORD RouterWin::GetServicePid(LPCWSTR serviceName)
BOOL RouterWin::ListProcessThreads( DWORD dwOwnerPID ) BOOL RouterWin::ListProcessThreads( DWORD dwOwnerPID )
{ {
HANDLE hThreadSnap = INVALID_HANDLE_VALUE; HANDLE hThreadSnap = INVALID_HANDLE_VALUE;
THREADENTRY32 te32; THREADENTRY32 te32;
// Take a snapshot of all running threads // Take a snapshot of all running threads
hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 );
if( hThreadSnap == INVALID_HANDLE_VALUE ) if( hThreadSnap == INVALID_HANDLE_VALUE )
return( FALSE ); return( FALSE );
// Fill in the size of the structure before using it. // Fill in the size of the structure before using it.
te32.dwSize = sizeof(THREADENTRY32); te32.dwSize = sizeof(THREADENTRY32);
// Retrieve information about the first thread, // Retrieve information about the first thread,
// and exit if unsuccessful // and exit if unsuccessful
if( !Thread32First( hThreadSnap, &te32 ) ) 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 )
{ {
HANDLE threadHandle = OpenThread (PROCESS_QUERY_INFORMATION, FALSE, te32.th32ThreadID); //printError( TEXT("Thread32First") ); // show cause of failure
qDebug() << "OpenThread GetLastError:"<< te32.th32ThreadID << GetLastError() << threadHandle; CloseHandle( hThreadSnap ); // clean the snapshot object
ULONG64 cycles = 0; return( FALSE );
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 ); // Now walk the thread list of the system,
return( TRUE ); // 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) BOOL RouterWin::EnableDebugPrivilege(VOID)
{ {
HANDLE hToken = NULL; HANDLE hToken = NULL;
TOKEN_PRIVILEGES priv; TOKEN_PRIVILEGES priv;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken))
return FALSE; return FALSE;
if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid)) if (!LookupPrivilegeValueW(NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid))
return FALSE; return FALSE;
priv.PrivilegeCount = 1; priv.PrivilegeCount = 1;
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 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) BOOL RouterWin::InitNtFunctions(VOID)
{ {
HMODULE hModule; HMODULE hModule;
hModule = GetModuleHandleW(L"ntdll.dll"); hModule = GetModuleHandleW(L"ntdll.dll");
if (hModule == NULL) if (hModule == NULL)
return FALSE; return FALSE;
//NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendThread"); //NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendThread");
NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendProcess"); NtSuspendProcess = (decltype(NtSuspendProcess))GetProcAddress(hModule, "NtSuspendProcess");
if (NtSuspendProcess == NULL) if (NtSuspendProcess == NULL)
return FALSE; return FALSE;
//NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeThread"); //NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeThread");
NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeProcess"); NtResumeProcess = (decltype(NtResumeProcess))GetProcAddress(hModule, "NtResumeProcess");
if (NtResumeProcess == NULL) if (NtResumeProcess == NULL)
return FALSE; return FALSE;
return TRUE; return TRUE;
} }
BOOL RouterWin::SuspendProcess(BOOL fSuspend, DWORD dwProcessId) BOOL RouterWin::SuspendProcess(BOOL fSuspend, DWORD dwProcessId)

View file

@ -35,6 +35,7 @@ class RouterWin : public QObject
public: public:
static RouterWin& Instance(); static RouterWin& Instance();
int routeAddList(const QString &gw, const QStringList &ips); int routeAddList(const QString &gw, const QStringList &ips);
bool clearSavedRoutes(); bool clearSavedRoutes();
int routeDeleteList(const QString &gw, const QStringList &ips); int routeDeleteList(const QString &gw, const QStringList &ips);

View file

@ -24,6 +24,8 @@ SOURCES = \
systemservice.cpp systemservice.cpp
win32 { win32 {
include(../killswitch/killswitch.pri)
HEADERS += \ HEADERS += \
tapcontroller_win.h \ tapcontroller_win.h \
router_win.h router_win.h