From c3fdd977b1aaa5d4c803530e5d6f41ef87b93645 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 29 Nov 2023 22:50:36 +0200 Subject: [PATCH 01/27] Windows OpenVPN/OpenVPN+Cloak killswitch feature --- client/protocols/openvpnprotocol.cpp | 23 ++++++-- client/protocols/openvpnprotocol.h | 1 + ipc/ipc_interface.rep | 5 ++ ipc/ipcserver.cpp | 79 ++++++++++++++++++++++++++-- ipc/ipcserver.h | 5 ++ 5 files changed, 105 insertions(+), 8 deletions(-) diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index c38c6eea..c7cc839d 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include "logger.h" #include "openvpnprotocol.h" @@ -53,6 +54,11 @@ void OpenVpnProtocol::stop() QThread::msleep(10); m_managementServer.stop(); } + +#ifdef Q_OS_WIN + IpcClient::Interface()->disableKillSwitch(); +#endif + setConnectionState(Vpn::ConnectionState::Disconnected); } @@ -85,13 +91,13 @@ void OpenVpnProtocol::killOpenVpnProcess() void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration) { if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) { + m_configData = configuration; QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject(); m_configFile.open(); m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); m_configFile.close(); m_configFileName = m_configFile.fileName(); - qDebug().noquote() << QString("Set config data") << m_configFileName; } } @@ -320,14 +326,25 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) // line looks like // PUSH: Received control message: 'PUSH_REPLY,route 10.8.0.1,topology net30,ping 10,ping-restart // 120,ifconfig 10.8.0.6 10.8.0.5,peer-id 0,cipher AES-256-GCM' - QStringList params = line.split(","); for (const QString &l : params) { if (l.contains("ifconfig")) { if (l.split(" ").size() == 3) { m_vpnLocalAddress = l.split(" ").at(1); m_vpnGateway = l.split(" ").at(2); - +#ifdef Q_OS_WIN + QList netInterfaces = QNetworkInterface::allInterfaces(); + for (int i = 0; i < netInterfaces.size(); i++) { + for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) + { + if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { + IpcClient::Interface()->enableKillSwitch(netInterfaces.at(i).index()); + m_configData.insert("vpnGateway", m_vpnGateway); + IpcClient::Interface()->enablePeerTraffic(m_configData); + } + } + } +#endif qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); } } diff --git a/client/protocols/openvpnprotocol.h b/client/protocols/openvpnprotocol.h index ad80fe50..b07d1268 100644 --- a/client/protocols/openvpnprotocol.h +++ b/client/protocols/openvpnprotocol.h @@ -44,6 +44,7 @@ private: ManagementServer m_managementServer; QString m_configFileName; + QJsonObject m_configData; QTemporaryFile m_configFile; uint selectMgmtPort(); diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 8970f7c8..faf70079 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -1,5 +1,7 @@ #include #include +#include +#include "../client/daemon/interfaceconfig.h" class IpcInterface { @@ -22,5 +24,8 @@ class IpcInterface SLOT( bool copyWireguardConfig(const QString &sourcePath) ); SLOT( bool isWireguardRunning() ); SLOT( bool isWireguardConfigExists(const QString &configPath) ); + SLOT( bool enableKillSwitch(int vpnAdapterIndex) ); + SLOT( bool disableKillSwitch() ); + SLOT( bool enablePeerTraffic(const QJsonObject &configStr)); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index e9f57c60..4517a5bf 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -8,8 +8,11 @@ #include "router.h" #include "logger.h" +#include "../client/protocols/protocols_defs.h" #ifdef Q_OS_WIN #include "tapcontroller_win.h" +#include "../client/platforms/windows/daemon/windowsfirewall.h" + #endif IpcServer::IpcServer(QObject *parent): @@ -22,15 +25,14 @@ int IpcServer::createPrivilegedProcess() qDebug() << "IpcServer::createPrivilegedProcess"; #endif +#ifdef Q_OS_WIN + WindowsFirewall::instance()->init(); +#endif + m_localpid++; ProcessDescriptor pd(this); -// pd.serverNode->setHostUrl(QUrl(amnezia::getIpcProcessUrl(m_localpid))); -// pd.serverNode->enableRemoting(pd.ipcProcess.data()); - - - //pd.localServer = QSharedPointer(new QLocalServer(this)); pd.localServer->setSocketOptions(QLocalServer::WorldAccessOption); if (!pd.localServer->listen(amnezia::getIpcProcessUrl(m_localpid))) { @@ -223,3 +225,70 @@ bool IpcServer::isWireguardConfigExists(const QString &configPath) return QFileInfo::exists(configPath); } + +bool IpcServer::enableKillSwitch(int vpnAdapterIndex) +{ +#ifdef Q_OS_WIN + return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex); +#endif + return true; +} + +bool IpcServer::disableKillSwitch() +{ +#ifdef Q_OS_WIN + return WindowsFirewall::instance()->disableKillSwitch(); +#endif + return true; +} + +bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) +{ +#ifdef Q_OS_WIN + InterfaceConfig config; + config.m_dnsServer = configStr.value(amnezia::config_key::dns1).toString(); + config.m_serverPublicKey = "openvpn"; + config.m_serverIpv4Gateway = configStr.value("vpnGateway").toString(); + + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + + qDebug() << "splitTunnelType " << splitTunnelType << "splitTunnelSites " << splitTunnelSites; + + QStringList AllowedIPAddesses; + + // Use APP split tunnel + if (splitTunnelType == 0 || splitTunnelType == 2) { + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress("0.0.0.0"), 0)); + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress("::"), 0)); + } + + if (splitTunnelType == 1) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + qDebug() << "ipRange " << ipRange; + if (ipRange.split('/').size() > 1){ + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress(ipRange.split('/')[0]), atoi(ipRange.split('/')[1].toLocal8Bit()))); + } else { + config.m_allowedIPAddressRanges.append( + IPAddress(QHostAddress(ipRange), 32)); + + } + } + } + + config.m_excludedAddresses.append(configStr.value(amnezia::config_key::hostName).toString()); + if (splitTunnelType == 2) { + for (auto v : splitTunnelSites) { + QString ipRange = v.toString(); + config.m_excludedAddresses.append(ipRange); + } + } + + return WindowsFirewall::instance()->enablePeerTraffic(config); +#endif + return true; +} diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index d5706784..1508049a 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include "../client/daemon/interfaceconfig.h" #include "ipc.h" #include "ipcserverprocess.h" @@ -28,6 +30,9 @@ public: virtual bool copyWireguardConfig(const QString &sourcePath) override; virtual bool isWireguardRunning() override; virtual bool isWireguardConfigExists(const QString &configPath) override; + virtual bool enableKillSwitch(int vpnAdapterIndex) override; + virtual bool disableKillSwitch() override; + virtual bool enablePeerTraffic(const QJsonObject &configStr) override; private: int m_localpid = 0; From d94e27bfa9df98a9795530731ad4e0fddda1d904 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 16 Dec 2023 09:19:04 -0500 Subject: [PATCH 02/27] Linux killswitch --- .../platforms/linux/daemon/linuxfirewall.cpp | 462 ++++++++++++++++++ client/platforms/linux/daemon/linuxfirewall.h | 73 +++ .../linux/daemon/wireguardutilslinux.cpp | 39 ++ .../linux/daemon/wireguardutilslinux.h | 5 +- client/protocols/openvpnprotocol.cpp | 7 +- ipc/ipc_interface.rep | 4 +- ipc/ipcserver.cpp | 48 +- ipc/ipcserver.h | 4 +- service/server/CMakeLists.txt | 2 + 9 files changed, 627 insertions(+), 17 deletions(-) create mode 100644 client/platforms/linux/daemon/linuxfirewall.cpp create mode 100644 client/platforms/linux/daemon/linuxfirewall.h diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp new file mode 100644 index 00000000..ab51ea74 --- /dev/null +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -0,0 +1,462 @@ +#include "linuxfirewall.h" +#include "logger.h" +#include + +#define BRAND_CODE "amn" + +namespace { +Logger logger("LinuxFirewall"); +} // namespace + +namespace +{ +const QString kAnchorName{BRAND_CODE "vpn"}; +const QString kPacketTag{"0x3211"}; +const QString kCGroupId{"0x567"}; +const QString enabledKeyTemplate = "enabled:%1:%2"; +const QString disabledKeyTemplate = "disabled:%1:%2"; +const QString kVpnGroupName = BRAND_CODE "vpn"; +QHash anchorCallbacks; +} + +QString LinuxFirewall::kRtableName = QStringLiteral("%1rt").arg(kAnchorName); +QString LinuxFirewall::kOutputChain = QStringLiteral("OUTPUT"); +QString LinuxFirewall::kPostRoutingChain = QStringLiteral("POSTROUTING"); +QString LinuxFirewall::kPreRoutingChain = QStringLiteral("PREROUTING"); +QString LinuxFirewall::kRootChain = QStringLiteral("%1.anchors").arg(kAnchorName); +QString LinuxFirewall::kFilterTable = QStringLiteral("filter"); +QString LinuxFirewall::kNatTable = QStringLiteral("nat"); +QString LinuxFirewall::kRawTable = QStringLiteral("raw"); +QString LinuxFirewall::kMangleTable = QStringLiteral("mangle"); + +static QString getCommand(LinuxFirewall::IPVersion ip) +{ + return ip == LinuxFirewall::IPv6 ? QStringLiteral("ip6tables") : QStringLiteral("iptables"); +} + +int LinuxFirewall::createChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName) +{ + if (ip == Both) + { + int result4 = createChain(IPv4, chain, tableName); + int result6 = createChain(IPv6, chain, tableName); + return result4 ? result4 : result6; + } + const QString cmd = getCommand(ip); + return execute(QStringLiteral("%1 -N %2 -t %3 || %1 -F %2 -t %3").arg(cmd, chain, tableName)); +} + +int LinuxFirewall::deleteChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& tableName) +{ + if (ip == Both) + { + int result4 = deleteChain(IPv4, chain, tableName); + int result6 = deleteChain(IPv6, chain, tableName); + return result4 ? result4 : result6; + } + const QString cmd = getCommand(ip); + return execute(QStringLiteral("if %1 -L %2 -n -t %3 > /dev/null 2> /dev/null ; then %1 -F %2 -t %3 && %1 -X %2 -t %3; fi").arg(cmd, chain, tableName)); +} + +int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst, const QString& tableName) +{ + if (ip == Both) + { + int result4 = linkChain(IPv4, chain, parent, mustBeFirst, tableName); + int result6 = linkChain(IPv6, chain, parent, mustBeFirst, tableName); + return result4 ? result4 : result6; + } + const QString cmd = getCommand(ip); + if (mustBeFirst) + { + // This monster shell script does the following: + // 1. Check if a rule with the appropriate target exists at the top of the parent chain + // 2. If not, insert a jump rule at the top of the parent chain + // 3. Look for and delete a single rule with the designated target at an index > 1 + // (we can't safely delete all rules at once since rule numbers change) + // TODO: occasionally this script results in warnings in logs "Bad rule (does a matching rule exist in the chain?)" - this happens when + // the e.g OUTPUT chain is empty but this script attempts to delete things from it anyway. It doesn't cause any problems, but we should still fix at some point.. + return execute(QStringLiteral("if ! %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) == 1 && $2 == \"%3\" { found=1 } END { if(found==1) { exit 0 } else { exit 1 } }' ; then %1 -I %2 -j %3 -t %4 && %1 -L %2 -n --line-numbers -t %4 2> /dev/null | awk 'int($1) > 1 && $2 == \"%3\" { print $1; exit }' | xargs %1 -t %4 -D %2 ; fi").arg(cmd, parent, chain, tableName)); + } + else + return execute(QStringLiteral("if ! %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -A %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName)); +} + +int LinuxFirewall::unlinkChain(LinuxFirewall::IPVersion ip, const QString& chain, const QString& parent, const QString& tableName) +{ + if (ip == Both) + { + int result4 = unlinkChain(IPv4, chain, parent, tableName); + int result6 = unlinkChain(IPv6, chain, parent, tableName); + return result4 ? result4 : result6; + } + const QString cmd = getCommand(ip); + return execute(QStringLiteral("if %1 -C %2 -j %3 -t %4 2> /dev/null ; then %1 -D %2 -j %3 -t %4; fi").arg(cmd, parent, chain, tableName)); +} + +void LinuxFirewall::ensureRootAnchorPriority(LinuxFirewall::IPVersion ip) +{ + linkChain(ip, kRootChain, kOutputChain, true); +} + +void LinuxFirewall::installAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName, + const FilterCallbackFunc& enableFunc, const FilterCallbackFunc& disableFunc) +{ + if (ip == Both) + { + installAnchor(IPv4, anchor, rules, tableName, enableFunc, disableFunc); + installAnchor(IPv6, anchor, rules, tableName, enableFunc, disableFunc); + return; + } + + const QString cmd = getCommand(ip); + const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor); + const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor); + + // Start by defining a placeholder chain, which stays locked into place + // in the root chain without being removed or recreated, ensuring the + // intended precedence order. + createChain(ip, anchorChain, tableName); + linkChain(ip, anchorChain, kRootChain, false, tableName); + + if(enableFunc) + { + const QString key = enabledKeyTemplate.arg(tableName, anchor); + if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = enableFunc; + } + if(disableFunc) + { + const QString key = disabledKeyTemplate.arg(tableName, anchor); + if(!anchorCallbacks.contains(key)) anchorCallbacks[key] = disableFunc; + } + + // Create the actual rule chain, which we'll insert or remove from the + // placeholder anchor when needed. + createChain(ip, actualChain, tableName); + for (const QString& rule : rules) + execute(QStringLiteral("%1 -A %2 %3 -t %4").arg(cmd, actualChain, rule, tableName)); +} + +void LinuxFirewall::uninstallAnchor(LinuxFirewall::IPVersion ip, const QString& anchor, const QString& tableName) +{ + if (ip == Both) + { + uninstallAnchor(IPv4, anchor, tableName); + uninstallAnchor(IPv6, anchor, tableName); + return; + } + + const QString cmd = getCommand(ip); + const QString anchorChain = QStringLiteral("%1.a.%2").arg(kAnchorName, anchor); + const QString actualChain = QStringLiteral("%1.%2").arg(kAnchorName, anchor); + + unlinkChain(ip, anchorChain, kRootChain, tableName); + deleteChain(ip, anchorChain, tableName); + deleteChain(ip, actualChain, tableName); +} + +QStringList LinuxFirewall::getDNSRules(const QStringList& servers) +{ + QStringList result; + for (const QString& server : servers) + { + result << QStringLiteral("-o amn0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); + result << QStringLiteral("-o amn0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); + result << QStringLiteral("-o tun0+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server); + result << QStringLiteral("-o tun0+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server); + } + return result; +} + +QStringList LinuxFirewall::getExcludeRule(const QStringList& servers) +{ + QStringList result; + for (const QString& server : servers) + { + result << QStringLiteral("-d %1 -j ACCEPT").arg(server); + } + return result; +} + + +void LinuxFirewall::install() +{ + // Clean up any existing rules if they exist. + uninstall(); + + // Create a root filter chain to hold all our other anchors in order. + createChain(Both, kRootChain, kFilterTable); + + // Create a root raw chain + createChain(Both, kRootChain, kRawTable); + + // Create a root NAT chain + createChain(Both, kRootChain, kNatTable); + + // Create a root Mangle chain + createChain(Both, kRootChain, kMangleTable); + + // Install our filter rulesets in each corresponding anchor chain. + installAnchor(Both, QStringLiteral("000.allowLoopback"), { + QStringLiteral("-o lo+ -j ACCEPT"), + }); + + installAnchor(IPv4, QStringLiteral("320.allowDNS"), {}); + + installAnchor(Both, QStringLiteral("310.blockDNS"), { + QStringLiteral("-p udp --dport 53 -j REJECT"), + QStringLiteral("-p tcp --dport 53 -j REJECT"), + }); + installAnchor(IPv4, QStringLiteral("300.allowLAN"), { + QStringLiteral("-d 10.0.0.0/8 -j ACCEPT"), + QStringLiteral("-d 169.254.0.0/16 -j ACCEPT"), + QStringLiteral("-d 172.16.0.0/12 -j ACCEPT"), + QStringLiteral("-d 192.168.0.0/16 -j ACCEPT"), + QStringLiteral("-d 224.0.0.0/4 -j ACCEPT"), + QStringLiteral("-d 255.255.255.255/32 -j ACCEPT"), + }); + installAnchor(IPv6, QStringLiteral("300.allowLAN"), { + QStringLiteral("-d fc00::/7 -j ACCEPT"), + QStringLiteral("-d fe80::/10 -j ACCEPT"), + QStringLiteral("-d ff00::/8 -j ACCEPT"), + }); + + + installAnchor(IPv4, QStringLiteral("290.allowDHCP"), { + QStringLiteral("-p udp -d 255.255.255.255 --sport 68 --dport 67 -j ACCEPT"), + }); + installAnchor(IPv6, QStringLiteral("290.allowDHCP"), { + QStringLiteral("-p udp -d ff00::/8 --sport 546 --dport 547 -j ACCEPT"), + }); + installAnchor(IPv6, QStringLiteral("250.blockIPv6"), { + QStringLiteral("! -o lo+ -j REJECT"), + }); + + installAnchor(Both, QStringLiteral("200.allowVPN"), { + QStringLiteral("-o amn0+ -j ACCEPT"), + QStringLiteral("-o tun0+ -j ACCEPT"), + }); + + installAnchor(Both, QStringLiteral("100.blockAll"), { + QStringLiteral("-j REJECT"), + }); + + // NAT rules + installAnchor(Both, QStringLiteral("100.transIp"), { + + // Only need the original interface, not the IP. + // The interface should remain much more stable/unchangeable than the IP + // (IP can change when changing networks, but interface only changes if adding/removing NICs) + // this is just a stub rule - the real rule is set at run-time + // and updates dynamically (via replaceAnchor) when our interface changes + // it'll take this form: "-o -j MASQUERADE" + QStringLiteral("-j MASQUERADE") + }, kNatTable); + + // Mangle rules + installAnchor(Both, QStringLiteral("100.tagPkts"), { + QStringLiteral("-m cgroup --cgroup %1 -j MARK --set-mark %2").arg(kCGroupId, kPacketTag) + }, kMangleTable, setupTrafficSplitting, teardownTrafficSplitting); + + // A rule to mitigate CVE-2019-14899 - drop packets addressed to the local + // VPN IP but that are not actually received on the VPN interface. + // See here: https://seclists.org/oss-sec/2019/q4/122 + installAnchor(Both, QStringLiteral("100.vpnTunOnly"), { + // To be replaced at runtime + QStringLiteral("-j ACCEPT") + }, kRawTable); + + + // Insert our fitler root chain at the top of the OUTPUT chain. + linkChain(Both, kRootChain, kOutputChain, true, kFilterTable); + + // Insert our NAT root chain at the top of the POSTROUTING chain. + linkChain(Both, kRootChain, kPostRoutingChain, true, kNatTable); + + // Insert our Mangle root chain at the top of the OUTPUT chain. + linkChain(Both, kRootChain, kOutputChain, true, kMangleTable); + + // Insert our Raw root chain at the top of the PREROUTING chain. + linkChain(Both, kRootChain, kPreRoutingChain, true, kRawTable); + + setupTrafficSplitting(); +} + +void LinuxFirewall::uninstall() +{ + // Filter chain + unlinkChain(Both, kRootChain, kOutputChain, kFilterTable); + deleteChain(Both, kRootChain, kFilterTable); + + // Raw chain + unlinkChain(Both, kRootChain, kPreRoutingChain, kRawTable); + deleteChain(Both, kRootChain, kRawTable); + + // NAT chain + unlinkChain(Both, kRootChain, kPostRoutingChain, kNatTable); + deleteChain(Both, kRootChain, kNatTable); + + // Mangle chain + unlinkChain(Both, kRootChain, kOutputChain, kMangleTable); + deleteChain(Both, kRootChain, kMangleTable); + + // Remove filter anchors + uninstallAnchor(Both, QStringLiteral("000.allowLoopback")); + uninstallAnchor(Both, QStringLiteral("400.allowPIA")); + uninstallAnchor(IPv4, QStringLiteral("320.allowDNS")); + uninstallAnchor(Both, QStringLiteral("310.blockDNS")); + uninstallAnchor(Both, QStringLiteral("300.allowLAN")); + uninstallAnchor(Both, QStringLiteral("290.allowDHCP")); + uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6")); + uninstallAnchor(Both, QStringLiteral("200.allowVPN")); + uninstallAnchor(Both, QStringLiteral("100.blockAll")); + + // Remove Nat anchors + uninstallAnchor(Both, QStringLiteral("100.transIp"), kNatTable); + + // Remove Mangle anchors + uninstallAnchor(Both, QStringLiteral("100.tagPkts"), kMangleTable); + + // Remove Raw anchors + uninstallAnchor(Both, QStringLiteral("100.vpnTunOnly"), kRawTable); + + teardownTrafficSplitting(); + + logger.debug() << "LinuxFirewall::uninstall() complete"; +} + +bool LinuxFirewall::isInstalled() +{ + return execute(QStringLiteral("iptables -C %1 -j %2 2> /dev/null").arg(kOutputChain, kRootChain)) == 0; +} + +void LinuxFirewall::enableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName) +{ + if (ip == Both) + { + enableAnchor(IPv4, anchor, tableName); + enableAnchor(IPv6, anchor, tableName); + return; + } + const QString cmd = getCommand(ip); + const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)"); + + execute(QStringLiteral("if %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: ON' ; else echo '%2%3: OFF -> ON' ; %1 -A %5.a.%2 -j %5.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName)); +} + +void LinuxFirewall::replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName) +{ + if (ip == Both) + { + replaceAnchor(IPv4, anchor, newRule, tableName); + replaceAnchor(IPv6, anchor, newRule, tableName); + return; + } + const QString cmd = getCommand(ip); + const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)"); + + execute(QStringLiteral("%1 -R %7.%2 1 %3 -t %4 ; echo 'Replaced rule %7.%2 %5 with %6'").arg(cmd, anchor, newRule, tableName, ipStr, newRule, kAnchorName)); +} + +void LinuxFirewall::disableAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName) +{ + if (ip == Both) + { + disableAnchor(IPv4, anchor, tableName); + disableAnchor(IPv6, anchor, tableName); + return; + } + const QString cmd = getCommand(ip); + const QString ipStr = ip == IPv6 ? QStringLiteral("(IPv6)") : QStringLiteral("(IPv4)"); + execute(QStringLiteral("if ! %1 -C %5.a.%2 -j %5.%2 -t %4 2> /dev/null ; then echo '%2%3: OFF' ; else echo '%2%3: ON -> OFF' ; %1 -F %5.a.%2 -t %4; fi").arg(cmd, anchor, ipStr, tableName, kAnchorName)); +} + +bool LinuxFirewall::isAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, const QString& tableName) +{ + const QString cmd = getCommand(ip); + return execute(QStringLiteral("%1 -C %4.a.%2 -j %4.%2 -t %3 2> /dev/null").arg(cmd, anchor, tableName, kAnchorName)) == 0; +} + +void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString &anchor, bool enabled, const QString &tableName) +{ + if (enabled) + { + enableAnchor(ip, anchor, tableName); + const QString key = enabledKeyTemplate.arg(tableName, anchor); + if(anchorCallbacks.contains(key)) anchorCallbacks[key](); + } + else + { + disableAnchor(ip, anchor, tableName); + const QString key = disabledKeyTemplate.arg(tableName, anchor); + if(anchorCallbacks.contains(key)) anchorCallbacks[key](); + } +} + +void LinuxFirewall::updateDNSServers(const QStringList& servers) +{ + static QStringList existingServers {}; + + existingServers = servers; + execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName)); + for (const QString& rule : getDNSRules(servers)) + execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule)); +} + +void LinuxFirewall::updateExcludeAddrs(const QStringList& servers) +{ + static QStringList existingServers {}; + + existingServers = servers; + execute(QStringLiteral("iptables -F %1.100.blockAll").arg(kAnchorName)); + for (const QString& rule : getExcludeRule(servers)) + execute(QStringLiteral("iptables -A %1.100.blockAll %2").arg(kAnchorName, rule)); +} + + +int waitForExitCode(QProcess& process) +{ + if (!process.waitForFinished() || process.error() == QProcess::FailedToStart) + return -2; + else if (process.exitStatus() != QProcess::NormalExit) + return -1; + else + return process.exitCode(); +} + +int LinuxFirewall::execute(const QString &command, bool ignoreErrors) +{ + QProcess p; + p.start(QStringLiteral("/bin/bash"), {QStringLiteral("-c"), command}, QProcess::ReadOnly); + p.closeWriteChannel(); + + int exitCode = waitForExitCode(p); + auto out = p.readAllStandardOutput().trimmed(); + auto err = p.readAllStandardError().trimmed(); + if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors) + logger.warning() << "(" << exitCode << ") $ " << command; + else if (false) + logger.debug() << "(" << exitCode << ") $ " << command; + if (!out.isEmpty()) + logger.info() << out; + if (!err.isEmpty()) + logger.warning() << err; + return exitCode; +} + +void LinuxFirewall::setupTrafficSplitting() +{ + auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/"; + logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting"; + execute(QStringLiteral("if [ ! -d %1 ] ; then mkdir %1 ; sleep 0.1 ; echo %2 > %1/net_cls.classid ; fi").arg(cGroupDir).arg(kCGroupId)); + // Set a rule with priority 100 (lower priority than local but higher than main/default, 0 is highest priority) + execute(QStringLiteral("if ! ip rule list | grep -q %1 ; then ip rule add from all fwmark %1 lookup %2 pri 100 ; fi").arg(kPacketTag, kRtableName)); +} + +void LinuxFirewall::teardownTrafficSplitting() +{ + logger.info() << "Tearing down cgroup and routing rules"; + execute(QStringLiteral("if ip rule list | grep -q %1; then ip rule del from all fwmark %1 lookup %2 2> /dev/null ; fi").arg(kPacketTag, kRtableName)); + execute(QStringLiteral("ip route flush table %1").arg(kRtableName)); + execute(QStringLiteral("ip route flush cache")); +} diff --git a/client/platforms/linux/daemon/linuxfirewall.h b/client/platforms/linux/daemon/linuxfirewall.h new file mode 100644 index 00000000..486fafbc --- /dev/null +++ b/client/platforms/linux/daemon/linuxfirewall.h @@ -0,0 +1,73 @@ +#ifndef LINUXFIREWALL_H +#define LINUXFIREWALL_H + + +#include +#include + +// Descriptor for a set of firewall rules to be appled. +// +struct FirewallParams +{ + QStringList dnsServers; + // QSharedPointer adapter; + QVector excludeApps; // Apps to exclude if VPN exemptions are enabled + + QStringList excludeAddrs; + // The follow flags indicate which general rulesets are needed. Note that + // this is after some sanity filtering, i.e. an allow rule may be listed + // as not needed if there were no block rules preceding it. The rulesets + // should be thought of as in last-match order. + + bool blockAll; // Block all traffic by default + bool allowVPN; // Exempt traffic through VPN tunnel + bool allowDHCP; // Exempt DHCP traffic + bool blockIPv6; // Block all IPv6 traffic + bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic + bool blockDNS; // Block all DNS traffic except specified DNS servers + bool allowPIA; // Exempt PIA executables + bool allowLoopback; // Exempt loopback traffic + bool allowHnsd; // Exempt Handshake DNS traffic + bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead) +}; + +class LinuxFirewall +{ +public: + enum IPVersion { IPv4, IPv6, Both }; + // Table names + static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable; +public: + using FilterCallbackFunc = std::function; +private: + static int createChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable); + static int deleteChain(IPVersion ip, const QString& chain, const QString& tableName = kFilterTable); + static int linkChain(IPVersion ip, const QString& chain, const QString& parent, bool mustBeFirst = false, const QString& tableName = kFilterTable); + static int unlinkChain(IPVersion ip, const QString& chain, const QString& parent, const QString& tableName = kFilterTable); + static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {}); + static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable); + static QStringList getDNSRules(const QStringList& servers); + static QStringList getExcludeRule(const QStringList& servers); + static void setupTrafficSplitting(); + static void teardownTrafficSplitting(); + static int execute(const QString& command, bool ignoreErrors = false); +private: + // Chain names + static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain; + +public: + static void install(); + static void uninstall(); + static bool isInstalled(); + static void ensureRootAnchorPriority(IPVersion ip = Both); + static void enableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable); + static void disableAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable); + static bool isAnchorEnabled(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable); + static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable); + static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName); + static void updateDNSServers(const QStringList& servers); + static void updateExcludeAddrs(const QStringList& servers); +}; + + +#endif // LINUXFIREWALL_H diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 792120a7..79f7dd2d 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -11,7 +11,9 @@ #include #include #include +#include +#include "linuxfirewall.h" #include "leakdetector.h" #include "logger.h" @@ -117,6 +119,12 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); } + + FirewallParams params {}; + params.dnsServers.append(config.m_dnsServer); + params.excludeAddrs.append(config.m_serverIpv4AddrIn); + applyFirewallRules(params); + return (err == 0); } @@ -140,6 +148,9 @@ bool WireguardUtilsLinux::deleteInterface() { // Garbage collect. QDir wgRuntimeDir(WG_RUNTIME_DIR); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); + + // double-check + ensure our firewall is installed and enabled + LinuxFirewall::uninstall(); return true; } @@ -252,6 +263,34 @@ QList WireguardUtilsLinux::getPeerStatus() { return peerList; } + +void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params) +{ + // double-check + ensure our firewall is installed and enabled + if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); + + // Note: rule precedence is handled inside IpTablesFirewall + LinuxFirewall::ensureRootAnchorPriority(); + + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true); + LinuxFirewall::updateDNSServers(params.dnsServers); + LinuxFirewall::updateExcludeAddrs(params.excludeAddrs); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); + + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, + QStringLiteral("100.vpnTunOnly"), + true, + LinuxFirewall::kRawTable); + +} + bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) { if (!m_rtmonitor) { return false; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.h b/client/platforms/linux/daemon/wireguardutilslinux.h index a8320c95..9746ea4b 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.h +++ b/client/platforms/linux/daemon/wireguardutilslinux.h @@ -8,8 +8,11 @@ #include #include + #include "daemon/wireguardutils.h" #include "linuxroutemonitor.h" +#include "linuxfirewall.h" + class WireguardUtilsLinux final : public WireguardUtils { Q_OBJECT @@ -34,7 +37,7 @@ public: bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; - + void applyFirewallRules(FirewallParams& params); signals: void backendFailure(); diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index c7cc839d..0c12271c 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -55,7 +55,7 @@ void OpenVpnProtocol::stop() m_managementServer.stop(); } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) IpcClient::Interface()->disableKillSwitch(); #endif @@ -338,12 +338,15 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) for (int j=0; j < netInterfaces.at(i).addressEntries().size(); j++) { if (m_vpnLocalAddress == netInterfaces.at(i).addressEntries().at(j).ip().toString()) { - IpcClient::Interface()->enableKillSwitch(netInterfaces.at(i).index()); + IpcClient::Interface()->enableKillSwitch(QJsonObject(), netInterfaces.at(i).index()); m_configData.insert("vpnGateway", m_vpnGateway); IpcClient::Interface()->enablePeerTraffic(m_configData); } } } +#endif +#ifdef Q_OS_LINUX + IpcClient::Interface()->enableKillSwitch(m_configData, 0); #endif qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index faf70079..67520834 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -24,8 +24,8 @@ class IpcInterface SLOT( bool copyWireguardConfig(const QString &sourcePath) ); SLOT( bool isWireguardRunning() ); SLOT( bool isWireguardConfigExists(const QString &configPath) ); - SLOT( bool enableKillSwitch(int vpnAdapterIndex) ); SLOT( bool disableKillSwitch() ); - SLOT( bool enablePeerTraffic(const QJsonObject &configStr)); + SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); + SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 4517a5bf..5fb32101 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -12,7 +12,10 @@ #ifdef Q_OS_WIN #include "tapcontroller_win.h" #include "../client/platforms/windows/daemon/windowsfirewall.h" +#endif +#ifdef Q_OS_LINUX +#include "../client/platforms/linux/daemon/linuxfirewall.h" #endif IpcServer::IpcServer(QObject *parent): @@ -217,21 +220,44 @@ bool IpcServer::isWireguardRunning() #endif } -bool IpcServer::isWireguardConfigExists(const QString &configPath) -{ -#ifdef MZ_DEBUG - qDebug() << "IpcServer::isWireguardConfigExists"; -#endif - - return QFileInfo::exists(configPath); -} - -bool IpcServer::enableKillSwitch(int vpnAdapterIndex) +bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex); #endif + + // double-check + ensure our firewall is installed and enabled + if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); + + // Note: rule precedence is handled inside IpTablesFirewall + LinuxFirewall::ensureRootAnchorPriority(); + + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + // LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true); + QStringList serverAddr; + serverAddr.append(configStr.value(amnezia::config_key::hostName).toString()); + LinuxFirewall::updateExcludeAddrs(serverAddr); + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + dnsServers.append("127.0.0.1"); + dnsServers.append("127.0.0.53"); + LinuxFirewall::updateDNSServers(dnsServers); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); + + // LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, + // QStringLiteral("100.vpnTunOnly"), + // true, + // LinuxFirewall::kRawTable); return true; + + } bool IpcServer::disableKillSwitch() @@ -239,6 +265,8 @@ bool IpcServer::disableKillSwitch() #ifdef Q_OS_WIN return WindowsFirewall::instance()->disableKillSwitch(); #endif + + LinuxFirewall::uninstall(); return true; } diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 1508049a..9a68ed5b 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -30,9 +30,9 @@ public: virtual bool copyWireguardConfig(const QString &sourcePath) override; virtual bool isWireguardRunning() override; virtual bool isWireguardConfigExists(const QString &configPath) override; - virtual bool enableKillSwitch(int vpnAdapterIndex) override; - virtual bool disableKillSwitch() override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override; + virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; + virtual bool disableKillSwitch() override; private: int m_localpid = 0; diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index e34f5ca3..31bf5d3d 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -223,6 +223,8 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp ) endif() From 1a17f2956a6bf7235a6136c6e36a2b4893c935f8 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 16 Dec 2023 10:05:54 -0500 Subject: [PATCH 03/27] Fix build action --- ipc/ipc_interface.rep | 3 --- ipc/ipcserver.cpp | 55 ++++--------------------------------------- ipc/ipcserver.h | 3 --- 3 files changed, 4 insertions(+), 57 deletions(-) diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 67520834..b7305250 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -21,9 +21,6 @@ class IpcInterface SLOT( void cleanUp() ); SLOT( void setLogsEnabled(bool enabled) ); - SLOT( bool copyWireguardConfig(const QString &sourcePath) ); - SLOT( bool isWireguardRunning() ); - SLOT( bool isWireguardConfigExists(const QString &configPath) ); SLOT( bool disableKillSwitch() ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 5fb32101..cc918629 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -170,55 +170,6 @@ void IpcServer::setLogsEnabled(bool enabled) } } -bool IpcServer::copyWireguardConfig(const QString &sourcePath) -{ -#ifdef MZ_DEBUG - qDebug() << "IpcServer::copyWireguardConfig"; -#endif - -#ifdef Q_OS_LINUX - const QString wireguardConfigPath = "/etc/wireguard/wg99.conf"; - if (QFile::exists(wireguardConfigPath)) - { - QFile::remove(wireguardConfigPath); - } - - if (!QFile::copy(sourcePath, wireguardConfigPath)) { - qDebug() << "WireguardProtocol::WireguardProtocol error occurred while copying wireguard config:"; - return false; - } - return true; -#else - return false; -#endif -} - -bool IpcServer::isWireguardRunning() -{ -#ifdef MZ_DEBUG - qDebug() << "IpcServer::isWireguardRunning"; -#endif - -#ifdef Q_OS_LINUX - QProcess checkWireguardStatusProcess; - - connect(&checkWireguardStatusProcess, &QProcess::errorOccurred, this, [](QProcess::ProcessError error) { - qDebug() << "WireguardProtocol::WireguardProtocol error occurred while checking wireguard status: " << error; - }); - - checkWireguardStatusProcess.setProgram("/bin/wg"); - checkWireguardStatusProcess.setArguments(QStringList{"show"}); - checkWireguardStatusProcess.start(); - checkWireguardStatusProcess.waitForFinished(10000); - QString output = checkWireguardStatusProcess.readAllStandardOutput(); - if (!output.isEmpty()) { - return true; - } - return false; -#else - return false; -#endif -} bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { @@ -226,6 +177,7 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex); #endif +#ifdef Q_OS_LINUX // double-check + ensure our firewall is installed and enabled if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); @@ -255,9 +207,8 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd // QStringLiteral("100.vpnTunOnly"), // true, // LinuxFirewall::kRawTable); +#endif return true; - - } bool IpcServer::disableKillSwitch() @@ -266,7 +217,9 @@ bool IpcServer::disableKillSwitch() return WindowsFirewall::instance()->disableKillSwitch(); #endif +#ifdef Q_OS_LINUX LinuxFirewall::uninstall(); +#endif return true; } diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 9a68ed5b..20bbb191 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -27,9 +27,6 @@ public: virtual QStringList getTapList() override; virtual void cleanUp() override; virtual void setLogsEnabled(bool enabled) override; - virtual bool copyWireguardConfig(const QString &sourcePath) override; - virtual bool isWireguardRunning() override; - virtual bool isWireguardConfigExists(const QString &configPath) override; virtual bool enablePeerTraffic(const QJsonObject &configStr) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; From 3d2174d84efeff3f39093d75716dd4a6af5d2c44 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 23 Dec 2023 12:51:55 +0200 Subject: [PATCH 04/27] MacOS WG/AWG killswitch --- .../platforms/macos/daemon/macosfirewall.cpp | 167 ++++++++++++++++++ client/platforms/macos/daemon/macosfirewall.h | 63 +++++++ .../macos/daemon/wireguardutilsmacos.cpp | 50 ++++++ .../macos/daemon/wireguardutilsmacos.h | 2 + client/protocols/openvpnovercloakprotocol.cpp | 2 +- client/protocols/openvpnprotocol.cpp | 4 +- client/protocols/wireguardprotocol.cpp | 39 ---- client/protocols/wireguardprotocol.h | 9 - .../data/macos/pf/amn.000.allowLoopback.conf | 3 + deploy/data/macos/pf/amn.100.blockAll.conf | 3 + deploy/data/macos/pf/amn.110.allowNets.conf | 2 + deploy/data/macos/pf/amn.120.blockNets.conf | 2 + .../macos/pf/amn.150.allowExcludedApps.conf | 2 + deploy/data/macos/pf/amn.200.allowVPN.conf | 9 + deploy/data/macos/pf/amn.250.blockIPv6.conf | 2 + deploy/data/macos/pf/amn.290.allowDHCP.conf | 5 + deploy/data/macos/pf/amn.300.allowLAN.conf | 3 + deploy/data/macos/pf/amn.310.blockDNS.conf | 7 + deploy/data/macos/pf/amn.350.allowHnsd.conf | 14 ++ deploy/data/macos/pf/amn.400.allowPIA.conf | 2 + deploy/data/macos/pf/amn.conf | 16 ++ ipc/ipcserver.cpp | 40 +++++ service/server/CMakeLists.txt | 2 + 23 files changed, 397 insertions(+), 51 deletions(-) create mode 100644 client/platforms/macos/daemon/macosfirewall.cpp create mode 100644 client/platforms/macos/daemon/macosfirewall.h create mode 100644 deploy/data/macos/pf/amn.000.allowLoopback.conf create mode 100644 deploy/data/macos/pf/amn.100.blockAll.conf create mode 100644 deploy/data/macos/pf/amn.110.allowNets.conf create mode 100644 deploy/data/macos/pf/amn.120.blockNets.conf create mode 100644 deploy/data/macos/pf/amn.150.allowExcludedApps.conf create mode 100644 deploy/data/macos/pf/amn.200.allowVPN.conf create mode 100644 deploy/data/macos/pf/amn.250.blockIPv6.conf create mode 100644 deploy/data/macos/pf/amn.290.allowDHCP.conf create mode 100644 deploy/data/macos/pf/amn.300.allowLAN.conf create mode 100644 deploy/data/macos/pf/amn.310.blockDNS.conf create mode 100644 deploy/data/macos/pf/amn.350.allowHnsd.conf create mode 100644 deploy/data/macos/pf/amn.400.allowPIA.conf create mode 100644 deploy/data/macos/pf/amn.conf diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp new file mode 100644 index 00000000..f72b0efc --- /dev/null +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -0,0 +1,167 @@ +#include "macosfirewall.h" +#include "logger.h" +#include +#include + +#define BRAND_IDENTIFIER "amn" + +namespace { + Logger logger("MacOSFirewall"); +} // namespace + +#include "macosfirewall.h" + +#define ResourceDir "./pf" +#define DaemonDataDir "./pf" + +#include + +static QString kRootAnchor = QStringLiteral(BRAND_IDENTIFIER); +static QByteArray kPfWarning = "pfctl: Use of -f option, could result in flushing of rules\npresent in the main ruleset added by the system at startup.\nSee /etc/pf.conf for further details.\n"; + +int waitForExitCode(QProcess& process) +{ + if (!process.waitForFinished() || process.error() == QProcess::FailedToStart) + return -2; + else if (process.exitStatus() != QProcess::NormalExit) + return -1; + else + return process.exitCode(); +} + +int MacOSFirewall::execute(const QString& command, bool ignoreErrors) +{ + QProcess p; + + p.start(QStringLiteral("/bin/bash"), { QStringLiteral("-c"), command }, QProcess::ReadOnly); + p.closeWriteChannel(); + int exitCode = waitForExitCode(p); + auto out = p.readAllStandardOutput().trimmed(); + + auto err = p.readAllStandardError().replace(kPfWarning, "").trimmed(); + if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors) + logger.info() << "(" << exitCode << ") $ " << command; + else if (false) + logger.info() << "(" << exitCode << ") $ " << command; + if (!out.isEmpty()) logger.info() << out; + if (!err.isEmpty()) logger.info() << err; + return exitCode; +} + +void MacOSFirewall::installRootAnchors() +{ + logger.info() << "Installing PF root anchors"; + + // Append our NAT anchors by reading back and re-applying NAT rules only + auto insertNatAnchors = QStringLiteral( + "( " + R"(pfctl -sn | grep -v '%1/*'; )" // Translation rules (includes both nat and rdr, despite the modifier being 'nat') + R"(echo 'nat-anchor "%2/*"'; )" // PIA's translation anchors + R"(echo 'rdr-anchor "%3/*"'; )" + R"(echo 'load anchor "%4" from "%5/%6.conf"'; )" // Load the PIA anchors from file + ") | pfctl -N -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor); + + execute(insertNatAnchors); + + // Append our filter anchor by reading back and re-applying filter rules + // only. pfctl -sr also includes scrub rules, but these will be ignored + // due to -R. + auto insertFilterAnchor = QStringLiteral( + "( " + R"(pfctl -sr | grep -v '%1/*'; )" // Filter rules (everything from pfctl -sr except 'scrub') + R"(echo 'anchor "%2/*"'; )" // PIA's filter anchors + R"(echo 'load anchor "%3" from "%4/%5.conf"'; )" // Load the PIA anchors from file + " ) | pfctl -R -f -").arg(kRootAnchor, kRootAnchor, kRootAnchor, ResourceDir, kRootAnchor); + execute(insertFilterAnchor); +} + +void MacOSFirewall::install() +{ + // remove hard-coded (legacy) pia anchor from /etc/pf.conf if it exists + execute(QStringLiteral("if grep -Fq '%1' /etc/pf.conf ; then echo \"`cat /etc/pf.conf | grep -vF '%1'`\" > /etc/pf.conf ; fi").arg(kRootAnchor)); + + // Clean up any existing rules if they exist. + uninstall(); + + timespec waitTime{0, 10'000'000}; + ::nanosleep(&waitTime, nullptr); + + logger.info() << "Installing PF root anchor"; + + installRootAnchors(); + execute(QStringLiteral("pfctl -E 2>&1 | grep -F 'Token : ' | cut -c9- > '%1/pf.token'").arg(DaemonDataDir)); +} + + +void MacOSFirewall::uninstall() +{ + logger.info() << "Uninstalling PF root anchor"; + + execute(QStringLiteral("pfctl -q -a '%1' -F all").arg(kRootAnchor)); + execute(QStringLiteral("test -f '%1/pf.token' && pfctl -X `cat '%1/pf.token'` && rm '%1/pf.token'").arg(DaemonDataDir)); + execute(QStringLiteral("test -f /etc/pf.conf && pfctl -F all -f /etc/pf.conf")); +} + +bool MacOSFirewall::isInstalled() +{ + return isPFEnabled() && isRootAnchorLoaded(); +} + +bool MacOSFirewall::isPFEnabled() +{ + return 0 == execute(QStringLiteral("test -s '%1/pf.token' && pfctl -s References | grep -qFf '%1/pf.token'").arg(DaemonDataDir), true); +} + +void MacOSFirewall::ensureRootAnchorPriority() +{ + // We check whether our anchor appears last in the ruleset. If it does not, then remove it and re-add it last (this happens atomically). + // Appearing last ensures priority. + execute(QStringLiteral("if ! pfctl -sr | tail -1 | grep -qF '%1'; then echo -e \"$(pfctl -sr | grep -vF '%1')\\n\"'anchor \"%1\"' | pfctl -f - ; fi").arg(kRootAnchor)); +} + +bool MacOSFirewall::isRootAnchorLoaded() +{ + // Our Root anchor is loaded if: + // 1. It is is included among the top-level anchors + // 2. It is not empty (i.e it contains sub-anchors) + return 0 == execute(QStringLiteral("pfctl -sr | grep -q '%1' && pfctl -q -a '%1' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor), true); +} + +void MacOSFirewall::enableAnchor(const QString& anchor) +{ + execute(QStringLiteral("if pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: ON' ; else echo '%2: OFF -> ON' ; pfctl -q -a '%1/%2' -F all -f '%3/%1.%2.conf' ; fi").arg(kRootAnchor, anchor, ResourceDir)); +} + +void MacOSFirewall::disableAnchor(const QString& anchor) +{ + execute(QStringLiteral("if ! pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q . ; then echo '%2: OFF' ; else echo '%2: ON -> OFF' ; pfctl -q -a '%1/%2' -F all ; fi").arg(kRootAnchor, anchor)); +} + +bool MacOSFirewall::isAnchorEnabled(const QString& anchor) +{ + return 0 == execute(QStringLiteral("pfctl -q -a '%1/%2' -s rules 2> /dev/null | grep -q .").arg(kRootAnchor, anchor), true); +} + +void MacOSFirewall::setAnchorEnabled(const QString& anchor, bool enabled) +{ + if (enabled) + enableAnchor(anchor); + else + disableAnchor(anchor); +} + +void MacOSFirewall::setAnchorTable(const QString& anchor, bool enabled, const QString& table, const QStringList& items) +{ + if (enabled) + execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T replace %4").arg(kRootAnchor, anchor, table, items.join(' '))); + else + execute(QStringLiteral("pfctl -q -a '%1/%2' -t '%3' -T kill").arg(kRootAnchor, anchor, table), true); +} + +void MacOSFirewall::setAnchorWithRules(const QString& anchor, bool enabled, const QStringList &ruleList) +{ + if (!enabled) + return (void)execute(QStringLiteral("pfctl -q -a '%1/%2' -F rules").arg(kRootAnchor, anchor), true); + else + return (void)execute(QStringLiteral("echo -e \"%1\" | pfctl -q -a '%2/%3' -f -").arg(ruleList.join('\n'), kRootAnchor, anchor), true); +} diff --git a/client/platforms/macos/daemon/macosfirewall.h b/client/platforms/macos/daemon/macosfirewall.h new file mode 100644 index 00000000..8b13d363 --- /dev/null +++ b/client/platforms/macos/daemon/macosfirewall.h @@ -0,0 +1,63 @@ +#ifndef MACOSFIREWALL_H +#define MACOSFIREWALL_H + +#include +#include + +// Descriptor for a set of firewall rules to be appled. +// +struct FirewallParams +{ + QStringList dnsServers; + // QSharedPointer adapter; + QVector excludeApps; // Apps to exclude if VPN exemptions are enabled + + QStringList allowAddrs; + QStringList blockAddrs; + + // The follow flags indicate which general rulesets are needed. Note that + // this is after some sanity filtering, i.e. an allow rule may be listed + // as not needed if there were no block rules preceding it. The rulesets + // should be thought of as in last-match order. + + bool blockAll; // Block all traffic by default + bool blockNets; + bool allowNets; + bool allowVPN; // Exempt traffic through VPN tunnel + bool allowDHCP; // Exempt DHCP traffic + bool blockIPv6; // Block all IPv6 traffic + bool allowLAN; // Exempt LAN traffic, including IPv6 LAN traffic + bool blockDNS; // Block all DNS traffic except specified DNS servers + bool allowPIA; // Exempt PIA executables + bool allowLoopback; // Exempt loopback traffic + bool allowHnsd; // Exempt Handshake DNS traffic + bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead) +}; + +// TODO: Break out firewall handling to a base class that can be used directly +// by the base daemon class, for some common functionality. + +class MacOSFirewall +{ + +private: + static int execute(const QString &command, bool ignoreErrors = false); + static bool isPFEnabled(); + static bool isRootAnchorLoaded(); + +public: + static void install(); + static void uninstall(); + static bool isInstalled(); + static void enableAnchor(const QString &anchor); + static void disableAnchor(const QString &anchor); + static bool isAnchorEnabled(const QString &anchor); + static void setAnchorEnabled(const QString &anchor, bool enable); + static void setAnchorTable(const QString &anchor, bool enabled, const QString &table, const QStringList &items); + static void setAnchorWithRules(const QString &anchor, bool enabled, const QStringList &rules); + static void ensureRootAnchorPriority(); + static void installRootAnchors(); +}; + + +#endif // MACOSFIREWALL_H diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.cpp b/client/platforms/macos/daemon/wireguardutilsmacos.cpp index ef13f4c7..718edaba 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.cpp +++ b/client/platforms/macos/daemon/wireguardutilsmacos.cpp @@ -114,9 +114,30 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) { } int err = uapiErrno(uapiCommand(message)); + if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); + } else { + FirewallParams params { }; + params.dnsServers.append(config.m_dnsServer); + if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){ + params.blockAll = true; + if (config.m_excludedAddresses.size()) { + params.allowNets = true; + foreach (auto net, config.m_excludedAddresses) { + params.allowAddrs.append(net.toUtf8()); + } + } + } else { + params.blockNets = true; + foreach (auto net, config.m_allowedIPAddressRanges) { + params.blockAddrs.append(net.toString()); + } + } + + applyFirewallRules(params); } + return (err == 0); } @@ -140,6 +161,10 @@ bool WireguardUtilsMacos::deleteInterface() { // Garbage collect. QDir wgRuntimeDir(WG_RUNTIME_DIR); QFile::remove(wgRuntimeDir.filePath(QString(WG_INTERFACE) + ".name")); + + // double-check + ensure our firewall is installed and enabled + MacOSFirewall::uninstall(); + return true; } @@ -302,6 +327,31 @@ bool WireguardUtilsMacos::addExclusionRoute(const IPAddress& prefix) { return m_rtmonitor->addExclusionRoute(prefix); } +void WireguardUtilsMacos::applyFirewallRules(FirewallParams& params) +{ + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), params.blockAll); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), params.allowNets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), params.allowNets, + QStringLiteral("allownets"), params.allowAddrs); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), params.blockNets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), params.blockNets, + QStringLiteral("blocknets"), params.blockAddrs); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), params.dnsServers); +} + bool WireguardUtilsMacos::deleteExclusionRoute(const IPAddress& prefix) { if (!m_rtmonitor) { return false; diff --git a/client/platforms/macos/daemon/wireguardutilsmacos.h b/client/platforms/macos/daemon/wireguardutilsmacos.h index aa9f19eb..243f4b64 100644 --- a/client/platforms/macos/daemon/wireguardutilsmacos.h +++ b/client/platforms/macos/daemon/wireguardutilsmacos.h @@ -10,6 +10,7 @@ #include "daemon/wireguardutils.h" #include "macosroutemonitor.h" +#include "macosfirewall.h" class WireguardUtilsMacos final : public WireguardUtils { Q_OBJECT @@ -34,6 +35,7 @@ class WireguardUtilsMacos final : public WireguardUtils { bool addExclusionRoute(const IPAddress& prefix) override; bool deleteExclusionRoute(const IPAddress& prefix) override; + void applyFirewallRules(FirewallParams& params); signals: void backendFailure(); diff --git a/client/protocols/openvpnovercloakprotocol.cpp b/client/protocols/openvpnovercloakprotocol.cpp index 7000e5ef..706e651a 100644 --- a/client/protocols/openvpnovercloakprotocol.cpp +++ b/client/protocols/openvpnovercloakprotocol.cpp @@ -66,7 +66,7 @@ ErrorCode OpenVpnOverCloakProtocol::start() emit protocolError(amnezia::ErrorCode::CloakExecutableCrashed); stop(); } - if (exitCode !=0 ){ + if (exitCode !=0 ) { emit protocolError(amnezia::ErrorCode::InternalError); stop(); } diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index 0c12271c..f9196fc3 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -55,7 +55,7 @@ void OpenVpnProtocol::stop() m_managementServer.stop(); } -#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) +#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS) IpcClient::Interface()->disableKillSwitch(); #endif @@ -345,7 +345,7 @@ void OpenVpnProtocol::updateVpnGateway(const QString &line) } } #endif -#ifdef Q_OS_LINUX +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) IpcClient::Interface()->enableKillSwitch(m_configData, 0); #endif qDebug() << QString("Set vpn local address %1, gw %2").arg(m_vpnLocalAddress).arg(vpnGateway()); diff --git a/client/protocols/wireguardprotocol.cpp b/client/protocols/wireguardprotocol.cpp index 3b95a41a..61b2e261 100644 --- a/client/protocols/wireguardprotocol.cpp +++ b/client/protocols/wireguardprotocol.cpp @@ -13,9 +13,6 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) { - m_configFile.setFileName(QDir::tempPath() + QDir::separator() + serviceName() + ".conf"); - writeWireguardConfiguration(configuration); - m_impl.reset(new LocalSocketController()); connect(m_impl.get(), &ControllerImpl::connected, this, [this](const QString &pubkey, const QDateTime &connectionTimestamp) { @@ -50,45 +47,9 @@ ErrorCode WireguardProtocol::stopMzImpl() return ErrorCode::NoError; } -void WireguardProtocol::writeWireguardConfiguration(const QJsonObject &configuration) -{ - QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject(); - - if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { - qCritical() << "Failed to save wireguard config to" << m_configFile.fileName(); - return; - } - - m_configFile.write(jConfig.value(config_key::config).toString().toUtf8()); - m_configFile.close(); - - - m_configFileName = m_configFile.fileName(); - - m_isConfigLoaded = true; - - qDebug().noquote() << QString("Set config data") << configPath(); - qDebug().noquote() << QString("Set config data") - << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8(); -} - -QString WireguardProtocol::configPath() const -{ - return m_configFileName; -} - -QString WireguardProtocol::serviceName() const -{ - return "AmneziaVPN.WireGuard0"; -} ErrorCode WireguardProtocol::start() { - if (!m_isConfigLoaded) { - setLastError(ErrorCode::ConfigMissing); - return lastError(); - } - return startMzImpl(); } diff --git a/client/protocols/wireguardprotocol.h b/client/protocols/wireguardprotocol.h index 4a6ae1e6..6d1a0518 100644 --- a/client/protocols/wireguardprotocol.h +++ b/client/protocols/wireguardprotocol.h @@ -26,15 +26,6 @@ public: ErrorCode stopMzImpl(); private: - QString configPath() const; - void writeWireguardConfiguration(const QJsonObject &configuration); - QString serviceName() const; - -private: - QString m_configFileName; - QFile m_configFile; - - bool m_isConfigLoaded = false; QScopedPointer m_impl; }; diff --git a/deploy/data/macos/pf/amn.000.allowLoopback.conf b/deploy/data/macos/pf/amn.000.allowLoopback.conf new file mode 100644 index 00000000..3c85b9c3 --- /dev/null +++ b/deploy/data/macos/pf/amn.000.allowLoopback.conf @@ -0,0 +1,3 @@ +# Always allow at least loopback/localhost traffic +set skip on lo0 +pass quick on lo0 flags any diff --git a/deploy/data/macos/pf/amn.100.blockAll.conf b/deploy/data/macos/pf/amn.100.blockAll.conf new file mode 100644 index 00000000..32182ed9 --- /dev/null +++ b/deploy/data/macos/pf/amn.100.blockAll.conf @@ -0,0 +1,3 @@ +# Block all traffic by default (can be overridden by later rules) +block out all flags any no state + diff --git a/deploy/data/macos/pf/amn.110.allowNets.conf b/deploy/data/macos/pf/amn.110.allowNets.conf new file mode 100644 index 00000000..6fed3b46 --- /dev/null +++ b/deploy/data/macos/pf/amn.110.allowNets.conf @@ -0,0 +1,2 @@ +table {} +pass out to flags any no state diff --git a/deploy/data/macos/pf/amn.120.blockNets.conf b/deploy/data/macos/pf/amn.120.blockNets.conf new file mode 100644 index 00000000..028f1c4f --- /dev/null +++ b/deploy/data/macos/pf/amn.120.blockNets.conf @@ -0,0 +1,2 @@ +table {} +block out to flags any no state diff --git a/deploy/data/macos/pf/amn.150.allowExcludedApps.conf b/deploy/data/macos/pf/amn.150.allowExcludedApps.conf new file mode 100644 index 00000000..57e15e99 --- /dev/null +++ b/deploy/data/macos/pf/amn.150.allowExcludedApps.conf @@ -0,0 +1,2 @@ +# Rules are set at runtime + diff --git a/deploy/data/macos/pf/amn.200.allowVPN.conf b/deploy/data/macos/pf/amn.200.allowVPN.conf new file mode 100644 index 00000000..6e1b74bc --- /dev/null +++ b/deploy/data/macos/pf/amn.200.allowVPN.conf @@ -0,0 +1,9 @@ +# Exempt the tunnel interface(s) used by the VPN connection + +utunInterfaces = "{ \ + utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \ + utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \ + utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \ +}" + +pass out on $utunInterfaces flags any no state diff --git a/deploy/data/macos/pf/amn.250.blockIPv6.conf b/deploy/data/macos/pf/amn.250.blockIPv6.conf new file mode 100644 index 00000000..f48d8886 --- /dev/null +++ b/deploy/data/macos/pf/amn.250.blockIPv6.conf @@ -0,0 +1,2 @@ +# Block all outgoing IPv6 traffic (even over the VPN) +block return out inet6 flags any no state diff --git a/deploy/data/macos/pf/amn.290.allowDHCP.conf b/deploy/data/macos/pf/amn.290.allowDHCP.conf new file mode 100644 index 00000000..5d92105d --- /dev/null +++ b/deploy/data/macos/pf/amn.290.allowDHCP.conf @@ -0,0 +1,5 @@ +# Allow DHCP +pass out inet proto udp from port 68 to 255.255.255.255 port 67 no state + +# Allow DHCPv6 +pass out inet6 proto udp from port 546 to ff00::/8 port 547 no state diff --git a/deploy/data/macos/pf/amn.300.allowLAN.conf b/deploy/data/macos/pf/amn.300.allowLAN.conf new file mode 100644 index 00000000..0ee82265 --- /dev/null +++ b/deploy/data/macos/pf/amn.300.allowLAN.conf @@ -0,0 +1,3 @@ +# Allow LAN IP ranges +table { 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, 224.0.0.0/4, 255.255.255.255/32, fc00::/7, fe80::/10, ff00::/8 } +pass out to flags any no state diff --git a/deploy/data/macos/pf/amn.310.blockDNS.conf b/deploy/data/macos/pf/amn.310.blockDNS.conf new file mode 100644 index 00000000..b7ca429b --- /dev/null +++ b/deploy/data/macos/pf/amn.310.blockDNS.conf @@ -0,0 +1,7 @@ +# Block all DNS traffic +block return out proto { tcp, udp } to port 53 flags any no state + +# Allow our DNS servers +table {} +pass out proto { tcp, udp } to port 53 flags any no state + diff --git a/deploy/data/macos/pf/amn.350.allowHnsd.conf b/deploy/data/macos/pf/amn.350.allowHnsd.conf new file mode 100644 index 00000000..3565ffcf --- /dev/null +++ b/deploy/data/macos/pf/amn.350.allowHnsd.conf @@ -0,0 +1,14 @@ +utunInterfaces = "{ \ + utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \ + utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \ + utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \ +}" + +hnsdGroup=amnhnsd + +# Block everything from handshake group +# Without this initial block hnsd traffic could possibly travel outside the tunnel (we don't trust the routing table) +block return out group $hnsdGroup flags any no state + +# Next, poke a hole in this block but only for traffic on the tunnel (port 13038 is the handshake control port) +pass out on $utunInterfaces proto { tcp, udp } to port { 53, 13038 } group $hnsdGroup flags any no state diff --git a/deploy/data/macos/pf/amn.400.allowPIA.conf b/deploy/data/macos/pf/amn.400.allowPIA.conf new file mode 100644 index 00000000..7c8a3680 --- /dev/null +++ b/deploy/data/macos/pf/amn.400.allowPIA.conf @@ -0,0 +1,2 @@ +# Allow traffic by privileged group (used by daemon) +pass out proto { tcp, udp } group { amnvpn } flags any no state diff --git a/deploy/data/macos/pf/amn.conf b/deploy/data/macos/pf/amn.conf new file mode 100644 index 00000000..224017d2 --- /dev/null +++ b/deploy/data/macos/pf/amn.conf @@ -0,0 +1,16 @@ +# This root anchor file establishes multiple sub-anchors which can be +# individually turned on or off; they have a numeric prefix in order to +# produce a well-defined alphabetical order. + +anchor "000.allowLoopback" +anchor "100.blockAll" +anchor "110.allowNets" +anchor "120.blockNets" +anchor "150.allowExcludedApps" +anchor "200.allowVPN" +anchor "250.blockIPv6" +anchor "290.allowDHCP" +anchor "300.allowLAN" +anchor "310.blockDNS" +anchor "350.allowHnsd" +anchor "400.allowPIA" diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index cc918629..580cf1a9 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -18,6 +18,10 @@ #include "../client/platforms/linux/daemon/linuxfirewall.h" #endif +#ifdef Q_OS_MACOS +#include "../client/platforms/macos/daemon/macosfirewall.h" +#endif + IpcServer::IpcServer(QObject *parent): IpcInterfaceSource(parent) {} @@ -208,6 +212,37 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd // true, // LinuxFirewall::kRawTable); #endif + +#ifdef Q_OS_MACOS + + if (configStr.value(amnezia::config_key::splitTunnelType) == 0) { + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), false); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), false, + QStringLiteral("allownets"), QStringList()); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), false); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), false, + QStringLiteral("blocknets"), QStringList()); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); + } +#endif + return true; } @@ -220,6 +255,11 @@ bool IpcServer::disableKillSwitch() #ifdef Q_OS_LINUX LinuxFirewall::uninstall(); #endif + +#ifdef Q_OS_MACOS + MacOSFirewall::uninstall(); +#endif + return true; } diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 31bf5d3d..985186f0 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -182,6 +182,7 @@ if(APPLE) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.h ) set(SOURCES ${SOURCES} @@ -195,6 +196,7 @@ if(APPLE) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosroutemonitor.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/wireguardutilsmacos.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/macos/daemon/macosfirewall.cpp ) endif() From a4f3d08c029ee23df638d8187660639ca2fd89cd Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 23 Dec 2023 14:21:13 +0200 Subject: [PATCH 05/27] Fix PF config path --- client/platforms/macos/daemon/macosfirewall.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp index f72b0efc..358bd594 100644 --- a/client/platforms/macos/daemon/macosfirewall.cpp +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -11,8 +11,8 @@ namespace { #include "macosfirewall.h" -#define ResourceDir "./pf" -#define DaemonDataDir "./pf" +#define ResourceDir qApp->applicationDirPath() + "/pf" +#define DaemonDataDir qApp->applicationDirPath() + "/pf" #include From a8f5e95fb17710115eb2eeb0df9dfcafdd208f66 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 23 Dec 2023 16:04:17 +0200 Subject: [PATCH 06/27] MacOS OpenVPN/OpenVPN over Cloak killswitch --- ipc/ipcserver.cpp | 72 +++++++++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 25 deletions(-) diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 580cf1a9..49ef9b66 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -214,33 +214,55 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd #endif #ifdef Q_OS_MACOS + int splitTunnelType = configStr.value("splitTunnelType").toInt(); + QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); + bool blockAll = 0; + bool allowNets = 0; + bool blockNets = 0; + QStringList allownets; + QStringList blocknets; - if (configStr.value(amnezia::config_key::splitTunnelType) == 0) { - // double-check + ensure our firewall is installed and enabled. This is necessary as - // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) - if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); - - MacOSFirewall::ensureRootAnchorPriority(); - MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), false); - MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), false, - QStringLiteral("allownets"), QStringList()); - - MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), false); - MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), false, - QStringLiteral("blocknets"), QStringList()); - MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); - MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); - - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); - MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); + if (splitTunnelType == 0) + { + blockAll = true; + } else if (splitTunnelType == 1) + { + blockNets = true; + for (auto v : splitTunnelSites) { + blocknets.append(v.toString()); + } + } else if (splitTunnelType == 2) { + blockAll = true; + allowNets = true; + for (auto v : splitTunnelSites) { + allownets.append(v.toString()); + } } + + // double-check + ensure our firewall is installed and enabled. This is necessary as + // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) + if (!MacOSFirewall::isInstalled()) MacOSFirewall::install(); + + MacOSFirewall::ensureRootAnchorPriority(); + MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); + MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); + MacOSFirewall::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, + QStringLiteral("allownets"), allownets); + + MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets); + MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, + QStringLiteral("blocknets"), blocknets); + MacOSFirewall::setAnchorEnabled(QStringLiteral("200.allowVPN"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); + MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); + + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); + MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); #endif return true; From ec4574bfcfc8c2fd2a08e8895562962158fbf55f Mon Sep 17 00:00:00 2001 From: KsZnak Date: Mon, 22 Jan 2024 01:52:14 +0200 Subject: [PATCH 07/27] Update amneziavpn_ru.ts --- client/translations/amneziavpn_ru.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index ed580ec0..ddd7b0e9 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -416,7 +416,7 @@ Already installed containers were found on the server. All installed containers OpenVPN settings - Настройки OpenVPN + OpenVPN настройки @@ -1192,7 +1192,7 @@ Already installed containers were found on the server. All installed containers If AmneziaDNS is not used or installed - Эти серверы будут использоваться, если не включен AmneziaDNS + Эти адреса будут использоваться, если не включен AmneziaDNS @@ -2047,7 +2047,7 @@ and will not be shared or disclosed to the Amnezia or any third parties Rename - + Переименовать @@ -2062,12 +2062,12 @@ and will not be shared or disclosed to the Amnezia or any third parties Revoke - + Отозвать Revoke the config for a user - %1? - + Отозвать доступ для пользователя - %1? From 874de74ac85b7f3c1e715d836327146421dd9e8e Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 22 Jan 2024 20:32:40 +0200 Subject: [PATCH 08/27] Add exclusion for VPN Server host (MacOS/OpenVPN) --- ipc/ipcserver.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 49ef9b66..a19039f6 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -225,6 +225,8 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd if (splitTunnelType == 0) { blockAll = true; + allowNets = true; + allownets.append(configStr.value(amnezia::config_key::hostName).toString()); } else if (splitTunnelType == 1) { blockNets = true; @@ -234,6 +236,7 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd } else if (splitTunnelType == 2) { blockAll = true; allowNets = true; + allownets.append(configStr.value(amnezia::config_key::hostName).toString()); for (auto v : splitTunnelSites) { allownets.append(v.toString()); } From 4d88eb8e79c583ea93ff4ad9be96e5118b4a8c7a Mon Sep 17 00:00:00 2001 From: Igor Sorokin Date: Tue, 23 Jan 2024 18:41:33 +0300 Subject: [PATCH 09/27] Try to expand 'internal error' --- client/platforms/ios/ios_controller.mm | 62 ++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 3 deletions(-) diff --git a/client/platforms/ios/ios_controller.mm b/client/platforms/ios/ios_controller.mm index c27badda..2ff65775 100644 --- a/client/platforms/ios/ios_controller.mm +++ b/client/platforms/ios/ios_controller.mm @@ -255,20 +255,76 @@ void IosController::vpnStatusDidChange(void *pNotification) if (@available(iOS 16.0, *)) { [session fetchLastDisconnectErrorWithCompletionHandler:^(NSError * _Nullable error) { if (error != nil) { - qDebug() << "Disconnect error" << error.domain << error.localizedDescription; + qDebug() << "Disconnect error" << error.domain << error.code << error.localizedDescription; if ([error.domain isEqualToString:NEVPNConnectionErrorDomain]) { switch (error.code) { - case 1: + case NEVPNConnectionErrorOverslept: + qDebug() << "Disconnect error info" << "The VPN connection was terminated because the system slept for an extended period of time."; + break; + case NEVPNConnectionErrorNoNetworkAvailable: + qDebug() << "Disconnect error info" << "The VPN connection could not be established because the system is not connected to a network."; + break; + case NEVPNConnectionErrorUnrecoverableNetworkChange: + qDebug() << "Disconnect error info" << "The VPN connection was terminated because the network conditions changed in such a way that the VPN connection could not be maintained."; + break; + case NEVPNConnectionErrorConfigurationFailed: + qDebug() << "Disconnect error info" << "The VPN connection could not be established because the configuration is invalid. "; + break; + case NEVPNConnectionErrorServerAddressResolutionFailed: + qDebug() << "Disconnect error info" << "The address of the VPN server could not be determined."; + break; + case NEVPNConnectionErrorServerNotResponding: + qDebug() << "Disconnect error info" << "Network communication with the VPN server has failed."; + break; + case NEVPNConnectionErrorServerDead: + qDebug() << "Disconnect error info" << "The VPN server is no longer functioning."; + break; + case NEVPNConnectionErrorAuthenticationFailed: + qDebug() << "Disconnect error info" << "The user credentials were rejected by the VPN server."; + break; + case NEVPNConnectionErrorClientCertificateInvalid: + qDebug() << "Disconnect error info" << "The client certificate is invalid."; + break; + case NEVPNConnectionErrorClientCertificateNotYetValid: + qDebug() << "Disconnect error info" << "The client certificate will not be valid until some future point in time."; + break; + case NEVPNConnectionErrorClientCertificateExpired: + qDebug() << "Disconnect error info" << "The validity period of the client certificate has passed."; + break; + case NEVPNConnectionErrorPluginFailed: + qDebug() << "Disconnect error info" << "The VPN plugin died unexpectedly."; + break; + case NEVPNConnectionErrorConfigurationNotFound: + qDebug() << "Disconnect error info" << "The VPN configuration could not be found."; + break; + case NEVPNConnectionErrorPluginDisabled: + qDebug() << "Disconnect error info" << "The VPN plugin could not be found or needed to be updated."; + break; + case NEVPNConnectionErrorNegotiationFailed: + qDebug() << "Disconnect error info" << "The VPN protocol negotiation failed."; + break; + case NEVPNConnectionErrorServerDisconnected: + qDebug() << "Disconnect error info" << "The VPN server terminated the connection."; + break; + case NEVPNConnectionErrorServerCertificateInvalid: + qDebug() << "Disconnect error info" << "The server certificate is invalid."; + break; + case NEVPNConnectionErrorServerCertificateNotYetValid: + qDebug() << "Disconnect error info" << "The server certificate will not be valid until some future point in time."; + break; + case NEVPNConnectionErrorServerCertificateExpired: + qDebug() << "Disconnect error info" << "The validity period of the server certificate has passed."; break; default: + qDebug() << "Disconnect error info" << "Unknown code."; break; } } NSError *underlyingError = error.userInfo[@"NSUnderlyingError"]; if (underlyingError != nil) { - qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.localizedDescription; + qDebug() << "Disconnect underlying error" << underlyingError.domain << underlyingError.code << underlyingError.localizedDescription; if ([underlyingError.domain isEqualToString:@"NEAgentErrorDomain"]) { switch (underlyingError.code) { From f55bd5e1a11a0d17c9116c7a9d5e652bf1815837 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 23 Jan 2024 23:56:16 +0700 Subject: [PATCH 10/27] fixed first container selection on HomeContainersListView after server cleanup --- client/ui/qml/Components/HomeContainersListView.qml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ui/qml/Components/HomeContainersListView.qml b/client/ui/qml/Components/HomeContainersListView.qml index 2c07ca65..3043e97f 100644 --- a/client/ui/qml/Components/HomeContainersListView.qml +++ b/client/ui/qml/Components/HomeContainersListView.qml @@ -30,7 +30,9 @@ ListView { target: ServersModel function onCurrentlyProcessedServerIndexChanged() { - menuContent.checkCurrentItem() + if (ContainersModel.getDefaultContainer()) { + menuContent.checkCurrentItem() + } } } @@ -76,9 +78,8 @@ ListView { } if (checked) { - ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index)) - containersDropDown.menuVisible = false + ServersModel.setDefaultContainer(proxyContainersModel.mapToSource(index)) } else { if (!isSupported && isInstalled) { PageController.showErrorMessage(qsTr("The selected protocol is not supported on the current platform")) From 0160b0f9dcd3c8acb078569439b739073afe1c51 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Tue, 23 Jan 2024 23:57:14 +0700 Subject: [PATCH 11/27] editServer now does not update the whole model, but only the modified element --- client/ui/models/servers_model.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/client/ui/models/servers_model.cpp b/client/ui/models/servers_model.cpp index 1922e188..ad9b0059 100644 --- a/client/ui/models/servers_model.cpp +++ b/client/ui/models/servers_model.cpp @@ -251,10 +251,9 @@ void ServersModel::addServer(const QJsonObject &server) void ServersModel::editServer(const QJsonObject &server) { - beginResetModel(); m_settings->editServer(m_currentlyProcessedServerIndex, server); - m_servers = m_settings->serversArray(); - endResetModel(); + m_servers.replace(m_currentlyProcessedServerIndex, m_settings->serversArray().at(m_currentlyProcessedServerIndex)); + emit dataChanged(index(m_currentlyProcessedServerIndex, 0), index(m_currentlyProcessedServerIndex, 0)); updateContainersModel(); } From 83ec073734da436564064ff3003d9a20b95e87a9 Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Mon, 22 Jan 2024 16:14:59 +0700 Subject: [PATCH 12/27] fixed adding admin user to client management --- client/vpnconnection.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 75483d89..d3588fe4 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -253,10 +253,13 @@ QString VpnConnection::createVpnConfigurationForProto(int serverIndex, const Ser m_settings->setProtocolConfig(serverIndex, container, proto, protoObject); } - QEventLoop wait; - emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials); - QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit); - wait.exec(); + if ((container != DockerContainer::Cloak && container != DockerContainer::ShadowSocks) || + ((container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks) && proto == Proto::OpenVpn)) { + QEventLoop wait; + emit m_configurator->newVpnConfigCreated(clientId, QString("Admin [%1]").arg(QSysInfo::prettyProductName()), container, credentials); + QObject::connect(m_configurator.get(), &VpnConfigurator::clientModelUpdated, &wait, &QEventLoop::quit); + wait.exec(); + } } return configData; From 885e22be7c1a9bdd3c8da82931f57f2a5e5cc82c Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 23 Jan 2024 15:16:28 -0500 Subject: [PATCH 13/27] Fix autostart for Linux Desktop --- client/ui/qautostart.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/ui/qautostart.cpp b/client/ui/qautostart.cpp index a7f49b2d..b0165dc4 100644 --- a/client/ui/qautostart.cpp +++ b/client/ui/qautostart.cpp @@ -124,8 +124,13 @@ void Autostart::setAutostart(bool autostart) { if (file.open(QIODevice::ReadWrite)) { QTextStream stream(&file); stream << "[Desktop Entry]" << Qt::endl; - stream << "Exec=" << appPath() << Qt::endl; + stream << "Exec=AmneziaVPN" << Qt::endl; stream << "Type=Application" << Qt::endl; + stream << "Name=AmneziaVPN" << Qt::endl; + stream << "Comment=Client of your self-hosted VPN" << Qt::endl; + stream << "Icon=/usr/share/pixmaps/AmneziaVPN.png" << Qt::endl; + stream << "Categories=Network;Qt;Security;" << Qt::endl; + stream << "Terminal=false" << Qt::endl; } } } From b3eda4106db64d7a384ca35888ddb00dbeef0e7c Mon Sep 17 00:00:00 2001 From: Igor Sorokin Date: Tue, 23 Jan 2024 23:41:08 +0300 Subject: [PATCH 14/27] Fix: Share view is not showing on iPadOS --- client/platforms/ios/MobileUtils.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/client/platforms/ios/MobileUtils.mm b/client/platforms/ios/MobileUtils.mm index fbf26ffd..58faad88 100644 --- a/client/platforms/ios/MobileUtils.mm +++ b/client/platforms/ios/MobileUtils.mm @@ -43,6 +43,7 @@ bool MobileUtils::shareText(const QStringList& filesToSend) { UIPopoverPresentationController *popController = activityController.popoverPresentationController; if (popController) { popController.sourceView = qtController.view; + popController.sourceRect = CGRectMake(100, 100, 100, 100); } QEventLoop wait; From 140b5c429201fc1848666634a21d74279929a66b Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Wed, 24 Jan 2024 12:27:11 +0700 Subject: [PATCH 15/27] random ports are now used for easy setup --- client/ui/qml/Pages2/PageSetupWizardEasy.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Pages2/PageSetupWizardEasy.qml b/client/ui/qml/Pages2/PageSetupWizardEasy.qml index f16eec40..95951507 100644 --- a/client/ui/qml/Pages2/PageSetupWizardEasy.qml +++ b/client/ui/qml/Pages2/PageSetupWizardEasy.qml @@ -112,7 +112,7 @@ PageType { var defaultContainerProto = ContainerProps.defaultProtocol(dockerContainer) containers.dockerContainer = dockerContainer - containers.containerDefaultPort = ProtocolProps.defaultPort(defaultContainerProto) + containers.containerDefaultPort = ProtocolProps.getPortForInstall(defaultContainerProto) containers.containerDefaultTransportProto = ProtocolProps.defaultTransportProto(defaultContainerProto) } } From 6f02d4eb62b34a6045599c6b88ed43ae176cad11 Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 24 Jan 2024 16:34:37 +0300 Subject: [PATCH 16/27] Remove useless 'Open folder with logs' button for Adnroid too --- client/ui/qml/Pages2/PageSettingsLogging.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ui/qml/Pages2/PageSettingsLogging.qml b/client/ui/qml/Pages2/PageSettingsLogging.qml index d00bdeb0..b302bffc 100644 --- a/client/ui/qml/Pages2/PageSettingsLogging.qml +++ b/client/ui/qml/Pages2/PageSettingsLogging.qml @@ -66,8 +66,8 @@ PageType { ColumnLayout { Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: Qt.platform.os === "ios"? 0 : root.width / 3 - visible: Qt.platform.os !== "ios" + Layout.preferredWidth: GC.isMobile() ? 0 : root.width / 3 + visible: !GC.isMobile() ImageButtonType { Layout.alignment: Qt.AlignHCenter @@ -91,7 +91,7 @@ PageType { ColumnLayout { Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / ( Qt.platform.os === "ios" ? 2 : 3 ) + Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 ) ImageButtonType { Layout.alignment: Qt.AlignHCenter @@ -132,7 +132,7 @@ PageType { ColumnLayout { Layout.alignment: Qt.AlignBaseline - Layout.preferredWidth: root.width / ( Qt.platform.os === "ios" ? 2 : 3 ) + Layout.preferredWidth: root.width / ( GC.isMobile() ? 2 : 3 ) ImageButtonType { Layout.alignment: Qt.AlignHCenter From b68c9cc1457a86159a72394227534adc14eebf3c Mon Sep 17 00:00:00 2001 From: albexk Date: Wed, 24 Jan 2024 17:00:48 +0300 Subject: [PATCH 17/27] Fix the double extension of the config file name: 'amnezia_config.vpn.vpn' --- client/ui/qml/Components/ShareConnectionDrawer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ui/qml/Components/ShareConnectionDrawer.qml b/client/ui/qml/Components/ShareConnectionDrawer.qml index e354e951..45ef84a6 100644 --- a/client/ui/qml/Components/ShareConnectionDrawer.qml +++ b/client/ui/qml/Components/ShareConnectionDrawer.qml @@ -25,7 +25,7 @@ DrawerType { property string configExtension: ".vpn" property string configCaption: qsTr("Save AmneziaVPN config") - property string configFileName: "amnezia_config.vpn" + property string configFileName: "amnezia_config" width: parent.width height: parent.height * 0.9 From 5c9d45a8a83f078a23193808b114850861dcbd95 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 24 Jan 2024 17:20:50 -0500 Subject: [PATCH 18/27] Use MacOS logic for LinuxFirewall --- .../platforms/linux/daemon/linuxfirewall.cpp | 36 +++++++++-- client/platforms/linux/daemon/linuxfirewall.h | 12 ++-- .../linux/daemon/wireguardutilslinux.cpp | 41 ++++++++----- client/protocols/openvpnprotocol.cpp | 20 ++++--- .../ui/models/protocols/ikev2ConfigModel.cpp | 2 +- ipc/ipcserver.cpp | 59 ++++++++----------- 6 files changed, 104 insertions(+), 66 deletions(-) diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp index ab51ea74..0d32b56d 100644 --- a/client/platforms/linux/daemon/linuxfirewall.cpp +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -168,7 +168,7 @@ QStringList LinuxFirewall::getDNSRules(const QStringList& servers) return result; } -QStringList LinuxFirewall::getExcludeRule(const QStringList& servers) +QStringList LinuxFirewall::getAllowRule(const QStringList& servers) { QStringList result; for (const QString& server : servers) @@ -178,6 +178,16 @@ QStringList LinuxFirewall::getExcludeRule(const QStringList& servers) return result; } +QStringList LinuxFirewall::getBlockRule(const QStringList& servers) +{ + QStringList result; + for (const QString& server : servers) + { + result << QStringLiteral("-d %1 -j REJECT").arg(server); + } + return result; +} + void LinuxFirewall::install() { @@ -237,10 +247,13 @@ void LinuxFirewall::install() QStringLiteral("-o tun0+ -j ACCEPT"), }); + installAnchor(IPv4, QStringLiteral("120.blockNets"), {}); + + installAnchor(IPv4, QStringLiteral("110.allowNets"), {}); + installAnchor(Both, QStringLiteral("100.blockAll"), { QStringLiteral("-j REJECT"), }); - // NAT rules installAnchor(Both, QStringLiteral("100.transIp"), { @@ -309,6 +322,8 @@ void LinuxFirewall::uninstall() uninstallAnchor(Both, QStringLiteral("290.allowDHCP")); uninstallAnchor(IPv6, QStringLiteral("250.blockIPv6")); uninstallAnchor(Both, QStringLiteral("200.allowVPN")); + uninstallAnchor(IPv4, QStringLiteral("120.blockNets")); + uninstallAnchor(IPv4, QStringLiteral("110.allowNets")); uninstallAnchor(Both, QStringLiteral("100.blockAll")); // Remove Nat anchors @@ -403,16 +418,25 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers) execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule)); } -void LinuxFirewall::updateExcludeAddrs(const QStringList& servers) +void LinuxFirewall::updateAllowNets(const QStringList& servers) { static QStringList existingServers {}; existingServers = servers; - execute(QStringLiteral("iptables -F %1.100.blockAll").arg(kAnchorName)); - for (const QString& rule : getExcludeRule(servers)) - execute(QStringLiteral("iptables -A %1.100.blockAll %2").arg(kAnchorName, rule)); + execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName)); + for (const QString& rule : getAllowRule(servers)) + execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule)); } +void LinuxFirewall::updateBlockNets(const QStringList& servers) +{ + static QStringList existingServers {}; + + existingServers = servers; + execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName)); + for (const QString& rule : getBlockRule(servers)) + execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule)); +} int waitForExitCode(QProcess& process) { diff --git a/client/platforms/linux/daemon/linuxfirewall.h b/client/platforms/linux/daemon/linuxfirewall.h index 486fafbc..9e9412a4 100644 --- a/client/platforms/linux/daemon/linuxfirewall.h +++ b/client/platforms/linux/daemon/linuxfirewall.h @@ -12,8 +12,8 @@ struct FirewallParams QStringList dnsServers; // QSharedPointer adapter; QVector excludeApps; // Apps to exclude if VPN exemptions are enabled - - QStringList excludeAddrs; + QStringList allowAddrs; + QStringList blockAddrs; // The follow flags indicate which general rulesets are needed. Note that // this is after some sanity filtering, i.e. an allow rule may be listed // as not needed if there were no block rules preceding it. The rulesets @@ -29,6 +29,8 @@ struct FirewallParams bool allowLoopback; // Exempt loopback traffic bool allowHnsd; // Exempt Handshake DNS traffic bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead) + bool allowNets; + bool blockNets; }; class LinuxFirewall @@ -47,7 +49,8 @@ private: static void installAnchor(IPVersion ip, const QString& anchor, const QStringList& rules, const QString& tableName = kFilterTable, const FilterCallbackFunc& enableFunc = {}, const FilterCallbackFunc& disableFunc = {}); static void uninstallAnchor(IPVersion ip, const QString& anchor, const QString& tableName = kFilterTable); static QStringList getDNSRules(const QStringList& servers); - static QStringList getExcludeRule(const QStringList& servers); + static QStringList getAllowRule(const QStringList& servers); + static QStringList getBlockRule(const QStringList& servers); static void setupTrafficSplitting(); static void teardownTrafficSplitting(); static int execute(const QString& command, bool ignoreErrors = false); @@ -66,7 +69,8 @@ public: static void setAnchorEnabled(IPVersion ip, const QString& anchor, bool enabled, const QString& tableName = kFilterTable); static void replaceAnchor(LinuxFirewall::IPVersion ip, const QString &anchor, const QString &newRule, const QString& tableName); static void updateDNSServers(const QStringList& servers); - static void updateExcludeAddrs(const QStringList& servers); + static void updateAllowNets(const QStringList& servers); + static void updateBlockNets(const QStringList& servers); }; diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 79f7dd2d..e5dce524 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -118,12 +118,26 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) { int err = uapiErrno(uapiCommand(message)); if (err != 0) { logger.error() << "Interface configuration failed:" << strerror(err); - } + } else { + FirewallParams params { }; + params.dnsServers.append(config.m_dnsServer); + if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){ + params.blockAll = true; + if (config.m_excludedAddresses.size()) { + params.allowNets = true; + foreach (auto net, config.m_excludedAddresses) { + params.allowAddrs.append(net.toUtf8()); + } + } + } else { + params.blockNets = true; + foreach (auto net, config.m_allowedIPAddressRanges) { + params.blockAddrs.append(net.toString()); + } + } - FirewallParams params {}; - params.dnsServers.append(config.m_dnsServer); - params.excludeAddrs.append(config.m_serverIpv4AddrIn); - applyFirewallRules(params); + applyFirewallRules(params); + } return (err == 0); } @@ -273,22 +287,19 @@ void WireguardUtilsLinux::applyFirewallRules(FirewallParams& params) LinuxFirewall::ensureRootAnchorPriority(); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), params.blockAll); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), params.allowNets); + LinuxFirewall::updateAllowNets(params.allowAddrs); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), params.blockNets); + LinuxFirewall::updateBlockNets(params.blockAddrs); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); LinuxFirewall::updateDNSServers(params.dnsServers); - LinuxFirewall::updateExcludeAddrs(params.excludeAddrs); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); - - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, - QStringLiteral("100.vpnTunOnly"), - true, - LinuxFirewall::kRawTable); - } bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) { diff --git a/client/protocols/openvpnprotocol.cpp b/client/protocols/openvpnprotocol.cpp index f9196fc3..5f8db625 100644 --- a/client/protocols/openvpnprotocol.cpp +++ b/client/protocols/openvpnprotocol.cpp @@ -144,12 +144,18 @@ uint OpenVpnProtocol::selectMgmtPort() void OpenVpnProtocol::updateRouteGateway(QString line) { - // TODO: fix for macos - line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); - if (!line.contains("/")) - return; - m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); - m_routeGateway.replace(" ", ""); + if (line.contains("net_route_v4_best_gw")) { + QStringList params = line.split(" "); + if (params.size() == 6) { + m_routeGateway = params.at(3); + } + } else { + line = line.split("ROUTE_GATEWAY", Qt::SkipEmptyParts).at(1); + if (!line.contains("/")) + return; + m_routeGateway = line.split("/", Qt::SkipEmptyParts).first(); + m_routeGateway.replace(" ", ""); + } qDebug() << "Set VPN route gateway" << m_routeGateway; } @@ -288,7 +294,7 @@ void OpenVpnProtocol::onReadyReadDataFromManagementServer() } } - if (line.contains("ROUTE_GATEWAY")) { + if (line.contains("ROUTE_GATEWAY") || line.contains("net_route_v4_best_gw")) { updateRouteGateway(line); } diff --git a/client/ui/models/protocols/ikev2ConfigModel.cpp b/client/ui/models/protocols/ikev2ConfigModel.cpp index f22b965c..a11b6652 100644 --- a/client/ui/models/protocols/ikev2ConfigModel.cpp +++ b/client/ui/models/protocols/ikev2ConfigModel.cpp @@ -1,4 +1,4 @@ -#include "ikev2ConfigModel.h".h " +#include "ikev2ConfigModel.h" #include "protocols/protocols_defs.h" diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index a19039f6..c0e87fc9 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -181,39 +181,7 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex); #endif -#ifdef Q_OS_LINUX - // double-check + ensure our firewall is installed and enabled - if (!LinuxFirewall::isInstalled()) LinuxFirewall::install(); - - // Note: rule precedence is handled inside IpTablesFirewall - LinuxFirewall::ensureRootAnchorPriority(); - - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("200.allowVPN"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); - // LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("310.blockDNS"), true); - QStringList serverAddr; - serverAddr.append(configStr.value(amnezia::config_key::hostName).toString()); - LinuxFirewall::updateExcludeAddrs(serverAddr); - QStringList dnsServers; - dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); - dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); - dnsServers.append("127.0.0.1"); - dnsServers.append("127.0.0.53"); - LinuxFirewall::updateDNSServers(dnsServers); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); - LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); - - // LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, - // QStringLiteral("100.vpnTunOnly"), - // true, - // LinuxFirewall::kRawTable); -#endif - -#ifdef Q_OS_MACOS +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) int splitTunnelType = configStr.value("splitTunnelType").toInt(); QJsonArray splitTunnelSites = configStr.value("splitTunnelSites").toArray(); bool blockAll = 0; @@ -241,6 +209,31 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd allownets.append(v.toString()); } } +#endif + +#ifdef Q_OS_LINUX + // double-check + ensure our firewall is installed and enabled + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); + LinuxFirewall::updateAllowNets(allownets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); + LinuxFirewall::updateBlockNets(blocknets); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true); + QStringList dnsServers; + dnsServers.append(configStr.value(amnezia::config_key::dns1).toString()); + dnsServers.append(configStr.value(amnezia::config_key::dns2).toString()); + dnsServers.append("127.0.0.1"); + dnsServers.append("127.0.0.53"); + LinuxFirewall::updateDNSServers(dnsServers); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true); + LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true); +#endif + +#ifdef Q_OS_MACOS // double-check + ensure our firewall is installed and enabled. This is necessary as // other software may disable pfctl before re-enabling with their own rules (e.g other VPNs) From 427b43c99b188e40a6cbf4caf6447f3b412c686a Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 27 Jan 2024 07:50:50 -0500 Subject: [PATCH 19/27] Add code license --- .../platforms/linux/daemon/linuxfirewall.cpp | 14 ++++++++++++++ client/platforms/linux/daemon/linuxfirewall.h | 16 ++++++++++++++-- .../platforms/macos/daemon/macosfirewall.cpp | 14 ++++++++++++++ client/platforms/macos/daemon/macosfirewall.h | 19 ++++++++++++++----- 4 files changed, 56 insertions(+), 7 deletions(-) diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp index 0d32b56d..0c6edbaf 100644 --- a/client/platforms/linux/daemon/linuxfirewall.cpp +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -1,3 +1,17 @@ +// Copyright (c) 2024 AmneziaVPN +// This file has been modified for AmneziaVPN +// +// This file is based on the work of the Private Internet Access Desktop Client. +// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3. +// +// The modified version of this file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this file. If not, see . + #include "linuxfirewall.h" #include "logger.h" #include diff --git a/client/platforms/linux/daemon/linuxfirewall.h b/client/platforms/linux/daemon/linuxfirewall.h index 9e9412a4..ef2521a4 100644 --- a/client/platforms/linux/daemon/linuxfirewall.h +++ b/client/platforms/linux/daemon/linuxfirewall.h @@ -1,3 +1,17 @@ +// Copyright (c) 2024 AmneziaVPN +// This file has been modified for AmneziaVPN +// +// This file is based on the work of the Private Internet Access Desktop Client. +// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3. +// +// The modified version of this file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this file. If not, see . + #ifndef LINUXFIREWALL_H #define LINUXFIREWALL_H @@ -10,7 +24,6 @@ struct FirewallParams { QStringList dnsServers; - // QSharedPointer adapter; QVector excludeApps; // Apps to exclude if VPN exemptions are enabled QStringList allowAddrs; QStringList blockAddrs; @@ -73,5 +86,4 @@ public: static void updateBlockNets(const QStringList& servers); }; - #endif // LINUXFIREWALL_H diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp index 358bd594..67dda9cf 100644 --- a/client/platforms/macos/daemon/macosfirewall.cpp +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -1,3 +1,17 @@ +// Copyright (c) 2024 AmneziaVPN +// This file has been modified for AmneziaVPN +// +// This file is based on the work of the Private Internet Access Desktop Client. +// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3. +// +// The modified version of this file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this file. If not, see . + #include "macosfirewall.h" #include "logger.h" #include diff --git a/client/platforms/macos/daemon/macosfirewall.h b/client/platforms/macos/daemon/macosfirewall.h index 8b13d363..0c56dcaa 100644 --- a/client/platforms/macos/daemon/macosfirewall.h +++ b/client/platforms/macos/daemon/macosfirewall.h @@ -1,3 +1,17 @@ +// Copyright (c) 2024 AmneziaVPN +// This file has been modified for AmneziaVPN +// +// This file is based on the work of the Private Internet Access Desktop Client. +// The original code of the Private Internet Access Desktop Client is copyrighted (c) 2023 Private Internet Access, Inc. and licensed under GPL3. +// +// The modified version of this file is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this file. If not, see . + #ifndef MACOSFIREWALL_H #define MACOSFIREWALL_H @@ -9,7 +23,6 @@ struct FirewallParams { QStringList dnsServers; - // QSharedPointer adapter; QVector excludeApps; // Apps to exclude if VPN exemptions are enabled QStringList allowAddrs; @@ -34,9 +47,6 @@ struct FirewallParams bool allowVpnExemptions; // Exempt specified traffic from the tunnel (route it over the physical uplink instead) }; -// TODO: Break out firewall handling to a base class that can be used directly -// by the base daemon class, for some common functionality. - class MacOSFirewall { @@ -59,5 +69,4 @@ public: static void installRootAnchors(); }; - #endif // MACOSFIREWALL_H From 30af81fe0a78d598f346aa601fdf2aee7659ccd7 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 27 Jan 2024 07:59:36 -0500 Subject: [PATCH 20/27] Move linuxfirewall header to "headers" part --- service/server/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 985186f0..742b8ae3 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -213,6 +213,7 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.h ) set(SOURCES ${SOURCES} @@ -225,7 +226,6 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/wireguardutilslinux.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxroutemonitor.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxfirewall.cpp ) endif() From 3afbc248b14164d68570a78dca7288d9762f1d75 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 27 Jan 2024 16:55:05 +0300 Subject: [PATCH 21/27] Refactor split-tunneling: separate site addresses from routes --- .../android/awg/src/main/kotlin/AwgConfig.kt | 2 +- .../amnezia/vpn/protocol/openvpn/OpenVpn.kt | 13 +--- .../vpn/protocol/openvpn/OpenVpnConfig.kt | 2 +- .../protocolApi/src/main/kotlin/Protocol.kt | 44 +++--------- .../src/main/kotlin/ProtocolConfig.kt | 67 +++++++++++++++++-- .../vpn/protocol/wireguard/WireguardConfig.kt | 2 +- 6 files changed, 76 insertions(+), 54 deletions(-) diff --git a/client/android/awg/src/main/kotlin/AwgConfig.kt b/client/android/awg/src/main/kotlin/AwgConfig.kt index 372747f2..014c6e0a 100644 --- a/client/android/awg/src/main/kotlin/AwgConfig.kt +++ b/client/android/awg/src/main/kotlin/AwgConfig.kt @@ -99,7 +99,7 @@ class AwgConfig private constructor( fun setH3(h3: Long) = apply { this.h3 = h3 } fun setH4(h4: Long) = apply { this.h4 = h4 } - override fun build(): AwgConfig = AwgConfig(this) + override fun build(): AwgConfig = configBuild().run { AwgConfig(this@Builder) } } companion object { diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt index 34069a0d..34f2934b 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt @@ -2,7 +2,6 @@ package org.amnezia.vpn.protocol.openvpn import android.content.Context import android.net.VpnService.Builder -import android.os.Build import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -14,7 +13,6 @@ import org.amnezia.vpn.protocol.ProtocolState import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.VpnStartException -import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.getLocalNetworks import org.json.JSONObject @@ -79,16 +77,7 @@ open class OpenVpn : Protocol() { if (evalConfig.error) { throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") } - configBuilder.apply { - // fix for split tunneling - // The exclude split tunneling OpenVpn configuration does not contain a default route. - // It is required for split tunneling in newer versions of Android. - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - addRoute(InetNetwork("0.0.0.0", 0)) - addRoute(InetNetwork("::", 0)) - } - configSplitTunneling(config) - } + configBuilder.configSplitTunneling(config) scope.launch { val status = client.connect() diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnConfig.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnConfig.kt index 36d8d93b..9554f978 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnConfig.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnConfig.kt @@ -11,7 +11,7 @@ class OpenVpnConfig private constructor( class Builder : ProtocolConfig.Builder(false) { override var mtu: Int = OPENVPN_DEFAULT_MTU - override fun build(): OpenVpnConfig = OpenVpnConfig(this) + override fun build(): OpenVpnConfig = configBuild().run { OpenVpnConfig(this@Builder) } } companion object { diff --git a/client/android/protocolApi/src/main/kotlin/Protocol.kt b/client/android/protocolApi/src/main/kotlin/Protocol.kt index b2c52e9a..b729f9f7 100644 --- a/client/android/protocolApi/src/main/kotlin/Protocol.kt +++ b/client/android/protocolApi/src/main/kotlin/Protocol.kt @@ -14,8 +14,6 @@ import java.util.zip.ZipFile import kotlinx.coroutines.flow.MutableStateFlow import org.amnezia.vpn.util.Log import org.amnezia.vpn.util.net.InetNetwork -import org.amnezia.vpn.util.net.IpRange -import org.amnezia.vpn.util.net.IpRangeSet import org.json.JSONObject private const val TAG = "Protocol" @@ -53,40 +51,16 @@ abstract class Protocol { val splitTunnelType = config.optInt("splitTunnelType") if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return val splitTunnelSites = config.getJSONArray("splitTunnelSites") - when (splitTunnelType) { - SPLIT_TUNNEL_INCLUDE -> { - // remove default routes, if any - removeRoute(InetNetwork("0.0.0.0", 0)) - removeRoute(InetNetwork("::", 0)) - // add routes from config - for (i in 0 until splitTunnelSites.length()) { - val address = InetNetwork.parse(splitTunnelSites.getString(i)) - addRoute(address) - } - } + val addressHandlerFunc = when (splitTunnelType) { + SPLIT_TUNNEL_INCLUDE -> ::includeAddress + SPLIT_TUNNEL_EXCLUDE -> ::excludeAddress - SPLIT_TUNNEL_EXCLUDE -> { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - // exclude routes from config - for (i in 0 until splitTunnelSites.length()) { - val address = InetNetwork.parse(splitTunnelSites.getString(i)) - excludeRoute(address) - } - } else { - // For older versions of Android, build a list of subnets without excluded addresses - val ipRangeSet = IpRangeSet() - ipRangeSet.remove(IpRange("127.0.0.0", 8)) - for (i in 0 until splitTunnelSites.length()) { - val address = InetNetwork.parse(splitTunnelSites.getString(i)) - ipRangeSet.remove(IpRange(address)) - } - // remove default routes, if any - removeRoute(InetNetwork("0.0.0.0", 0)) - removeRoute(InetNetwork("::", 0)) - ipRangeSet.subnets().forEach(::addRoute) - addRoute(InetNetwork("2000::", 3)) - } - } + else -> throw BadConfigException("Unexpected value of the 'splitTunnelType' parameter: $splitTunnelType") + } + + for (i in 0 until splitTunnelSites.length()) { + val address = InetNetwork.parse(splitTunnelSites.getString(i)) + addressHandlerFunc(address) } } diff --git a/client/android/protocolApi/src/main/kotlin/ProtocolConfig.kt b/client/android/protocolApi/src/main/kotlin/ProtocolConfig.kt index a4d7683e..75ba1abf 100644 --- a/client/android/protocolApi/src/main/kotlin/ProtocolConfig.kt +++ b/client/android/protocolApi/src/main/kotlin/ProtocolConfig.kt @@ -5,6 +5,8 @@ import android.os.Build import androidx.annotation.RequiresApi import java.net.InetAddress import org.amnezia.vpn.util.net.InetNetwork +import org.amnezia.vpn.util.net.IpRange +import org.amnezia.vpn.util.net.IpRangeSet open class ProtocolConfig protected constructor( val addresses: Set, @@ -12,6 +14,8 @@ open class ProtocolConfig protected constructor( val searchDomain: String?, val routes: Set, val excludedRoutes: Set, + val includedAddresses: Set, + val excludedAddresses: Set, val excludedApplications: Set, val httpProxy: ProxyInfo?, val allowAllAF: Boolean, @@ -25,6 +29,8 @@ open class ProtocolConfig protected constructor( builder.searchDomain, builder.routes, builder.excludedRoutes, + builder.includedAddresses, + builder.excludedAddresses, builder.excludedApplications, builder.httpProxy, builder.allowAllAF, @@ -37,6 +43,8 @@ open class ProtocolConfig protected constructor( internal val dnsServers: MutableSet = hashSetOf() internal val routes: MutableSet = hashSetOf() internal val excludedRoutes: MutableSet = hashSetOf() + internal val includedAddresses: MutableSet = hashSetOf() + internal val excludedAddresses: MutableSet = hashSetOf() internal val excludedApplications: MutableSet = hashSetOf() internal var searchDomain: String? = null @@ -71,12 +79,15 @@ open class ProtocolConfig protected constructor( fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) } fun clearRoutes() = apply { this.routes.clear() } - @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route } - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) fun excludeRoutes(routes: Collection) = apply { this.excludedRoutes += routes } + fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr } + fun includeAddresses(addresses: Collection) = apply { this.includedAddresses += addresses } + + fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr } + fun excludeAddresses(addresses: Collection) = apply { this.excludedAddresses += addresses } + fun excludeApplication(application: String) = apply { this.excludedApplications += application } fun excludeApplications(applications: Collection) = apply { this.excludedApplications += applications } @@ -91,6 +102,48 @@ open class ProtocolConfig protected constructor( fun setMtu(mtu: Int) = apply { this.mtu = mtu } + private fun processSplitTunneling() { + if (includedAddresses.isNotEmpty() && excludedAddresses.isNotEmpty()) { + throw BadConfigException("Config contains addresses for inclusive and exclusive split tunneling at the same time") + } + + if (includedAddresses.isNotEmpty()) { + // remove default routes, if any + removeRoute(InetNetwork("0.0.0.0", 0)) + removeRoute(InetNetwork("::", 0)) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + // for older versions of Android, add the default route to the excluded routes + // to correctly build the excluded subnets list later + excludeRoute(InetNetwork("0.0.0.0", 0)) + } + addRoutes(includedAddresses) + } else if (excludedAddresses.isNotEmpty()) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + // default routes are required for split tunneling in newer versions of Android + addRoute(InetNetwork("0.0.0.0", 0)) + addRoute(InetNetwork("::", 0)) + } + excludeRoutes(excludedAddresses) + } + } + + private fun processExcludedRoutes() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + // for older versions of Android, build a list of subnets without excluded routes + // and add them to routes + val ipRangeSet = IpRangeSet() + ipRangeSet.remove(IpRange("127.0.0.0", 8)) + excludedRoutes.forEach { + ipRangeSet.remove(IpRange(it)) + } + // remove default routes, if any + removeRoute(InetNetwork("0.0.0.0", 0)) + removeRoute(InetNetwork("::", 0)) + ipRangeSet.subnets().forEach(::addRoute) + addRoute(InetNetwork("2000::", 3)) + } + } + private fun validate() { val errorMessage = StringBuilder() @@ -103,7 +156,13 @@ open class ProtocolConfig protected constructor( if (errorMessage.isNotEmpty()) throw BadConfigException(errorMessage.toString()) } - open fun build(): ProtocolConfig = validate().run { ProtocolConfig(this@Builder) } + protected fun configBuild() { + processSplitTunneling() + processExcludedRoutes() + validate() + } + + open fun build(): ProtocolConfig = configBuild().run { ProtocolConfig(this@Builder) } } companion object { diff --git a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt index 76ccd905..0e303f0e 100644 --- a/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt +++ b/client/android/wireguard/src/main/kotlin/org/amnezia/vpn/protocol/wireguard/WireguardConfig.kt @@ -75,7 +75,7 @@ open class WireguardConfig protected constructor( fun setPrivateKeyHex(privateKeyHex: String) = apply { this.privateKeyHex = privateKeyHex } - override fun build(): WireguardConfig = WireguardConfig(this) + override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) } } companion object { From cbd6755aa5b78eb52636612f0f5ef401ff5370d6 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 27 Jan 2024 17:30:56 +0300 Subject: [PATCH 22/27] Fix OpenVpn over Cloak --- client/android/cloak/src/main/kotlin/Cloak.kt | 10 ++++++++++ .../kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt | 3 +++ .../org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt | 4 +--- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/android/cloak/src/main/kotlin/Cloak.kt b/client/android/cloak/src/main/kotlin/Cloak.kt index 5a549130..651e353b 100644 --- a/client/android/cloak/src/main/kotlin/Cloak.kt +++ b/client/android/cloak/src/main/kotlin/Cloak.kt @@ -3,6 +3,9 @@ package org.amnezia.vpn.protocol.cloak import android.util.Base64 import net.openvpn.ovpn3.ClientAPI_Config import org.amnezia.vpn.protocol.openvpn.OpenVpn +import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig +import org.amnezia.vpn.util.net.InetNetwork +import org.amnezia.vpn.util.net.parseInetAddress import org.json.JSONObject /** @@ -51,6 +54,13 @@ class Cloak : OpenVpn() { return openVpnConfig } + override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) { + // exclude remote server ip from vpn routes + val remoteServer = config.getString("hostName") + val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer)) + configBuilder.excludeRoute(remoteServerAddress) + } + private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { cloakConfigJson.put("NumConn", 1) cloakConfigJson.put("ProxyMethod", "openvpn") diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt index 34f2934b..9e1c62cc 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt @@ -77,6 +77,7 @@ open class OpenVpn : Protocol() { if (evalConfig.error) { throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") } + configPluggableTransport(configBuilder, config) configBuilder.configSplitTunneling(config) scope.launch { @@ -111,6 +112,8 @@ open class OpenVpn : Protocol() { return openVpnConfig } + protected open fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {} + private fun makeEstablish(vpnBuilder: Builder): (OpenVpnConfig.Builder) -> Int = { configBuilder -> val openVpnConfig = configBuilder.build() buildVpnInterface(openVpnConfig, vpnBuilder) diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt index f489c980..4f0f1796 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpnClient.kt @@ -91,9 +91,7 @@ class OpenVpnClient( // metric is optional and should be ignored if < 0 override fun tun_builder_exclude_route(address: String, prefix_length: Int, metric: Int, ipv6: Boolean): Boolean { Log.d(TAG, "tun_builder_exclude_route: $address, $prefix_length, $metric, $ipv6") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - configBuilder.excludeRoute(InetNetwork(address, prefix_length)) - } + configBuilder.excludeRoute(InetNetwork(address, prefix_length)) return true } From 0b6dc5bcfc6a4246dde5e7929ca2e43551c98711 Mon Sep 17 00:00:00 2001 From: albexk Date: Sat, 27 Jan 2024 17:34:57 +0300 Subject: [PATCH 23/27] Add unbinding and destroying vpn service after disconnection --- client/android/src/org/amnezia/vpn/AmneziaActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt index 3583a383..9a813626 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaActivity.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaActivity.kt @@ -64,6 +64,7 @@ class AmneziaActivity : QtActivity() { ServiceEvent.DISCONNECTED -> { QtAndroidController.onVpnDisconnected() + doUnbindService() } ServiceEvent.RECONNECTING -> { From f7df621c56b9b3fedb551f589574b517f40af4ca Mon Sep 17 00:00:00 2001 From: "vladimir.kuznetsov" Date: Thu, 25 Jan 2024 18:42:17 +0300 Subject: [PATCH 24/27] fixed cache clearing when deleting admin configure - added permissions for the crl.pem file --- client/ui/controllers/exportController.cpp | 3 +- client/ui/models/clientManagementModel.cpp | 34 ++++++++++++---------- client/ui/models/clientManagementModel.h | 4 +-- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/client/ui/controllers/exportController.cpp b/client/ui/controllers/exportController.cpp index 4f3fe7d5..2b102e13 100644 --- a/client/ui/controllers/exportController.cpp +++ b/client/ui/controllers/exportController.cpp @@ -327,7 +327,8 @@ void ExportController::updateClientManagementModel(const DockerContainer contain void ExportController::revokeConfig(const int row, const DockerContainer container, ServerCredentials credentials) { - ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials); + ErrorCode errorCode = m_clientManagementModel->revokeClient(row, container, credentials, + m_serversModel->getCurrentlyProcessedServerIndex()); if (errorCode != ErrorCode::NoError) { emit exportErrorOccurred(errorString(errorCode)); } diff --git a/client/ui/models/clientManagementModel.cpp b/client/ui/models/clientManagementModel.cpp index 0b1be2cc..7c81c80e 100644 --- a/client/ui/models/clientManagementModel.cpp +++ b/client/ui/models/clientManagementModel.cpp @@ -296,30 +296,36 @@ ErrorCode ClientManagementModel::renameClient(const int row, const QString &clie } ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContainer container, - ServerCredentials credentials) + ServerCredentials credentials, const int serverIndex) { ErrorCode errorCode = ErrorCode::NoError; + auto client = m_clientsTable.at(row).toObject(); + QString clientId = client.value(configKey::clientId).toString(); if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { - errorCode = revokeOpenVpn(row, container, credentials); + errorCode = revokeOpenVpn(row, container, credentials, serverIndex); } else if (container == DockerContainer::WireGuard || container == DockerContainer::Awg) { errorCode = revokeWireGuard(row, container, credentials); } if (errorCode == ErrorCode::NoError) { - auto client = m_clientsTable.at(row).toObject(); - QString clientId = client.value(configKey::clientId).toString(); - - const auto server = m_settings->defaultServer(); + const auto server = m_settings->server(serverIndex); QJsonArray containers = server.value(config_key::containers).toArray(); for (auto i = 0; i < containers.size(); i++) { auto containerConfig = containers.at(i).toObject(); auto containerType = ContainerProps::containerFromString(containerConfig.value(config_key::container).toString()); - auto protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject(); + if (containerType == container) { + QJsonObject protocolConfig; + if (container == DockerContainer::ShadowSocks || container == DockerContainer::Cloak) { + protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)).toObject(); + } else { + protocolConfig = containerConfig.value(ContainerProps::containerTypeToString(containerType)).toObject(); + } - if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) { - emit adminConfigRevoked(container); + if (protocolConfig.value(config_key::last_config).toString().contains(clientId)) { + emit adminConfigRevoked(container); + } } } } @@ -328,7 +334,7 @@ ErrorCode ClientManagementModel::revokeClient(const int row, const DockerContain } ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContainer container, - ServerCredentials credentials) + ServerCredentials credentials, const int serverIndex) { auto client = m_clientsTable.at(row).toObject(); QString clientId = client.value(configKey::clientId).toString(); @@ -337,6 +343,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai "cd /opt/amnezia/openvpn ;\\" "easyrsa revoke %1 ;\\" "easyrsa gen-crl ;\\" + "chmod 666 pki/crl.pem ;\\" "cp pki/crl.pem .'") .arg(clientId); @@ -356,12 +363,7 @@ ErrorCode ClientManagementModel::revokeOpenVpn(const int row, const DockerContai const QByteArray clientsTableString = QJsonDocument(m_clientsTable).toJson(); QString clientsTableFile = QString("/opt/amnezia/%1/clientsTable"); - if (container == DockerContainer::OpenVpn || container == DockerContainer::ShadowSocks - || container == DockerContainer::Cloak) { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); - } else { - clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(container)); - } + clientsTableFile = clientsTableFile.arg(ContainerProps::containerTypeToString(DockerContainer::OpenVpn)); error = serverController.uploadTextFileToContainer(container, credentials, clientsTableString, clientsTableFile); if (error != ErrorCode::NoError) { logger.error() << "Failed to upload the clientsTable file to the server"; diff --git a/client/ui/models/clientManagementModel.h b/client/ui/models/clientManagementModel.h index ba36c26f..c003881b 100644 --- a/client/ui/models/clientManagementModel.h +++ b/client/ui/models/clientManagementModel.h @@ -28,7 +28,7 @@ public slots: ServerCredentials credentials); ErrorCode renameClient(const int row, const QString &userName, const DockerContainer container, ServerCredentials credentials, bool addTimeStamp = false); - ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials); + ErrorCode revokeClient(const int index, const DockerContainer container, ServerCredentials credentials, const int serverIndex); protected: QHash roleNames() const override; @@ -41,7 +41,7 @@ private: void migration(const QByteArray &clientsTableString); - ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials); + ErrorCode revokeOpenVpn(const int row, const DockerContainer container, ServerCredentials credentials, const int serverIndex); ErrorCode revokeWireGuard(const int row, const DockerContainer container, ServerCredentials credentials); ErrorCode getOpenVpnClients(ServerController &serverController, DockerContainer container, ServerCredentials credentials, int &count); From e0891e1a156861ee3c1fc5b84f3264eab3cbe3cb Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 28 Jan 2024 05:39:12 -0500 Subject: [PATCH 25/27] Change license text --- .../platforms/linux/daemon/linuxfirewall.cpp | 18 ++++++++++++++++++ client/platforms/linux/daemon/linuxfirewall.h | 18 ++++++++++++++++++ .../platforms/macos/daemon/macosfirewall.cpp | 18 ++++++++++++++++++ client/platforms/macos/daemon/macosfirewall.h | 18 ++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/client/platforms/linux/daemon/linuxfirewall.cpp b/client/platforms/linux/daemon/linuxfirewall.cpp index 0c6edbaf..393c24f2 100644 --- a/client/platforms/linux/daemon/linuxfirewall.cpp +++ b/client/platforms/linux/daemon/linuxfirewall.cpp @@ -1,3 +1,21 @@ +// Copyright (c) 2023 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access Desktop Client. +// +// The Private Internet Access Desktop Client is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// The Private Internet Access Desktop Client is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Private Internet Access Desktop Client. If not, see +// . + // Copyright (c) 2024 AmneziaVPN // This file has been modified for AmneziaVPN // diff --git a/client/platforms/linux/daemon/linuxfirewall.h b/client/platforms/linux/daemon/linuxfirewall.h index ef2521a4..38049265 100644 --- a/client/platforms/linux/daemon/linuxfirewall.h +++ b/client/platforms/linux/daemon/linuxfirewall.h @@ -1,3 +1,21 @@ +// Copyright (c) 2023 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access Desktop Client. +// +// The Private Internet Access Desktop Client is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// The Private Internet Access Desktop Client is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Private Internet Access Desktop Client. If not, see +// . + // Copyright (c) 2024 AmneziaVPN // This file has been modified for AmneziaVPN // diff --git a/client/platforms/macos/daemon/macosfirewall.cpp b/client/platforms/macos/daemon/macosfirewall.cpp index 67dda9cf..0fe51f23 100644 --- a/client/platforms/macos/daemon/macosfirewall.cpp +++ b/client/platforms/macos/daemon/macosfirewall.cpp @@ -1,3 +1,21 @@ +// Copyright (c) 2023 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access Desktop Client. +// +// The Private Internet Access Desktop Client is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// The Private Internet Access Desktop Client is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Private Internet Access Desktop Client. If not, see +// . + // Copyright (c) 2024 AmneziaVPN // This file has been modified for AmneziaVPN // diff --git a/client/platforms/macos/daemon/macosfirewall.h b/client/platforms/macos/daemon/macosfirewall.h index 0c56dcaa..faa87c8c 100644 --- a/client/platforms/macos/daemon/macosfirewall.h +++ b/client/platforms/macos/daemon/macosfirewall.h @@ -1,3 +1,21 @@ +// Copyright (c) 2023 Private Internet Access, Inc. +// +// This file is part of the Private Internet Access Desktop Client. +// +// The Private Internet Access Desktop Client is free software: you can +// redistribute it and/or modify it under the terms of the GNU General Public +// License as published by the Free Software Foundation, either version 3 of +// the License, or (at your option) any later version. +// +// The Private Internet Access Desktop Client is distributed in the hope that +// it will be useful, but WITHOUT ANY WARRANTY; without even the implied +// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with the Private Internet Access Desktop Client. If not, see +// . + // Copyright (c) 2024 AmneziaVPN // This file has been modified for AmneziaVPN // From ce0a4f1f961e1495941be676e834e1dda74346fe Mon Sep 17 00:00:00 2001 From: albexk Date: Sun, 28 Jan 2024 17:29:54 +0300 Subject: [PATCH 26/27] Version bump 4.2.1.1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 06e92993..42cd7799 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) set(PROJECT AmneziaVPN) -project(${PROJECT} VERSION 4.2.1.0 +project(${PROJECT} VERSION 4.2.1.1 DESCRIPTION "AmneziaVPN" HOMEPAGE_URL "https://amnezia.org/" ) @@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d") set(RELEASE_DATE "${CURRENT_DATE}") set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) -set(APP_ANDROID_VERSION_CODE 42) +set(APP_ANDROID_VERSION_CODE 43) if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") set(MZ_PLATFORM_NAME "linux") From 7b0a9a055f1805cfc34f537e83b05224421b7345 Mon Sep 17 00:00:00 2001 From: KsZnak Date: Sun, 28 Jan 2024 21:46:20 +0200 Subject: [PATCH 27/27] Update amneziavpn_ru.ts --- client/translations/amneziavpn_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/translations/amneziavpn_ru.ts b/client/translations/amneziavpn_ru.ts index ddd7b0e9..c4c9fe58 100644 --- a/client/translations/amneziavpn_ru.ts +++ b/client/translations/amneziavpn_ru.ts @@ -379,7 +379,7 @@ Already installed containers were found on the server. All installed containers Save and Restart Amnezia - Сохранить и пререзагрузить Amnezia + Сохранить и перезагрузить Amnezia