feat: per-tunnel Windows firewall for seamless WG switch

This commit is contained in:
cd-amn
2026-05-26 17:08:43 +00:00
parent eb42ce8fef
commit 9a1e380ffb
13 changed files with 293 additions and 183 deletions
@@ -79,7 +79,7 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
QString ip = NetworkUtilities::getIPAddress(host);
if (!ip.isEmpty()) {
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList { ip });
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QString(), QStringList { ip });
if (!reply.waitForFinished(1000) || !reply.returnValue())
qWarning() << "GatewayController::prepareRequest(): Failed to execute remote addKillSwitchAllowedRange call";
});
+5
View File
@@ -41,6 +41,10 @@ public:
State state() const { return m_state; }
QSharedPointer<VpnProtocol> protocol() const { return m_protocol; }
const QString& handoverIfname() const { return m_handoverIfname; }
void setHandoverIfname(const QString& ifname) { m_handoverIfname = ifname; }
void clearHandoverIfname() { m_handoverIfname.clear(); }
virtual void prepare();
virtual void commit();
virtual void deactivate();
@@ -61,6 +65,7 @@ protected:
QString m_ifname;
QString m_remoteAddress;
QString m_handoverIfname;
amnezia::DockerContainer m_container;
QJsonObject m_config;
QSharedPointer<VpnProtocol> m_protocol;
+58 -26
View File
@@ -31,37 +31,27 @@ void VpnTrafficGuard::setConfig(const QJsonObject &config)
m_config = config;
}
bool VpnTrafficGuard::allowEndpoint(const QString &remoteAddress)
bool VpnTrafficGuard::allowEndpoint(const QString &remoteAddress, const QString &ifname)
{
#ifdef AMNEZIA_DESKTOP
if (remoteAddress.isEmpty()) {
return false;
}
if (!m_allowedEndpoints.contains(remoteAddress)) {
m_allowedEndpoints.append(remoteAddress);
if (m_allowedEndpoints.contains(remoteAddress)) {
return true;
}
m_allowedEndpoints.append(remoteAddress);
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(QStringList(remoteAddress));
QRemoteObjectPendingReply<bool> reply = iface->addKillSwitchAllowedRange(ifname, QStringList(remoteAddress));
return reply.waitForFinished(1000) && reply.returnValue();
});
#else
Q_UNUSED(remoteAddress)
Q_UNUSED(ifname)
return true;
#endif
}
void VpnTrafficGuard::revokeEndpoint(const QString &remoteAddress)
{
#ifdef AMNEZIA_DESKTOP
m_allowedEndpoints.removeAll(remoteAddress);
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
iface->resetKillSwitchAllowedRange(m_allowedEndpoints);
});
#else
Q_UNUSED(remoteAddress)
#endif
}
void VpnTrafficGuard::setupRoutes(const QJsonObject &vpnConfiguration, const QSharedPointer<VpnProtocol> &protocol, const QString &remoteAddress)
{
#ifdef AMNEZIA_DESKTOP
@@ -175,9 +165,29 @@ void VpnTrafficGuard::addSplitTunnelRoutes(const QString &gw, amnezia::RouteMode
#endif
}
void VpnTrafficGuard::applyFirewall(const QString &gateway, const QString &localAddress)
void VpnTrafficGuard::finishFirewallHandover(Tunnel* tunnel)
{
#if defined(AMNEZIA_DESKTOP) && defined(Q_OS_WIN)
if (!tunnel) return;
const QString handoverIfname = tunnel->handoverIfname();
if (handoverIfname.isEmpty() || handoverIfname == tunnel->ifname()) {
tunnel->clearHandoverIfname();
return;
}
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->disableKillSwitchForTunnel(handoverIfname, QStringList());
});
tunnel->clearHandoverIfname();
#else
Q_UNUSED(tunnel)
#endif
}
void VpnTrafficGuard::applyKillSwitch(Tunnel* tunnel, const QString &gateway, const QString &localAddress)
{
#ifdef AMNEZIA_DESKTOP
finishFirewallHandover(tunnel);
QJsonObject updatedConfig = m_config;
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
#ifdef Q_OS_WIN
@@ -213,10 +223,10 @@ void VpnTrafficGuard::applyFirewall(const QString &gateway, const QString &local
NetworkUtilities::getIPAddress(updatedConfig.value(amnezia::configKey::hostName).toString()));
QRemoteObjectPendingReply<bool> reply = iface->enableKillSwitch(updatedConfig, 0);
//TODO: why it takes so long?
if (!reply.waitForFinished(5000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::applyFirewall: Failed to enable killswitch";
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::applyKillSwitch: Failed to enable killswitch";
} else {
qDebug() << "VpnTrafficGuard::applyFirewall: Successfully enabled killswitch";
qDebug() << "VpnTrafficGuard::applyKillSwitch: Successfully enabled killswitch";
}
}
#endif
@@ -250,7 +260,7 @@ void VpnTrafficGuard::flushAll()
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
m_allowedEndpoints.clear();
//TODO: why it takes so long?
if (!reply.waitForFinished(5000) || !reply.returnValue()) {
if (!reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnTrafficGuard::flushAll: Failed to disable killswitch";
} else {
qDebug() << "VpnTrafficGuard::flushAll: Successfully disabled killswitch";
@@ -318,7 +328,7 @@ void VpnTrafficGuard::reserve(Tunnel* tunnel)
{
if (!tunnel) return;
#ifdef AMNEZIA_DESKTOP
allowEndpoint(tunnel->remoteAddress());
allowEndpoint(tunnel->remoteAddress(), tunnel->ifname());
#else
Q_UNUSED(tunnel)
#endif
@@ -327,8 +337,12 @@ void VpnTrafficGuard::reserve(Tunnel* tunnel)
void VpnTrafficGuard::release(Tunnel* tunnel)
{
if (!tunnel) return;
disconnect(tunnel, nullptr, this, nullptr);
#ifdef AMNEZIA_DESKTOP
revokeEndpoint(tunnel->remoteAddress());
m_allowedEndpoints.removeAll(tunnel->remoteAddress());
IpcClient::withInterface([this, &tunnel](QSharedPointer<IpcInterfaceReplica> iface) {
iface->disableKillSwitchForTunnel(tunnel->ifname(), m_allowedEndpoints);
});
#else
Q_UNUSED(tunnel)
#endif
@@ -396,6 +410,17 @@ void VpnTrafficGuard::commit(Tunnel* tunnel)
{
if (!tunnel) return;
applyPolicy(tunnel);
connect(tunnel, &Tunnel::activated, this, [this, tunnel] {
if (auto p = tunnel->protocol()) {
applyKillSwitch(tunnel, p->vpnGateway(), p->vpnLocalAddress());
}
});
#ifdef Q_OS_WIN
connect(tunnel, &Tunnel::addressesUpdated, this,
[this, tunnel](const QString& gw, const QString& la) {
applyKillSwitch(tunnel, gw, la);
});
#endif
tunnel->commit();
}
@@ -410,11 +435,18 @@ void VpnTrafficGuard::tearDown(Tunnel* tunnel)
void VpnTrafficGuard::swap(Tunnel* from, Tunnel* to)
{
if (!to) return;
applyPolicy(to);
to->commit();
if (from) {
to->setHandoverIfname(from->ifname());
}
commit(to);
if (from) {
m_allowedEndpoints.removeAll(from->remoteAddress());
#ifndef Q_OS_WIN
IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) {
iface->resetKillSwitchAllowedRange(m_allowedEndpoints);
});
#endif
revokePolicy(from);
release(from);
from->deactivate();
}
}
+3 -3
View File
@@ -20,9 +20,8 @@ public:
const QString &remoteAddress);
void flushAll();
bool allowEndpoint(const QString &remoteAddress);
void revokeEndpoint(const QString &remoteAddress);
void applyFirewall(const QString &vpnGateway, const QString &vpnLocalAddress);
bool allowEndpoint(const QString &remoteAddress, const QString &ifname = QString());
void applyKillSwitch(Tunnel* tunnel, const QString &vpnGateway, const QString &vpnLocalAddress);
void reserve(Tunnel* tunnel);
void release(Tunnel* tunnel);
@@ -36,6 +35,7 @@ public:
private:
void addSplitTunnelRoutes(const QString &gateway, amnezia::RouteMode mode);
void finishFirewallHandover(Tunnel* tunnel);
SecureAppSettingsRepository* m_appSettingsRepository;
QJsonObject m_config;
bool m_ipv6RoutingStopped = false;
@@ -159,7 +159,7 @@ bool WindowsFirewall::initSublayer() {
return true;
}
bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
bool WindowsFirewall::enableInterface(int vpnAdapterIndex, const QString& ifname) {
// Checks if the FW_Rule was enabled succesfully,
// disables the whole killswitch and returns false if not.
#define FW_OK(rule) \
@@ -182,31 +182,39 @@ bool WindowsFirewall::enableInterface(int vpnAdapterIndex) {
} \
}
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex;
if (vpnAdapterIndex < 0)
{
logger.info() << "Enabling Killswitch Using Adapter:" << vpnAdapterIndex
<< "ifname:" << ifname;
QList<uint64_t>& perTunnel = ifname.isEmpty() ? m_globalRules
: m_tunnelRules[ifname];
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")) {
if (!blockTrafficTo(allv4, MED_WEIGHT, "Block Internet", perTunnel)) {
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"));
IPAddress allv6("::/0");
if (!blockTrafficTo(allv6, MED_WEIGHT, "Block Internet", perTunnel)) {
return false;
}
} else {
FW_OK(allowTrafficOfAdapter(vpnAdapterIndex, MED_WEIGHT,
"Allow usage of VPN Adapter", perTunnel));
}
logger.debug() << "Killswitch on! Rules:" << m_activeRules.length();
if (m_globalRules.isEmpty()) {
FW_OK(allowDHCPTraffic(MED_WEIGHT, "Allow DHCP Traffic", m_globalRules));
FW_OK(allowHyperVTraffic(MAX_WEIGHT, "Allow Hyper-V Traffic", m_globalRules));
FW_OK(allowTrafficForAppOnAll(getCurrentPath(), MAX_WEIGHT,
"Allow all for AmneziaVPN.exe", m_globalRules));
FW_OK(blockTrafficOnPort(53, MED_WEIGHT, "Block all DNS", m_globalRules));
FW_OK(allowLoopbackTraffic(MED_WEIGHT,
"Allow Loopback traffic on device %1",
m_globalRules));
}
logger.debug() << "Killswitch on! Globals:" << m_globalRules.length()
<< "Tunnel[" << ifname
<< "]:" << m_tunnelRules.value(ifname).length();
return true;
#undef FW_OK
}
@@ -226,7 +234,8 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
// Blocking unprotected traffic
for (const IPAddress& prefix : ranges) {
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic")) {
if (!allowTrafficTo(prefix, LOW_WEIGHT + 1, "Allow LAN bypass traffic",
m_globalRules)) {
return false;
}
}
@@ -242,7 +251,10 @@ bool WindowsFirewall::enableLanBypass(const QList<IPAddress>& ranges) {
}
// Allow unprotected traffic sent to the following address ranges.
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
bool WindowsFirewall::allowTrafficRange(const QStringList& ranges, const QString& ifname) {
QList<uint64_t>& target = ifname.isEmpty() ? m_globalRules
: m_tunnelRules[ifname];
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
@@ -255,8 +267,9 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
});
for (const QString& addr : ranges) {
logger.debug() << "Allow killswitch exclude: " << addr;
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT, "Allow killswitch bypass traffic")) {
logger.debug() << "Allow killswitch exclude: " << addr << "ifname:" << ifname;
if (!allowTrafficTo(QHostAddress(addr), HIGH_WEIGHT,
"Allow killswitch bypass traffic", target)) {
return false;
}
}
@@ -273,6 +286,10 @@ bool WindowsFirewall::allowTrafficRange(const QStringList& ranges) {
bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
QList<uint64_t>& target = config.m_ifname.isEmpty()
? m_globalRules
: m_tunnelRules[config.m_ifname];
// Start the firewall transaction
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
@@ -288,12 +305,12 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
logger.info() << "Enabling traffic for peer"
<< config.m_serverPublicKey;
if (!blockTrafficTo(config.m_allowedIPAddressRanges, LOW_WEIGHT,
"Block Internet", config.m_serverPublicKey)) {
"Block Internet", target)) {
return false;
}
if (!config.m_primaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_primaryDnsServer), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) {
"Allow DNS-Server", target)) {
return false;
}
// In some cases, we might configure a 2nd DNS server for IPv6, however
@@ -302,7 +319,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) {
target)) {
return false;
}
}
@@ -310,7 +327,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (!config.m_secondaryDnsServer.isEmpty()) {
if (!allowTrafficTo(QHostAddress(config.m_secondaryDnsServer), 53, HIGH_WEIGHT,
"Allow DNS-Server", config.m_serverPublicKey)) {
"Allow DNS-Server", target)) {
return false;
}
// In some cases, we might configure a 2nd DNS server for IPv6, however
@@ -319,7 +336,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
if (config.m_secondaryDnsServer == config.m_serverIpv4Gateway) {
if (!allowTrafficTo(QHostAddress(config.m_serverIpv6Gateway), 53,
HIGH_WEIGHT, "Allow extra IPv6 DNS-Server",
config.m_serverPublicKey)) {
target)) {
return false;
}
}
@@ -328,7 +345,7 @@ 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)) {
"Allow DNS-Server", target)) {
return false;
}
}
@@ -338,7 +355,7 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
logger.debug() << "excludedAddresses range: " << i;
if (!allowTrafficTo(i, HIGH_WEIGHT,
"Allow Ecxlude route", config.m_serverPublicKey)) {
"Allow Ecxlude route", target)) {
return false;
}
}
@@ -354,35 +371,6 @@ bool WindowsFirewall::enablePeerTraffic(const InterfaceConfig& config) {
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();
}
@@ -400,11 +388,13 @@ bool WindowsFirewall::allowAllTraffic() {
return false;
}
for (const auto& filterID : m_peerRules.values()) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
for (const auto& bucket : qAsConst(m_tunnelRules)) {
for (const auto& filterID : bucket) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
}
for (const auto& filterID : qAsConst(m_activeRules)) {
for (const auto& filterID : qAsConst(m_globalRules)) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
@@ -415,15 +405,42 @@ bool WindowsFirewall::allowAllTraffic() {
<< result;
return false;
}
m_peerRules.clear();
m_activeRules.clear();
m_tunnelRules.clear();
m_globalRules.clear();
logger.debug() << "Firewall Disabled!";
return true;
}
bool WindowsFirewall::disableKillSwitchForTunnel(const QString& ifname) {
if (ifname.isEmpty() || !m_tunnelRules.contains(ifname)) {
return true;
}
auto result = FwpmTransactionBegin(m_sessionHandle, NULL);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionBegin0 failed. Return value:" << result;
return false;
}
const QList<uint64_t> filters = m_tunnelRules.take(ifname);
logger.info() << "Disabling killswitch filters for tunnel" << ifname
<< "count:" << filters.length();
for (const auto& filterID : filters) {
FwpmFilterDeleteById0(m_sessionHandle, filterID);
}
result = FwpmTransactionCommit0(m_sessionHandle);
if (result != ERROR_SUCCESS) {
logger.error() << "FwpmTransactionCommit0 failed. Return value:" << result;
return false;
}
return true;
}
bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
int weight,
const QString& title) {
const QString& title,
QList<uint64_t>& target) {
DWORD result = ERROR_SUCCESS;
Q_ASSERT(weight <= 15);
@@ -460,7 +477,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
{
QString desc("Permit (out) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, desc)) {
if (!enableFilter(&filter, title, desc, target)) {
return false;
}
}
@@ -468,7 +485,7 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
{
QString desc("Permit (in) IPv4 Traffic of: " + appName);
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, desc)) {
if (!enableFilter(&filter, title, desc, target)) {
return false;
}
}
@@ -476,7 +493,8 @@ bool WindowsFirewall::allowTrafficForAppOnAll(const QString& exePath,
}
bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
const QString& title) {
const QString& title,
QList<uint64_t>& target) {
FWPM_FILTER_CONDITION0 conds;
// Condition: Request must be targeting the TUN interface
conds.fieldKey = FWPM_CONDITION_INTERFACE_INDEX;
@@ -498,25 +516,25 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
// #1 Permit outbound IPv4 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) {
description.arg("out").arg(networkAdapter), target)) {
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))) {
description.arg("in").arg(networkAdapter), target)) {
return false;
}
// #3 Permit outbound IPv6 traffic.
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title,
description.arg("out").arg(networkAdapter))) {
description.arg("out").arg(networkAdapter), target)) {
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))) {
description.arg("in").arg(networkAdapter), target)) {
return false;
}
return true;
@@ -524,7 +542,7 @@ bool WindowsFirewall::allowTrafficOfAdapter(int networkAdapter, uint8_t weight,
bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
const QString& title,
const QString& peer) {
QList<uint64_t>& target) {
GUID layerKeyOut;
GUID layerKeyIn;
if (addr.type() == QAbstractSocket::IPv4Protocol) {
@@ -562,11 +580,11 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
// 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)) {
if (!enableFilter(&filter, title, description.arg("to"), target)) {
return false;
}
filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title, description.arg("from"), peer)) {
if (!enableFilter(&filter, title, description.arg("from"), target)) {
return false;
}
return true;
@@ -574,7 +592,7 @@ bool WindowsFirewall::allowTrafficTo(const IPAddress& addr, int weight,
bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
int weight, const QString& title,
const QString& peer) {
QList<uint64_t>& target) {
bool isIPv4 = targetIP.protocol() == QAbstractSocket::IPv4Protocol;
GUID layerOut =
isIPv4 ? FWPM_LAYER_ALE_AUTH_CONNECT_V4 : FWPM_LAYER_ALE_AUTH_CONNECT_V6;
@@ -623,19 +641,20 @@ bool WindowsFirewall::allowTrafficTo(const QHostAddress& targetIP, uint port,
filter.layerKey = layerOut;
if (!enableFilter(&filter, title,
description.arg("to").arg(targetIP.toString()).arg(port),
peer)) {
target)) {
return false;
}
filter.layerKey = layerIn;
if (!enableFilter(&filter, title,
description.arg("from").arg(targetIP.toString()).arg(port),
peer)) {
target)) {
return false;
}
return true;
}
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target) {
// Allow outbound DHCPv4
{
FWPM_FILTER_CONDITION0 conds[4];
@@ -672,7 +691,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, "Allow Outbound DHCP")) {
if (!enableFilter(&filter, title, "Allow Outbound DHCP", target)) {
return false;
}
}
@@ -705,7 +724,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, "Allow inbound DHCP")) {
if (!enableFilter(&filter, title, "Allow inbound DHCP", target)) {
return false;
}
}
@@ -740,7 +759,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
filter.subLayerKey = ST_FW_WINFW_BASELINE_SUBLAYER_KEY;
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V6;
if (!enableFilter(&filter, title, "Allow outbound DHCPv6")) {
if (!enableFilter(&filter, title, "Allow outbound DHCPv6", target)) {
return false;
}
}
@@ -773,7 +792,7 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
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")) {
if (!enableFilter(&filter, title, "Allow inbound DHCPv6", target)) {
return false;
}
}
@@ -781,7 +800,8 @@ bool WindowsFirewall::allowDHCPTraffic(uint8_t weight, const QString& title) {
}
// Allows the internal Hyper-V Switches to work.
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target) {
FWPM_FILTER_CONDITION0 cond;
// Condition: Request must be targeting the TUN interface
cond.fieldKey = FWPM_CONDITION_L2_FLAGS;
@@ -801,12 +821,12 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
// #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")) {
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V outbound", target)) {
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")) {
if (!enableFilter(&filter, title, "Permit Hyper-V => Hyper-V inbound", target)) {
return false;
}
return true;
@@ -814,7 +834,7 @@ bool WindowsFirewall::allowHyperVTraffic(uint8_t weight, const QString& title) {
bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
const QString& title,
const QString& peer) {
QList<uint64_t>& target) {
QString description("Block traffic %1 %2 ");
auto lower = addr.address();
@@ -852,12 +872,12 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
filter.layerKey = layerKeyOut;
if (!enableFilter(&filter, title, description.arg("to").arg(addr.toString()),
peer)) {
target)) {
return false;
}
filter.layerKey = layerKeyIn;
if (!enableFilter(&filter, title,
description.arg("from").arg(addr.toString()), peer)) {
description.arg("from").arg(addr.toString()), target)) {
return false;
}
return true;
@@ -865,9 +885,9 @@ bool WindowsFirewall::blockTrafficTo(const IPAddress& addr, uint8_t weight,
bool WindowsFirewall::blockTrafficTo(const QList<IPAddress>& rangeList,
uint8_t weight, const QString& title,
const QString& peer) {
QList<uint64_t>& target) {
for (auto range : rangeList) {
if (!blockTrafficTo(range, weight, title, peer)) {
if (!blockTrafficTo(range, weight, title, target)) {
logger.info() << "Setting Range of" << range.toString() << "failed";
return false;
}
@@ -923,7 +943,8 @@ void WindowsFirewall::importAddress(const QHostAddress& addr,
}
bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
const QString& title) {
const QString& title,
QList<uint64_t>& target) {
// Allow Traffic to IP with PORT using any protocol
FWPM_FILTER_CONDITION0 conds[3];
conds[0].fieldKey = FWPM_CONDITION_IP_PROTOCOL;
@@ -953,20 +974,20 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
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))) {
if (!enableFilter(&filter, title, description.arg("outgoing v6").arg(port), target)) {
return false;
}
filter.layerKey = FWPM_LAYER_ALE_AUTH_CONNECT_V4;
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port))) {
if (!enableFilter(&filter, title, description.arg("outgoing v4").arg(port), target)) {
return false;
}
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V4;
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port))) {
if (!enableFilter(&filter, title, description.arg("incoming v4").arg(port), target)) {
return false;
}
filter.layerKey = FWPM_LAYER_ALE_AUTH_RECV_ACCEPT_V6;
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port))) {
if (!enableFilter(&filter, title, description.arg("incoming v6").arg(port), target)) {
return false;
}
return true;
@@ -974,7 +995,7 @@ bool WindowsFirewall::blockTrafficOnPort(uint port, uint8_t weight,
bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
const QString& description,
const QString& peer) {
QList<uint64_t>& target) {
uint64_t filterID = 0;
auto name = title.toStdWString();
auto desc = description.toStdWString();
@@ -987,16 +1008,12 @@ bool WindowsFirewall::enableFilter(FWPM_FILTER0* filter, const QString& title,
return false;
}
logger.info() << "Filter added: " << title << ":" << description;
if (peer.isEmpty()) {
m_activeRules.append(filterID);
} else {
m_peerRules.insert(peer, filterID);
}
target.append(filterID);
return true;
}
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
const QString& title) {
bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target) {
QList<QNetworkInterface> networkInterfaces =
QNetworkInterface::allInterfaces();
for (const auto& iface : networkInterfaces) {
@@ -1004,7 +1021,7 @@ bool WindowsFirewall::allowLoopbackTraffic(uint8_t weight,
continue;
}
if (!allowTrafficOfAdapter(iface.index(), weight,
title.arg(iface.name()))) {
title.arg(iface.name()), target)) {
return false;
}
}
@@ -15,6 +15,7 @@
#include <QByteArray>
#include <QHostAddress>
#include <QMap>
#include <QObject>
#include <QString>
@@ -38,38 +39,42 @@ class WindowsFirewall final : public QObject {
static WindowsFirewall* create(QObject* parent);
~WindowsFirewall() override;
bool enableInterface(int vpnAdapterIndex);
bool enableInterface(int vpnAdapterIndex, const QString& ifname = QString());
bool enableLanBypass(const QList<IPAddress>& ranges);
bool enablePeerTraffic(const InterfaceConfig& config);
bool disablePeerTraffic(const QString& pubkey);
bool disableKillSwitch();
bool disableKillSwitchForTunnel(const QString& ifname);
bool allowAllTraffic();
bool allowTrafficRange(const QStringList& ranges);
bool allowTrafficRange(const QStringList& ranges, const QString& ifname = QString());
private:
static bool initSublayer();
WindowsFirewall(HANDLE session, QObject* parent);
HANDLE m_sessionHandle;
bool m_init = false;
QList<uint64_t> m_activeRules;
QMultiMap<QString, uint64_t> m_peerRules;
QList<uint64_t> m_globalRules;
QMap<QString, QList<uint64_t>> m_tunnelRules;
bool allowTrafficForAppOnAll(const QString& exePath, int weight,
const QString& title);
const QString& title, QList<uint64_t>& target);
bool blockTrafficTo(const QList<IPAddress>& range, uint8_t weight,
const QString& title, const QString& peer = QString());
const QString& title, QList<uint64_t>& target);
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);
const QString& title, QList<uint64_t>& target);
bool blockTrafficOnPort(uint port, uint8_t weight, const QString& title,
QList<uint64_t>& target);
bool allowTrafficTo(const IPAddress& addr, int weight, const QString& title,
const QString& peer = QString());
QList<uint64_t>& target);
bool allowTrafficTo(const QHostAddress& targetIP, uint port, int weight,
const QString& title, const QString& peer = QString());
const QString& title, QList<uint64_t>& target);
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);
const QString& title, QList<uint64_t>& target);
bool allowDHCPTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target);
bool allowHyperVTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target);
bool allowLoopbackTraffic(uint8_t weight, const QString& title,
QList<uint64_t>& target);
// Utils
QString getCurrentPath();
@@ -78,8 +83,7 @@ class WindowsFirewall final : public QObject {
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());
const QString& description, QList<uint64_t>& target);
};
#endif // WINDOWSFIREWALL_H
+34 -18
View File
@@ -166,20 +166,37 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
NetworkUtilities::getIPAddress(vpnConfiguration.value(configKey::hostName).toString());
#ifdef AMNEZIA_DESKTOP
// Seamless WG -> WG switch path: already connected via Tunnel, new container is also WG.
if (m_active
&& m_connectionState == Vpn::ConnectionState::Connected
&& VpnProtocol::isWireGuardBased(container)) {
if (!m_trafficGuard->allowEndpoint(resolvedRemote)) {
const bool isWg = VpnProtocol::isWireGuardBased(container);
const QString preAllocatedIfname = isWg ? allocateIfname() : QString();
bool seamlessSwitch = m_active
&& m_connectionState == Vpn::ConnectionState::Connected
&& isWg;
#ifdef Q_OS_WIN
if (seamlessSwitch) {
auto clientIpFor = [](const QJsonObject& cfg) {
const QString proto = cfg.value("protocol").toString();
return cfg.value(proto + "_config_data").toObject().value(configKey::clientIp).toString();
};
if (clientIpFor(vpnConfiguration) == clientIpFor(m_active->config())) {
seamlessSwitch = false;
}
}
#endif
if (seamlessSwitch) {
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
releaseIfname(preAllocatedIfname);
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
return;
}
startTunnelSwitch(container, vpnConfiguration, resolvedRemote);
startTunnelSwitch(container, vpnConfiguration, resolvedRemote, preAllocatedIfname);
return;
}
if (!m_trafficGuard->allowEndpoint(resolvedRemote)) {
if (!m_trafficGuard->allowEndpoint(resolvedRemote, preAllocatedIfname)) {
if (isWg) releaseIfname(preAllocatedIfname);
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(ErrorCode::AmneziaServiceConnectionFailed);
return;
@@ -212,10 +229,9 @@ void VpnConnection::connectToVpn(const QString &serverId, DockerContainer contai
m_remoteAddress = resolvedRemote;
#ifdef AMNEZIA_DESKTOP
if (VpnProtocol::isWireGuardBased(container)) {
const QString ifname = allocateIfname();
config.insert("ifname", ifname);
m_active = new Tunnel(ifname, container, config, resolvedRemote, this);
if (isWg) {
config.insert("ifname", preAllocatedIfname);
m_active = new Tunnel(preAllocatedIfname, container, config, resolvedRemote, this);
wireTunnelSignals(m_active, /*isActive=*/true);
wireDaemonReconnectSignals();
m_trafficGuard->setConfig(config);
@@ -255,7 +271,10 @@ void VpnConnection::createProtocolConnections()
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, m_trafficGuard.data(), &VpnTrafficGuard::applyFirewall);
connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated, this,
[this](const QString& gateway, const QString& localAddress) {
m_trafficGuard->applyKillSwitch(nullptr, gateway, localAddress);
});
wireDaemonReconnectSignals();
}
@@ -532,13 +551,14 @@ void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
void VpnConnection::startTunnelSwitch(DockerContainer container,
const QJsonObject &vpnConfiguration,
const QString &resolvedRemote)
const QString &resolvedRemote,
const QString &stagingIfname)
{
QJsonObject config = vpnConfiguration;
config.insert("ifname", stagingIfname);
appendKillSwitchConfig(config);
appendSplitTunnelingConfig(config);
const QString stagingIfname = allocateIfname();
m_staging = new Tunnel(stagingIfname, container, config, resolvedRemote, this);
wireTunnelSignals(m_staging, /*isActive=*/false);
@@ -576,10 +596,6 @@ void VpnConnection::onTunnelActivated()
if (tunnel == m_active) {
setConnectionState(Vpn::ConnectionState::Connected);
if (auto proto = m_active->protocol()) {
m_trafficGuard->applyFirewall(proto->vpnGateway(),
proto->vpnLocalAddress());
}
}
}
+2 -1
View File
@@ -108,7 +108,8 @@ private:
void startTunnelSwitch(DockerContainer container,
const QJsonObject &vpnConfiguration,
const QString &resolvedRemote);
const QString &resolvedRemote,
const QString &stagingIfname);
private slots:
void onTunnelPrepared();
+2 -1
View File
@@ -35,9 +35,10 @@ class IpcInterface
SLOT( bool StopRoutingIpv6() );
SLOT( bool disableKillSwitch() );
SLOT( bool disableKillSwitchForTunnel( const QString &ifname, const QStringList &remainingRanges ) );
SLOT( bool disableAllTraffic() );
SLOT( bool refreshKillSwitch( bool enabled ) );
SLOT( bool addKillSwitchAllowedRange( const QStringList ranges ) );
SLOT( bool addKillSwitchAllowedRange( const QString &ifname, const QStringList ranges ) );
SLOT( bool resetKillSwitchAllowedRange( const QStringList ranges ) );
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
+12 -3
View File
@@ -282,13 +282,13 @@ bool IpcServer::resetKillSwitchAllowedRange(QStringList ranges)
return KillSwitch::instance()->resetAllowedRange(ranges);
}
bool IpcServer::addKillSwitchAllowedRange(QStringList ranges)
bool IpcServer::addKillSwitchAllowedRange(const QString &ifname, QStringList ranges)
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::addKillSwitchAllowedRange";
qDebug() << "IpcServer::addKillSwitchAllowedRange" << ifname;
#endif
return KillSwitch::instance()->addAllowedRange(ranges);
return KillSwitch::instance()->addAllowedRange(ifname, ranges);
}
bool IpcServer::disableAllTraffic()
@@ -318,6 +318,15 @@ bool IpcServer::disableKillSwitch()
return KillSwitch::instance()->disableKillSwitch();
}
bool IpcServer::disableKillSwitchForTunnel(const QString &ifname, const QStringList &remainingRanges)
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::disableKillSwitchForTunnel" << ifname;
#endif
return KillSwitch::instance()->disableKillSwitchForTunnel(ifname, remainingRanges);
}
bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
{
#ifdef MZ_DEBUG
+2 -1
View File
@@ -41,11 +41,12 @@ public:
virtual bool StartRoutingIpv6() override;
virtual bool StopRoutingIpv6() override;
virtual bool disableAllTraffic() override;
virtual bool addKillSwitchAllowedRange(QStringList ranges) override;
virtual bool addKillSwitchAllowedRange(const QString &ifname, QStringList ranges) override;
virtual bool resetKillSwitchAllowedRange(QStringList ranges) override;
virtual bool enablePeerTraffic(const QJsonObject &configStr) override;
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
virtual bool disableKillSwitch() override;
virtual bool disableKillSwitchForTunnel(const QString &ifname, const QStringList &remainingRanges) override;
virtual bool refreshKillSwitch( bool enabled ) override;
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
virtual bool restoreResolvers() override;
+26 -3
View File
@@ -159,6 +159,16 @@ bool KillSwitch::disableKillSwitch() {
return true;
}
bool KillSwitch::disableKillSwitchForTunnel(const QString& ifname, const QStringList& remainingRanges) {
#ifdef Q_OS_WIN
Q_UNUSED(remainingRanges)
return WindowsFirewall::create(this)->disableKillSwitchForTunnel(ifname);
#else
Q_UNUSED(ifname)
return resetAllowedRange(remainingRanges);
#endif
}
bool KillSwitch::disableAllTraffic() {
#ifdef Q_OS_WIN
WindowsFirewall::create(this)->enableInterface(-1);
@@ -221,7 +231,15 @@ bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
return true;
}
bool KillSwitch::addAllowedRange(const QStringList &ranges) {
bool KillSwitch::addAllowedRange(const QString &ifname, const QStringList &ranges) {
#ifdef Q_OS_WIN
if (!ifname.isEmpty()) {
return WindowsFirewall::create(this)->allowTrafficRange(ranges, ifname);
}
#else
Q_UNUSED(ifname)
#endif
for (const QString &range : ranges) {
if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
m_allowedRanges.append(range);
@@ -242,7 +260,11 @@ bool KillSwitch::enablePeerTraffic(const QJsonObject &configStr) {
config.m_secondaryDnsServer = configStr.value(amnezia::configKey::dns2).toString();
}
config.m_serverPublicKey = "openvpn";
config.m_ifname = configStr.value("ifname").toString();
const QString protocolName = configStr.value(amnezia::configKey::vpnProto).toString();
const QString pubkey = configStr.value(protocolName + "_config_data").toObject()
.value(amnezia::configKey::serverPubKey).toString();
config.m_serverPublicKey = pubkey.isEmpty() ? QStringLiteral("openvpn") : pubkey;
config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString();
config.m_serverIpv4AddrIn = configStr.value("vpnServer").toString();
int vpnAdapterIndex = resolveVpnAdapterIndex(configStr);
@@ -306,10 +328,11 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
#ifdef Q_OS_WIN
Q_UNUSED(vpnAdapterIndex)
const int resolvedIndex = resolveVpnAdapterIndex(configStr);
const QString ifname = configStr.value("ifname").toString();
if (configStr.value("splitTunnelType").toInt() != 0) {
WindowsFirewall::create(this)->allowAllTraffic();
}
return WindowsFirewall::create(this)->enableInterface(resolvedIndex);
return WindowsFirewall::create(this)->enableInterface(resolvedIndex, ifname);
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
+2 -1
View File
@@ -14,11 +14,12 @@ public:
bool init();
bool refresh(bool enabled);
bool disableKillSwitch();
bool disableKillSwitchForTunnel(const QString& ifname, const QStringList& remainingRanges);
bool disableAllTraffic();
bool enablePeerTraffic(const QJsonObject &configStr);
bool enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex);
bool resetAllowedRange(const QStringList &ranges);
bool addAllowedRange(const QStringList &ranges);
bool addAllowedRange(const QString &ifname, const QStringList &ranges);
bool isStrictKillSwitchEnabled();
private: