From e792117be1dcccc2b43f964300560c05648b8319 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Wed, 12 Mar 2025 23:32:00 +0200 Subject: [PATCH] Add network status check for AWG/WG protocol --- client/cmake/sources.cmake | 30 +++ client/mozilla/localsocketcontroller.cpp | 19 ++ client/mozilla/localsocketcontroller.h | 7 + client/mozilla/pinghelper.cpp | 7 +- client/mozilla/pinghelper.h | 2 + client/mozilla/pingsenderfactory.cpp | 21 +- client/mozilla/pingsenderfactory.h | 7 +- client/platforms/linux/linuxpingsender.cpp | 185 ++++++++++++++++++ client/platforms/linux/linuxpingsender.h | 39 ++++ .../platforms/windows/windowspingsender.cpp | 1 + service/server/CMakeLists.txt | 2 + 11 files changed, 304 insertions(+), 16 deletions(-) create mode 100644 client/platforms/linux/linuxpingsender.cpp create mode 100644 client/platforms/linux/linuxpingsender.h diff --git a/client/cmake/sources.cmake b/client/cmake/sources.cmake index c3af531a..be85b1d1 100644 --- a/client/cmake/sources.cmake +++ b/client/cmake/sources.cmake @@ -33,6 +33,10 @@ set(HEADERS ${HEADERS} # Mozilla headres set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/mozilla/models/server.h + ${CLIENT_ROOT_DIR}/mozilla/dnspingsender.h + ${CLIENT_ROOT_DIR}/mozilla/pinghelper.h + ${CLIENT_ROOT_DIR}/mozilla/pingsender.h + ${CLIENT_ROOT_DIR}/mozilla/pingsenderfactory.h ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.h ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.h ${CLIENT_ROOT_DIR}/mozilla/controllerimpl.h @@ -84,6 +88,10 @@ set(SOURCES ${SOURCES} # Mozilla sources set(SOURCES ${SOURCES} ${CLIENT_ROOT_DIR}/mozilla/models/server.cpp + ${CLIENT_ROOT_DIR}/mozilla/dnspingsender.cpp + ${CLIENT_ROOT_DIR}/mozilla/pinghelper.cpp + ${CLIENT_ROOT_DIR}/mozilla/pingsender.cpp + ${CLIENT_ROOT_DIR}/mozilla/pingsenderfactory.cpp ${CLIENT_ROOT_DIR}/mozilla/shared/ipaddress.cpp ${CLIENT_ROOT_DIR}/mozilla/shared/leakdetector.cpp ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp @@ -147,13 +155,25 @@ set(SOURCES ${SOURCES} ${UI_CONTROLLERS_CPP} ) +if (LINUX) + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/platforms/linux/linuxpingsender.h + ) + + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/platforms/linux/linuxpingsender.cpp + ) +endif() + if(WIN32) set(HEADERS ${HEADERS} ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.h + ${CLIENT_ROOT_DIR}/platforms/windows/windowspingsender.h ) set(SOURCES ${SOURCES} ${CLIENT_ROOT_DIR}/protocols/ikev2_vpn_protocol_windows.cpp + ${CLIENT_ROOT_DIR}/platforms/windows/windowspingsender.cpp ) set(RESOURCES ${RESOURCES} @@ -161,6 +181,16 @@ if(WIN32) ) endif() +if (APPLE AND NOT IOS AND NOT MACOS_NE) + set(HEADERS ${HEADERS} + ${CLIENT_ROOT_DIR}/platforms/macos/macospingsender.h + ) + + set(SOURCES ${SOURCES} + ${CLIENT_ROOT_DIR}/platforms/macos/macosspingsender.cpp + ) +endif() + if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) message("Client desktop build") add_compile_definitions(AMNEZIA_DESKTOP) diff --git a/client/mozilla/localsocketcontroller.cpp b/client/mozilla/localsocketcontroller.cpp index 1081bcae..31060068 100644 --- a/client/mozilla/localsocketcontroller.cpp +++ b/client/mozilla/localsocketcontroller.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include "ipaddress.h" #include "leakdetector.h" @@ -48,6 +49,15 @@ LocalSocketController::LocalSocketController() { m_initializingTimer.setSingleShot(true); connect(&m_initializingTimer, &QTimer::timeout, this, &LocalSocketController::initializeInternal); + + connect(&m_pingHelper, &PingHelper::connectionLose, this, [this]() { + logger.debug() << "Connection Lose"; + m_pingHelper.stop(); + this->deactivate(); + QThread::msleep(3000); + this->activate(m_RawConfig); + }); + } LocalSocketController::~LocalSocketController() { @@ -116,6 +126,8 @@ void LocalSocketController::daemonConnected() { void LocalSocketController::activate(const QJsonObject &rawConfig) { + m_RawConfig = rawConfig; + QString protocolName = rawConfig.value("protocol").toString(); int splitTunnelType = rawConfig.value("splitTunnelType").toInt(); @@ -258,6 +270,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) { json.insert(amnezia::config_key::transportPacketMagicHeader, wgConfig.value(amnezia::config_key::transportPacketMagicHeader)); } + write(json); } @@ -362,6 +375,8 @@ void LocalSocketController::parseCommand(const QByteArray& command) { return; } + qDebug() << command; + QJsonObject obj = json.object(); QJsonValue typeValue = obj.value("type"); if (!typeValue.isString()) { @@ -406,6 +421,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { } if (type == "status") { + QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway"); if (!serverIpv4Gateway.isString()) { logger.error() << "Unexpected serverIpv4Gateway value"; @@ -418,6 +434,8 @@ void LocalSocketController::parseCommand(const QByteArray& command) { return; } + m_pingHelper.start(serverIpv4Gateway.toString(), deviceIpv4Address.toString()); + QJsonValue txBytes = obj.value("txBytes"); if (!txBytes.isDouble()) { logger.error() << "Unexpected txBytes value"; @@ -451,6 +469,7 @@ void LocalSocketController::parseCommand(const QByteArray& command) { logger.debug() << "Handshake completed with:" << pubkey.toString(); emit connected(pubkey.toString()); + checkStatus(); return; } diff --git a/client/mozilla/localsocketcontroller.h b/client/mozilla/localsocketcontroller.h index f0652e5a..e3d1f7b1 100644 --- a/client/mozilla/localsocketcontroller.h +++ b/client/mozilla/localsocketcontroller.h @@ -11,6 +11,9 @@ #include #include "controllerimpl.h" +#include "mozilla/pinghelper.h" +#include "qjsonobject.h" + class QJsonObject; @@ -60,7 +63,11 @@ class LocalSocketController final : public ControllerImpl { std::function m_logCallback = nullptr; + QJsonObject m_RawConfig; + PingHelper m_pingHelper; + QTimer m_initializingTimer; + QTimer m_statusTimer; uint32_t m_initializingRetry = 0; }; diff --git a/client/mozilla/pinghelper.cpp b/client/mozilla/pinghelper.cpp index 44920bf6..d0aa4250 100644 --- a/client/mozilla/pinghelper.cpp +++ b/client/mozilla/pinghelper.cpp @@ -41,6 +41,7 @@ void PingHelper::start(const QString& serverIpv4Gateway, m_gateway = QHostAddress(serverIpv4Gateway); m_source = QHostAddress(deviceIpv4Address.section('/', 0, 0)); + m_pingSender = PingSenderFactory::create(m_source, this); // Some platforms require root access to send and receive ICMP pings. If @@ -53,8 +54,10 @@ void PingHelper::start(const QString& serverIpv4Gateway, connect(m_pingSender, &PingSender::recvPing, this, &PingHelper::pingReceived, Qt::QueuedConnection); - connect(m_pingSender, &PingSender::criticalPingError, this, - []() { logger.info() << "Encountered Unrecoverable ping error"; }); + connect(m_pingSender, &PingSender::criticalPingError, this, [this]() { + logger.info() << "Encountered Unrecoverable ping error"; + emit connectionLose(); + }); // Reset the ping statistics m_sequence = 0; diff --git a/client/mozilla/pinghelper.h b/client/mozilla/pinghelper.h index 00466f53..f4c511b5 100644 --- a/client/mozilla/pinghelper.h +++ b/client/mozilla/pinghelper.h @@ -33,6 +33,8 @@ class PingHelper final : public QObject { signals: void pingSentAndReceived(qint64 msec); + void connectionLose(); + private: void nextPing(); diff --git a/client/mozilla/pingsenderfactory.cpp b/client/mozilla/pingsenderfactory.cpp index c7ecdec8..e4cbef91 100644 --- a/client/mozilla/pingsenderfactory.cpp +++ b/client/mozilla/pingsenderfactory.cpp @@ -5,27 +5,26 @@ #include "pingsenderfactory.h" #if defined(MZ_LINUX) || defined(MZ_ANDROID) -//# include "platforms/linux/linuxpingsender.h" + # include "platforms/linux/linuxpingsender.h" #elif defined(MZ_MACOS) || defined(MZ_IOS) -# include "platforms/macos/macospingsender.h" + # include "platforms/macos/macospingsender.h" #elif defined(MZ_WINDOWS) -# include "platforms/windows/windowspingsender.h" -#elif defined(MZ_DUMMY) || defined(UNIT_TEST) -# include "platforms/dummy/dummypingsender.h" + # include "platforms/windows/windowspingsender.h" +#elif defined(MZ_WASM) || defined(UNIT_TEST) + # include "platforms/dummy/dummypingsender.h" #else -# error "Unsupported platform" + # error "Unsupported platform" #endif PingSender* PingSenderFactory::create(const QHostAddress& source, QObject* parent) { #if defined(MZ_LINUX) || defined(MZ_ANDROID) - return nullptr; - // return new LinuxPingSender(source, parent); + return new LinuxPingSender(source, parent); #elif defined(MZ_MACOS) || defined(MZ_IOS) - return new MacOSPingSender(source, parent); + return new MacOSPingSender(source, parent); #elif defined(MZ_WINDOWS) - return new WindowsPingSender(source, parent); + return new WindowsPingSender(source, parent); #else - return new DummyPingSender(source, parent); + return new DummyPingSender(source, parent); #endif } diff --git a/client/mozilla/pingsenderfactory.h b/client/mozilla/pingsenderfactory.h index 7482d36d..69e9c911 100644 --- a/client/mozilla/pingsenderfactory.h +++ b/client/mozilla/pingsenderfactory.h @@ -10,9 +10,10 @@ class QHostAddress; class QObject; class PingSenderFactory final { - public: - PingSenderFactory() = delete; - static PingSender* create(const QHostAddress& source, QObject* parent); +public: + PingSenderFactory() = delete; + static PingSender* create(const QHostAddress& source, QObject* parent); }; + #endif // PINGSENDERFACTORY_H diff --git a/client/platforms/linux/linuxpingsender.cpp b/client/platforms/linux/linuxpingsender.cpp new file mode 100644 index 00000000..16fd8d14 --- /dev/null +++ b/client/platforms/linux/linuxpingsender.cpp @@ -0,0 +1,185 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "linuxpingsender.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "leakdetector.h" +#include "logger.h" +#include "qhostaddress.h" + +namespace { +Logger logger("LinuxPingSender"); +} + +int LinuxPingSender::createSocket() { + // Try creating an ICMP socket. This would be the ideal choice, but it can + // fail depending on the kernel config (see: sys.net.ipv4.ping_group_range) + m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); + if (m_socket >= 0) { + m_ident = 0; + return m_socket; + } + if ((errno != EPERM) && (errno != EACCES)) { + return -1; + } + + // As a fallback, create a raw socket, which requires root permissions + // or CAP_NET_RAW to be granted to the VPN client. + m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); + if (m_socket < 0) { + return -1; + } + m_ident = getpid() & 0xffff; + + // Attach a BPF filter to discard everything but replies to our echo. + struct sock_filter bpf_prog[] = { + BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */ + BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */ + BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */ + BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */ + BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */ + BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */ + BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */ + }; + struct sock_fprog filter = { + .len = sizeof(bpf_prog) / sizeof(struct sock_filter), + .filter = bpf_prog, + }; + setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter)); + + return m_socket; +} + +LinuxPingSender::LinuxPingSender(const QHostAddress& source, QObject* parent) + : PingSender(parent) { + MZ_COUNT_CTOR(LinuxPingSender); + + logger.debug() << "LinuxPingSender(" + logger.sensitive(source.toString()) + + ") created"; + + m_socket = createSocket(); + if (m_socket < 0) { + logger.error() << "Socket creation error: " << strerror(errno); + return; + } + + quint32 ipv4addr = INADDR_ANY; + if (!source.isNull()) { + ipv4addr = source.toIPv4Address(); + } + struct sockaddr_in addr; + memset(&addr, 0, sizeof addr); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = qToBigEndian(ipv4addr); + + if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) { + close(m_socket); + m_socket = -1; + logger.error() << "bind error:" << strerror(errno); + return; + } + + m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this); + if (m_ident) { + connect(m_notifier, &QSocketNotifier::activated, this, + &LinuxPingSender::rawSocketReady); + } else { + connect(m_notifier, &QSocketNotifier::activated, this, + &LinuxPingSender::icmpSocketReady); + } +} + +LinuxPingSender::~LinuxPingSender() { + MZ_COUNT_DTOR(LinuxPingSender); + if (m_socket >= 0) { + close(m_socket); + } +} + +void LinuxPingSender::sendPing(const QHostAddress& dest, quint16 sequence) { + quint32 ipv4dest = dest.toIPv4Address(); + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = qToBigEndian(ipv4dest); + + struct icmphdr packet; + memset(&packet, 0, sizeof(packet)); + packet.type = ICMP_ECHO; + packet.un.echo.id = htons(m_ident); + packet.un.echo.sequence = htons(sequence); + packet.checksum = inetChecksum(&packet, sizeof(packet)); + + int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr, + sizeof(addr)); + if (rc < 0) { + logger.error() << "failed to send:" << strerror(errno); + if (errno == ENETUNREACH) { + emit criticalPingError(); + } + } +} + +void LinuxPingSender::icmpSocketReady() { + socklen_t slen = 0; + unsigned char data[2048]; + int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen); + if (rc <= 0) { + logger.error() << "recvfrom failed:" << strerror(errno); + return; + } + + struct icmphdr packet; + if (rc >= (int)sizeof(packet)) { + memcpy(&packet, data, sizeof(packet)); + if (packet.type == ICMP_ECHOREPLY) { + emit recvPing(htons(packet.un.echo.sequence)); + } + } +} + +void LinuxPingSender::rawSocketReady() { + socklen_t slen = 0; + unsigned char data[2048]; + int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen); + if (rc <= 0) { + logger.error() << "recvfrom failed:" << strerror(errno); + return; + } + + // Check the IP header + const struct iphdr* ip = (struct iphdr*)data; + int iphdrlen = ip->ihl * 4; + if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) { + logger.error() << "malformed IP packet:" << strerror(errno); + return; + } + + // Check the ICMP packet + struct icmphdr packet; + if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) { + logger.warning() << "invalid checksum"; + return; + } + if (rc >= (iphdrlen + (int)sizeof(packet))) { + memcpy(&packet, data + iphdrlen, sizeof(packet)); + quint16 id = htons(m_ident); + if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) { + emit recvPing(htons(packet.un.echo.sequence)); + } + } +} diff --git a/client/platforms/linux/linuxpingsender.h b/client/platforms/linux/linuxpingsender.h new file mode 100644 index 00000000..7bfb1ffa --- /dev/null +++ b/client/platforms/linux/linuxpingsender.h @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef LINUXPINGSENDER_H +#define LINUXPINGSENDER_H + +#include + +#include "../client/mozilla/pingsender.h" + +class QSocketNotifier; + +class LinuxPingSender final : public PingSender { + Q_OBJECT + Q_DISABLE_COPY_MOVE(LinuxPingSender) + + public: + LinuxPingSender(const QHostAddress& source, QObject* parent = nullptr); + ~LinuxPingSender(); + + bool isValid() override { return (m_socket >= 0); }; + + void sendPing(const QHostAddress& dest, quint16 sequence) override; + + private: + int createSocket(); + + private slots: + void rawSocketReady(); + void icmpSocketReady(); + + private: + QSocketNotifier* m_notifier = nullptr; + int m_socket = -1; + quint16 m_ident = 0; +}; + +#endif // LINUXPINGSENDER_H diff --git a/client/platforms/windows/windowspingsender.cpp b/client/platforms/windows/windowspingsender.cpp index 8b07a80f..a39ac2de 100644 --- a/client/platforms/windows/windowspingsender.cpp +++ b/client/platforms/windows/windowspingsender.cpp @@ -179,6 +179,7 @@ void WindowsPingSender::pingEventReady() { return; } QString errmsg = WindowsUtils::getErrorMessage(); + emit criticalPingError(); logger.error() << "No ping reply. Code: " << error << " Message: " << errmsg; return; diff --git a/service/server/CMakeLists.txt b/service/server/CMakeLists.txt index 28174774..494e4bed 100644 --- a/service/server/CMakeLists.txt +++ b/service/server/CMakeLists.txt @@ -212,6 +212,7 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.h + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxpingsender.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dbustypeslinux.h ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.h @@ -226,6 +227,7 @@ if(LINUX) ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcher.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxnetworkwatcherworker.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxdependencies.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/linuxpingsender.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/dnsutilslinux.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/iputilslinux.cpp ${CMAKE_CURRENT_SOURCE_DIR}/../../client/platforms/linux/daemon/linuxdaemon.cpp