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