feature: fillswitch strict mode (#1333)

* Add allowed DNS list for killswitch

* Windows killswitch strict mode backend part

* Killswitch strict mode for Linux and MacOS

* Windows fixes

* feature: Add Kill Switch settings page with strict mode option

* fix windows build after merge

* Refresh killswitch mode when it toggled

* Use HLM to store strictMode flag

* Some Linux updates

* feat: Enhance VerticalRadioButton with improved styling and disabled states

* Refresh killSwitch state update

* Fix build

* refactor: Modularize header components

* Change kill switch radio button styling

* Fix strict kill switch mode handling

* Refactor: Replace HeaderType with new Types for headers in QML pages

* Remove deprecated HeaderType QML component

* Refresh strict mode killswitch after global toggle change

* Implement model, controller and UI for killswitch dns exceptions

* Connect backend part and UI

* Change label text to DNS exceptions

* Remove HeaderType from PageSettingsApiDevices

* Some pretty fixes

* Fix problem with definition sequence of PageSettingsKillSwitchExceptions.pml elements

* Add exclusion method for Windows firewall

* Change ubuntu version in deploy script

* Update ubuntu version in GH actions

* Add confirmation popup for strict killswitch mode

* Add qt standard path for build script

* Add method to killswitch for expanding strickt mode exceptions list and fix allowTrafficTo() for Windows. Also Added cache in KillSwitch class for exceptions

* Add insertion of gateway address to strict killswitch exceptions

* Review fixes

* buildfix and naming

---------

Co-authored-by: aiamnezia <ai@amnezia.org>
This commit is contained in:
Mykola Baibuz 2025-05-02 23:54:36 -07:00 committed by GitHub
parent 5bd88ac2e9
commit f6d7552b58
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
88 changed files with 1718 additions and 418 deletions

View file

@ -455,9 +455,6 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
void LinuxFirewall::updateAllowNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));

View file

@ -17,6 +17,8 @@
#include "leakdetector.h"
#include "logger.h"
#include "killswitch.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
@ -182,7 +184,7 @@ bool WireguardUtilsLinux::deleteInterface() {
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
LinuxFirewall::uninstall();
KillSwitch::instance()->disableKillSwitch();
return true;
}

View file

@ -16,6 +16,8 @@
#include "leakdetector.h"
#include "logger.h"
#include "killswitch.h"
constexpr const int WG_TUN_PROC_TIMEOUT = 5000;
constexpr const char* WG_RUNTIME_DIR = "/var/run/amneziawg";
@ -180,7 +182,7 @@ bool WireguardUtilsMacos::deleteInterface() {
QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name"));
// double-check + ensure our firewall is installed and enabled
MacOSFirewall::uninstall();
KillSwitch::instance()->disableKillSwitch();
return true;
}

View file

@ -29,6 +29,8 @@
#include "logger.h"
#include "platforms/windows/windowsutils.h"
#include "killswitch.h"
#define IPV6_ADDRESS_SIZE 16
// ID for the Firewall Sublayer
@ -180,16 +182,29 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
} \
}
logger.info() << "Enabling firewall Using Adapter:" << vpnAdapterIndex;
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"));
"Allow usage of VPN Adapter"));
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic"));
FW_OK(allowHyperVTraffic(MED_WEIGHT, "Allow Hyper-V 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"));
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
"Allow Loopback traffic on device %1"));
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
return true;
@ -226,6 +241,37 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
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), LOW_WEIGHT + 1, "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);
@ -262,12 +308,20 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
}
}
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)) {
"Allow Ecxlude route", config.m_serverPublicKey)) {
return false;
}
}
@ -313,37 +367,41 @@ bool WindowsFirewall::disablePeerTraffic(const QString& pubkey) {
}
bool WindowsFirewall::disableKillSwitch() {
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
auto cleanup = qScopeGuard([&] {
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) {
FwpmTransactionAbort0(m_sessionHandle);
logger.error() << "FwpmTransactionBegin0 failed. Return value:.\n"
<< result;
return false;
}
});
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 : m_peerRules.values()) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
for (const auto& filterID : qAsConst(m_activeRules)) {
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;
// 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,

View file

@ -43,6 +43,8 @@ class WindowsFirewall final : public QObject {
bool enablePeerTraffic(const InterfaceConfig& config);
bool disablePeerTraffic(const QString& pubkey);
bool disableKillSwitch();
bool allowAllTraffic();
bool allowTrafficRange(const QStringList& ranges);
private:
static bool initSublayer();