VPNC control and logging (#748)

VPNC control and logging
This commit is contained in:
isamnezia 2024-04-15 01:04:01 +03:00 committed by GitHub
parent f588fe29db
commit 151e662027
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 149 additions and 35 deletions

View file

@ -108,6 +108,7 @@ target_sources(${PROJECT} PRIVATE
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift ${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift ${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift ${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
) )
target_sources(${PROJECT} PRIVATE target_sources(${PROJECT} PRIVATE

View file

@ -16,6 +16,11 @@ struct Log {
private static let appGroupID = "group.org.amnezia.AmneziaVPN" private static let appGroupID = "group.org.amnezia.AmneziaVPN"
static let appLogURL = {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
return sharedContainerURL.appendingPathComponent("app.log", isDirectory: false)
}()
static let neLogURL = { static let neLogURL = {
let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)! let sharedContainerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupID)!
return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false) return sharedContainerURL.appendingPathComponent("ne.log", isDirectory: false)
@ -70,8 +75,12 @@ struct Log {
} }
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) { static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
NSLog("\(title) \(message)")
guard isLoggingEnabled else { return } guard isLoggingEnabled else { return }
osLog.log(level: type, "\(title) \(message)")
let date = Date() let date = Date()
let level = Record.Level(from: type) let level = Record.Level(from: type)
let messages = message.split(whereSeparator: \.isNewline) let messages = message.split(whereSeparator: \.isNewline)
@ -107,3 +116,7 @@ extension Log: CustomStringConvertible {
.joined(separator: "\n") .joined(separator: "\n")
} }
} }
func log(_ type: OSLogType, title: String = "", message: String) {
Log.log(type, title: "App: \(title)", message: message, url: Log.appLogURL)
}

View file

@ -1,50 +1,33 @@
import Foundation import Foundation
import NetworkExtension
public func swiftUpdateLogData(_ qtString: std.string) -> std.string { public func swiftUpdateLogData(_ qtString: std.string) -> std.string {
let qtLog = Log(String(describing: qtString)) let qtLog = Log(String(describing: qtString))
var log = qtLog var log = qtLog
if let appLog = Log(at: Log.appLogURL) {
appLog.records.forEach {
log.records.append($0)
}
}
if let neLog = Log(at: Log.neLogURL) { if let neLog = Log(at: Log.neLogURL) {
neLog.records.forEach { neLog.records.forEach {
log.records.append($0) log.records.append($0)
} }
}
log.records.sort { log.records.sort {
$0.date < $1.date $0.date < $1.date
}
} }
return std.string(log.description) return std.string(log.description)
} }
public func swiftDeleteLog() { public func swiftDeleteLog() {
Log.clear(at: Log.appLogURL)
Log.clear(at: Log.neLogURL) Log.clear(at: Log.neLogURL)
} }
public func toggleLogging(_ isEnabled: Bool) { public func toggleLogging(_ isEnabled: Bool) {
Log.isLoggingEnabled = isEnabled Log.isLoggingEnabled = isEnabled
} }
public func clearSettings() {
NETunnelProviderManager.loadAllFromPreferences { managers, error in
if let error {
NSLog("clearSettings removeFromPreferences error: \(error.localizedDescription)")
return
}
managers?.forEach { manager in
manager.removeFromPreferences { error in
if let error {
NSLog("NE removeFromPreferences error: \(error.localizedDescription)")
} else {
manager.loadFromPreferences { error in
if let error {
NSLog("NE loadFromPreferences after remove error: \(error.localizedDescription)")
}
}
}
}
}
}
}

View file

@ -94,6 +94,8 @@ extension PacketTunnelProvider {
} }
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
ovpnLog(.info, message: "Stopping tunnel: reason: \(reason.description)")
stopHandler = completionHandler stopHandler = completionHandler
if vpnReachability.isTracking { if vpnReachability.isTracking {
vpnReachability.stopTracking() vpnReachability.stopTracking()

View file

@ -200,7 +200,7 @@ extension PacketTunnelProvider {
// } // }
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
wg_log(.info, staticMessage: "Stopping tunnel") wg_log(.info, message: "Stopping tunnel: reason: \(reason.description)")
wgAdapter.stop { error in wgAdapter.stop { error in
ErrorNotifier.removeLastErrorFile() ErrorNotifier.removeLastErrorFile()

View file

@ -200,3 +200,46 @@ extension WireGuardLogLevel {
} }
} }
} }
extension NEProviderStopReason: CustomStringConvertible {
public var description: String {
switch self {
case .none:
return "No specific reason"
case .userInitiated:
return "The user stopped the NE"
case .providerFailed:
return "The NE failed to function correctly"
case .noNetworkAvailable:
return "No network connectivity is currently available"
case .unrecoverableNetworkChange:
return "The devices network connectivity changed"
case .providerDisabled:
return "The NE was disabled"
case .authenticationCanceled:
return "The authentication process was canceled"
case .configurationFailed:
return "The VPNC is invalid"
case .idleTimeout:
return "The session timed out"
case .configurationDisabled:
return "The VPNC was disabled"
case .configurationRemoved:
return "The VPNC was removed"
case .superceded:
return "VPNC was superceded by a higher-priority VPNC"
case .userLogout:
return "The user logged out"
case .userSwitch:
return "The current console user changed"
case .connectionFailed:
return "The connection failed"
case .sleep:
return "A stop reason indicating the VPNC enabled disconnect on sleep and the device went to sleep"
case .appUpdate:
return "appUpdat"
@unknown default:
return "@unknown default"
}
}
}

