Merge branch 'dev' of github.com:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vladimir.kuznetsov 2024-01-31 12:42:19 +07:00
commit 4723019624
44 changed files with 1365 additions and 178 deletions

View file

@ -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")

View file

@ -98,11 +98,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
"own security protocol with SSL/TLS for key exchange.") },
{ DockerContainer::ShadowSocks,
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but is "
"recognised by analysis systems in some highly censored regions.") },
QObject::tr("ShadowSocks - masks VPN traffic, making it similar to normal web traffic, but it "
"may be recognized by analysis systems in some highly censored regions.") },
{ DockerContainer::Cloak,
QObject::tr("OpenVPN over Cloak - OpenVPN with VPN masquerading as web traffic and protection against "
"active-probbing detection. Ideal for bypassing blocking in regions with the highest levels "
"active-probing detection. Ideal for bypassing blocking in regions with the highest levels "
"of censorship.") },
{ DockerContainer::WireGuard,
QObject::tr("WireGuard - New popular VPN protocol with high performance, high speed and low power "
@ -119,7 +119,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
{ DockerContainer::Dns,
QObject::tr("Replace the current DNS server with your own. This will increase your privacy level.") },
{ DockerContainer::Sftp,
QObject::tr("Creates a file vault on your server to securely store and transfer files.") } };
QObject::tr("Create a file vault on your server to securely store and transfer files.") } };
}
QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
@ -153,8 +153,8 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol.") },
{ DockerContainer::Cloak,
QObject::tr("This is a combination of the OpenVPN protocol and the Cloak plugin designed specifically for "
"blocking protection.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all Internet traffic between the client "
"protecting against blocking.\n\n"
"OpenVPN provides a secure VPN connection by encrypting all internet traffic between the client "
"and the server.\n\n"
"Cloak protects OpenVPN from detection and blocking. \n\n"
"Cloak can modify packet metadata so that it completely masks VPN traffic as normal web traffic, "
@ -172,7 +172,7 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
"* Works over TCP network protocol, 443 port.\n") },
{ DockerContainer::WireGuard,
QObject::tr("A relatively new popular VPN protocol with a simplified architecture.\n"
"Provides stable VPN connection, high performance on all devices. Uses hard-coded encryption "
"WireGuard provides stable VPN connection and high performance on all devices. It uses hard-coded encryption "
"settings. WireGuard compared to OpenVPN has lower latency and better data transfer throughput.\n"
"WireGuard is very susceptible to blocking due to its distinct packet signatures. "
"Unlike some other VPN protocols that employ obfuscation techniques, "

View file

@ -17,41 +17,41 @@ public class Logger {
deinit {}
func log(message: String) {
let suiteName = "group.org.amnezia.AmneziaVPN"
let logKey = "logMessages"
let sharedDefaults = UserDefaults(suiteName: suiteName)
var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
logs.append(message)
sharedDefaults?.set(logs, forKey: logKey)
// let suiteName = "group.org.amnezia.AmneziaVPN"
// let logKey = "logMessages"
// let sharedDefaults = UserDefaults(suiteName: suiteName)
// var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? []
// logs.append(message)
// sharedDefaults?.set(logs, forKey: logKey)
}
func writeLog(to targetFile: String) -> Bool {
private func writeLog(to targetFile: String) -> Bool {
return true;
}
static func configureGlobal(tagged tag: String, withFilePath filePath: String?) {
if Logger.global != nil {
return
}
Logger.global = Logger(tagged: tag)
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
appVersion += " (\(appBuild))"
}
Logger.global?.log(message: "App version: \(appVersion)")
// if Logger.global != nil {
// return
// }
//
// Logger.global = Logger(tagged: tag)
//
// var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version"
//
// if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String {
// appVersion += " (\(appBuild))"
// }
//
// Logger.global?.log(message: "App version: \(appVersion)")
}
}
func wg_log(_ type: OSLogType, staticMessage msg: StaticString) {
os_log(msg, log: OSLog.default, type: type)
Logger.global?.log(message: "\(msg)")
// os_log(msg, log: OSLog.default, type: type)
// Logger.global?.log(message: "\(msg)")
}
func wg_log(_ type: OSLogType, message msg: String) {
os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
Logger.global?.log(message: msg)
// os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg)
// Logger.global?.log(message: msg)
}

View file

@ -0,0 +1,518 @@
// 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
// <https://www.gnu.org/licenses/>.
// 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 <https://www.gnu.org/licenses/>.
#include "linuxfirewall.h"
#include "logger.h"
#include <QProcess>
#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<QString, LinuxFirewall::FilterCallbackFunc> 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::getAllowRule(const QStringList& servers)
{
QStringList result;
for (const QString& server : servers)
{
result << QStringLiteral("-d %1 -j ACCEPT").arg(server);
}
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()
{
// 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(IPv4, QStringLiteral("120.blockNets"), {});
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
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 <interface name> -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(IPv4, QStringLiteral("120.blockNets"));
uninstallAnchor(IPv4, QStringLiteral("110.allowNets"));
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::updateAllowNets(const QStringList& servers)
{
static QStringList existingServers {};
existingServers = servers;
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
for (const QString& rule : getAllowRule(servers))
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
}
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)
{
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"));
}

View file

@ -0,0 +1,107 @@
// 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
// <https://www.gnu.org/licenses/>.
// 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 <https://www.gnu.org/licenses/>.
#ifndef LINUXFIREWALL_H
#define LINUXFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> 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 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)
bool allowNets;
bool blockNets;
};
class LinuxFirewall
{
public:
enum IPVersion { IPv4, IPv6, Both };
// Table names
static QString kFilterTable, kNatTable, kMangleTable, kRtableName, kRawTable;
public:
using FilterCallbackFunc = std::function<void()>;
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 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);
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 updateAllowNets(const QStringList& servers);
static void updateBlockNets(const QStringList& servers);
};
#endif // LINUXFIREWALL_H

View file

@ -11,7 +11,9 @@
#include <QFile>
#include <QLocalSocket>
#include <QTimer>
#include <QThread>
#include "linuxfirewall.h"
#include "leakdetector.h"
#include "logger.h"
@ -116,7 +118,27 @@ 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());
}
}
applyFirewallRules(params);
}
return (err == 0);
}
@ -140,6 +162,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 +277,31 @@ QList<WireguardUtils::PeerStatus> 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"), 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::IPv4, QStringLiteral("310.blockDNS"), true);
LinuxFirewall::updateDNSServers(params.dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
}
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddress& prefix) {
if (!m_rtmonitor) {
return false;

View file

@ -8,8 +8,11 @@
#include <QObject>
#include <QProcess>
#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();

View file

@ -0,0 +1,199 @@
// 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
// <https://www.gnu.org/licenses/>.
// 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 <https://www.gnu.org/licenses/>.
#include "macosfirewall.h"
#include "logger.h"
#include <QProcess>
#include <QCoreApplication>
#define BRAND_IDENTIFIER "amn"
namespace {
Logger logger("MacOSFirewall");
} // namespace
#include "macosfirewall.h"
#define ResourceDir qApp->applicationDirPath() + "/pf"
#define DaemonDataDir qApp->applicationDirPath() + "/pf"
#include <QProcess>
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);
}

