mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-07-22 00:31:04 +03:00
Compare commits
5 Commits
chore-xcod
...
fix/ipc-in
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
041cdf39b4 | ||
|
|
35acbadd43 | ||
|
|
5d774e6c38 | ||
|
|
bc2a0ca649 | ||
|
|
3c6ac431b4 |
@@ -33,6 +33,7 @@
|
||||
#include "linuxfirewall.h"
|
||||
#include "logger.h"
|
||||
#include "xray_defs.h"
|
||||
#include <QFileInfo>
|
||||
#include <QProcess>
|
||||
|
||||
#define BRAND_CODE "amn"
|
||||
@@ -109,7 +110,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
|
||||
// (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));
|
||||
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 -r %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));
|
||||
@@ -291,6 +292,8 @@ void LinuxFirewall::install()
|
||||
|
||||
installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
|
||||
|
||||
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
|
||||
|
||||
installAnchor(Both, QStringLiteral("100.blockAll"), {
|
||||
QStringLiteral("-j REJECT"),
|
||||
});
|
||||
@@ -454,16 +457,33 @@ 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));
|
||||
const QString chain = QStringLiteral("%1.320.allowDNS").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
const QStringList ifaces = {
|
||||
QStringLiteral("amn0+"), QStringLiteral("tun0+"), QStringLiteral("tun2+")
|
||||
};
|
||||
for (const QString& server : servers) {
|
||||
for (const QString& iface : ifaces) {
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
|
||||
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("udp"),
|
||||
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
|
||||
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("tcp"),
|
||||
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxFirewall::updateAllowNets(const QStringList& 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));
|
||||
const QString chain = QStringLiteral("%1.110.allowNets").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
for (const QString& server : servers)
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
|
||||
QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
}
|
||||
|
||||
void LinuxFirewall::updateBlockNets(const QStringList& servers)
|
||||
@@ -471,9 +491,12 @@ 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));
|
||||
const QString chain = QStringLiteral("%1.120.blockNets").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
for (const QString& server : servers)
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
|
||||
QStringLiteral("-j"), QStringLiteral("REJECT")});
|
||||
}
|
||||
|
||||
int waitForExitCode(QProcess& process)
|
||||
@@ -506,10 +529,39 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
int LinuxFirewall::executeIptables(const QString &program, const QStringList &args, bool ignoreErrors)
|
||||
{
|
||||
QProcess p;
|
||||
p.start(program, args, 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 << ") $ " << program << args.join(QLatin1Char(' '));
|
||||
if (!out.isEmpty())
|
||||
logger.info() << out;
|
||||
if (!err.isEmpty())
|
||||
logger.warning() << err;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
void LinuxFirewall::setupTrafficSplitting()
|
||||
{
|
||||
const QString cgroupBase = QStringLiteral("/sys/fs/cgroup/net_cls");
|
||||
if (!QFileInfo::exists(cgroupBase)) {
|
||||
logger.warning() << "net_cls cgroup v1 not available, traffic splitting disabled";
|
||||
return;
|
||||
}
|
||||
|
||||
execute(QStringLiteral(
|
||||
"if ! grep -qE '^[0-9]+[[:space:]]+%1$' /etc/iproute2/rt_tables 2>/dev/null ; then "
|
||||
"echo '200 %1' >> /etc/iproute2/rt_tables ; fi"
|
||||
).arg(kRtableName));
|
||||
|
||||
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
|
||||
logger.info() << "Should be setting up cgroup in" << cGroupDir << "for traffic splitting";
|
||||
logger.info() << "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));
|
||||
@@ -518,7 +570,7 @@ void LinuxFirewall::setupTrafficSplitting()
|
||||
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("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 2>/dev/null || true").arg(kRtableName));
|
||||
execute(QStringLiteral("ip route flush cache"));
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ private:
|
||||
static void setupTrafficSplitting();
|
||||
static void teardownTrafficSplitting();
|
||||
static int execute(const QString& command, bool ignoreErrors = false);
|
||||
static int executeIptables(const QString& program, const QStringList& args, bool ignoreErrors = false);
|
||||
private:
|
||||
// Chain names
|
||||
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
|
||||
|
||||
12
conanfile.py
12
conanfile.py
@@ -19,26 +19,26 @@ class AmneziaVPN(ConanFile):
|
||||
|
||||
if has_service:
|
||||
if os == "Windows":
|
||||
self.requires("awg-windows/0.1.9")
|
||||
self.requires("awg-windows/0.2.0")
|
||||
self.requires("tap-windows6/9.27.0")
|
||||
self.requires("win-split-tunnel/1.2.5.0")
|
||||
self.requires("wintun/0.14.1")
|
||||
else:
|
||||
self.requires("awg-go/0.2.18")
|
||||
self.requires("awg-go/0.2.19")
|
||||
|
||||
self.requires("amnezia-xray-bindings/1.1.0")
|
||||
self.requires("amnezia-xray-bindings/1.2.0")
|
||||
self.requires("tun2socks/2.6.0")
|
||||
self.requires("openvpn/2.7.0")
|
||||
self.requires("v2ray-rules-dat/202603162227")
|
||||
|
||||
if has_ne:
|
||||
self.requires("awg-apple/2.0.2")
|
||||
self.requires("awg-apple/2.0.3")
|
||||
self.requires("hev-socks5-tunnel/2.15.0", options={"as_framework": True})
|
||||
self.requires("openvpnadapter/1.0.0")
|
||||
|
||||
if os == "Android":
|
||||
self.requires("amnezia-libxray/1.0.0")
|
||||
self.requires("awg-android/2.0.1")
|
||||
self.requires("amnezia-libxray/1.0.1")
|
||||
self.requires("awg-android/2.0.2")
|
||||
self.requires("openvpn-pt-android/1.0.0")
|
||||
|
||||
# expicitly use libssh@amnezia to prevent it from being downloaded from conan-center
|
||||
|
||||
51
ipc/ipc.h
51
ipc/ipc.h
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QRegularExpression>
|
||||
#include <QSet>
|
||||
|
||||
#include "../client/core/utils/utilities.h"
|
||||
|
||||
@@ -15,7 +17,8 @@ enum PermittedProcess {
|
||||
OpenVPN,
|
||||
Wireguard,
|
||||
Tun2Socks,
|
||||
CertUtil
|
||||
CertUtil,
|
||||
_Count
|
||||
};
|
||||
|
||||
inline QString permittedProcessPath(PermittedProcess pid)
|
||||
@@ -57,16 +60,56 @@ inline QStringList sanitizeArguments(PermittedProcess proc, const QStringList &a
|
||||
QList<Validator> positionalArgs;
|
||||
|
||||
switch (proc) {
|
||||
case OpenVPN: {
|
||||
static const QSet<QString> blocked = {
|
||||
QStringLiteral("--script-security"),
|
||||
QStringLiteral("--up"),
|
||||
QStringLiteral("--down"),
|
||||
QStringLiteral("--route-up"),
|
||||
QStringLiteral("--ipchange"),
|
||||
QStringLiteral("--tls-verify"),
|
||||
QStringLiteral("--plugin"),
|
||||
QStringLiteral("--auth-user-pass-verify"),
|
||||
QStringLiteral("--learn-address"),
|
||||
QStringLiteral("--client-connect"),
|
||||
QStringLiteral("--client-disconnect"),
|
||||
QStringLiteral("--management"),
|
||||
QStringLiteral("--management-external-key")
|
||||
};
|
||||
QStringList out;
|
||||
for (int i = 0; i < args.size(); ++i) {
|
||||
if (blocked.contains(args[i])) {
|
||||
qWarning() << "IPC: blocked OpenVPN argument:" << args[i];
|
||||
++i; // skip following value
|
||||
continue;
|
||||
}
|
||||
out << args[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
case Wireguard: {
|
||||
static const QRegularExpression hookRe(
|
||||
QStringLiteral(R"((?i)(PostUp|PreUp|PostDown|PreDown)\s*=)"));
|
||||
QStringList out;
|
||||
for (const QString& a : args) {
|
||||
if (hookRe.match(a).hasMatch()) {
|
||||
qWarning() << "IPC: blocked WireGuard hook argument:" << a;
|
||||
continue;
|
||||
}
|
||||
out << a;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
case Tun2Socks:
|
||||
namedArgs["-device"] = [](const QString& v) { return v.startsWith("tun://"); };
|
||||
namedArgs["-proxy"] = [](const QString& v) { return v.startsWith("socks5://"); };
|
||||
break;
|
||||
default:
|
||||
//FIXME
|
||||
case CertUtil:
|
||||
return args;
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
QStringList sanitized;
|
||||
|
||||
for (int i = 0, pos = 0; i < args.size(); i++) {
|
||||
|
||||
@@ -22,6 +22,27 @@
|
||||
#include "tapcontroller_win.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
extern uid_t g_allowedUid;
|
||||
extern bool g_allowedUidSet;
|
||||
|
||||
static bool checkPrivPeerCredentials(QLocalSocket *socket) {
|
||||
struct ucred cred{};
|
||||
socklen_t len = sizeof(cred);
|
||||
if (getsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) {
|
||||
qWarning() << "IpcServer: SO_PEERCRED failed, rejecting privileged process connection";
|
||||
return false;
|
||||
}
|
||||
if (cred.uid == 0) return true;
|
||||
if (g_allowedUidSet && cred.uid == g_allowedUid) return true;
|
||||
qWarning() << "IpcServer: rejected privileged process connection from unauthorized UID" << cred.uid;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
|
||||
{
|
||||
@@ -48,8 +69,16 @@ int IpcServer::createPrivilegedProcess()
|
||||
// Make sure any connections are handed to QtRO
|
||||
QObject::connect(pd.localServer.data(), &QLocalServer::newConnection, this, [pd]() {
|
||||
qDebug() << "IpcServer new connection";
|
||||
QLocalSocket *conn = pd.localServer->nextPendingConnection();
|
||||
#ifdef Q_OS_LINUX
|
||||
if (!checkPrivPeerCredentials(conn)) {
|
||||
conn->close();
|
||||
conn->deleteLater();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (pd.serverNode) {
|
||||
pd.serverNode->addHostSideConnection(pd.localServer->nextPendingConnection());
|
||||
pd.serverNode->addHostSideConnection(conn);
|
||||
pd.serverNode->enableRemoting(pd.ipcProcess.data());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -77,6 +77,11 @@ void IpcServerProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
|
||||
|
||||
void IpcServerProcess::setProgram(int programId)
|
||||
{
|
||||
if (programId <= static_cast<int>(amnezia::PermittedProcess::Invalid) ||
|
||||
programId >= static_cast<int>(amnezia::PermittedProcess::_Count)) {
|
||||
qWarning() << "IPC: invalid programId" << programId << ", ignoring";
|
||||
return;
|
||||
}
|
||||
m_program = static_cast<amnezia::PermittedProcess>(programId);
|
||||
m_process->setProgram(amnezia::permittedProcessPath(m_program));
|
||||
m_process->setArguments({});
|
||||
|
||||
@@ -11,7 +11,7 @@ from pathlib import Path
|
||||
|
||||
class AmneziaLibxray(ConanFile):
|
||||
name = "amnezia-libxray"
|
||||
version = "1.0.0"
|
||||
version = "1.0.1"
|
||||
settings = "os", "arch", "compiler"
|
||||
|
||||
def configure(self):
|
||||
@@ -29,8 +29,8 @@ class AmneziaLibxray(ConanFile):
|
||||
raise ConanInvalidConfiguration(f"{self.name} v{self.version} does not support {self.settings.os}")
|
||||
|
||||
def source(self):
|
||||
get(self, "https://github.com/amnezia-vpn/amnezia-libxray/archive/refs/tags/v1.0.0.zip",
|
||||
sha256="0c50c5acd5063a9fc3cfbb5b3e11481d30cfa3762b3cb1d72130248ff498e9df", strip_root=True
|
||||
get(self, f"https://github.com/amnezia-vpn/amnezia-libxray/archive/refs/tags/v{self.version}.zip",
|
||||
sha256="f17bca781d4a2fad4dfda9e8b1c0f6960a3f75f6218b906b1b0e2458652ffa5a", strip_root=True
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
|
||||
@@ -13,7 +13,7 @@ import shlex
|
||||
|
||||
class AmneziaXrayBindings(ConanFile):
|
||||
name = "amnezia-xray-bindings"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
settings = "os", "arch", "compiler"
|
||||
|
||||
_arch_map = {
|
||||
@@ -76,8 +76,8 @@ class AmneziaXrayBindings(ConanFile):
|
||||
)
|
||||
|
||||
def source(self):
|
||||
get(self, "https://github.com/amnezia-vpn/amnezia-xray-bindings/archive/v1.1.0.zip",
|
||||
sha256="6ea768ec7002cedd422a39aea17704b888acaf794432aa5937cfc92fb6d80eb5", strip_root=True)
|
||||
get(self, f"https://github.com/amnezia-vpn/amnezia-xray-bindings/archive/v{self.version}.zip",
|
||||
sha256="45b687dcd90cf1d953ece9c9f42d0b02785b2bbb33a97f5bad229f048da34d7c", strip_root=True)
|
||||
|
||||
def generate(self):
|
||||
tc = AutotoolsToolchain(self)
|
||||
|
||||
@@ -9,7 +9,7 @@ import platform
|
||||
|
||||
class AwgAndroid(ConanFile):
|
||||
name = "awg-android"
|
||||
version = "2.0.1"
|
||||
version = "2.0.2"
|
||||
settings = "os", "arch", "build_type", "compiler"
|
||||
|
||||
def configure(self):
|
||||
|
||||
@@ -9,7 +9,7 @@ import os
|
||||
|
||||
class AwgApple(ConanFile):
|
||||
name = "awg-apple"
|
||||
version = "2.0.2"
|
||||
version = "2.0.3"
|
||||
settings = "os", "arch", "compiler"
|
||||
|
||||
@property
|
||||
@@ -39,7 +39,7 @@ class AwgApple(ConanFile):
|
||||
|
||||
def source(self):
|
||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-apple/archive/refs/tags/v{self.version}.zip",
|
||||
sha256="a04f49eac9f82bbf5dd9031bab188d44de2b3482efde1b6e970821de1d5a3c5d", strip_root=True
|
||||
sha256="a868f0dfbded869b6c08b7a7a0e8c1a5485e649a179f4d1715b1b93664ea4fd4", strip_root=True
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
|
||||
@@ -14,7 +14,7 @@ import shlex
|
||||
|
||||
class AwgGo(ConanFile):
|
||||
name = "awg-go"
|
||||
version = "0.2.18"
|
||||
version = "0.2.19"
|
||||
package_type = "application"
|
||||
settings = "os", "arch"
|
||||
|
||||
@@ -61,7 +61,7 @@ class AwgGo(ConanFile):
|
||||
|
||||
def source(self):
|
||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-go/archive/refs/tags/v{self.version}.zip",
|
||||
sha256="58eefbd012e79bd1525f0e02d748979e9480acc1a339df8ceb3b9ffafcedb1ba", strip_root=True
|
||||
sha256="430396f9f2d4f6559ac69f178b707f955f7f847723b78e9de748b73d7e718798", strip_root=True
|
||||
)
|
||||
|
||||
def generate(self):
|
||||
|
||||
@@ -8,7 +8,7 @@ import os
|
||||
|
||||
class AwgWindows(ConanFile):
|
||||
name = "awg-windows"
|
||||
version = "0.1.9"
|
||||
version = "0.2.0"
|
||||
settings = "os", "arch"
|
||||
|
||||
@property
|
||||
@@ -63,7 +63,7 @@ class AwgWindows(ConanFile):
|
||||
|
||||
def source(self):
|
||||
get(self, f"https://github.com/amnezia-vpn/amneziawg-windows/archive/refs/tags/v{self.version}.zip",
|
||||
sha256="5c29a75cb2beae291cc51b64840a39f838da5f300b9e956f7964813a687ec74c", strip_root=True)
|
||||
sha256="9e74240b9bae2f9c8a9381bdf6e5a43ad37653de186e01e0f5a92935e4310252", strip_root=True)
|
||||
|
||||
def generate(self):
|
||||
tc = AutotoolsToolchain(self)
|
||||
|
||||
@@ -333,6 +333,12 @@ if(APPLE)
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
COMPONENT AmneziaVPN
|
||||
)
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E $<IF:$<CONFIG:Debug>,copy_directory,true>
|
||||
${CMAKE_SOURCE_DIR}/deploy/data/macos/pf
|
||||
"$<TARGET_FILE_DIR:${PROJECT}>/pf"
|
||||
COMMAND_EXPAND_LISTS
|
||||
)
|
||||
endif()
|
||||
|
||||
# install non-linked dependencies
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <QApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QRegularExpression>
|
||||
|
||||
#include "../client/core/utils/protocolEnum.h"
|
||||
#include "../client/core/protocols/protocolUtils.h"
|
||||
@@ -11,6 +12,37 @@
|
||||
#include "qjsonarray.h"
|
||||
#include "version.h"
|
||||
|
||||
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
static bool isValidIpOrCidr(const QString &value) {
|
||||
static const QRegularExpression re(
|
||||
QStringLiteral(R"(^(\d{1,3}\.){3}\d{1,3}(/\d{1,2})?$)"));
|
||||
if (!re.match(value).hasMatch()) return false;
|
||||
const QStringList ipParts = value.split(QLatin1Char('/'))[0].split(QLatin1Char('.'));
|
||||
for (const QString &part : ipParts) {
|
||||
bool ok;
|
||||
int octet = part.toInt(&ok);
|
||||
if (!ok || octet < 0 || octet > 255) return false;
|
||||
}
|
||||
if (value.contains(QLatin1Char('/'))) {
|
||||
bool ok;
|
||||
int prefix = value.split(QLatin1Char('/'))[1].toInt(&ok);
|
||||
if (!ok || prefix < 0 || prefix > 32) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static QStringList filterIpList(const QStringList &values) {
|
||||
QStringList safe;
|
||||
for (const QString &v : values) {
|
||||
if (isValidIpOrCidr(v))
|
||||
safe << v;
|
||||
else
|
||||
qWarning() << "IPC: rejected invalid IP/CIDR value:" << v;
|
||||
}
|
||||
return safe;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "../client/platforms/windows/daemon/windowsfirewall.h"
|
||||
#include "../client/platforms/windows/daemon/windowsdaemon.h"
|
||||
@@ -166,7 +198,11 @@ bool KillSwitch::disableAllTraffic() {
|
||||
|
||||
bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
m_allowedRanges = filterIpList(ranges);
|
||||
#else
|
||||
m_allowedRanges = ranges;
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true);
|
||||
@@ -189,7 +225,12 @@ bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
|
||||
}
|
||||
|
||||
bool KillSwitch::addAllowedRange(const QStringList &ranges) {
|
||||
for (const QString &range : ranges) {
|
||||
#ifdef Q_OS_LINUX
|
||||
const QStringList safeRanges = filterIpList(ranges);
|
||||
#else
|
||||
const QStringList &safeRanges = ranges;
|
||||
#endif
|
||||
for (const QString &range : safeRanges) {
|
||||
if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
|
||||
m_allowedRanges.append(range);
|
||||
}
|
||||
@@ -317,9 +358,9 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
|
||||
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::updateAllowNets(filterIpList(allownets));
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll);
|
||||
LinuxFirewall::updateBlockNets(blocknets);
|
||||
LinuxFirewall::updateBlockNets(filterIpList(blocknets));
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("130.allowMarkedXray"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
|
||||
@@ -328,23 +369,36 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
|
||||
QStringList dnsServers;
|
||||
|
||||
dnsServers.append(configStr.value(amnezia::configKey::dns1).toString());
|
||||
const QString dns1 = configStr.value(amnezia::configKey::dns1).toString();
|
||||
if (isValidIpOrCidr(dns1))
|
||||
dnsServers.append(dns1);
|
||||
else if (!dns1.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid dns1:" << dns1;
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString());
|
||||
if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||
const QString dns2 = configStr.value(amnezia::configKey::dns2).toString();
|
||||
if (isValidIpOrCidr(dns2))
|
||||
dnsServers.append(dns2);
|
||||
else if (!dns2.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid dns2:" << dns2;
|
||||
}
|
||||
|
||||
dnsServers.append("127.0.0.1");
|
||||
dnsServers.append("127.0.0.53");
|
||||
|
||||
|
||||
|
||||
for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
|
||||
if (!dns.isString()) {
|
||||
break;
|
||||
}
|
||||
dnsServers.append(dns.toString());
|
||||
const QString dnsStr = dns.toString();
|
||||
if (isValidIpOrCidr(dnsStr))
|
||||
dnsServers.append(dnsStr);
|
||||
else if (!dnsStr.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid allowedDnsServer:" << dnsStr;
|
||||
}
|
||||
|
||||
|
||||
LinuxFirewall::updateDNSServers(dnsServers);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
|
||||
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
|
||||
@@ -360,28 +414,40 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
|
||||
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::setAnchorTable(QStringLiteral("110.allowNets"), allowNets, QStringLiteral("allownets"), filterIpList(allownets));
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("120.blockNets"), blockNets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), blocknets);
|
||||
MacOSFirewall::setAnchorTable(QStringLiteral("120.blockNets"), blockNets, QStringLiteral("blocknets"), filterIpList(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::configKey::dns1).toString());
|
||||
const QString dns1 = configStr.value(amnezia::configKey::dns1).toString();
|
||||
if (isValidIpOrCidr(dns1))
|
||||
dnsServers.append(dns1);
|
||||
else if (!dns1.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid dns1:" << dns1;
|
||||
|
||||
// We don't use secondary DNS if primary DNS is AmneziaDNS
|
||||
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString());
|
||||
if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
|
||||
const QString dns2 = configStr.value(amnezia::configKey::dns2).toString();
|
||||
if (isValidIpOrCidr(dns2))
|
||||
dnsServers.append(dns2);
|
||||
else if (!dns2.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid dns2:" << dns2;
|
||||
}
|
||||
|
||||
|
||||
for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
|
||||
if (!dns.isString()) {
|
||||
break;
|
||||
}
|
||||
dnsServers.append(dns.toString());
|
||||
const QString dnsStr = dns.toString();
|
||||
if (isValidIpOrCidr(dnsStr))
|
||||
dnsServers.append(dnsStr);
|
||||
else if (!dnsStr.isEmpty())
|
||||
qWarning() << "IPC: rejected invalid allowedDnsServer:" << dnsStr;
|
||||
}
|
||||
|
||||
MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
|
||||
|
||||
@@ -17,6 +17,35 @@
|
||||
#include "tapcontroller_win.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
uid_t g_allowedUid = static_cast<uid_t>(-1);
|
||||
bool g_allowedUidSet = false;
|
||||
|
||||
static bool checkPeerCredentials(QLocalSocket *socket) {
|
||||
struct ucred cred{};
|
||||
socklen_t len = sizeof(cred);
|
||||
if (getsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_PEERCRED, &cred, &len) != 0) {
|
||||
qWarning() << "LocalServer: SO_PEERCRED failed, rejecting connection";
|
||||
return false;
|
||||
}
|
||||
if (cred.uid == 0) return true;
|
||||
if (!g_allowedUidSet) {
|
||||
g_allowedUid = cred.uid;
|
||||
g_allowedUidSet = true;
|
||||
qDebug() << "LocalServer: registered session UID" << g_allowedUid;
|
||||
}
|
||||
if (cred.uid != g_allowedUid) {
|
||||
qWarning() << "LocalServer: rejected connection from unauthorized UID" << cred.uid;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
Logger logger("WgDaemonServer");
|
||||
}
|
||||
@@ -35,7 +64,15 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
|
||||
|
||||
QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() {
|
||||
qDebug() << "LocalServer new connection";
|
||||
m_serverNode.addHostSideConnection(m_server->nextPendingConnection());
|
||||
QLocalSocket *conn = m_server->nextPendingConnection();
|
||||
#ifdef Q_OS_LINUX
|
||||
if (!checkPeerCredentials(conn)) {
|
||||
conn->close();
|
||||
conn->deleteLater();
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
m_serverNode.addHostSideConnection(conn);
|
||||
|
||||
if (!m_isRemotingEnabled) {
|
||||
m_isRemotingEnabled = true;
|
||||
|
||||
Reference in New Issue
Block a user