View file

@ -0,0 +1,50 @@
import Foundation
import NetworkExtension
public func removeVPNC(_ vpncName: std.string) {
let vpncName = String(describing: vpncName)
Task {
await getManagers()?.first { manager in
if let name = manager.localizedDescription, name == vpncName {
Task {
await remove(manager)
}
return true
} else {
return false
}
}
}
}
public func clearSettings() {
Task {
await getManagers()?.forEach { manager in
Task {
await remove(manager)
}
}
}
}
func getManagers() async -> [NETunnelProviderManager]? {
do {
return try await NETunnelProviderManager.loadAllFromPreferences()
} catch {
log(.error, title: "VPNC: ", message: "loadAllFromPreferences error: \(error.localizedDescription)")
return nil
}
}
func remove(_ manager: NETunnelProviderManager) async {
let vpncName = manager.localizedDescription ?? "Unknown"
do {
try await manager.removeFromPreferences()
try await manager.loadFromPreferences()
log(.info, title: "VPNC: ", message: "Remove \(vpncName)")
} catch {
log(.error, title: "VPNC: ", message: "Failed to remove \(vpncName) (\(error.localizedDescription))")
}
}

View file

@ -86,6 +86,9 @@ struct WGConfig: Decodable {
AllowedIPs = \(allowedIPs.joined(separator: ", ")) AllowedIPs = \(allowedIPs.joined(separator: ", "))
Endpoint = \(hostName):\(port) Endpoint = \(hostName):\(port)
PersistentKeepalive = \(persistentKeepAlive) PersistentKeepalive = \(persistentKeepAlive)
SplitTunnelType = \(splitTunnelType)
SplitTunnelSites = \(splitTunnelSites.joined(separator: ", "))
""" """
} }
} }

View file

