Compare commits

...

7 Commits

Author SHA1 Message Date
NickVs2015 cbcdf0f221 fix: remove comments 2026-06-26 16:48:47 +03:00
NickVs2015 79e28c55df fix: suppress linux firewall errors and add missing 400.allowPIA anchor 2026-06-26 16:17:08 +03:00
NickVs2015 80238a0620 fix: linux DNS set on connect and cleanup on disconnect for AWG/WireGuard
- Set DNS resolvers via systemd-resolved after flushDns on connect (vpnConnection.cpp)
- RevertLink after systemd-resolved restart on disconnect (DnsUtilsLinux)
- Add scheduleRetry() to retry full DNS setup on D-Bus transient failures
- Reduce D-Bus call timeout to 5s; use systemctl restart for flushDns
- Guard maybeUpdateResolvers with #ifndef Q_OS_LINUX to preserve macOS/Windows DNS
2026-06-26 13:08:39 +03:00
NickVs2015 2c68aa4185 fix: control resetIpStack when destroy DnsUtilsLinux 2026-06-25 12:39:40 +03:00
NickVs2015 b6f15b4b49 fix: linux reconnect, DNS rewrite, dbus async, killswitch and NM fixes 2026-06-25 12:39:40 +03:00
NickVs2015 e3e6b15ff1 fix: extend IPC security validation to macOS firewall 2026-06-25 11:00:04 +03:00
NickVs2015 f75b239d69 fix: resolve critical IPC security vulnerabilities
- Validate IP/CIDR values from IPC before passing to Linux firewall
- Replace shell interpolation with direct execve in firewall update functions
- Block dangerous OpenVPN/WireGuard arguments in sanitizeArguments()
- Add programId bounds check in IpcServerProcess::setProgram()
- Add SO_PEERCRED peer authentication for IPC connections on Linux
2026-06-24 12:00:40 +03:00
14 changed files with 496 additions and 102 deletions
+15 -7
View File
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) }; return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif #endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100; constexpr int BUFFER_SIZE = 8192;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0; int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0; int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg; struct nlmsghdr *nlh, *nlmsg;
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
// This struct contain route attributes (route type) // This struct contain route attributes (route type)
struct rtattr *route_attribute; struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE]; char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE]; char msgbuf[100], buffer[BUFFER_SIZE];
char *ptr = buffer; char *ptr = buffer;
struct timeval tv; struct timeval tv;
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
nlh = (struct nlmsghdr *) ptr; nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */ /* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) || if((NLMSG_OK(nlh, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR)) (nlh->nlmsg_type == NLMSG_ERROR))
{ {
perror("Error in received packet"); perror("Error in received packet");
return {}; return {};
@@ -355,13 +355,15 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
} }
/* Break if its not a multi part message */ /* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0) if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
break; break;
} }
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
/* parse response */ /* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes)) int remaining = msg_len + received_bytes;
nlh = (struct nlmsghdr *) buffer;
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
{ {
/* Get the route data */ /* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh); route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
@@ -370,6 +372,10 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
if (route_entry->rtm_table != RT_TABLE_MAIN) if (route_entry->rtm_table != RT_TABLE_MAIN)
continue; continue;
/* Reset per-route to avoid cross-route state pollution */
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
route_attribute = (struct rtattr *) RTM_RTA(route_entry); route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh); route_attribute_len = RTM_PAYLOAD(nlh);
@@ -395,6 +401,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
break; break;
} }
} }
if (!(*gateway_address) || !(*interface))
qDebug() << "getGatewayAndIface: no gateway found";
close(sock); close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) }; return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
#endif #endif
+14 -15
View File
@@ -78,14 +78,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false; return false;
} }
if (!dnsutils()->restoreResolvers()) {
return false;
}
if (!maybeUpdateResolvers(config)) {
return false;
}
bool status = run(Switch, config); bool status = run(Switch, config);
logger.debug() << "Connection status:" << status; logger.debug() << "Connection status:" << status;
if (status) { if (status) {
@@ -142,10 +134,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false; return false;
} }
if (!maybeUpdateResolvers(config)) {
return false;
}
// set routing // set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) { for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) { if (!wgutils()->updateRoutePrefix(ip)) {
@@ -154,6 +142,12 @@ bool Daemon::activate(const InterfaceConfig& config) {
} }
} }
#ifndef Q_OS_LINUX
if (!maybeUpdateResolvers(config)) {
return false;
}
#endif
bool status = run(Up, config); bool status = run(Up, config);
logger.debug() << "Connection status:" << status; logger.debug() << "Connection status:" << status;
if (status) { if (status) {
@@ -168,15 +162,20 @@ bool Daemon::activate(const InterfaceConfig& config) {
bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) { bool Daemon::maybeUpdateResolvers(const InterfaceConfig& config) {
if ((config.m_hopType == InterfaceConfig::MultiHopExit) || if ((config.m_hopType == InterfaceConfig::MultiHopExit) ||
(config.m_hopType == InterfaceConfig::SingleHop)) { (config.m_hopType == InterfaceConfig::SingleHop)) {
if (!dnsutils()) {
logger.error() << "dnsutils is null, cannot update resolvers";
return false;
}
QList<QHostAddress> resolvers; QList<QHostAddress> resolvers;
resolvers.append(QHostAddress(config.m_primaryDnsServer)); resolvers.append(QHostAddress(config.m_primaryDnsServer));
if (!config.m_secondaryDnsServer.isEmpty()) { if (!config.m_secondaryDnsServer.isEmpty()) {
resolvers.append(QHostAddress(config.m_secondaryDnsServer)); resolvers.append(QHostAddress(config.m_secondaryDnsServer));
} }
// If the DNS is not the Gateway, it's a user defined DNS // If the DNS is the Gateway, also add IPv6 gateway (only if non-empty)
// thus, not add any other :) if (config.m_primaryDnsServer == config.m_serverIpv4Gateway &&
if (config.m_primaryDnsServer == config.m_serverIpv4Gateway) { !config.m_serverIpv6Gateway.isEmpty()) {
resolvers.append(QHostAddress(config.m_serverIpv6Gateway)); resolvers.append(QHostAddress(config.m_serverIpv6Gateway));
} }
+151 -34
View File
@@ -7,8 +7,11 @@
#include <net/if.h> #include <net/if.h>
#include <QDBusVariant> #include <QDBusVariant>
#include <QNetworkInterface>
#include <QTimer>
#include <QtDBus/QtDBus> #include <QtDBus/QtDBus>
#include "core/utils/networkUtilities.h"
#include "leakdetector.h" #include "leakdetector.h"
#include "logger.h" #include "logger.h"
@@ -27,24 +30,78 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
logger.debug() << "DnsUtilsLinux created."; logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus(); QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, auto* watcher = new QDBusServiceWatcher(
DBUS_RESOLVE_MANAGER, conn, this); DBUS_RESOLVE_SERVICE, conn,
QDBusServiceWatcher::WatchForRegistration |
QDBusServiceWatcher::WatchForUnregistration, this);
connect(watcher, &QDBusServiceWatcher::serviceRegistered,
this, &DnsUtilsLinux::onResolverRegistered);
connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
this, &DnsUtilsLinux::onResolverUnregistered);
if (conn.interface()->isServiceRegistered(DBUS_RESOLVE_SERVICE)) {
onResolverRegistered();
}
}
void DnsUtilsLinux::onResolverRegistered() {
m_resolver.reset(new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER,
QDBusConnection::systemBus()));
logger.debug() << "systemd-resolved available, DNS resolver initialized";
if (m_revertAfterRegister > 0) {
logger.debug() << "Calling RevertLink after restart for ifindex" << m_revertAfterRegister;
QDBusMessage msg = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "RevertLink");
msg.setArguments({QVariant::fromValue(m_revertAfterRegister)});
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
int savedIdx = m_revertAfterRegister;
m_revertAfterRegister = 0;
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this,
[this, savedIdx](QDBusPendingCallWatcher* w) {
QDBusPendingReply<> r = *w;
if (r.isError()) {
logger.debug() << "RevertLink after restart failed for ifindex" << savedIdx
<< ":" << r.error().message();
} else {
logger.debug() << "RevertLink after restart succeeded for ifindex" << savedIdx;
}
w->deleteLater();
});
}
if (!m_pendingIfname.isEmpty()) {
logger.debug() << "Re-applying DNS configuration for" << m_pendingIfname;
updateResolvers(m_pendingIfname, m_pendingResolvers);
}
}
void DnsUtilsLinux::onResolverUnregistered() {
logger.debug() << "systemd-resolved disappeared, dropping DNS resolver";
m_resolver.reset();
} }
DnsUtilsLinux::~DnsUtilsLinux() { DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux); MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin(); if (m_revertOnDestroy && m_resolver) {
iterator != m_linkDomains.constEnd(); ++iterator) { if (m_gatewayIfindex > 0)
QList<QVariant> argumentList; setLinkDefaultRoute(m_gatewayIfindex, true);
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) { for (auto iterator = m_linkDomains.constBegin();
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex); iterator != m_linkDomains.constEnd(); ++iterator) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(iterator.key());
argumentList << QVariant::fromValue(iterator.value());
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
argumentList);
}
if (m_ifindex > 0) {
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
}
} }
logger.debug() << "DnsUtilsLinux destroyed."; logger.debug() << "DnsUtilsLinux destroyed.";
@@ -52,12 +109,37 @@ DnsUtilsLinux::~DnsUtilsLinux() {
bool DnsUtilsLinux::updateResolvers(const QString& ifname, bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) { const QList<QHostAddress>& resolvers) {
m_revertAfterRegister = 0;
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
m_ifindex = if_nametoindex(qPrintable(ifname)); m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) { if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname; logger.error() << "Unable to resolve ifindex for" << ifname;
return false; return false;
} }
// Reset retry counter only when called externally (not from scheduleRetry)
if (ifname != m_pendingIfname || resolvers != m_pendingResolvers)
m_domainRetries = 0;
m_pendingIfname = ifname;
m_pendingResolvers = resolvers;
if (!m_resolver) {
logger.debug() << "systemd-resolved not ready, queuing DNS configuration";
return true;
}
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
setLinkDNS(m_ifindex, resolvers); setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true); setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains(); updateLinkDomains();
@@ -65,38 +147,54 @@ bool DnsUtilsLinux::updateResolvers(const QString& ifname,
} }
bool DnsUtilsLinux::restoreResolvers() { bool DnsUtilsLinux::restoreResolvers() {
m_revertOnDestroy = true;
m_pendingIfname.clear();
m_pendingResolvers.clear();
if (m_gatewayIfindex > 0) {
setLinkDefaultRoute(m_gatewayIfindex, true);
m_gatewayIfindex = 0;
}
for (auto iterator = m_linkDomains.constBegin(); for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) { iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value()); setLinkDomains(iterator.key(), iterator.value());
} }
m_linkDomains.clear(); m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) { if (m_ifindex > 0) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)}; m_revertAfterRegister = m_ifindex;
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_ifindex = 0; m_ifindex = 0;
} }
return true; return true;
} }
void DnsUtilsLinux::scheduleRetry() {
if (m_pendingIfname.isEmpty() || m_retryPending || m_domainRetries >= 5)
return;
m_retryPending = true;
++m_domainRetries;
logger.debug() << "Retrying full DNS setup (" << m_domainRetries << "/5)";
QTimer::singleShot(1000, this, [this]() {
m_retryPending = false;
if (!m_pendingIfname.isEmpty())
updateResolvers(m_pendingIfname, m_pendingResolvers);
});
}
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) { void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call; QDBusPendingReply<> reply = *call;
if (reply.isError()) { if (reply.isError()) {
logger.error() << "Error received from the DBus service"; logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)";
scheduleRetry();
} }
delete call; delete call;
} }
void DnsUtilsLinux::setLinkDNS(int ifindex, void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) { const QList<QHostAddress>& resolvers) {
if (!m_resolver) return;
QList<DnsResolver> resolverList; QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE]; char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf); const char* ifname = if_indextoname(ifindex, ifnamebuf);
@@ -111,8 +209,10 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
QList<QVariant> argumentList; QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex); argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList); argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList( QDBusMessage msg = QDBusMessage::createMethodCall(
QStringLiteral("SetLinkDNS"), argumentList); DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDNS");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -121,6 +221,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
void DnsUtilsLinux::setLinkDomains(int ifindex, void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) { const QList<DnsLinkDomain>& domains) {
if (!m_resolver) return;
char ifnamebuf[IF_NAMESIZE]; char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf); const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) { if (ifname) {
@@ -135,8 +236,10 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
QList<QVariant> argumentList; QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex); argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains); argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList( QDBusMessage msg = QDBusMessage::createMethodCall(
QStringLiteral("SetLinkDomains"), argumentList); DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDomains");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -144,11 +247,14 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
} }
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) { void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
if (!m_resolver) return;
QList<QVariant> argumentList; QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex); argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable); argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList( QDBusMessage msg = QDBusMessage::createMethodCall(
QStringLiteral("SetLinkDefaultRoute"), argumentList); DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_RESOLVE_MANAGER, "SetLinkDefaultRoute");
msg.setArguments(argumentList);
QDBusPendingReply<> reply = QDBusConnection::systemBus().asyncCall(msg, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -156,6 +262,7 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
} }
void DnsUtilsLinux::updateLinkDomains() { void DnsUtilsLinux::updateLinkDomains() {
if (!m_resolver) return;
/* Get the list of search domains, and remove any others that might conspire /* Get the list of search domains, and remove any others that might conspire
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't * to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
* seem to be able to demarshall complex property types. * seem to be able to demarshall complex property types.
@@ -165,7 +272,7 @@ void DnsUtilsLinux::updateLinkDomains() {
message << QString(DBUS_RESOLVE_MANAGER); message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains"); message << QString("Domains");
QDBusPendingReply<QVariant> reply = QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message); m_resolver->connection().asyncCall(message, 5000);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this, QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
@@ -174,11 +281,13 @@ void DnsUtilsLinux::updateLinkDomains() {
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) { void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call; QDBusPendingReply<QVariant> reply = *call;
call->deleteLater();
if (reply.isError()) { if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service"; logger.debug() << "DBus Domains call failed (may be transient after systemd-resolved restart)";
delete call; scheduleRetry();
return; return;
} }
m_domainRetries = 0;
/* Update the state of the DNS domains */ /* Update the state of the DNS domains */
m_linkDomains.clear(); m_linkDomains.clear();
@@ -204,9 +313,17 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
} }
/* Add a root search domain for the new interface. */ /* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root}; if (m_ifindex > 0) {
setLinkDomains(m_ifindex, newlist); setLinkDomains(m_ifindex, {root});
delete call;
/* Disable DefaultRoute on the physical gateway so systemd-resolved
* routes all DNS through the VPN interface. */
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
m_gatewayIfindex = gwIdx;
setLinkDefaultRoute(gwIdx, false);
}
}
} }
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy; static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
+18 -1
View File
@@ -6,7 +6,12 @@
#define DNSUTILSLINUX_H #define DNSUTILSLINUX_H
#include <QDBusInterface> #include <QDBusInterface>
#include <QScopedPointer>
#include <QDBusPendingCallWatcher> #include <QDBusPendingCallWatcher>
#include <QDBusServiceWatcher>
#include <QHostAddress>
#include <QList>
#include <QString>
#include "daemon/dnsutils.h" #include "daemon/dnsutils.h"
#include "dbustypeslinux.h" #include "dbustypeslinux.h"
@@ -29,13 +34,25 @@ class DnsUtilsLinux final : public DnsUtils {
void updateLinkDomains(); void updateLinkDomains();
private slots: private slots:
void onResolverRegistered();
void onResolverUnregistered();
void dnsCallCompleted(QDBusPendingCallWatcher*); void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*); void dnsDomainsReceived(QDBusPendingCallWatcher*);
private:
void scheduleRetry();
private: private:
int m_ifindex = 0; int m_ifindex = 0;
int m_gatewayIfindex = 0;
int m_domainRetries = 0;
bool m_revertOnDestroy = false;
bool m_retryPending = false;
int m_revertAfterRegister = 0;
QMap<int, DnsLinkDomainList> m_linkDomains; QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr; QScopedPointer<QDBusInterface> m_resolver;
QString m_pendingIfname;
QList<QHostAddress> m_pendingResolvers;
}; };
#endif // DNSUTILSLINUX_H #endif // DNSUTILSLINUX_H
+65 -20
View File
@@ -33,6 +33,7 @@
#include "linuxfirewall.h" #include "linuxfirewall.h"
#include "logger.h" #include "logger.h"
#include "xray_defs.h" #include "xray_defs.h"
#include <QFileInfo>
#include <QProcess> #include <QProcess>
#define BRAND_CODE "amn" #define BRAND_CODE "amn"
@@ -102,14 +103,7 @@ int LinuxFirewall::linkChain(LinuxFirewall::IPVersion ip, const QString& chain,
const QString cmd = getCommand(ip); const QString cmd = getCommand(ip);
if (mustBeFirst) if (mustBeFirst)
{ {
// This monster shell script does the following: 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));
// 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 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)); 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 +285,8 @@ void LinuxFirewall::install()
installAnchor(IPv4, QStringLiteral("110.allowNets"), {}); installAnchor(IPv4, QStringLiteral("110.allowNets"), {});
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
installAnchor(Both, QStringLiteral("100.blockAll"), { installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"), QStringLiteral("-j REJECT"),
}); });
@@ -454,16 +450,33 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
static QStringList existingServers {}; static QStringList existingServers {};
existingServers = servers; existingServers = servers;
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName)); const QString chain = QStringLiteral("%1.320.allowDNS").arg(kAnchorName);
for (const QString& rule : getDNSRules(servers)) executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule)); 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) void LinuxFirewall::updateAllowNets(const QStringList& servers)
{ {
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName)); const QString chain = QStringLiteral("%1.110.allowNets").arg(kAnchorName);
for (const QString& rule : getAllowRule(servers)) executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule)); for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("ACCEPT")});
} }
void LinuxFirewall::updateBlockNets(const QStringList& servers) void LinuxFirewall::updateBlockNets(const QStringList& servers)
@@ -471,9 +484,12 @@ void LinuxFirewall::updateBlockNets(const QStringList& servers)
static QStringList existingServers {}; static QStringList existingServers {};
existingServers = servers; existingServers = servers;
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName)); const QString chain = QStringLiteral("%1.120.blockNets").arg(kAnchorName);
for (const QString& rule : getBlockRule(servers)) executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule)); for (const QString& server : servers)
executeIptables(QStringLiteral("iptables"),
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
QStringLiteral("-j"), QStringLiteral("REJECT")});
} }
int waitForExitCode(QProcess& process) int waitForExitCode(QProcess& process)
@@ -506,10 +522,39 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
return exitCode; 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() 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/"; 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)); 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) // 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)); 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 +563,7 @@ void LinuxFirewall::setupTrafficSplitting()
void LinuxFirewall::teardownTrafficSplitting() void LinuxFirewall::teardownTrafficSplitting()
{ {
logger.info() << "Tearing down cgroup and routing rules"; 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("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 table %1 2>/dev/null || true").arg(kRtableName));
execute(QStringLiteral("ip route flush cache")); execute(QStringLiteral("ip route flush cache"));
} }
@@ -85,6 +85,7 @@ private:
static void setupTrafficSplitting(); static void setupTrafficSplitting();
static void teardownTrafficSplitting(); static void teardownTrafficSplitting();
static int execute(const QString& command, bool ignoreErrors = false); static int execute(const QString& command, bool ignoreErrors = false);
static int executeIptables(const QString& program, const QStringList& args, bool ignoreErrors = false);
private: private:
// Chain names // Chain names
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain; static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
@@ -237,7 +237,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
// Exclude the server address, except for multihop exit servers. // Exclude the server address, except for multihop exit servers.
if ((config.m_hopType != InterfaceConfig::MultiHopExit) && if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
(m_rtmonitor != nullptr)) { (m_rtmonitor != nullptr)) {
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn)); if (!config.m_serverIpv4AddrIn.isEmpty() &&
!m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn))) {
logger.error() << "No gateway — cannot add server exclusion route";
return false;
}
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn)); m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
} }
+23
View File
@@ -17,6 +17,7 @@
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
#include "core/utils/ipcClient.h" #include "core/utils/ipcClient.h"
#include <core/protocols/wireGuardProtocol.h> #include <core/protocols/wireGuardProtocol.h>
#include "daemon/wireguardutils.h"
#endif #endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
@@ -127,6 +128,23 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
else else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS"; qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
#ifdef Q_OS_LINUX
if (ContainerUtils::isAwgContainer(container) || container == DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString();
QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString();
QList<QHostAddress> resolvers;
if (!dns1.isEmpty()) resolvers << QHostAddress(dns1);
if (!dns2.isEmpty()) resolvers << QHostAddress(dns2);
if (!resolvers.isEmpty()) {
auto r = iface->updateResolvers(WG_INTERFACE, resolvers);
if (r.waitForFinished() && r.returnValue())
qDebug() << "VpnConnection: DNS resolvers set via systemd-resolved";
else
qWarning() << "VpnConnection: Failed to set DNS resolvers";
}
}
#endif
if (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard) { if (!ContainerUtils::isAwgContainer(container) && container != DockerContainer::WireGuard) {
QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString(); QString dns1 = m_vpnConfiguration.value(configKey::dns1).toString();
QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString(); QString dns2 = m_vpnConfiguration.value(configKey::dns2).toString();
@@ -160,6 +178,11 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
} break; } break;
case Vpn::ConnectionState::Disconnected: case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error: { case Vpn::ConnectionState::Error: {
#ifdef Q_OS_LINUX
if (ContainerUtils::isAwgContainer(container) || container == DockerContainer::WireGuard) {
iface->restoreResolvers();
}
#endif
auto flushDns = iface->flushDns(); auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue()) if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS"; qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
+47 -4
View File
@@ -3,6 +3,8 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QRegularExpression>
#include <QSet>
#include "../client/core/utils/utilities.h" #include "../client/core/utils/utilities.h"
@@ -15,7 +17,8 @@ enum PermittedProcess {
OpenVPN, OpenVPN,
Wireguard, Wireguard,
Tun2Socks, Tun2Socks,
CertUtil CertUtil,
_Count
}; };
inline QString permittedProcessPath(PermittedProcess pid) inline QString permittedProcessPath(PermittedProcess pid)
@@ -57,16 +60,56 @@ inline QStringList sanitizeArguments(PermittedProcess proc, const QStringList &a
QList<Validator> positionalArgs; QList<Validator> positionalArgs;
switch (proc) { 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: case Tun2Socks:
namedArgs["-device"] = [](const QString& v) { return v.startsWith("tun://"); }; namedArgs["-device"] = [](const QString& v) { return v.startsWith("tun://"); };
namedArgs["-proxy"] = [](const QString& v) { return v.startsWith("socks5://"); }; namedArgs["-proxy"] = [](const QString& v) { return v.startsWith("socks5://"); };
break; break;
default: case CertUtil:
//FIXME
return args; return args;
default:
return {};
} }
QStringList sanitized; QStringList sanitized;
for (int i = 0, pos = 0; i < args.size(); i++) { for (int i = 0, pos = 0; i < args.size(); i++) {
+30 -1
View File
@@ -22,6 +22,27 @@
#include "tapcontroller_win.h" #include "tapcontroller_win.h"
#endif #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) IpcServer::IpcServer(QObject *parent) : IpcInterfaceSource(parent)
{ {
@@ -48,8 +69,16 @@ int IpcServer::createPrivilegedProcess()
// Make sure any connections are handed to QtRO // Make sure any connections are handed to QtRO
QObject::connect(pd.localServer.data(), &QLocalServer::newConnection, this, [pd]() { QObject::connect(pd.localServer.data(), &QLocalServer::newConnection, this, [pd]() {
qDebug() << "IpcServer new connection"; 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) { if (pd.serverNode) {
pd.serverNode->addHostSideConnection(pd.localServer->nextPendingConnection()); pd.serverNode->addHostSideConnection(conn);
pd.serverNode->enableRemoting(pd.ipcProcess.data()); pd.serverNode->enableRemoting(pd.ipcProcess.data());
} }
}); });
+5
View File
@@ -77,6 +77,11 @@ void IpcServerProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
void IpcServerProcess::setProgram(int programId) 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_program = static_cast<amnezia::PermittedProcess>(programId);
m_process->setProgram(amnezia::permittedProcessPath(m_program)); m_process->setProgram(amnezia::permittedProcessPath(m_program));
m_process->setArguments({}); m_process->setArguments({});
+79 -13
View File
@@ -3,6 +3,7 @@
#include <QApplication> #include <QApplication>
#include <QHostAddress> #include <QHostAddress>
#include <QRegularExpression>
#include "../client/core/utils/protocolEnum.h" #include "../client/core/utils/protocolEnum.h"
#include "../client/core/protocols/protocolUtils.h" #include "../client/core/protocols/protocolUtils.h"
@@ -11,6 +12,37 @@
#include "qjsonarray.h" #include "qjsonarray.h"
#include "version.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 #ifdef Q_OS_WIN
#include "../client/platforms/windows/daemon/windowsfirewall.h" #include "../client/platforms/windows/daemon/windowsfirewall.h"
#include "../client/platforms/windows/daemon/windowsdaemon.h" #include "../client/platforms/windows/daemon/windowsdaemon.h"
@@ -166,7 +198,11 @@ bool KillSwitch::disableAllTraffic() {
bool KillSwitch::resetAllowedRange(const QStringList &ranges) { bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
#ifdef Q_OS_LINUX
m_allowedRanges = filterIpList(ranges);
#else
m_allowedRanges = ranges; m_allowedRanges = ranges;
#endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), true);
@@ -189,7 +225,12 @@ bool KillSwitch::resetAllowedRange(const QStringList &ranges) {
} }
bool KillSwitch::addAllowedRange(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)) { if (!range.isEmpty() && !m_allowedRanges.contains(range)) {
m_allowedRanges.append(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("000.allowLoopback"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll); LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("100.blockAll"), blockAll);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("110.allowNets"), allowNets);
LinuxFirewall::updateAllowNets(allownets); LinuxFirewall::updateAllowNets(filterIpList(allownets));
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("120.blockNets"), blockAll); 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::Both, QStringLiteral("130.allowMarkedXray"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("200.allowVPN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv6, QStringLiteral("250.blockIPv6"), true);
@@ -328,21 +369,34 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true); LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), true);
QStringList dnsServers; 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 // We don't use secondary DNS if primary DNS is AmneziaDNS
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString()); 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.1");
dnsServers.append("127.0.0.53"); dnsServers.append("127.0.0.53");
for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) { for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
if (!dns.isString()) { if (!dns.isString()) {
break; 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::updateDNSServers(dnsServers);
@@ -360,28 +414,40 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("000.allowLoopback"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll); MacOSFirewall::setAnchorEnabled(QStringLiteral("100.blockAll"), blockAll);
MacOSFirewall::setAnchorEnabled(QStringLiteral("110.allowNets"), allowNets); 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::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("200.allowVPN"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("250.blockIPv6"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("290.allowDHCP"), true);
MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true); MacOSFirewall::setAnchorEnabled(QStringLiteral("300.allowLAN"), true);
QStringList dnsServers; 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 // We don't use secondary DNS if primary DNS is AmneziaDNS
if (!configStr.value(amnezia::configKey::dns1).toString().contains(amnezia::protocols::dns::amneziaDnsIp)) { if (!dns1.contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsServers.append(configStr.value(amnezia::configKey::dns2).toString()); 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()) { for (auto dns : configStr.value(amnezia::configKey::allowedDnsServers).toArray()) {
if (!dns.isString()) { if (!dns.isString()) {
break; 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); MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true);
+38 -1
View File
@@ -17,6 +17,35 @@
#include "tapcontroller_win.h" #include "tapcontroller_win.h"
#endif #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 { namespace {
Logger logger("WgDaemonServer"); Logger logger("WgDaemonServer");
} }
@@ -35,7 +64,15 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() { QObject::connect(m_server.data(), &QLocalServer::newConnection, this, [this]() {
qDebug() << "LocalServer new connection"; 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) { if (!m_isRemotingEnabled) {
m_isRemotingEnabled = true; m_isRemotingEnabled = true;
+1 -1
View File
@@ -182,7 +182,7 @@ bool RouterLinux::flushDns()
if (output.isEmpty()) if (output.isEmpty())
qDebug().noquote() << "Flush dns completed"; qDebug().noquote() << "Flush dns completed";
else else
qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output; qDebug().noquote() << "OUTPUT systemctl restart: " + output;
return true; return true;
} }