diff --git a/client/core/utils/networkUtilities.cpp b/client/core/utils/networkUtilities.cpp index 96b9131be..10cd2d689 100644 --- a/client/core/utils/networkUtilities.cpp +++ b/client/core/utils/networkUtilities.cpp @@ -286,7 +286,7 @@ QPair NetworkUtilities::getGatewayAndIface() return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) }; #endif #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 sock = -1, msgseq = 0; struct nlmsghdr *nlh, *nlmsg; @@ -294,7 +294,7 @@ QPair NetworkUtilities::getGatewayAndIface() // 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 msgbuf[100], buffer[BUFFER_SIZE]; char *ptr = buffer; struct timeval tv; @@ -339,8 +339,8 @@ QPair NetworkUtilities::getGatewayAndIface() nlh = (struct nlmsghdr *) ptr; /* Check if the header is valid */ - if((NLMSG_OK(nlmsg, received_bytes) == 0) || - (nlmsg->nlmsg_type == NLMSG_ERROR)) + if((NLMSG_OK(nlh, received_bytes) == 0) || + (nlh->nlmsg_type == NLMSG_ERROR)) { perror("Error in received packet"); return {}; @@ -355,13 +355,15 @@ QPair NetworkUtilities::getGatewayAndIface() } /* 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; } - while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid())); + while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid())); /* 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 */ route_entry = (struct rtmsg *) NLMSG_DATA(nlh); @@ -370,6 +372,10 @@ QPair NetworkUtilities::getGatewayAndIface() if (route_entry->rtm_table != RT_TABLE_MAIN) 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_len = RTM_PAYLOAD(nlh); @@ -395,6 +401,8 @@ QPair NetworkUtilities::getGatewayAndIface() break; } } + if (!(*gateway_address) || !(*interface)) + qDebug() << "getGatewayAndIface: no gateway found"; close(sock); return { gateway_address, QNetworkInterface::interfaceFromName(interface) }; #endif diff --git a/client/daemon/daemon.cpp b/client/daemon/daemon.cpp index e74a613f5..4c06cabe9 100644 --- a/client/daemon/daemon.cpp +++ b/client/daemon/daemon.cpp @@ -613,7 +613,7 @@ void Daemon::checkHandshake() { pendingHandshakes++; } } - + // Check again if there were connections that haven't completed a handshake. if (pendingHandshakes > 0) { m_handshakeTimer.start(HANDSHAKE_POLL_MSEC); diff --git a/client/platforms/linux/daemon/dnsutilslinux.cpp b/client/platforms/linux/daemon/dnsutilslinux.cpp index cc47202b5..133bc6b0a 100644 --- a/client/platforms/linux/daemon/dnsutilslinux.cpp +++ b/client/platforms/linux/daemon/dnsutilslinux.cpp @@ -7,8 +7,11 @@ #include #include +#include +#include #include +#include "core/utils/networkUtilities.h" #include "leakdetector.h" #include "logger.h" @@ -27,24 +30,56 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) { logger.debug() << "DnsUtilsLinux created."; QDBusConnection conn = QDBusConnection::systemBus(); - m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, - DBUS_RESOLVE_MANAGER, conn, this); + auto* watcher = new QDBusServiceWatcher( + 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_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() { MZ_COUNT_DTOR(DnsUtilsLinux); - for (auto iterator = m_linkDomains.constBegin(); - iterator != m_linkDomains.constEnd(); ++iterator) { - QList argumentList; - argumentList << QVariant::fromValue(iterator.key()); - argumentList << QVariant::fromValue(iterator.value()); - m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"), - argumentList); - } + if (m_resolver) { + if (m_gatewayIfindex > 0) + setLinkDefaultRoute(m_gatewayIfindex, true); - if (m_ifindex > 0) { - m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex); + for (auto iterator = m_linkDomains.constBegin(); + iterator != m_linkDomains.constEnd(); ++iterator) { + QList 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."; @@ -52,12 +87,31 @@ DnsUtilsLinux::~DnsUtilsLinux() { bool DnsUtilsLinux::updateResolvers(const QString& ifname, const QList& resolvers) { + if (m_gatewayIfindex > 0) { + setLinkDefaultRoute(m_gatewayIfindex, true); + m_gatewayIfindex = 0; + } + m_ifindex = if_nametoindex(qPrintable(ifname)); if (m_ifindex <= 0) { logger.error() << "Unable to resolve ifindex for" << ifname; return false; } + 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); setLinkDefaultRoute(m_ifindex, true); updateLinkDomains(); @@ -65,6 +119,14 @@ bool DnsUtilsLinux::updateResolvers(const QString& ifname, } bool DnsUtilsLinux::restoreResolvers() { + m_pendingIfname.clear(); + m_pendingResolvers.clear(); + + if (m_gatewayIfindex > 0) { + setLinkDefaultRoute(m_gatewayIfindex, true); + m_gatewayIfindex = 0; + } + for (auto iterator = m_linkDomains.constBegin(); iterator != m_linkDomains.constEnd(); ++iterator) { setLinkDomains(iterator.key(), iterator.value()); @@ -72,7 +134,7 @@ bool DnsUtilsLinux::restoreResolvers() { m_linkDomains.clear(); /* Revert the VPN interface's DNS configuration */ - if (m_ifindex > 0) { + if (m_ifindex > 0 && m_resolver) { QList argumentList = {QVariant::fromValue(m_ifindex)}; QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList( QStringLiteral("RevertLink"), argumentList); @@ -90,13 +152,14 @@ bool DnsUtilsLinux::restoreResolvers() { void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) { QDBusPendingReply<> reply = *call; if (reply.isError()) { - logger.error() << "Error received from the DBus service"; + logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)"; } delete call; } void DnsUtilsLinux::setLinkDNS(int ifindex, const QList& resolvers) { + if (!m_resolver) return; QList resolverList; char ifnamebuf[IF_NAMESIZE]; const char* ifname = if_indextoname(ifindex, ifnamebuf); @@ -121,6 +184,7 @@ void DnsUtilsLinux::setLinkDNS(int ifindex, void DnsUtilsLinux::setLinkDomains(int ifindex, const QList& domains) { + if (!m_resolver) return; char ifnamebuf[IF_NAMESIZE]; const char* ifname = if_indextoname(ifindex, ifnamebuf); if (ifname) { @@ -144,6 +208,7 @@ void DnsUtilsLinux::setLinkDomains(int ifindex, } void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) { + if (!m_resolver) return; QList argumentList; argumentList << QVariant::fromValue(ifindex); argumentList << QVariant::fromValue(enable); @@ -156,6 +221,7 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) { } void DnsUtilsLinux::updateLinkDomains() { + if (!m_resolver) return; /* 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. @@ -174,11 +240,20 @@ void DnsUtilsLinux::updateLinkDomains() { void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; + call->deleteLater(); if (reply.isError()) { - logger.error() << "Error retrieving the DNS domains from the DBus service"; - delete call; + // systemd-resolved may still be starting up after a restart — retry a few times + if (m_ifindex > 0 && m_domainRetries++ < 5) { + logger.debug() << "systemd-resolved not ready yet, retrying DNS setup (" + << m_domainRetries << "/5)"; + QTimer::singleShot(500, this, &DnsUtilsLinux::updateLinkDomains); + } else { + logger.warning() << "Failed to configure DNS after 5 retries"; + m_domainRetries = 0; + } return; } + m_domainRetries = 0; /* Update the state of the DNS domains */ m_linkDomains.clear(); @@ -204,9 +279,17 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) { } /* Add a root search domain for the new interface. */ - QList newlist = {root}; - setLinkDomains(m_ifindex, newlist); - delete call; + if (m_ifindex > 0) { + setLinkDomains(m_ifindex, {root}); + + /* 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; diff --git a/client/platforms/linux/daemon/dnsutilslinux.h b/client/platforms/linux/daemon/dnsutilslinux.h index e4bbd2734..cdc1492df 100644 --- a/client/platforms/linux/daemon/dnsutilslinux.h +++ b/client/platforms/linux/daemon/dnsutilslinux.h @@ -6,7 +6,12 @@ #define DNSUTILSLINUX_H #include +#include #include +#include +#include +#include +#include #include "daemon/dnsutils.h" #include "dbustypeslinux.h" @@ -29,13 +34,19 @@ class DnsUtilsLinux final : public DnsUtils { void updateLinkDomains(); private slots: + void onResolverRegistered(); + void onResolverUnregistered(); void dnsCallCompleted(QDBusPendingCallWatcher*); void dnsDomainsReceived(QDBusPendingCallWatcher*); private: int m_ifindex = 0; + int m_gatewayIfindex = 0; + int m_domainRetries = 0; QMap m_linkDomains; - QDBusInterface* m_resolver = nullptr; + QScopedPointer m_resolver; + QString m_pendingIfname; + QList m_pendingResolvers; }; #endif // DNSUTILSLINUX_H diff --git a/client/platforms/linux/daemon/wireguardutilslinux.cpp b/client/platforms/linux/daemon/wireguardutilslinux.cpp index 1b7cddc8e..be9ead885 100644 --- a/client/platforms/linux/daemon/wireguardutilslinux.cpp +++ b/client/platforms/linux/daemon/wireguardutilslinux.cpp @@ -237,7 +237,11 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) { // Exclude the server address, except for multihop exit servers. if ((config.m_hopType != InterfaceConfig::MultiHopExit) && (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)); } diff --git a/service/server/router_linux.cpp b/service/server/router_linux.cpp index 44f0e17d4..05becb8dc 100644 --- a/service/server/router_linux.cpp +++ b/service/server/router_linux.cpp @@ -167,22 +167,27 @@ bool RouterLinux::flushDns() //check what the dns manager use if (isServiceActive("nscd.service")) { - qDebug() << "Restarting nscd.service"; - p.start("systemctl", { "restart", "nscd" }); + qDebug() << "Flushing nscd cache"; + p.start("nscd", { "--invalidate=hosts" }); } else if (isServiceActive("systemd-resolved.service")) { - qDebug() << "Restarting systemd-resolved.service"; - p.start("systemctl", { "restart", "systemd-resolved" }); + qDebug() << "Flushing systemd-resolved DNS cache"; + p.start("resolvectl", { "flush-caches" }); } else { qDebug() << "No suitable DNS manager found."; return false; } p.waitForFinished(); - QByteArray output(p.readAll()); + QByteArray output = p.readAll(); + if ((p.exitStatus() != QProcess::NormalExit) || (p.exitCode() != 0)) { + qDebug().noquote() << "Failed to flush DNS: " + output; + return false; + } + if (output.isEmpty()) qDebug().noquote() << "Flush dns completed"; else - qDebug().noquote() << "OUTPUT systemctl restart nscd/systemd-resolved: " + output; + qDebug().noquote() << "OUTPUT dns flush: " + output; return true; }