@ -89,9 +89,11 @@ bool IosController::initialize()
for (NETunnelProviderManager *manager in managers) { for (NETunnelProviderManager *manager in managers) {
qDebug() << "IosController::initialize : VPNC: " << manager.localizedDescription;
if (manager.connection.status == NEVPNStatusConnected) { if (manager.connection.status == NEVPNStatusConnected) {
m_currentTunnel = manager; m_currentTunnel = manager;
qDebug() << "IosController::initialize : VPN already connected"; qDebug() << "IosController::initialize : VPN already connected with" << manager.localizedDescription;
emit connectionStateChanged(Vpn::ConnectionState::Connected); emit connectionStateChanged(Vpn::ConnectionState::Connected);
break; break;
@ -138,7 +140,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
[NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) { [NETunnelProviderManager loadAllFromPreferencesWithCompletionHandler:^(NSArray<NETunnelProviderManager *> * _Nullable managers, NSError * _Nullable error) {
@try { @try {
if (error) { if (error) {
qDebug() << "IosController::connectVpn : Error:" << [error.localizedDescription UTF8String]; qDebug() << "IosController::connectVpn : VPNC: loadAllFromPreferences error:" << [error.localizedDescription UTF8String];
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
ok = false; ok = false;
return; return;
@ -151,7 +153,7 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
for (NETunnelProviderManager *manager in managers) { for (NETunnelProviderManager *manager in managers) {
if ([manager.localizedDescription isEqualToString:tunnelName.toNSString()]) { if ([manager.localizedDescription isEqualToString:tunnelName.toNSString()]) {
m_currentTunnel = manager; m_currentTunnel = manager;
qDebug() << "IosController::connectVpn : Using existing tunnel"; qDebug() << "IosController::connectVpn : Using existing tunnel:" << manager.localizedDescription;
if (manager.connection.status == NEVPNStatusConnected) { if (manager.connection.status == NEVPNStatusConnected) {
emit connectionStateChanged(Vpn::ConnectionState::Connected); emit connectionStateChanged(Vpn::ConnectionState::Connected);
return; return;
@ -162,10 +164,10 @@ bool IosController::connectVpn(amnezia::Proto proto, const QJsonObject& configur
} }
if (!m_currentTunnel) { if (!m_currentTunnel) {
qDebug() << "IosController::connectVpn : Creating new tunnel";
isNewTunnelCreated = true; isNewTunnelCreated = true;
m_currentTunnel = [[NETunnelProviderManager alloc] init]; m_currentTunnel = [[NETunnelProviderManager alloc] init];
m_currentTunnel.localizedDescription = [NSString stringWithUTF8String:tunnelName.toStdString().c_str()]; m_currentTunnel.localizedDescription = [NSString stringWithUTF8String:tunnelName.toStdString().c_str()];
qDebug() << "IosController::connectVpn : Creating new tunnel" << m_currentTunnel.localizedDescription;
} }
} }
@ -598,13 +600,14 @@ void IosController::startTunnel()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (saveError) { if (saveError) {
qDebug().nospace() << "IosController::startTunnel" << protocolName << ": Connect " << protocolName << " Tunnel Save Error" << saveError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
return; return;
} }
[m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) { [m_currentTunnel loadFromPreferencesWithCompletionHandler:^(NSError *loadError) {
if (loadError) { if (loadError) {
qDebug().nospace() << "IosController::start" << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String; qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << ": Connect " << protocolName << " Tunnel Load Error" << loadError.localizedDescription.UTF8String;
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
return; return;
} }
@ -615,11 +618,11 @@ void IosController::startTunnel()
BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError]; BOOL started = [m_currentTunnel.connection startVPNTunnelWithOptions:nil andReturnError:&startError];
if (!started || startError) { if (!started || startError) {
qDebug().nospace() << "IosController::start" << protocolName << " : Connect " << protocolName << " Tunnel Start Error" qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Connect " << protocolName << " Tunnel Start Error"
<< (startError ? startError.localizedDescription.UTF8String : ""); << (startError ? startError.localizedDescription.UTF8String : "");
emit connectionStateChanged(Vpn::ConnectionState::Error); emit connectionStateChanged(Vpn::ConnectionState::Error);
} else { } else {
qDebug().nospace() << "IosController::start" << protocolName << " : Starting the tunnel succeeded"; qDebug().nospace() << "IosController::startTunnel :" << m_currentTunnel.localizedDescription << protocolName << " : Starting the tunnel succeeded";
} }
}]; }];
}); });

View file

@ -16,6 +16,10 @@
#include "ui/models/protocols/awgConfigModel.h" #include "ui/models/protocols/awgConfigModel.h"
#include "ui/models/protocols/wireguardConfigModel.h" #include "ui/models/protocols/wireguardConfigModel.h"
#ifdef Q_OS_IOS
#include <AmneziaVPN-Swift.h>
#endif
namespace namespace
{ {
Logger logger("ServerController"); Logger logger("ServerController");
@ -564,6 +568,15 @@ void InstallController::removeApiConfig()
{ {
auto serverConfig = m_serversModel->getServerConfig(m_serversModel->getDefaultServerIndex()); auto serverConfig = m_serversModel->getServerConfig(m_serversModel->getDefaultServerIndex());
#ifdef Q_OS_IOS
QString vpncName = QString("%1 (%2) %3")
.arg(serverConfig[config_key::description].toString())
.arg(serverConfig[config_key::hostName].toString())
.arg(serverConfig[config_key::vpnproto].toString());
AmneziaVPN::removeVPNC(vpncName.toStdString());
#endif
serverConfig.remove(config_key::dns1); serverConfig.remove(config_key::dns1);
serverConfig.remove(config_key::dns2); serverConfig.remove(config_key::dns2);
serverConfig.remove(config_key::containers); serverConfig.remove(config_key::containers);

View file

@ -78,6 +78,9 @@ void SettingsController::toggleLogging(bool enable)
#endif #endif
if (enable == true) { if (enable == true) {
checkIfNeedDisableLogs(); checkIfNeedDisableLogs();
qInfo().noquote() << QString("Logging has enabled on %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
} }
emit loggingStateChanged(); emit loggingStateChanged();
} }