View file

@ -0,0 +1,90 @@
// 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
// <https://www.gnu.org/licenses/>.
// 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 <https://www.gnu.org/licenses/>.
#ifndef MACOSFIREWALL_H
#define MACOSFIREWALL_H
#include <QString>
#include <QStringList>
// Descriptor for a set of firewall rules to be appled.
//
struct FirewallParams
{
QStringList dnsServers;
QVector<QString> 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)
};
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

View file

@ -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;

View file

@ -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();

View file

@ -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();
}

View file

@ -4,6 +4,7 @@
#include <QRandomGenerator>
#include <QTcpServer>
#include <QTcpSocket>
#include <QNetworkInterface>
#include "logger.h"
#include "openvpnprotocol.h"
@ -53,6 +54,11 @@ void OpenVpnProtocol::stop()
QThread::msleep(10);
m_managementServer.stop();
}
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
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;
}
}
@ -138,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;
}
@ -282,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);
}
@ -320,14 +332,28 @@ 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<QNetworkInterface> 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(QJsonObject(), netInterfaces.at(i).index());
m_configData.insert("vpnGateway", m_vpnGateway);
IpcClient::Interface()->enablePeerTraffic(m_configData);
}
}
}
#endif
#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());
}
}

View file

@ -44,6 +44,7 @@ private:
ManagementServer m_managementServer;
QString m_configFileName;
QJsonObject m_configData;
QTemporaryFile m_configFile;
uint selectMgmtPort();

