amnezia-client/client/platforms/windows/daemon/windowsfirewall.cpp
Mykola Baibuz b341934863
fix: allow secondary DNS usage when AmneziaDNS is disabled (#1583)
* Allow secondary DNS usage when AmneziaDNS is disabled

* Don't setup secondary DNS for OpenVPN with AmneziaDNS

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
2025-07-02 10:16:58 +08:00

1012 lines
34 KiB
C++

/* 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 <comdef.h>
#include <fwpmu.h>
#include <guiddef.h>
#include <initguid.h>
#include <netfw.h>
#include <qaccessible.h>
#include <qassert.h>
#include <stdio.h>
#include <windows.h>
#include <Ws2tcpip.h>
#include "winsock.h"
#include <QApplication>
#include <QFileInfo>
#include <QHostAddress>
#include <QNetworkInterface>
#include <QObject>
#include <QScopeGuard>
#include <QtEndian>
#include "ipaddress.h"
#include "leakdetector.h"
#include "logger.h"
#include "platforms/windows/windowsutils.h"
#include "killswitch.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("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::create(QObject* parent) {
if (s_instance != nullptr) {
// Only one instance of the firewall is allowed
// Q_ASSERT(false);
return s_instance;
}
HANDLE engineHandle = nullptr;
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;
logger.debug() << "Opening the filter engine.";
result = FwpmEngineOpen0(nullptr, RPC_C_AUTHN_WINNT, nullptr, &session,
&engineHandle);
if (result != ERROR_SUCCESS) {
WindowsUtils::windowsLog("FwpmEngineOpen0 failed");
return nullptr;
}
logger.debug() << "Filter engine opened successfully.";
if (!initSublayer()) {
return nullptr;
}
s_instance = new WindowsFirewall(engineHandle, parent);
return s_instance;
}
WindowsFirewall::WindowsFirewall(HANDLE session, QObject* parent)
: QObject(parent), m_sessionHandle(session) {
MZ_COUNT_CTOR(WindowsFirewall);
}
WindowsFirewall::~WindowsFirewall() {
MZ_COUNT_DTOR(WindowsFirewall);
if (m_sessionHandle != INVALID_HANDLE_VALUE) {
CloseHandle(m_sessionHandle);
}
}
// static
bool WindowsFirewall::initSublayer() {
// If we were not able to aquire a handle, this will fail anyway.
// We need to open up another handle because of wfp rules:
// If a wfp resource was created with SESSION_DYNAMIC,
// 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));
logger.debug() << "Opening the filter engine";
result = FwpmEngineOpen0(NULL, RPC_C_AUTHN_WINNT, NULL, &session, &wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmEngineOpen0 failed. Return value:.\n" << result;
return false;
}
auto cleanup = qScopeGuard([&] { FwpmEngineClose0(wfp); });
// Check if the Layer Already Exists
FWPM_SUBLAYER0* maybeLayer;
result = FwpmSubLayerGetByKey0(wfp, &ST_FW_WINFW_BASELINE_SUBLAYER_KEY,
&maybeLayer);
if (result == ERROR_SUCCESS) {
logger.debug() << "The Sublayer Already Exists!";
FwpmFreeMemory0((void**)&maybeLayer);
return true;
}
// Step 1: Start Transaction
result = FwpmTransactionBegin(wfp, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
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"Amnezia-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) {
logger.error() << "FwpmSubLayerAdd0 failed. Return value:.\n" << result;
return false;
}
// Step 4: Commit!
result = FwpmTransactionCommit0(wfp);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
logger.debug() << "Initialised Sublayer";
return true;
}
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
// Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not.
#define FW_OK(rule) \
{ \
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) { \
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n" \
<< result; \
return false; \
} \
}
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
if (vpnAdapterIndex < 0)
{
IPAddress allv4("0.0.0.0/0");
if (!blockTrafficTo(allv4, MED_WEIGHT,
"Block Internet", "killswitch")) {
return false;
}
IPAddress allv6("::/0");
if (!blockTrafficTo(allv6, MED_WEIGHT,
"Block Internet", "killswitch")) {
return false;
}
} else
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter"));
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic"));
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
"Allow all for AmneziaVPN.exe"));
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS"));
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
"Allow Loopback traffic on device %1"));
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
return true;
#undef FW_OK
}
// Allow unprotected traffic sent to the following local address ranges.
bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
disableKillSwitch();
return false;
}
auto cleanup = qScopeGuard([&] {
FwpmTransactionAbort0(m_sessionHandle);
disableKillSwitch();
});
// Blocking unprotected traffic
for (const IPAddress& prefix : ranges) {
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
return false;
}
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
return false;
}
cleanup.dismiss();
return true;
}
// Allow unprotected traffic sent to the following address ranges.
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
disableKillSwitch();
return false;
}
auto cleanup = qScopeGuard([&] {
FwpmTransactionAbort0(m_sessionHandle);
disableKillSwitch();
});
for (const QString& addr : ranges) {
logger.debug() << "Allow killswitch exclude: " << addr;
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
return false;
}
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
return false;
}
cleanup.dismiss();
return true;
}
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
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_primaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 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_primaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) {
return false;
}
}
}
if (!config.m_secondaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 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_secondaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) {
return false;
}
}
}
for (const QString& dns : config.m_allowedDnsServers) {
logger.debug() << "Allow DNS: " << dns;
if (!allowTrafficTo(QHostAddress(dns), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) {
return false;
}
}
if (!config.m_excludedAddresses.empty()) {
for (const QString& i : config.m_excludedAddresses) {
logger.debug() << "excludedAddresses range: " << i;
if (!allowTrafficTo(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) {
return false;
}
}
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed with error:" << result;
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) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
logger.info() << "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) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
return true;
}
bool WindowsFirewall::disableKillSwitch() {
return KillSwitch::instance()->disableKillSwitch();
}
bool WindowsFirewall::allowAllTraffic() {
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
auto cleanup = qScopeGuard([&] {
if (result != ERROR_SUCCESS) {
FwpmTransactionAbort0(m_sessionHandle);
}
});
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
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) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:.\n"
<< result;
return false;
}
m_peerRules.clear();
m_activeRules.clear();
logger.debug() << "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) {
WindowsUtils::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;
// 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 IPAddress& addr, int weight,
const QString& title,
const QString& peer) {
GUID layerKeyOut;
GUID layerKeyIn;
if (addr.type() == QAbstractSocket::IPv4Protocol) {
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
} else {
layerKeyOut = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
layerKeyIn = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
}
// Match the IP address range.
FWPM_FILTER_CONDITION0 cond[1] = {};
FWP_RANGE0 ipRange;
QByteArray lowIpV6Buffer;
QByteArray highIpV6Buffer;
importAddress(addr.address(), ipRange.valueLow, &lowIpV6Buffer);
importAddress(addr.broadcastAddress(), ipRange.valueHigh, &highIpV6Buffer);
cond[0].fieldKey = FWPM_CONDITION_IP_REMOTE_ADDRESS;
cond[0].matchType = FWP_MATCH_RANGE;
cond[0].conditionValue.type = FWP_RANGE_TYPE;
cond[0].conditionValue.rangeValue = &ipRange;
// Assemble the Filter base
FWPM_FILTER0 filter;
memset(&filter, 0, sizeof(filter));
filter.action.type = FWP_ACTION_PERMIT;
filter.weight.type = FWP_UINT8;
filter.weight.uint8 = weight;
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.numFilterConditions = 1;
filter.filterCondition = cond;
// Send the filters down to the firewall.
QString description = "Permit traffic %1 " + addr.toString();
filter.layerKey = layerKeyOut;
if (!enableFilter(&filter, title, description.arg("to"), peer)) {
return false;
}
filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
return false;
}
return true;
}
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
int weight, const QString& title,
const QString& peer) {
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] = {};
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)) {
logger.info() << "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) {
WindowsUtils::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) {
logger.error() << "Failed to enable filter: " << title << " "
<< description;
return false;
}
logger.info() << "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;
}