diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 5a55ab665..83be4c91f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -131,7 +131,7 @@ jobs: Build-iOS: name: 'Build-iOS' - runs-on: macos-12 + runs-on: macos-13 env: QT_VERSION: 6.5.2 @@ -142,7 +142,7 @@ jobs: - name: 'Setup xcode' uses: maxim-lobanov/setup-xcode@v1 with: - xcode-version: '14.2' + xcode-version: '15.2' - name: 'Install desktop Qt' uses: jurplel/install-qt-action@v3 diff --git a/client/cmake/ios.cmake b/client/cmake/ios.cmake index 3234578e4..824c1cafe 100644 --- a/client/cmake/ios.cmake +++ b/client/cmake/ios.cmake @@ -16,6 +16,7 @@ find_library(FW_AVFOUNDATION AVFoundation) find_library(FW_FOUNDATION Foundation) find_library(FW_STOREKIT StoreKit) find_library(FW_USERNOTIFICATIONS UserNotifications) +find_library(FW_NETWORKEXTENSION NetworkExtension) set(LIBS ${LIBS} ${FW_AUTHENTICATIONSERVICES} @@ -24,6 +25,7 @@ set(LIBS ${LIBS} ${FW_FOUNDATION} ${FW_STOREKIT} ${FW_USERNOTIFICATIONS} + ${FW_NETWORKEXTENSION} ) @@ -84,9 +86,9 @@ set_target_properties(${PROJECT} PROPERTIES set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_SWIFT_VERSION "5.0" XCODE_ATTRIBUTE_CLANG_ENABLE_MODULES "YES" - XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/WireGuard-Bridging-Header.h" XCODE_ATTRIBUTE_SWIFT_PRECOMPILE_BRIDGING_HEADER "NO" XCODE_ATTRIBUTE_SWIFT_OBJC_INTERFACE_HEADER_NAME "AmneziaVPN-Swift.h" + XCODE_ATTRIBUTE_SWIFT_OBJC_INTEROP_MODE "objcxx" ) set_target_properties(${PROJECT} PROPERTIES XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "X7UJ388FXK" @@ -101,21 +103,10 @@ set(WG_APPLE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/3rd/amneziawg-apple/Sources) target_sources(${PROJECT} PRIVATE # ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosvpnprotocol.swift - ${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/ioslogger.swift - ${WG_APPLE_SOURCE_DIR}/Shared/Keychain.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddressRange.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/InterfaceConfiguration.swift - ${WG_APPLE_SOURCE_DIR}/Shared/Model/NETunnelProviderProtocol+Extension.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/TunnelConfiguration.swift - ${WG_APPLE_SOURCE_DIR}/Shared/Model/TunnelConfiguration+WgQuickConfig.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/Endpoint.swift - ${WG_APPLE_SOURCE_DIR}/Shared/Model/String+ArrayConversion.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PeerConfiguration.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/DNSServer.swift - ${WG_APPLE_SOURCE_DIR}/WireGuardApp/LocalizationHelper.swift - ${WG_APPLE_SOURCE_DIR}/Shared/FileManager+Extension.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKitC/x25519.c - ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift + ${CLIENT_ROOT_DIR}/platforms/ios/LogController.swift + ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift + ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ) target_sources(${PROJECT} PRIVATE diff --git a/client/ios/networkextension/CMakeLists.txt b/client/ios/networkextension/CMakeLists.txt index fb1bd3c1b..2dbf96398 100644 --- a/client/ios/networkextension/CMakeLists.txt +++ b/client/ios/networkextension/CMakeLists.txt @@ -81,8 +81,10 @@ target_sources(networkextension PRIVATE ${WG_APPLE_SOURCE_DIR}/WireGuardKit/IPAddress+AddrInfo.swift ${WG_APPLE_SOURCE_DIR}/WireGuardKit/PrivateKey.swift ${CLIENT_ROOT_DIR}/platforms/ios/iostunnel.swift + ${CLIENT_ROOT_DIR}/platforms/ios/NELogController.swift + ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift + ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm - ${CLIENT_ROOT_DIR}/platforms/ios/ioslogger.swift ) ## Build wireguard-go-version.h diff --git a/client/logger.cpp b/client/logger.cpp index 75c2d6523..a2c5607bd 100644 --- a/client/logger.cpp +++ b/client/logger.cpp @@ -18,6 +18,10 @@ #include #endif +#ifdef Q_OS_IOS + #include +#endif + QFile Logger::m_file; QTextStream Logger::m_textStream; QString Logger::m_logFileName = QString("%1.log").arg(APPLICATION_NAME); @@ -107,7 +111,14 @@ QString Logger::getLogFile() QFile file(userLogsFilePath()); file.open(QIODevice::ReadOnly); - return file.readAll(); + QString qtLog = file.readAll(); + +#ifdef Q_OS_IOS + return QString().fromStdString(AmneziaVPN::swiftUpdateLogData(qtLog.toStdString())); +#else + return qtLog; +#endif + } bool Logger::openLogsFolder() @@ -146,7 +157,11 @@ void Logger::clearLogs() file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.resize(0); file.close(); - + +#ifdef Q_OS_IOS + AmneziaVPN::swiftDeleteLog(); +#endif + if (isLogActive) { init(); } diff --git a/client/platforms/ios/Log.swift b/client/platforms/ios/Log.swift new file mode 100644 index 000000000..78ccc1581 --- /dev/null +++ b/client/platforms/ios/Log.swift @@ -0,0 +1,82 @@ +import Foundation +import os.log + +struct Log { + private static let IsLoggingEnabledKey = "IsLoggingEnabled" + static var isLoggingEnabled: Bool { + get { + sharedUserDefaults.bool(forKey: IsLoggingEnabledKey) + } + set { + sharedUserDefaults.setValue(newValue, forKey: IsLoggingEnabledKey) + } + } + + private static let appGroupID = "group.org.amnezia.AmneziaVPN" + + static let neLogURL = { + let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! + return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false) + }() + + private static var sharedUserDefaults = { + UserDefaults(suiteName: appGroupID)! + }() + + static let dateFormatter: DateFormatter = { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return dateFormatter + }() + + var records: [Record] + + init() { + self.records = [] + } + + init(_ str: String) { + self.records = str.split(whereSeparator: \.isNewline) + .compactMap { + Record(String($0))! + } + } + + init?(at url: URL) { + if !FileManager.default.fileExists(atPath: url.path) { + guard let _ = try? "".data(using: .utf8)?.write(to: url) else { return nil } + } + + guard let fileHandle = try? FileHandle(forUpdating: url) else { return nil } + + defer { fileHandle.closeFile() } + + guard + let data = try? fileHandle.readToEnd(), + let str = String(data: data, encoding: .utf8) else { + return nil + } + + self.init(str) + } + + static func clear(at url: URL) { + if FileManager.default.fileExists(atPath: url.path) { + guard let fileHandle = try? FileHandle(forUpdating: url) else { return } + + defer { fileHandle.closeFile() } + + try? fileHandle.truncate(atOffset: 0) + } + } +} + +extension Log: CustomStringConvertible { + var description: String { + records + .map { + $0.description + } + .joined(separator: "\n") + } +} diff --git a/client/platforms/ios/LogController.swift b/client/platforms/ios/LogController.swift new file mode 100644 index 000000000..b6b2d3b33 --- /dev/null +++ b/client/platforms/ios/LogController.swift @@ -0,0 +1,26 @@ +import Foundation + +public func swiftUpdateLogData(_ qtString: std.string) -> std.string { + let qtLog = Log(String(describing: qtString)) + var log = qtLog + + if let neLog = Log(at: Log.neLogURL) { + neLog.records.forEach { + log.records.append($0) + } + + log.records.sort { + $0.date < $1.date + } + } + + return std.string(log.description) +} + +public func swiftDeleteLog() { + Log.clear(at: Log.neLogURL) +} + +public func toggleLogging(_ isEnabled: Bool) { + Log.isLoggingEnabled = isEnabled +} diff --git a/client/platforms/ios/LogRecord.swift b/client/platforms/ios/LogRecord.swift new file mode 100644 index 000000000..d72b0a936 --- /dev/null +++ b/client/platforms/ios/LogRecord.swift @@ -0,0 +1,82 @@ +import Foundation +import os.log + +extension Log { + struct Record { + let date: Date + let level: Level + let message: String + + init?(_ str: String) { + let dateStr = String(str.prefix(19)) + guard let date = Log.dateFormatter.date(from: dateStr) else { return nil } + + let str = str.dropFirst(20) + + guard let endIndex = str.firstIndex(of: " ") else { return nil } + let levelStr = String(str[str.startIndex.. -#import -#import -#import - - const char* Action::start = "start"; const char* Action::restart = "restart"; const char* Action::stop = "stop"; diff --git a/client/platforms/ios/ioslogger.swift b/client/platforms/ios/ioslogger.swift deleted file mode 100644 index 747227d10..000000000 --- a/client/platforms/ios/ioslogger.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* 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/. */ - -import Foundation -import os.log - -public class Logger { - static var global: Logger? - - var tag: String - - init(tagged tag: String) { - self.tag = tag - } - - deinit {} - - func log(message: String) { -// let suiteName = "group.org.amnezia.AmneziaVPN" -// let logKey = "logMessages" -// let sharedDefaults = UserDefaults(suiteName: suiteName) -// var logs = sharedDefaults?.array(forKey: logKey) as? [String] ?? [] -// logs.append(message) -// sharedDefaults?.set(logs, forKey: logKey) - } - - private func writeLog(to targetFile: String) -> Bool { - return true; - } - - static func configureGlobal(tagged tag: String, withFilePath filePath: String?) { -// if Logger.global != nil { -// return -// } -// -// Logger.global = Logger(tagged: tag) -// -// var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" -// -// if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { -// appVersion += " (\(appBuild))" -// } -// -// Logger.global?.log(message: "App version: \(appVersion)") - } -} - -func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { -// os_log(msg, log: OSLog.default, type: type) -// Logger.global?.log(message: "\(msg)") -} - -func wg_log(_ type: OSLogType, message msg: String) { -// os_log("%{AMNEZIA}s", log: OSLog.default, type: type, msg) -// Logger.global?.log(message: msg) -} diff --git a/client/platforms/ios/iostunnel.swift b/client/platforms/ios/iostunnel.swift index 49e017674..5838ba973 100644 --- a/client/platforms/ios/iostunnel.swift +++ b/client/platforms/ios/iostunnel.swift @@ -33,8 +33,7 @@ struct Constants { static let kMessageKeySplitTunnelSites = "SplitTunnelSites" } -class PacketTunnelProvider: NEPacketTunnelProvider { - +class PacketTunnelProvider: NEPacketTunnelProvider { private lazy var wgAdapter: WireGuardAdapter = { return WireGuardAdapter(with: self) { logLevel, message in wg_log(logLevel.osLogLevel, message: message) @@ -61,8 +60,6 @@ class PacketTunnelProvider: NEPacketTunnelProvider { var protoType: TunnelProtoType = .none override init() { - Logger.configureGlobal(tagged: Constants.loggerTag, withFilePath: FileManager.logFileURL?.path) - Logger.global?.log(message: "Init NEPacketTunnelProvider") super.init() } @@ -71,17 +68,17 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let tmpStr = String(data: messageData, encoding: .utf8)! wg_log(.error, message: tmpStr) guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else { - Logger.global?.log(message: "Failed to serialize message from app") + log(.error, message: "Failed to serialize message from app") return } guard let completionHandler = completionHandler else { - Logger.global?.log(message: "Missing message completion handler") + log(.error, message: "Missing message completion handler") return } guard let action = message[Constants.kMessageKeyAction] as? String else { - Logger.global?.log(message: "Missing action key in app message") + log(.error, message: "Missing action key in app message") completionHandler(nil) return } @@ -112,7 +109,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider { let activationAttemptId = options?[Constants.kActivationAttemptId] as? String let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId) - Logger.global?.log(message: "PacketTunnelProvider startTunnel") + log(.info, message: "PacketTunnelProvider startTunnel") if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol { let providerConfiguration = protocolConfiguration.providerConfiguration @@ -492,10 +489,10 @@ class PacketTunnelProvider: NEPacketTunnelProvider { wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in self.dispatchQueue.async { if let error { - Logger.global?.log(message: "Failed to start an empty tunnel") + log(.error, message: "Failed to start an empty tunnel") completionHandler(error) } else { - Logger.global?.log(message: "Started an empty tunnel") + log(.info, message: "Started an empty tunnel") self.tunnelAdapterDidStart() } } diff --git a/client/ui/controllers/settingsController.cpp b/client/ui/controllers/settingsController.cpp index c7306a108..99645cded 100644 --- a/client/ui/controllers/settingsController.cpp +++ b/client/ui/controllers/settingsController.cpp @@ -12,6 +12,10 @@ #include #endif +#ifdef Q_OS_IOS + #include +#endif + SettingsController::SettingsController(const QSharedPointer &serversModel, const QSharedPointer &containersModel, const QSharedPointer &languageModel, @@ -82,6 +86,9 @@ bool SettingsController::isLoggingEnabled() void SettingsController::toggleLogging(bool enable) { m_settings->setSaveLogs(enable); +#ifdef Q_OS_IOS + AmneziaVPN::toggleLogging(enable); +#endif emit loggingStateChanged(); }