View file

@ -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();
}

View file

@ -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<ControllerImpl> m_impl;
};

View file

@ -379,7 +379,7 @@ Already installed containers were found on the server. All installed containers
<message>
<location filename="../ui/qml/Pages2/PageProtocolAwgSettings.qml" line="311"/>
<source>Save and Restart Amnezia</source>
<translation>Сохранить и пререзагрузить Amnezia</translation>
<translation>Сохранить и перезагрузить Amnezia</translation>
</message>
</context>
<context>

View file

@ -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));
}

View file

@ -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";

View file

@ -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<int, QByteArray> 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);

View file

@ -1,4 +1,4 @@
#include "ikev2ConfigModel.h".h "
#include "ikev2ConfigModel.h"
#include "protocols/protocols_defs.h"

View file

@ -81,7 +81,7 @@ PageType {
Layout.fillWidth: true
Layout.topMargin: 32
headerText: qsTr("VPN Addresses Subnet")
headerText: qsTr("VPN address subnet")
textFieldText: subnetAddress
textField.onEditingFinished: {

View file

@ -91,7 +91,7 @@ PageType {
onLinkActivated: Qt.openUrlExternally(link)
textFormat: Text.RichText
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this url.")
text: qsTr("Use <a href=\"https://www.torproject.org/download/\" style=\"color: #FBB26A;\">Tor Browser</a> to open this URL.")
}
ParagraphTextType {
@ -100,7 +100,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("After installation it takes several minutes while your onion site will become available in the Tor Network.")
text: qsTr("After creating your onion site, it takes a few minutes for the Tor network to make it available for use.")
}
ParagraphTextType {

View file

@ -53,7 +53,7 @@ PageType {
Layout.leftMargin: 16
Layout.rightMargin: 16
text: qsTr("Support the project with a donation")
text: qsTr("Support Amnezia")
horizontalAlignment: Text.AlignHCenter
}

View file

@ -83,7 +83,7 @@ PageType {
Layout.fillWidth: true
text: qsTr("DNS servers")
descriptionText: qsTr("If AmneziaDNS is not used or installed")
descriptionText: qsTr("When AmneziaDNS is not used or installed")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {
@ -117,7 +117,7 @@ PageType {
Layout.fillWidth: true
text: qsTr("App-based split tunneling")
descriptionText: qsTr("Allows you to use the VPN only for certain applications")
descriptionText: qsTr("Allows you to use the VPN only for certain Apps")
rightImageSource: "qrc:/images/controls/chevron-right.svg"
clickedFunction: function() {

View file

@ -56,7 +56,7 @@ PageType {
QtObject {
id: onlyForwardSites
property string name: qsTr("Addresses from the list should be accessed via VPN")
property string name: qsTr("Only the sites listed here will be accessed through the VPN")
property int type: routeMode.onlyForwardSites
}
QtObject {
@ -251,7 +251,7 @@ PageType {
TextFieldWithHeaderType {
Layout.fillWidth: true
textFieldPlaceholderText: qsTr("Site or IP")
textFieldPlaceholderText: qsTr("website or IP")
buttonImageSource: "qrc:/images/controls/plus.svg"
clickedFunc: function() {
@ -295,7 +295,7 @@ PageType {
Layout.fillWidth: true
Layout.margins: 16
headerText: qsTr("Import/Export Sites")
headerText: qsTr("Import / Export Sites")
}
LabelWithButtonType {

View file

@ -73,7 +73,7 @@ PageType {
property bool hidePassword: true
Layout.fillWidth: true
headerText: qsTr("Password / SSH private key")
headerText: qsTr("Password or SSH private key")
textField.echoMode: hidePassword ? TextInput.Password : TextInput.Normal
buttonImageSource: textFieldText !== "" ? (hidePassword ? "qrc:/images/controls/eye.svg" : "qrc:/images/controls/eye-off.svg")
: ""

View file

@ -0,0 +1,3 @@
# Always allow at least loopback/localhost traffic
set skip on lo0
pass quick on lo0 flags any

View file

@ -0,0 +1,3 @@
# Block all traffic by default (can be overridden by later rules)
block out all flags any no state

View file

@ -0,0 +1,2 @@
table <allownets> {}
pass out to <allownets> flags any no state

View file

@ -0,0 +1,2 @@
table <blocknets> {}
block out to <blocknets> flags any no state

View file

@ -0,0 +1,2 @@
# Rules are set at runtime

View file

@ -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

View file

@ -0,0 +1,2 @@
# Block all outgoing IPv6 traffic (even over the VPN)
block return out inet6 flags any no state

View file

@ -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

View file

@ -0,0 +1,3 @@
# Allow LAN IP ranges
table <lanips> { 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 <lanips> flags any no state

View file

@ -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 <dnsaddr> {}
pass out proto { tcp, udp } to <dnsaddr> port 53 flags any no state

View file

@ -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

View file

@ -0,0 +1,2 @@
# Allow traffic by privileged group (used by daemon)
pass out proto { tcp, udp } group { amnvpn } flags any no state

View file

@ -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"

View file

@ -1,5 +1,7 @@
#include <QtCore>
#include <QString>
#include <QJsonObject>
#include "../client/daemon/interfaceconfig.h"
class IpcInterface
{
@ -19,8 +21,8 @@ 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) );
};

View file

@ -8,8 +8,18 @@
#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
#ifdef Q_OS_LINUX
#include "../client/platforms/linux/daemon/linuxfirewall.h"
#endif
#ifdef Q_OS_MACOS
#include "../client/platforms/macos/daemon/macosfirewall.h"
#endif
IpcServer::IpcServer(QObject *parent):
@ -22,15 +32,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<QLocalServer>(new QLocalServer(this));
pd.localServer->setSocketOptions(QLocalServer::WorldAccessOption);
if (!pd.localServer->listen(amnezia::getIpcProcessUrl(m_localpid))) {
@ -165,61 +174,160 @@ void IpcServer::setLogsEnabled(bool enabled)
}
}
bool IpcServer::copyWireguardConfig(const QString &sourcePath)
bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex)
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::copyWireguardConfig";
#ifdef Q_OS_WIN
return WindowsFirewall::instance()->enableKillSwitch(vpnAdapterIndex);
#endif
#ifdef Q_OS_LINUX
const QString wireguardConfigPath = "/etc/wireguard/wg99.conf";
if (QFile::exists(wireguardConfigPath))
#if defined(Q_OS_LINUX) || defined(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 (splitTunnelType == 0)
{
QFile::remove(wireguardConfigPath);
blockAll = true;
allowNets = true;
allownets.append(configStr.value(amnezia::config_key::hostName).toString());
} else if (splitTunnelType == 1)
{
blockNets = true;
for (auto v : splitTunnelSites) {
blocknets.append(v.toString());
}
} 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());
}
}
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;
// 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)
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;
}
bool IpcServer::isWireguardConfigExists(const QString &configPath)
bool IpcServer::disableKillSwitch()
{
#ifdef MZ_DEBUG
qDebug() << "IpcServer::isWireguardConfigExists";
#ifdef Q_OS_WIN
return WindowsFirewall::instance()->disableKillSwitch();
#endif
return QFileInfo::exists(configPath);
#ifdef Q_OS_LINUX
LinuxFirewall::uninstall();
#endif
#ifdef Q_OS_MACOS
MacOSFirewall::uninstall();
#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;
}

View file

@ -4,6 +4,8 @@
#include <QLocalServer>
#include <QObject>
#include <QRemoteObjectNode>
#include <QJsonObject>
#include "../client/daemon/interfaceconfig.h"
#include "ipc.h"
#include "ipcserverprocess.h"
@ -25,9 +27,9 @@ 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;
private:
int m_localpid = 0;

View file

@ -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()
@ -211,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}
@ -223,6 +226,7 @@ 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.cpp
)
endif()