From 511b8fa6ccbd9df76d690d6a17f592a82c2113be Mon Sep 17 00:00:00 2001 From: AnhTVc Date: Sun, 31 Aug 2025 10:41:38 +0700 Subject: [PATCH] Fix macOS wakeup/sleep prob Fix macOS not receiving wakeup/sleep events --- client/daemon/interfaceconfig.h | 2 +- client/mozilla/networkwatcher.cpp | 48 ++-- client/platforms/ios/iosnetworkwatcher.mm | 3 + client/platforms/macos/macosnetworkwatcher.h | 3 + client/platforms/macos/macosnetworkwatcher.mm | 205 ++++++++++++++---- client/vpnconnection.cpp | 16 ++ client/vpnconnection.h | 3 + 7 files changed, 200 insertions(+), 80 deletions(-) diff --git a/client/daemon/interfaceconfig.h b/client/daemon/interfaceconfig.h index 6a816f87a..f629222b6 100644 --- a/client/daemon/interfaceconfig.h +++ b/client/daemon/interfaceconfig.h @@ -7,7 +7,7 @@ #include #include - +#include #include "ipaddress.h" class QJsonObject; diff --git a/client/mozilla/networkwatcher.cpp b/client/mozilla/networkwatcher.cpp index 6828a8671..c613c1067 100644 --- a/client/mozilla/networkwatcher.cpp +++ b/client/mozilla/networkwatcher.cpp @@ -77,46 +77,24 @@ void NetworkWatcher::initialize() { &NetworkWatcher::onSleepMode); m_impl->initialize(); - - -// TODO: IMPL FOR AMNEZIA -#if 0 - SettingsHolder* settingsHolder = SettingsHolder::instance(); - Q_ASSERT(settingsHolder); - - m_active = settingsHolder->unsecuredNetworkAlert() || - settingsHolder->captivePortalAlert(); - m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert(); - if (m_active) { + // Enable sleep/wake monitoring for VPN auto-reconnection + logger.debug() << "Starting NetworkWatcher for sleep/wake monitoring"; + logger.debug() << "About to call m_impl->start()"; + try { m_impl->start(); + logger.debug() << "m_impl->start() completed successfully"; + } catch (const std::exception& e) { + logger.error() << "Exception in m_impl->start():" << e.what(); + } catch (...) { + logger.error() << "Unknown exception in m_impl->start()"; } - - connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this, - &NetworkWatcher::settingsChanged); - connect(settingsHolder, &SettingsHolder::captivePortalAlertChanged, this, - &NetworkWatcher::settingsChanged); - -#endif + m_active = true; + m_reportUnsecuredNetwork = false; // Disable unsecured network alerts for Amnezia } void NetworkWatcher::settingsChanged() { -// TODO: IMPL FOR AMNEZIA -#if 0 - SettingsHolder* settingsHolder = SettingsHolder::instance(); - m_active = settingsHolder->unsecuredNetworkAlert() || - settingsHolder->captivePortalAlert(); - m_reportUnsecuredNetwork = settingsHolder->unsecuredNetworkAlert(); - - if (m_active) { - logger.debug() - << "Starting Network Watcher; Reporting of Unsecured Networks: " - << m_reportUnsecuredNetwork; - m_impl->start(); - } else { - logger.debug() << "Stopping Network Watcher"; - m_impl->stop(); - } -#endif + // For Amnezia: Keep NetworkWatcher always active for sleep/wake monitoring + logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active"; } void NetworkWatcher::onSleepMode() diff --git a/client/platforms/ios/iosnetworkwatcher.mm b/client/platforms/ios/iosnetworkwatcher.mm index 720b303b2..c9cf5fe16 100644 --- a/client/platforms/ios/iosnetworkwatcher.mm +++ b/client/platforms/ios/iosnetworkwatcher.mm @@ -34,6 +34,9 @@ void IOSNetworkWatcher::initialize() { }); nw_path_monitor_start(m_networkMonitor); + // Call start() to initialize sleep/wake monitoring (will call MacOSNetworkWatcher::start() if this is macOS) + this->start(); + //TODO IMPL FOR AMNEZIA } diff --git a/client/platforms/macos/macosnetworkwatcher.h b/client/platforms/macos/macosnetworkwatcher.h index faddc5652..a822f7f5c 100644 --- a/client/platforms/macos/macosnetworkwatcher.h +++ b/client/platforms/macos/macosnetworkwatcher.h @@ -20,12 +20,15 @@ class QString; class PowerNotificationsListener { public: + PowerNotificationsListener(class MacOSNetworkWatcher* watcher) : m_watcher(watcher) {} void registerForNotifications(); + void cleanup(); private: static void sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument); private: + class MacOSNetworkWatcher* m_watcher = nullptr; IONotificationPortRef notifyPortRef = nullptr; // notification port allocated by IORegisterForSystemPower io_object_t notifierObj = IO_OBJECT_NULL; // notifier object, used to deregister later io_connect_t rootPowerDomain = IO_OBJECT_NULL; // a reference to the Root Power Domain IOService diff --git a/client/platforms/macos/macosnetworkwatcher.mm b/client/platforms/macos/macosnetworkwatcher.mm index d4431941c..bce796c97 100644 --- a/client/platforms/macos/macosnetworkwatcher.mm +++ b/client/platforms/macos/macosnetworkwatcher.mm @@ -6,6 +6,11 @@ #include "leakdetector.h" #include "logger.h" +#include +#include +#include +#include + #import #import @@ -13,6 +18,37 @@ namespace { Logger logger("MacOSNetworkWatcher"); } +// Global variables for CFRunLoop thread +static pthread_t g_powerThread; +static CFRunLoopRef g_powerRunLoop = nullptr; +static bool g_shouldStopPowerThread = false; +static PowerNotificationsListener* g_powerListener = nullptr; + +// Thread function for dedicated CFRunLoop +void* powerMonitoringThread(void* arg) { + logger.debug() << "Power monitoring thread started"; + + PowerNotificationsListener* listener = static_cast(arg); + + // Get the runloop for this thread + g_powerRunLoop = CFRunLoopGetCurrent(); + + // Register for power notifications in this thread + listener->registerForNotifications(); + + // Run the CFRunLoop - this will block until CFRunLoopStop is called + while (!g_shouldStopPowerThread) { + CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0, true); + } + + // Cleanup + listener->cleanup(); + g_powerRunLoop = nullptr; + + logger.debug() << "Power monitoring thread finished"; + return nullptr; +} + @interface MacOSNetworkWatcherDelegate : NSObject { MacOSNetworkWatcher* m_watcher; } @@ -40,24 +76,45 @@ Logger logger("MacOSNetworkWatcher"); void PowerNotificationsListener::registerForNotifications() { + logger.debug() << "Registering for system power notifications in dedicated thread"; + rootPowerDomain = IORegisterForSystemPower(this, ¬ifyPortRef, sleepWakeupCallBack, ¬ifierObj); if (rootPowerDomain == IO_OBJECT_NULL) { - logger.debug() << "Failed to register for system power notifications!"; + logger.error() << "Failed to register for system power notifications!"; return; } - logger.debug() << "IORegisterForSystemPower OK! Root port:" << rootPowerDomain; - - // add the notification port to the application runloop - CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); + // Add the notification port to the current runloop (dedicated thread) + CFRunLoopAddSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); + logger.debug() << "Power notifications registered successfully"; } -static void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument) +void PowerNotificationsListener::cleanup() +{ + if (notifyPortRef != nullptr) { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), IONotificationPortGetRunLoopSource(notifyPortRef), kCFRunLoopCommonModes); + IONotificationPortDestroy(notifyPortRef); + notifyPortRef = nullptr; + } + + if (notifierObj != IO_OBJECT_NULL) { + IODeregisterForSystemPower(¬ifierObj); + notifierObj = IO_OBJECT_NULL; + } + + if (rootPowerDomain != IO_OBJECT_NULL) { + IOServiceClose(rootPowerDomain); + rootPowerDomain = IO_OBJECT_NULL; + } +} + +void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_t service, natural_t messageType, void *messageArgument) { Q_UNUSED(service) auto listener = static_cast(refParam); + logger.debug() << "Power callback received, messageType:" << messageType; switch (messageType) { case kIOMessageCanSystemSleep: /* Idle sleep is about to kick in. This message will not be sent for forced sleep. @@ -93,8 +150,7 @@ static void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_s * however the system WILL still go to sleep. */ - logger.debug() << "System power message: system WILL sleep."; - + logger.debug() << "System power message: system WILL sleep"; IOAllowPowerChange(listener->rootPowerDomain, reinterpret_cast(messageArgument)); break; @@ -115,7 +171,11 @@ static void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_s case kIOMessageSystemHasPoweredOn: /* Announces that the system and its devices have woken up. */ - logger.debug() << "System power message: system has powered on."; + logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread"; + if (listener->m_watcher) { + // Use QMetaObject::invokeMethod for thread-safe signal emission + QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection); + } break; default: @@ -125,12 +185,27 @@ static void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_s } } -MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent) { +MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : IOSNetworkWatcher(parent), m_powerlistener(this) { MZ_COUNT_CTOR(MacOSNetworkWatcher); } MacOSNetworkWatcher::~MacOSNetworkWatcher() { MZ_COUNT_DTOR(MacOSNetworkWatcher); + + // Stop the dedicated power monitoring thread + if (g_powerListener) { + logger.debug() << "Stopping dedicated power monitoring thread"; + g_shouldStopPowerThread = true; + + if (g_powerRunLoop) { + CFRunLoopStop(g_powerRunLoop); + } + + // Wait for thread to finish + pthread_join(g_powerThread, nullptr); + g_powerListener = nullptr; + } + if (m_delegate) { CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { @@ -154,7 +229,19 @@ void MacOSNetworkWatcher::start() { return; } - m_powerlistener.registerForNotifications(); + // Start dedicated power monitoring thread with CFRunLoop + if (!g_powerListener) { + g_powerListener = &m_powerlistener; + g_shouldStopPowerThread = false; + + int result = pthread_create(&g_powerThread, nullptr, powerMonitoringThread, &m_powerlistener); + if (result != 0) { + logger.error() << "Failed to create power monitoring thread:" << result; + g_powerListener = nullptr; + } else { + logger.debug() << "Power monitoring enabled"; + } + } CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { @@ -166,6 +253,8 @@ void MacOSNetworkWatcher::start() { m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this]; [client setDelegate:static_cast(m_delegate)]; [client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr]; + + logger.debug() << "MacOSNetworkWatcher started successfully"; } void MacOSNetworkWatcher::checkInterface() { @@ -176,42 +265,70 @@ void MacOSNetworkWatcher::checkInterface() { return; } - CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; - if (!client) { - logger.debug() << "Unable to retrieve the CWWiFiClient shared instance"; + // Use wdutil to get reliable WiFi information + QProcess process; + process.start("wdutil", QStringList() << "info"); + process.waitForFinished(5000); + + QString output = process.readAllStandardOutput(); + QString errorOutput = process.readAllStandardError(); + + logger.debug() << "wdutil exit code:" << process.exitCode(); + + if (process.exitCode() != 0) { + logger.debug() << "wdutil failed with exit code:" << process.exitCode(); return; } - - CWInterface* interface = [client interface]; - if (!interface) { - logger.debug() << "No default wifi interface"; - return; + + // Parse wdutil output to find WiFi connection info + QStringList lines = output.split('\n'); + QString ssid, interfaceName, security; + bool wifiSectionFound = false; + + for (int i = 0; i < lines.size(); i++) { + QString trimmedLine = lines[i].trimmed(); + + if (trimmedLine == "WIFI") { + wifiSectionFound = true; + continue; + } + + if (wifiSectionFound) { + // Stop parsing when we reach next section header (all caps after separator line) + if (trimmedLine.startsWith("————————")) { + if (i + 1 < lines.size()) { + QString nextLine = lines[i + 1].trimmed(); + if (!nextLine.isEmpty() && nextLine.length() > 2 && nextLine.toUpper() == nextLine && nextLine != "WIFI") { + break; + } + } + continue; // Skip separator lines + } + + if (trimmedLine.startsWith("Interface Name")) { + QStringList parts = trimmedLine.split(":"); + if (parts.size() >= 2) { + interfaceName = parts[1].trimmed(); + } + } else if (trimmedLine.startsWith("SSID")) { + QStringList parts = trimmedLine.split(":"); + if (parts.size() >= 2) { + ssid = parts[1].trimmed(); + } + } else if (trimmedLine.startsWith("Security")) { + QStringList parts = trimmedLine.split(":"); + if (parts.size() >= 2) { + security = parts[1].trimmed(); + } + } + } } - - if (![interface powerOn]) { - logger.debug() << "The interface is off"; - return; + + if (!ssid.isEmpty() && !interfaceName.isEmpty()) { + logger.debug() << "Found active WiFi connection on" << interfaceName + << "SSID:" << ssid << "Security:" << security; + } else { + logger.debug() << "No active WiFi connection found"; } - - NSString* ssidNS = [interface ssid]; - if (!ssidNS) { - logger.debug() << "WiFi is not in used"; - return; - } - - QString ssid = QString::fromNSString(ssidNS); - if (ssid.isEmpty()) { - logger.debug() << "WiFi doesn't have a valid SSID"; - return; - } - - CWSecurity security = [interface security]; - if (security == kCWSecurityNone || security == kCWSecurityWEP) { - logger.debug() << "Unsecured network found!"; - emit unsecuredNetwork(ssid, ssid); - return; - } - - logger.debug() << "Secure WiFi interface"; } diff --git a/client/vpnconnection.cpp b/client/vpnconnection.cpp index 24f2a6993..bde470eb5 100644 --- a/client/vpnconnection.cpp +++ b/client/vpnconnection.cpp @@ -288,11 +288,21 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede void VpnConnection::restartConnection() { + // Only reconnect if VPN was connected before sleep/network change + if (!m_wasConnectedBeforeSleep) { + qDebug() << "VPN was not connected before sleep/network change, skipping reconnection"; + return; + } + + qDebug() << "VPN was connected before sleep/network change, attempting reconnection"; this->disconnectFromVpn(); #ifdef Q_OS_LINUX QThread::msleep(5000); #endif this->connectToVpn(m_serverIndex, m_serverCredentials, m_dockerContainer, m_vpnConfiguration); + + // Reset the flag after reconnection attempt + m_wasConnectedBeforeSleep = false; } void VpnConnection::createProtocolConnections() @@ -308,11 +318,17 @@ void VpnConnection::createProtocolConnections() qDebug() << "Connection Lose"; auto result = IpcClient::Interface()->stopNetworkCheck(); result.waitForFinished(3000); + // Track VPN state before connection loss + m_wasConnectedBeforeSleep = isConnected(); + qDebug() << "VPN was connected before connection loss:" << m_wasConnectedBeforeSleep; this->restartConnection(); }); connect(IpcClient::Interface().data(), &IpcInterfaceReplica::networkChange, this, [this]() { qDebug() << "Network change"; + // Track VPN state before network change (including sleep/wake) + m_wasConnectedBeforeSleep = isConnected(); + qDebug() << "VPN was connected before network change:" << m_wasConnectedBeforeSleep; this->restartConnection(); }); #endif diff --git a/client/vpnconnection.h b/client/vpnconnection.h index 3d65e528c..f74676de7 100644 --- a/client/vpnconnection.h +++ b/client/vpnconnection.h @@ -81,6 +81,9 @@ private: ServerCredentials m_serverCredentials; int m_serverIndex; DockerContainer m_dockerContainer; + + // Track VPN state before sleep for smart reconnection + bool m_wasConnectedBeforeSleep = false; // Only for iOS for now, check counters QTimer m_checkTimer;