Compare commits

...

15 Commits

Author SHA1 Message Date
NickVs2015 89b94d2588 fix: resolve flush error and fix comment 2026-04-11 00:12:58 +03:00
NickVs2015 9166e17a23 fix: add support system with nscd service 2026-04-10 23:08:32 +03:00
NickVs2015 37d2b8716d fix: infinite reconnection when NM up 2026-04-10 23:06:16 +03:00
NickVs2015 92b168100a fix: Linux killswitch changes 2026-04-10 23:06:16 +03:00
NickVs2015 0f6db8a238 fix: Linux dns without dbus 2026-04-10 23:06:16 +03:00
NickVs2015 7c075625d2 fix: revert wireguardutilslinux 2026-04-03 20:21:00 +03:00
NickVs2015 c6a40b09c9 fix: reconnect 2026-04-03 20:11:28 +03:00
NickVs2015 560c4070b4 fix: add NM_STATE_DISABLED and check getGatewayAndIface more carefully 2026-04-03 08:47:15 +03:00
NickVs2015 f6806459fd fix: restore reconnect time 2026-04-02 10:26:16 +03:00
NickVs2015 646b1561f8 revert: restore linuxfirewall.cpp 2026-04-01 00:57:11 +03:00
NickVs2015 6d1e10a2e3 feat: catch state changed via check gateway 2026-04-01 00:43:46 +03:00
NickVs2015 af0d561c5c fix: add dns load/unload 2026-03-31 23:14:17 +03:00
NickVs2015 214b18f65f fix: improve reeconnection 2026-03-31 14:08:04 +03:00
NickVs2015 bd554fb730 fix: Dbus error, fix race conditional 2026-03-31 10:35:04 +03:00
NickVs2015 316e64122e fix: add linux reconnection 2026-03-26 22:31:26 +03:00
15 changed files with 366 additions and 357 deletions
+77 -91
View File
@@ -292,117 +292,103 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
#endif
#ifdef Q_OS_LINUX
constexpr int BUFFER_SIZE = 100;
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
int sock = -1, msgseq = 0;
struct nlmsghdr *nlh, *nlmsg;
struct rtmsg *route_entry;
// This struct contain route attributes (route type)
struct rtattr *route_attribute;
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
char *ptr = buffer;
struct timeval tv;
static constexpr size_t BUFFER_SIZE = 8192;
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (sock < 0) {
perror("socket failed");
return {};
}
memset(msgbuf, 0, sizeof(msgbuf));
memset(gateway_address, 0, sizeof(gateway_address));
memset(interface, 0, sizeof(interface));
memset(buffer, 0, sizeof(buffer));
struct timeval tv { 1, 0 };
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
/* point the header and the msg structure pointers into the buffer */
nlmsg = (struct nlmsghdr *)msgbuf;
struct {
struct nlmsghdr hdr;
struct rtmsg rt;
} req {};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
req.hdr.nlmsg_type = RTM_GETROUTE;
req.hdr.nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST;
req.hdr.nlmsg_seq = 1;
req.hdr.nlmsg_pid = static_cast<uint32_t>(getpid());
req.rt.rtm_family = AF_INET;
/* Fill in the nlmsg header*/
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
/* 1 Sec Timeout to avoid stall */
tv.tv_sec = 1;
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
/* send msg */
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
if (send(sock, &req, req.hdr.nlmsg_len, 0) < 0) {
perror("send failed");
close(sock);
return {};
}
/* receive response */
do
{
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
if (received_bytes < 0) {
perror("Error in recv");
return {};
}
char buffer[BUFFER_SIZE];
size_t total_len = 0;
bool done = false;
nlh = (struct nlmsghdr *) ptr;
/* Check if the header is valid */
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
(nlmsg->nlmsg_type == NLMSG_ERROR))
{
perror("Error in received packet");
return {};
}
/* If we received all data break */
if (nlh->nlmsg_type == NLMSG_DONE)
while (!done && total_len < BUFFER_SIZE) {
ssize_t n = recv(sock, buffer + total_len, BUFFER_SIZE - total_len, 0);
if (n <= 0)
break;
else {
ptr += received_bytes;
msg_len += received_bytes;
}
/* Break if its not a multi part message */
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
break;
}
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
/* parse response */
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
{
/* Get the route data */
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
/* We are just interested in main routing table */
if (route_entry->rtm_table != RT_TABLE_MAIN)
continue;
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
route_attribute_len = RTM_PAYLOAD(nlh);
/* Loop through all attributes */
for ( ; RTA_OK(route_attribute, route_attribute_len);
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
int scan_len = static_cast<int>(n);
for (struct nlmsghdr *h = reinterpret_cast<struct nlmsghdr *>(buffer + total_len);
NLMSG_OK(h, scan_len);
h = NLMSG_NEXT(h, scan_len))
{
switch(route_attribute->rta_type) {
case RTA_OIF:
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
break;
case RTA_GATEWAY:
inet_ntop(AF_INET, RTA_DATA(route_attribute),
gateway_address, sizeof(gateway_address));
break;
default:
if (h->nlmsg_type == NLMSG_DONE || h->nlmsg_type == NLMSG_ERROR) {
done = true;
break;
}
}
if ((*gateway_address) && (*interface)) {
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
break;
}
total_len += static_cast<size_t>(n);
}
QString resultGw;
QString resultIf;
int remaining = static_cast<int>(total_len);
for (struct nlmsghdr *nlh = reinterpret_cast<struct nlmsghdr *>(buffer);
NLMSG_OK(nlh, remaining);
nlh = NLMSG_NEXT(nlh, remaining))
{
if (nlh->nlmsg_type == NLMSG_DONE || nlh->nlmsg_type == NLMSG_ERROR)
break;
if (nlh->nlmsg_type != RTM_NEWROUTE)
continue;
struct rtmsg *rt = static_cast<struct rtmsg *>(NLMSG_DATA(nlh));
if (rt->rtm_table != RT_TABLE_MAIN || rt->rtm_family != AF_INET)
continue;
char route_gw[INET_ADDRSTRLEN] = {};
char route_if[IF_NAMESIZE] = {};
int attr_len = RTM_PAYLOAD(nlh);
for (struct rtattr *rta = RTM_RTA(rt);
RTA_OK(rta, attr_len);
rta = RTA_NEXT(rta, attr_len))
{
if (rta->rta_type == RTA_GATEWAY)
inet_ntop(AF_INET, RTA_DATA(rta), route_gw, sizeof(route_gw));
else if (rta->rta_type == RTA_OIF)
if_indextoname(*static_cast<int *>(RTA_DATA(rta)), route_if);
}
if (!route_gw[0] || !route_if[0])
continue;
QString ifStr(route_if);
if (ifStr.startsWith(QLatin1String("amn")) ||
ifStr.startsWith(QLatin1String("tun")))
continue;
resultGw = QString::fromLatin1(route_gw);
resultIf = QString::fromLatin1(route_if);
qDebug() << "Gateway " << route_gw << " for interface " << route_if;
break;
}
close(sock);
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
return { resultGw, QNetworkInterface::interfaceFromName(resultIf) };
#endif
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
QString gateway;
+20 -4
View File
@@ -142,10 +142,6 @@ bool Daemon::activate(const InterfaceConfig& config) {
return false;
}
if (!maybeUpdateResolvers(config)) {
return false;
}
// set routing
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
if (!wgutils()->updateRoutePrefix(ip)) {
@@ -215,6 +211,11 @@ bool Daemon::addExclusionRoute(const IPAddress& prefix) {
return true;
}
if (!wgutils()->addExclusionRoute(prefix)) {
if (!m_pendingExclusionRoutes.contains(prefix)) {
logger.warning() << "Exclusion route deferred (no gateway):"
<< prefix.toString();
m_pendingExclusionRoutes.append(prefix);
}
return false;
}
m_excludedAddrSet[prefix] = 1;
@@ -480,6 +481,7 @@ bool Daemon::deactivate(bool emitSignals) {
wgutils()->deleteExclusionRoute(iterator.key());
}
m_excludedAddrSet.clear();
m_pendingExclusionRoutes.clear();
m_connections.clear();
// Delete the interface
@@ -589,6 +591,19 @@ void Daemon::checkHandshake() {
logger.debug() << "Checking for handshake...";
if (!m_pendingExclusionRoutes.isEmpty()) {
QList<IPAddress> stillPending;
for (const IPAddress& prefix : m_pendingExclusionRoutes) {
if (!wgutils()->addExclusionRoute(prefix)) {
stillPending.append(prefix);
} else {
logger.debug() << "Deferred exclusion route added:" << prefix.toString();
m_excludedAddrSet[prefix] = 1;
}
}
m_pendingExclusionRoutes = stillPending;
}
int pendingHandshakes = 0;
QList<WireguardUtils::PeerStatus> peers = wgutils()->getPeerStatus();
for (ConnectionState& connection : m_connections) {
@@ -605,6 +620,7 @@ void Daemon::checkHandshake() {
}
if (status.m_handshake != 0) {
connection.m_date.setMSecsSinceEpoch(status.m_handshake);
maybeUpdateResolvers(config);
emit connected(status.m_pubkey);
}
}
+1
View File
@@ -87,6 +87,7 @@ class Daemon : public QObject {
};
QMap<InterfaceConfig::HopType, ConnectionState> m_connections;
QHash<IPAddress, int> m_excludedAddrSet;
QList<IPAddress> m_pendingExclusionRoutes;
QTimer m_handshakeTimer;
};
+127 -174
View File
@@ -4,20 +4,14 @@
#include "dnsutilslinux.h"
#include <net/if.h>
#include <QDBusVariant>
#include <QtDBus/QtDBus>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QProcess>
#include "leakdetector.h"
#include "logger.h"
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
constexpr const char* DBUS_PROPERTY_INTERFACE =
"org.freedesktop.DBus.Properties";
namespace {
Logger logger("DnsUtilsLinux");
}
@@ -25,188 +19,147 @@ Logger logger("DnsUtilsLinux");
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
MZ_COUNT_CTOR(DnsUtilsLinux);
logger.debug() << "DnsUtilsLinux created.";
QDBusConnection conn = QDBusConnection::systemBus();
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
DBUS_RESOLVE_MANAGER, conn, this);
}
DnsUtilsLinux::~DnsUtilsLinux() {
MZ_COUNT_DTOR(DnsUtilsLinux);
for (auto iterator = m_linkDomains.constBegin();
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.";
}
void DnsUtilsLinux::writeResolvConf(const QList<QHostAddress>& resolvers) {
if (resolvers.isEmpty()) return;
static const QString kPath = QStringLiteral("/etc/resolv.conf");
if (m_resolvConfOriginal.isEmpty()) {
QFileInfo fi(kPath);
if (fi.isSymLink()) {
m_resolvConfOriginal = fi.symLinkTarget();
logger.debug() << "Saved resolv.conf symlink target:"
<< m_resolvConfOriginal;
if (!m_stateFilePath.isEmpty()) {
QFile sf(m_stateFilePath);
if (sf.open(QIODevice::WriteOnly | QIODevice::Text))
sf.write(m_resolvConfOriginal.toUtf8());
}
} else {
QFile orig(kPath);
if (orig.open(QIODevice::ReadOnly | QIODevice::Text)) {
QByteArray content = orig.readAll();
orig.close();
m_resolvConfOriginal = QStringLiteral("__file__");
logger.debug() << "resolv.conf is a regular file; saved content for restore";
if (!m_stateFilePath.isEmpty()) {
QFile sf(m_stateFilePath);
if (sf.open(QIODevice::WriteOnly | QIODevice::Text)) {
sf.write("__file__:");
sf.write(content);
}
}
} else {
m_resolvConfOriginal = QStringLiteral("__file__");
}
}
}
QFile::remove(kPath);
QFile f(kPath);
if (!f.open(QIODevice::WriteOnly | QIODevice::Text)) {
logger.warning() << "Failed to write" << kPath << ":" << f.errorString();
if (m_resolvConfOriginal != QStringLiteral("__file__"))
QFile::link(m_resolvConfOriginal, kPath);
m_resolvConfOriginal.clear();
if (!m_stateFilePath.isEmpty()) QFile::remove(m_stateFilePath);
return;
}
for (const auto& r : resolvers) {
if (r.protocol() == QAbstractSocket::IPv4Protocol)
f.write(
QStringLiteral("nameserver %1\n").arg(r.toString()).toUtf8());
}
f.close();
logger.debug() << "Wrote resolv.conf with" << resolvers.size()
<< "DNS servers";
}
void DnsUtilsLinux::restoreResolvConf() {
if (m_resolvConfOriginal.isEmpty()) return;
const QString original = m_resolvConfOriginal;
m_resolvConfOriginal.clear();
if (!m_stateFilePath.isEmpty())
QFile::remove(m_stateFilePath);
static const char* kPath = "/etc/resolv.conf";
QFile::remove(kPath);
if (original == QStringLiteral("__file__")) {
if (!m_resolvConfSavedContent.isEmpty()) {
QFile f(kPath);
if (f.open(QIODevice::WriteOnly | QIODevice::Text)) {
f.write(m_resolvConfSavedContent.toUtf8());
logger.debug() << "Restored resolv.conf from saved content";
}
m_resolvConfSavedContent.clear();
} else if (QFile::exists(QStringLiteral("/run/systemd/resolve/stub-resolv.conf"))) {
QFile::link(QStringLiteral("/run/systemd/resolve/stub-resolv.conf"), kPath);
logger.debug() << "Restored resolv.conf symlink to stub-resolv.conf";
}
} else {
QFile::link(original, kPath);
logger.debug() << "Restored resolv.conf symlink to" << original;
}
}
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
const QList<QHostAddress>& resolvers) {
m_ifindex = if_nametoindex(qPrintable(ifname));
if (m_ifindex <= 0) {
logger.error() << "Unable to resolve ifindex for" << ifname;
return false;
}
m_stateFilePath = QStringLiteral("/run/amnezia-dns-%1").arg(ifname);
setLinkDNS(m_ifindex, resolvers);
setLinkDefaultRoute(m_ifindex, true);
updateLinkDomains();
writeResolvConf(resolvers);
return true;
}
bool DnsUtilsLinux::restoreResolvers() {
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
setLinkDomains(iterator.key(), iterator.value());
if (m_resolvConfOriginal.isEmpty()) {
QStringList candidates;
if (!m_stateFilePath.isEmpty()) {
candidates << m_stateFilePath;
} else {
QDir runDir(QStringLiteral("/run"));
for (const QString& name : runDir.entryList(
{QStringLiteral("amnezia-dns-*")}, QDir::Files)) {
candidates << runDir.filePath(name);
}
}
for (const QString& path : candidates) {
QFile sf(path);
if (sf.open(QIODevice::ReadOnly)) {
QByteArray data = sf.readAll();
sf.close();
m_stateFilePath = path;
if (data.startsWith("__file__:")) {
m_resolvConfOriginal = QStringLiteral("__file__");
m_resolvConfSavedContent = QString::fromUtf8(data.mid(9));
} else {
m_resolvConfOriginal = QString::fromUtf8(data).trimmed();
}
logger.debug() << "Recovered DNS original from" << path << ":"
<< m_resolvConfOriginal;
break;
}
}
}
m_linkDomains.clear();
/* Revert the VPN interface's DNS configuration */
if (m_ifindex > 0) {
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("RevertLink"), argumentList);
const bool hadDnsState = !m_resolvConfOriginal.isEmpty();
restoreResolvConf();
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
m_ifindex = 0;
if (hadDnsState && QFile::exists(QStringLiteral("/run/systemd/resolve"))) {
QProcess::startDetached("systemctl", {"restart", "systemd-resolved"});
}
return true;
}
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
QDBusPendingReply<> reply = *call;
if (reply.isError()) {
logger.error() << "Error received from the DBus service";
}
delete call;
}
void DnsUtilsLinux::setLinkDNS(int ifindex,
const QList<QHostAddress>& resolvers) {
QList<DnsResolver> resolverList;
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
for (const auto& ip : resolvers) {
resolverList.append(ip);
if (ifname) {
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
<< ifname;
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(resolverList);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDNS"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDomains(int ifindex,
const QList<DnsLinkDomain>& domains) {
char ifnamebuf[IF_NAMESIZE];
const char* ifname = if_indextoname(ifindex, ifnamebuf);
if (ifname) {
for (const auto& d : domains) {
// The DNS search domains often winds up revealing user's ISP which
// can correlate back to their location.
logger.debug() << "Setting DNS domain:" << logger.sensitive(d.domain)
<< "via" << ifname << (d.search ? "search" : "");
}
}
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(domains);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDomains"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
QList<QVariant> argumentList;
argumentList << QVariant::fromValue(ifindex);
argumentList << QVariant::fromValue(enable);
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
QStringLiteral("SetLinkDefaultRoute"), argumentList);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::updateLinkDomains() {
/* 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
* seem to be able to demarshall complex property types.
*/
QDBusMessage message = QDBusMessage::createMethodCall(
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
message << QString(DBUS_RESOLVE_MANAGER);
message << QString("Domains");
QDBusPendingReply<QVariant> reply =
m_resolver->connection().asyncCall(message);
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
}
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
QDBusPendingReply<QVariant> reply = *call;
if (reply.isError()) {
logger.error() << "Error retrieving the DNS domains from the DBus service";
delete call;
return;
}
/* Update the state of the DNS domains */
m_linkDomains.clear();
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
for (const auto& d : list) {
if (d.ifindex == 0) {
continue;
}
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
}
/* Drop any competing root search domains. */
DnsLinkDomain root = DnsLinkDomain(".", true);
for (auto iterator = m_linkDomains.constBegin();
iterator != m_linkDomains.constEnd(); ++iterator) {
if (!iterator.value().contains(root)) {
continue;
}
QList<DnsLinkDomain> newlist = iterator.value();
newlist.removeAll(root);
setLinkDomains(iterator.key(), newlist);
}
/* Add a root search domain for the new interface. */
QList<DnsLinkDomain> newlist = {root};
setLinkDomains(m_ifindex, newlist);
delete call;
}
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
+10 -15
View File
@@ -5,11 +5,11 @@
#ifndef DNSUTILSLINUX_H
#define DNSUTILSLINUX_H
#include <QDBusInterface>
#include <QDBusPendingCallWatcher>
#include <QHostAddress>
#include <QList>
#include <QString>
#include "daemon/dnsutils.h"
#include "dbustypeslinux.h"
class DnsUtilsLinux final : public DnsUtils {
Q_OBJECT
@@ -23,19 +23,14 @@ class DnsUtilsLinux final : public DnsUtils {
bool restoreResolvers() override;
private:
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers);
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains);
void setLinkDefaultRoute(int ifindex, bool enable);
void updateLinkDomains();
private slots:
void dnsCallCompleted(QDBusPendingCallWatcher*);
void dnsDomainsReceived(QDBusPendingCallWatcher*);
void writeResolvConf(const QList<QHostAddress>& resolvers);
void restoreResolvConf();
private:
int m_ifindex = 0;
QMap<int, DnsLinkDomainList> m_linkDomains;
QDBusInterface* m_resolver = nullptr;
QString m_resolvConfOriginal;
QString m_resolvConfSavedContent;
QString m_stateFilePath;
};
#endif // DNSUTILSLINUX_H
#endif
+29 -16
View File
@@ -32,6 +32,7 @@
#include "linuxfirewall.h"
#include "logger.h"
#include <QHostAddress>
#include <QProcess>
#define BRAND_CODE "amn"
@@ -108,7 +109,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));
@@ -192,12 +193,8 @@ 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);
result << QStringLiteral("-o tun2+ -d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-o tun2+ -d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-d %1 -p udp --dport 53 -j ACCEPT").arg(server);
result << QStringLiteral("-d %1 -p tcp --dport 53 -j ACCEPT").arg(server);
}
return result;
}
@@ -245,7 +242,7 @@ void LinuxFirewall::install()
QStringLiteral("-o lo+ -j ACCEPT"),
});
installAnchor(IPv4, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("320.allowDNS"), {});
installAnchor(Both, QStringLiteral("310.blockDNS"), {
QStringLiteral("-p udp --dport 53 -j REJECT"),
@@ -289,6 +286,7 @@ void LinuxFirewall::install()
installAnchor(Both, QStringLiteral("100.blockAll"), {
QStringLiteral("-j REJECT"),
});
installAnchor(Both, QStringLiteral("400.allowPIA"), {});
// NAT rules
installAnchor(Both, QStringLiteral("100.transIp"), {
@@ -351,7 +349,7 @@ void LinuxFirewall::uninstall()
// Remove filter anchors
uninstallAnchor(Both, QStringLiteral("000.allowLoopback"));
uninstallAnchor(Both, QStringLiteral("400.allowPIA"));
uninstallAnchor(IPv4, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("320.allowDNS"));
uninstallAnchor(Both, QStringLiteral("310.blockDNS"));
uninstallAnchor(Both, QStringLiteral("300.allowLAN"));
uninstallAnchor(Both, QStringLiteral("290.allowDHCP"));
@@ -372,6 +370,8 @@ void LinuxFirewall::uninstall()
teardownTrafficSplitting();
anchorCallbacks.clear();
logger.debug() << "LinuxFirewall::uninstall() complete";
}
@@ -445,12 +445,17 @@ void LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPVersion ip, const QString
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));
execute(QStringLiteral("ip6tables -F %1.320.allowDNS").arg(kAnchorName));
for (const QString& server : servers) {
if (server.isEmpty()) continue;
const QString cmd = (QHostAddress(server).protocol() == QAbstractSocket::IPv6Protocol)
? QStringLiteral("ip6tables")
: QStringLiteral("iptables");
execute(QStringLiteral("%1 -A %2.320.allowDNS -d %3 -p udp --dport 53 -j ACCEPT").arg(cmd, kAnchorName, server));
execute(QStringLiteral("%1 -A %2.320.allowDNS -d %3 -p tcp --dport 53 -j ACCEPT").arg(cmd, kAnchorName, server));
}
}
void LinuxFirewall::updateAllowNets(const QStringList& servers)
@@ -495,13 +500,20 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
logger.debug() << "(" << exitCode << ") $ " << command;
if (!out.isEmpty())
logger.info() << out;
if (!err.isEmpty())
if (!err.isEmpty() && !ignoreErrors)
logger.warning() << err;
return exitCode;
}
void LinuxFirewall::setupTrafficSplitting()
{
execute(QStringLiteral("grep -q '%1' /etc/iproute2/rt_tables || printf '200\\t%1\\n' >> /etc/iproute2/rt_tables").arg(kRtableName));
execute(QStringLiteral(
"if [ ! -d /sys/fs/cgroup/net_cls ] ; then "
"mkdir -p /sys/fs/cgroup/net_cls && "
"mount -t cgroup -o net_cls cgroup /sys/fs/cgroup/net_cls ; fi"));
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));
@@ -513,6 +525,7 @@ 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 table %1").arg(kRtableName), true);
execute(QStringLiteral("ip route flush cache"));
execute(QStringLiteral("sed -i '/%1/d' /etc/iproute2/rt_tables").arg(kRtableName));
}
@@ -164,8 +164,13 @@ bool LinuxRouteMonitor::rtmSendRoute(int action, int flags, int type,
}
if (rtm->rtm_type == RTN_THROW) {
QString gateway = NetworkUtilities::getGatewayAndIface().first;
if (gateway.isEmpty()) {
logger.warning() << "No default gateway available, skipping exclusion route";
return false;
}
struct in_addr ip4;
inet_pton(AF_INET, NetworkUtilities::getGatewayAndIface().first.toUtf8(), &ip4);
inet_pton(AF_INET, gateway.toUtf8(), &ip4);
nlmsg_append_attr(nlmsg, sizeof(buf), RTA_GATEWAY, &ip4, sizeof(ip4));
nlmsg_append_attr32(nlmsg, sizeof(buf), RTA_PRIORITY, 0);
rtm->rtm_type = RTN_UNICAST;
@@ -44,6 +44,9 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::wakeup);
connect(m_worker, &LinuxNetworkWatcherWorker::networkChanged, this,
[this]() { emit networkChanged(""); });
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because
// it makes the UI faster to appear, plus it gives a bit of delay between the
@@ -4,6 +4,7 @@
#include "linuxnetworkwatcherworker.h"
#include <QTimer>
#include <QtDBus/QtDBus>
#include "leakdetector.h"
@@ -37,6 +38,7 @@
enum NMState {
NM_STATE_UNKNOWN = 0,
NM_STATE_ASLEEP = 10,
NM_STATE_DISABLED = 10,
NM_STATE_DISCONNECTED = 20,
NM_STATE_DISCONNECTING = 30,
NM_STATE_CONNECTING = 40,
@@ -122,6 +124,12 @@ void LinuxNetworkWatcherWorker::initialize() {
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
}
QVariant currentState = nm.property("State");
if (currentState.isValid()) {
m_previousNMState = currentState.toUInt();
logger.debug() << "Initial NM state:" << m_previousNMState;
}
QDBusConnection::systemBus().connect(DBUS_NETWORKMANAGER,
DBUS_NETWORKMANAGER_PATH,
DBUS_NETWORKMANAGER,
@@ -199,10 +207,13 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{
if (state == NM_STATE_ASLEEP) {
emit wakeup();
}
logger.debug() << "NMStateChanged " << state;
logger.debug() << "NMStateChanged " << state;
}
if (state == NM_STATE_ASLEEP || state == NM_STATE_DISABLED) {
emit wakeup();
} else if (state >= NM_STATE_CONNECTED_SITE && m_previousNMState < NM_STATE_CONNECTED_SITE) {
emit networkChanged();
}
m_previousNMState = state;
}
@@ -24,6 +24,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId);
void wakeup();
void networkChanged();
public slots:
void initialize();
@@ -34,10 +35,12 @@ class LinuxNetworkWatcherWorker final : public QObject {
void NMStateChanged(quint32 state);
private:
// We collect the list of DBus wifi network device paths during the
// initialization. When a property of them changes, we check if the access
// point is active and unsecure.
QStringList m_devicePaths;
quint32 m_previousNMState = 0;
};
#endif // LINUXNETWORKWATCHERWORKER_H
+46 -26
View File
@@ -57,11 +57,15 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled);
if (reply.waitForFinished() && reply.returnValue())
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
else
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call";
auto reply = iface->refreshKillSwitch(enabled);
auto *watcher = new QRemoteObjectPendingCallWatcher(reply);
QObject::connect(watcher, &QRemoteObjectPendingCallWatcher::finished, [watcher]() {
if (watcher->returnValue().toBool())
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
else
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to refresh killswitch";
watcher->deleteLater();
});
});
#endif
}
@@ -76,15 +80,19 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
case Vpn::ConnectionState::Connected: {
iface->resetIpStack();
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
if (!ContainerProps::isAwgContainer(container) &&
container != DockerContainer::WireGuard) {
auto flushDnsReply = iface->flushDns();
auto *flushDnsWatcher = new QRemoteObjectPendingCallWatcher(flushDnsReply, this);
QObject::connect(flushDnsWatcher, &QRemoteObjectPendingCallWatcher::finished, this,
[flushDnsWatcher]() {
if (flushDnsWatcher->returnValue().toBool())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
flushDnsWatcher->deleteLater();
});
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
@@ -109,17 +117,25 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
} break;
case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error: {
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
auto flushDnsReply = iface->flushDns();
auto *flushDnsWatcher = new QRemoteObjectPendingCallWatcher(flushDnsReply);
QObject::connect(flushDnsWatcher, &QRemoteObjectPendingCallWatcher::finished, [flushDnsWatcher]() {
if (flushDnsWatcher->returnValue().toBool())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
flushDnsWatcher->deleteLater();
});
auto clearSavedRoutes = iface->clearSavedRoutes();
if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
auto clearSavedRoutesReply = iface->clearSavedRoutes();
auto *clearRoutesWatcher = new QRemoteObjectPendingCallWatcher(clearSavedRoutesReply);
QObject::connect(clearRoutesWatcher, &QRemoteObjectPendingCallWatcher::finished, [clearRoutesWatcher]() {
if (clearRoutesWatcher->returnValue().toBool())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
clearRoutesWatcher->deleteLater();
});
} break;
default:
break;
@@ -182,8 +198,12 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode)
}
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (reply.waitForFinished() || !reply.returnValue())
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
auto *w = new QRemoteObjectPendingCallWatcher(reply);
QObject::connect(w, &QRemoteObjectPendingCallWatcher::finished, [w]() {
if (!w->returnValue().toBool())
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
w->deleteLater();
});
});
break;
}
@@ -219,8 +239,8 @@ ErrorCode VpnConnection::lastError() const
return m_vpnProtocol.data()->lastError();
}
void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &vpnConfiguration)
void VpnConnection::connectToVpn(int serverIndex, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, const QJsonObject &vpnConfiguration)
{
qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2, route mode is")
.arg(serverIndex)
+2 -1
View File
@@ -44,7 +44,8 @@ public:
#endif
public slots:
void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
void connectToVpn(int serverIndex, const amnezia::ServerCredentials &credentials,
amnezia::DockerContainer container, const QJsonObject &vpnConfiguration);
void reconnectToVpn();
void disconnectFromVpn();
+11 -4
View File
@@ -53,7 +53,9 @@ bool KillSwitch::refresh(bool enabled)
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled);
if (!m_appSettigns.isNull()) {
m_appSettigns->setValue("Conf/strictKillSwitchEnabled", enabled);
}
#endif
if (isStrictKillSwitchEnabled()) {
@@ -70,6 +72,11 @@ bool KillSwitch::isStrictKillSwitchEnabled()
QSettings RegHLM("HKEY_LOCAL_MACHINE\\Software\\" + QString(ORGANIZATION_NAME)
+ "\\" + QString(APPLICATION_NAME), QSettings::NativeFormat);
return RegHLM.value("strictKillSwitchEnabled", false).toBool();
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
if (m_appSettigns.isNull()) {
return false;
}
#endif
return m_appSettigns->value("Conf/strictKillSwitchEnabled", false).toBool();
}
@@ -86,7 +93,7 @@ bool KillSwitch::disableKillSwitch() {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false);
} else {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("000.allowLoopback"), true);
@@ -98,7 +105,7 @@ bool KillSwitch::disableKillSwitch() {
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("290.allowDHCP"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("300.allowLAN"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("310.blockDNS"), false);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), false);
LinuxFirewall::uninstall();
}
@@ -335,7 +342,7 @@ bool KillSwitch::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIn
}
LinuxFirewall::updateDNSServers(dnsServers);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::IPv4, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("320.allowDNS"), true);
LinuxFirewall::setAnchorEnabled(LinuxFirewall::Both, QStringLiteral("400.allowPIA"), true);
#endif
+1 -1
View File
@@ -22,7 +22,7 @@ public:
bool isStrictKillSwitchEnabled();
private:
KillSwitch(QObject* parent) {};
explicit KillSwitch(QObject* parent) : QObject(parent) {}
QStringList m_allowedRanges;
QSharedPointer<SecureQSettings> m_appSettigns;
+14 -19
View File
@@ -120,18 +120,20 @@ bool RouterLinux::routeDelete(const QString &ipWithSubnet, const QString &gw, co
((struct sockaddr_in *)&route.rt_gateway)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_gateway)->sin_addr.s_addr = inet_addr(gw.toStdString().c_str());
((struct sockaddr_in *)&route.rt_gateway)->sin_port = 0;
// set host rejecting
((struct sockaddr_in *)&route.rt_dst)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_dst)->sin_addr.s_addr = inet_addr(ip.toStdString().c_str());
((struct sockaddr_in *)&route.rt_dst)->sin_port = 0;
// set mask
((struct sockaddr_in *)&route.rt_genmask)->sin_family = AF_INET;
((struct sockaddr_in *)&route.rt_genmask)->sin_addr.s_addr = inet_addr(mask.toStdString().c_str());
((struct sockaddr_in *)&route.rt_genmask)->sin_port = 0;
route.rt_flags = RTF_UP | RTF_GATEWAY;
route.rt_metric = 0;
//route.rt_dev = "ens33";
route.rt_metric = 0;
if (ioctl(sock, SIOCDELRT, &route) < 0)
{
@@ -155,36 +157,29 @@ bool RouterLinux::routeDeleteList(const QString &gw, const QStringList &ips)
bool RouterLinux::isServiceActive(const QString &serviceName) {
QProcess process;
process.start("systemctl", { "is-active", "--quiet", serviceName });
process.waitForFinished();
if (!process.waitForFinished(2000)) {
process.kill();
process.waitForFinished(500);
}
return process.exitCode() == 0;
}
bool RouterLinux::flushDns()
{
QProcess p;
p.setProcessChannelMode(QProcess::MergedChannels);
//check what the dns manager use
if (isServiceActive("nscd.service")) {
qDebug() << "Restarting nscd.service";
p.start("systemctl", { "restart", "nscd" });
qDebug() << "Flushing nscd cache";
QProcess::startDetached("nscd", { "--invalidate=hosts" });
return true;
} else if (isServiceActive("systemd-resolved.service")) {
qDebug() << "Restarting systemd-resolved.service";
p.start("systemctl", { "restart", "systemd-resolved" });
qDebug() << "Flushing systemd-resolved cache";
QProcess::startDetached("resolvectl", { "flush-caches" });
qDebug().noquote() << "Flush dns requested (non-blocking)";
return true;
} else {
qDebug() << "No suitable DNS manager found.";
return false;
}
p.waitForFinished();
QByteArray output(p.readAll());
if (output.isEmpty())
qDebug().noquote() << "Flush dns completed";
else
qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output;
return true;
}
bool RouterLinux::createTun(const QString &dev, const QString &subnet) {