mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-22 02:01:08 +07:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 073491ccb4 | |||
| 561b62cd40 | |||
| 1284ed4d84 | |||
| 6f34443191 | |||
| 02f186c54e | |||
| 784c6cf585 | |||
| 14f132e127 | |||
| 9cb624e681 | |||
| 516e3da7e2 | |||
| 0e83586cae | |||
| 95bdae68f4 |
+1
-1
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.4.1.4
|
||||
project(${PROJECT} VERSION 4.4.2.1
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
|
||||
@@ -71,7 +71,7 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
}
|
||||
|
||||
QRemoteObjectPendingReply<int> futureResult = Instance()->m_ipcClient->createPrivilegedProcess();
|
||||
futureResult.waitForFinished(1000);
|
||||
futureResult.waitForFinished(5000);
|
||||
|
||||
int pid = futureResult.returnValue();
|
||||
|
||||
|
||||
@@ -84,7 +84,8 @@ target_sources(networkextension PRIVATE
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPNAdapterDelegate.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+WireGuard.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/PacketTunnelProvider+OpenVPN.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/WGConfig.swift
|
||||
${CLIENT_ROOT_DIR}/platforms/ios/iosglue.mm
|
||||
)
|
||||
|
||||
@@ -124,10 +124,14 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
// json.insert("hopindex", QJsonValue((double)hop.m_hopindex));
|
||||
json.insert("privateKey", wgConfig.value(amnezia::config_key::client_priv_key));
|
||||
json.insert("deviceIpv4Address", wgConfig.value(amnezia::config_key::client_ip));
|
||||
// todo review wg ipv6
|
||||
#ifdef Q_OS_MACOS
|
||||
json.insert("deviceIpv6Address", "dead::1");
|
||||
#endif
|
||||
|
||||
// set up IPv6 unique-local-address, ULA, with "fd00::/8" prefix, not globally routable.
|
||||
// this will be default IPv6 gateway, OS recognizes that IPv6 link is local and switches to IPv4.
|
||||
// Otherwise some OSes (Linux) try IPv6 forever and hang.
|
||||
// https://en.wikipedia.org/wiki/Unique_local_address (RFC 4193)
|
||||
// https://man7.org/linux/man-pages/man5/gai.conf.5.html
|
||||
json.insert("deviceIpv6Address", "fd58:baa6:dead::1"); // simply "dead::1" is globally-routable, don't use it
|
||||
|
||||
json.insert("serverPublicKey", wgConfig.value(amnezia::config_key::server_pub_key));
|
||||
json.insert("serverPskKey", wgConfig.value(amnezia::config_key::psk_key));
|
||||
json.insert("serverIpv4AddrIn", wgConfig.value(amnezia::config_key::hostName));
|
||||
|
||||
@@ -2,6 +2,8 @@ import Foundation
|
||||
import os.log
|
||||
|
||||
struct Log {
|
||||
static let osLog = Logger()
|
||||
|
||||
private static let IsLoggingEnabledKey = "IsLoggingEnabled"
|
||||
static var isLoggingEnabled: Bool {
|
||||
get {
|
||||
@@ -29,16 +31,23 @@ struct Log {
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
var records: [Record]
|
||||
var records = [Record]()
|
||||
|
||||
var lastRecordDate = Date.distantPast
|
||||
|
||||
init() {
|
||||
self.records = []
|
||||
}
|
||||
|
||||
init(_ str: String) {
|
||||
self.records = str.split(whereSeparator: \.isNewline)
|
||||
.compactMap {
|
||||
Record(String($0))
|
||||
records = str.split(whereSeparator: \.isNewline)
|
||||
.map {
|
||||
if let record = Record(String($0)) {
|
||||
lastRecordDate = record.date
|
||||
return record
|
||||
} else {
|
||||
return Record(date: lastRecordDate, level: .error, message: "LOG: \($0)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +69,24 @@ struct Log {
|
||||
self.init(str)
|
||||
}
|
||||
|
||||
static func log(_ type: OSLogType, title: String = "", message: String, url: URL = neLogURL) {
|
||||
guard isLoggingEnabled else { return }
|
||||
|
||||
let date = Date()
|
||||
let level = Record.Level(from: type)
|
||||
let messages = message.split(whereSeparator: \.isNewline)
|
||||
|
||||
for index in 0..<messages.count {
|
||||
let message = String(messages[index])
|
||||
|
||||
if index != 0 && message.first != " " {
|
||||
Record(date: date, level: level, message: "\(title) \(message)").save(at: url)
|
||||
} else {
|
||||
Record(date: date, level: level, message: "\(title)\(message)").save(at: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static func clear(at url: URL) {
|
||||
if FileManager.default.fileExists(atPath: url.path) {
|
||||
guard let fileHandle = try? FileHandle(forUpdating: url) else { return }
|
||||
|
||||
@@ -30,6 +30,8 @@ extension Log {
|
||||
}
|
||||
|
||||
func save(at url: URL) {
|
||||
osLog.log(level: level.osLogType, "\(message)")
|
||||
|
||||
guard let data = "\n\(description)".data(using: .utf8) else { return }
|
||||
|
||||
if !FileManager.default.fileExists(atPath: url.path) {
|
||||
@@ -64,19 +66,38 @@ extension Log.Record {
|
||||
|
||||
init(from osLogType: OSLogType) {
|
||||
switch osLogType {
|
||||
case OSLogType.default:
|
||||
case .default:
|
||||
self = .info
|
||||
case OSLogType.info:
|
||||
case .info:
|
||||
self = .info
|
||||
case OSLogType.debug:
|
||||
case .debug:
|
||||
self = .debug
|
||||
case OSLogType.error:
|
||||
case .error:
|
||||
self = .error
|
||||
case OSLogType.fault:
|
||||
case .fault:
|
||||
self = .fatal
|
||||
default:
|
||||
self = .info
|
||||
}
|
||||
}
|
||||
|
||||
var osLogType: OSLogType {
|
||||
switch self {
|
||||
case .info:
|
||||
return .info
|
||||
case .debug:
|
||||
return .debug
|
||||
case .error:
|
||||
return .error
|
||||
case .fatal:
|
||||
return .fault
|
||||
case .warning:
|
||||
return .info
|
||||
case .critical:
|
||||
return .fault
|
||||
case .system:
|
||||
return .fault
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import Foundation
|
||||
import os.log
|
||||
|
||||
public func wg_log(_ type: OSLogType, staticMessage: StaticString) {
|
||||
guard Log.isLoggingEnabled else { return }
|
||||
|
||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: "\(staticMessage)").save(at: Log.neLogURL)
|
||||
public func wg_log(_ type: OSLogType, title: String = "", staticMessage: StaticString) {
|
||||
neLog(type, title: "WG: \(title)", message: "\(staticMessage)")
|
||||
}
|
||||
|
||||
public func wg_log(_ type: OSLogType, message: String) {
|
||||
log(type, message: message)
|
||||
public func wg_log(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "WG: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func log(_ type: OSLogType, message: String) {
|
||||
guard Log.isLoggingEnabled else { return }
|
||||
|
||||
Log.Record(date: Date(), level: Log.Record.Level(from: type), message: message).save(at: Log.neLogURL)
|
||||
public func ovpnLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
neLog(type, title: "OVPN: \(title)", message: message)
|
||||
}
|
||||
|
||||
public func neLog(_ type: OSLogType, title: String = "", message: String) {
|
||||
Log.log(type, title: "NE: \(title)", message: message)
|
||||
}
|
||||
|
||||
+103
-1
@@ -2,6 +2,106 @@ import Foundation
|
||||
import NetworkExtension
|
||||
import OpenVPNAdapter
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
|
||||
var str: String {
|
||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) config: \(config)"
|
||||
}
|
||||
}
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
ovpnLog(.error, message: "Can't start")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
// ovpnLog(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self))")
|
||||
|
||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||
ovpnLog(.info, title: "config: ", message: openVPNConfig.str)
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
ovpnLog(.error, message: "Can't parse config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
ovpnLog(.error, message: "Can't parse config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||
withShadowSocks viaSS: Bool = false,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
ovpnLog(.info, message: "Setup and launch")
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
ovpnLog(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// ovpn_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
}
|
||||
|
||||
func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": bytesin,
|
||||
"tx_bytes": bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
|
||||
func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
stopHandler = completionHandler
|
||||
if vpnReachability.isTracking {
|
||||
vpnReachability.stopTracking()
|
||||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
|
||||
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
|
||||
@@ -116,6 +216,8 @@ extension PacketTunnelProvider: OpenVPNAdapterDelegate {
|
||||
// Use this method to process any log message returned by OpenVPN library.
|
||||
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
|
||||
// Handle log messages
|
||||
wg_log(.info, message: logMessage)
|
||||
ovpnLog(.info, message: logMessage)
|
||||
}
|
||||
}
|
||||
|
||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||
@@ -0,0 +1,221 @@
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
extension PacketTunnelProvider {
|
||||
func startWireguard(activationAttemptId: String?,
|
||||
errorNotifier: ErrorNotifier,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start, config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||
let wgConfigStr = wgConfig.str
|
||||
wg_log(.info, title: "config: ", message: wgConfig.redux)
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
|
||||
for allowedIPString in wgConfig.splitTunnelSites {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else if wgConfig.splitTunnelType == 2 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
|
||||
for excludeIPString in wgConfig.splitTunnelSites {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
wg_log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
|
||||
let components = settings!.components(separatedBy: "\n")
|
||||
|
||||
var settingsDictionary: [String: String] = [:]
|
||||
for component in components {
|
||||
let pair = component.components(separatedBy: "=")
|
||||
if pair.count == 2 {
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
} else if messageData.count >= 1 {
|
||||
// Updates the tunnel configuration and responds with the active configuration
|
||||
wg_log(.info, message: "Switching tunnel configuration")
|
||||
guard let configString = String(data: messageData, encoding: .utf8)
|
||||
else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
// private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
//
|
||||
// let emptyTunnelConfiguration = TunnelConfiguration(
|
||||
// name: nil,
|
||||
// interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||
// peers: []
|
||||
// )
|
||||
//
|
||||
// wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||
// self.dispatchQueue.async {
|
||||
// if let error {
|
||||
// wg_log(.error, message: "Failed to start an empty tunnel")
|
||||
// completionHandler(error)
|
||||
// } else {
|
||||
// wg_log(.info, message: "Started an empty tunnel")
|
||||
// self.tunnelAdapterDidStart()
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
//
|
||||
// self.setTunnelNetworkSettings(settings) { error in
|
||||
// completionHandler(error)
|
||||
// }
|
||||
// }
|
||||
|
||||
// private func tunnelAdapterDidStart() {
|
||||
// dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
// // ...
|
||||
// }
|
||||
|
||||
func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ struct Constants {
|
||||
static let ovpnConfigKey = "ovpn"
|
||||
static let wireGuardConfigKey = "wireguard"
|
||||
static let loggerTag = "NET"
|
||||
|
||||
|
||||
static let kActionStart = "start"
|
||||
static let kActionRestart = "restart"
|
||||
static let kActionStop = "stop"
|
||||
@@ -34,62 +34,68 @@ struct Constants {
|
||||
}
|
||||
|
||||
class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
private lazy var wgAdapter = {
|
||||
lazy var wgAdapter = {
|
||||
WireGuardAdapter(with: self) { logLevel, message in
|
||||
wg_log(logLevel.osLogLevel, message: message)
|
||||
}
|
||||
}()
|
||||
|
||||
private lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||
|
||||
lazy var ovpnAdapter: OpenVPNAdapter = {
|
||||
let adapter = OpenVPNAdapter()
|
||||
adapter.delegate = self
|
||||
return adapter
|
||||
}()
|
||||
|
||||
|
||||
/// Internal queue.
|
||||
private let dispatchQueue = DispatchQueue(label: "PacketTunnel", qos: .utility)
|
||||
|
||||
private var openVPNConfig: Data?
|
||||
|
||||
var splitTunnelType: Int!
|
||||
var splitTunnelSites: [String]!
|
||||
|
||||
|
||||
let vpnReachability = OpenVPNReachability()
|
||||
|
||||
|
||||
var startHandler: ((Error?) -> Void)?
|
||||
var stopHandler: (() -> Void)?
|
||||
var protoType: TunnelProtoType = .none
|
||||
|
||||
|
||||
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
let tmpStr = String(data: messageData, encoding: .utf8)!
|
||||
wg_log(.error, message: tmpStr)
|
||||
guard let message = String(data: messageData, encoding: .utf8) else {
|
||||
if let completionHandler {
|
||||
completionHandler(nil)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
neLog(.info, title: "App said: ", message: message)
|
||||
|
||||
guard let message = try? JSONSerialization.jsonObject(with: messageData, options: []) as? [String: Any] else {
|
||||
log(.error, message: "Failed to serialize message from app")
|
||||
neLog(.error, message: "Failed to serialize message from app")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let completionHandler else {
|
||||
log(.error, message: "Missing message completion handler")
|
||||
neLog(.error, message: "Missing message completion handler")
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
guard let action = message[Constants.kMessageKeyAction] as? String else {
|
||||
log(.error, message: "Missing action key in app message")
|
||||
neLog(.error, message: "Missing action key in app message")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if action == Constants.kActionStatus {
|
||||
handleStatusAppMessage(messageData, completionHandler: completionHandler)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func startTunnel(options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchQueue.async {
|
||||
let activationAttemptId = options?[Constants.kActivationAttemptId] as? String
|
||||
let errorNotifier = ErrorNotifier(activationAttemptId: activationAttemptId)
|
||||
|
||||
log(.info, message: "PacketTunnelProvider startTunnel")
|
||||
|
||||
|
||||
neLog(.info, message: "Start tunnel")
|
||||
|
||||
if let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol {
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration
|
||||
if (providerConfiguration?[Constants.ovpnConfigKey] as? Data) != nil {
|
||||
@@ -100,7 +106,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
} else {
|
||||
self.protoType = .none
|
||||
}
|
||||
|
||||
|
||||
switch self.protoType {
|
||||
case .wireguard:
|
||||
self.startWireguard(activationAttemptId: activationAttemptId,
|
||||
@@ -116,7 +122,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
dispatchQueue.async {
|
||||
switch self.protoType {
|
||||
@@ -132,7 +138,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func handleStatusAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
switch protoType {
|
||||
case .wireguard:
|
||||
@@ -146,291 +152,18 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private methods
|
||||
private func startWireguard(activationAttemptId: String?,
|
||||
errorNotifier: ErrorNotifier,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let wgConfigData: Data = providerConfiguration[Constants.wireGuardConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start WireGuard config missing")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let wgConfig = try JSONDecoder().decode(WGConfig.self, from: wgConfigData)
|
||||
let wgConfigStr = wgConfig.str
|
||||
log(.info, message: "wgConfig: \(wgConfig.redux.replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
tunnelConfiguration.peers[index].allowedIPs.removeAll()
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
|
||||
for allowedIPString in wgConfig.splitTunnelSites {
|
||||
if let allowedIP = IPAddressRange(from: allowedIPString) {
|
||||
allowedIPs.append(allowedIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].allowedIPs = allowedIPs
|
||||
}
|
||||
} else if wgConfig.splitTunnelType == 2 {
|
||||
for index in tunnelConfiguration.peers.indices {
|
||||
var excludeIPs = [IPAddressRange]()
|
||||
|
||||
for excludeIPString in wgConfig.splitTunnelSites {
|
||||
if let excludeIP = IPAddressRange(from: excludeIPString) {
|
||||
excludeIPs.append(excludeIP)
|
||||
}
|
||||
}
|
||||
|
||||
tunnelConfiguration.peers[index].excludeIPs = excludeIPs
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wg_log(.info, message: "Starting wireguard tunnel from the " +
|
||||
(activationAttemptId == nil ? "OS directly, rather than the app" : "app"))
|
||||
|
||||
// Start the tunnel
|
||||
wgAdapter.start(tunnelConfiguration: tunnelConfiguration) { adapterError in
|
||||
guard let adapterError else {
|
||||
let interfaceName = self.wgAdapter.interfaceName ?? "unknown"
|
||||
wg_log(.info, message: "Tunnel interface is \(interfaceName)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
switch adapterError {
|
||||
case .cannotLocateTunnelFileDescriptor:
|
||||
wg_log(.error, staticMessage: "Starting tunnel failed: could not determine file descriptor")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
completionHandler(PacketTunnelProviderError.couldNotDetermineFileDescriptor)
|
||||
case .dnsResolution(let dnsErrors):
|
||||
let hostnamesWithDnsResolutionFailure = dnsErrors.map { $0.address }
|
||||
.joined(separator: ", ")
|
||||
wg_log(.error, message:
|
||||
"DNS resolution failed for the following hostnames: \(hostnamesWithDnsResolutionFailure)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
completionHandler(PacketTunnelProviderError.dnsResolutionFailure)
|
||||
case .setNetworkSettings(let error):
|
||||
wg_log(.error, message:
|
||||
"Starting tunnel failed with setTunnelNetworkSettings returning \(error.localizedDescription)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
completionHandler(PacketTunnelProviderError.couldNotSetNetworkSettings)
|
||||
case .startWireGuardBackend(let errorCode):
|
||||
wg_log(.error, message: "Starting tunnel failed with wgTurnOn returning \(errorCode)")
|
||||
errorNotifier.notify(PacketTunnelProviderError.couldNotStartBackend)
|
||||
completionHandler(PacketTunnelProviderError.couldNotStartBackend)
|
||||
case .invalidState:
|
||||
fatalError()
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
log(.error, message: "Can't parse WG config: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func startOpenVPN(completionHandler: @escaping (Error?) -> Void) {
|
||||
guard let protocolConfiguration = self.protocolConfiguration as? NETunnelProviderProtocol,
|
||||
let providerConfiguration = protocolConfiguration.providerConfiguration,
|
||||
let openVPNConfigData = providerConfiguration[Constants.ovpnConfigKey] as? Data else {
|
||||
wg_log(.error, message: "Can't start startOpenVPN()")
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
log(.info, message: "providerConfiguration: \(String(decoding: openVPNConfigData, as: UTF8.self).replacingOccurrences(of: "\n", with: " "))")
|
||||
|
||||
let openVPNConfig = try JSONDecoder().decode(OpenVPNConfig.self, from: openVPNConfigData)
|
||||
log(.info, message: "openVPNConfig: \(openVPNConfig.str.replacingOccurrences(of: "\n", with: " "))")
|
||||
let ovpnConfiguration = Data(openVPNConfig.config.utf8)
|
||||
setupAndlaunchOpenVPN(withConfig: ovpnConfiguration, completionHandler: completionHandler)
|
||||
} catch {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(error.localizedDescription)")
|
||||
|
||||
if let underlyingError = (error as NSError).userInfo[NSUnderlyingErrorKey] as? NSError {
|
||||
log(.error, message: "Can't parse OpenVPN config: \(underlyingError.localizedDescription)")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
private func stopWireguard(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
wg_log(.info, staticMessage: "Stopping tunnel")
|
||||
|
||||
wgAdapter.stop { error in
|
||||
ErrorNotifier.removeLastErrorFile()
|
||||
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to stop WireGuard adapter: \(error.localizedDescription)")
|
||||
}
|
||||
completionHandler()
|
||||
|
||||
#if os(macOS)
|
||||
// HACK: This is a filthy hack to work around Apple bug 32073323 (dup'd by us as 47526107).
|
||||
// Remove it when they finally fix this upstream and the fix has been rolled out to
|
||||
// sufficient quantities of users.
|
||||
exit(0)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private func stopOpenVPN(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) {
|
||||
stopHandler = completionHandler
|
||||
if vpnReachability.isTracking {
|
||||
vpnReachability.stopTracking()
|
||||
}
|
||||
ovpnAdapter.disconnect()
|
||||
}
|
||||
|
||||
func handleWireguardStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
|
||||
let components = settings!.components(separatedBy: "\n")
|
||||
|
||||
var settingsDictionary: [String: String] = [:]
|
||||
for component in components {
|
||||
let pair = component.components(separatedBy: "=")
|
||||
if pair.count == 2 {
|
||||
settingsDictionary[pair[0]] = pair[1]
|
||||
}
|
||||
}
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": settingsDictionary["rx_bytes"] ?? "0",
|
||||
"tx_bytes": settingsDictionary["tx_bytes"] ?? "0"
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
}
|
||||
|
||||
private func handleWireguardAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
if messageData.count == 1 && messageData[0] == 0 {
|
||||
wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
} else if messageData.count >= 1 {
|
||||
// Updates the tunnel configuration and responds with the active configuration
|
||||
wg_log(.info, message: "Switching tunnel configuration")
|
||||
guard let configString = String(data: messageData, encoding: .utf8)
|
||||
else {
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: configString)
|
||||
wgAdapter.update(tunnelConfiguration: tunnelConfiguration) { error in
|
||||
if let error {
|
||||
wg_log(.error, message: "Failed to switch tunnel configuration: \(error.localizedDescription)")
|
||||
completionHandler(nil)
|
||||
return
|
||||
}
|
||||
|
||||
self.wgAdapter.getRuntimeConfiguration { settings in
|
||||
var data: Data?
|
||||
if let settings {
|
||||
data = settings.data(using: .utf8)!
|
||||
}
|
||||
completionHandler(data)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
completionHandler(nil)
|
||||
}
|
||||
} else {
|
||||
completionHandler(nil)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleOpenVPNStatusMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)? = nil) {
|
||||
guard let completionHandler = completionHandler else { return }
|
||||
let bytesin = ovpnAdapter.transportStatistics.bytesIn
|
||||
let bytesout = ovpnAdapter.transportStatistics.bytesOut
|
||||
|
||||
let response: [String: Any] = [
|
||||
"rx_bytes": bytesin,
|
||||
"tx_bytes": bytesout
|
||||
]
|
||||
|
||||
completionHandler(try? JSONSerialization.data(withJSONObject: response, options: []))
|
||||
}
|
||||
|
||||
private func setupAndlaunchOpenVPN(withConfig ovpnConfiguration: Data,
|
||||
withShadowSocks viaSS: Bool = false,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "setupAndlaunchOpenVPN")
|
||||
|
||||
let str = String(decoding: ovpnConfiguration, as: UTF8.self)
|
||||
|
||||
let configuration = OpenVPNConfiguration()
|
||||
configuration.fileContent = ovpnConfiguration
|
||||
if str.contains("cloak") {
|
||||
configuration.setPTCloak()
|
||||
}
|
||||
|
||||
let evaluation: OpenVPNConfigurationEvaluation
|
||||
do {
|
||||
evaluation = try ovpnAdapter.apply(configuration: configuration)
|
||||
|
||||
} catch {
|
||||
completionHandler(error)
|
||||
return
|
||||
}
|
||||
|
||||
if !evaluation.autologin {
|
||||
wg_log(.info, message: "Implement login with user credentials")
|
||||
}
|
||||
|
||||
vpnReachability.startTracking { [weak self] status in
|
||||
guard status == .reachableViaWiFi else { return }
|
||||
self?.ovpnAdapter.reconnect(afterTimeInterval: 5)
|
||||
}
|
||||
|
||||
startHandler = completionHandler
|
||||
ovpnAdapter.connect(using: packetFlow)
|
||||
|
||||
// let ifaces = Interface.allInterfaces()
|
||||
// .filter { $0.family == .ipv4 }
|
||||
// .map { iface in iface.name }
|
||||
|
||||
// wg_log(.error, message: "Available TUN Interfaces: \(ifaces)")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Network observing methods
|
||||
|
||||
|
||||
private func startListeningForNetworkChanges() {
|
||||
stopListeningForNetworkChanges()
|
||||
addObserver(self, forKeyPath: Constants.kDefaultPathKey, options: .old, context: nil)
|
||||
}
|
||||
|
||||
|
||||
private func stopListeningForNetworkChanges() {
|
||||
removeObserver(self, forKeyPath: Constants.kDefaultPathKey)
|
||||
}
|
||||
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?,
|
||||
of object: Any?,
|
||||
change: [NSKeyValueChangeKey: Any]?,
|
||||
@@ -450,48 +183,13 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
|
||||
self.handle(networkChange: self.defaultPath!) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private func handle(networkChange changePath: NWPath, completion: @escaping (Error?) -> Void) {
|
||||
wg_log(.info, message: "Tunnel restarted.")
|
||||
startTunnel(options: nil, completionHandler: completion)
|
||||
}
|
||||
|
||||
private func startEmptyTunnel(completionHandler: @escaping (Error?) -> Void) {
|
||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
|
||||
let emptyTunnelConfiguration = TunnelConfiguration(
|
||||
name: nil,
|
||||
interface: InterfaceConfiguration(privateKey: PrivateKey()),
|
||||
peers: []
|
||||
)
|
||||
|
||||
wgAdapter.start(tunnelConfiguration: emptyTunnelConfiguration) { error in
|
||||
self.dispatchQueue.async {
|
||||
if let error {
|
||||
log(.error, message: "Failed to start an empty tunnel")
|
||||
completionHandler(error)
|
||||
} else {
|
||||
log(.info, message: "Started an empty tunnel")
|
||||
self.tunnelAdapterDidStart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let settings = NETunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
|
||||
|
||||
self.setTunnelNetworkSettings(settings) { error in
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
|
||||
private func tunnelAdapterDidStart() {
|
||||
dispatchPrecondition(condition: .onQueue(dispatchQueue))
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
|
||||
|
||||
extension WireGuardLogLevel {
|
||||
var osLogLevel: OSLogType {
|
||||
switch self {
|
||||
|
||||
@@ -89,14 +89,3 @@ struct WGConfig: Decodable {
|
||||
"""
|
||||
}
|
||||
}
|
||||
|
||||
struct OpenVPNConfig: Decodable {
|
||||
let config: String
|
||||
let mtu: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
|
||||
var str: String {
|
||||
"splitTunnelType: \(splitTunnelType) splitTunnelSites: \(splitTunnelSites) mtu: \(mtu) config: \(config)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ ErrorCode OpenVpnProtocol::start()
|
||||
return ErrorCode::AmneziaServiceConnectionFailed;
|
||||
}
|
||||
|
||||
m_openVpnProcess->waitForSource(1000);
|
||||
m_openVpnProcess->waitForSource(5000);
|
||||
if (!m_openVpnProcess->isInitialized()) {
|
||||
qWarning() << "IpcProcess replica is not connected!";
|
||||
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
|
||||
|
||||
@@ -226,9 +226,22 @@ void Settings::setSaveLogs(bool enabled)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (enabled) {
|
||||
setLogEnableDate(QDateTime::currentDateTime());
|
||||
}
|
||||
emit saveLogsChanged(enabled);
|
||||
}
|
||||
|
||||
QDateTime Settings::getLogEnableDate()
|
||||
{
|
||||
return value("Conf/logEnableDate").toDateTime();
|
||||
}
|
||||
|
||||
void Settings::setLogEnableDate(QDateTime date)
|
||||
{
|
||||
setValue("Conf/logEnableDate", date);
|
||||
}
|
||||
|
||||
QString Settings::routeModeString(RouteMode mode) const
|
||||
{
|
||||
switch (mode) {
|
||||
|
||||
@@ -100,6 +100,9 @@ public:
|
||||
}
|
||||
void setSaveLogs(bool enabled);
|
||||
|
||||
QDateTime getLogEnableDate();
|
||||
void setLogEnableDate(QDateTime date);
|
||||
|
||||
enum RouteMode {
|
||||
VpnAllSites,
|
||||
VpnOnlyForwardSites,
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<source>Connection...</source>
|
||||
<source>Connecting...</source>
|
||||
<translation>اتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -29,12 +29,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<source>Reconnection...</source>
|
||||
<source>Reconnecting...</source>
|
||||
<translation>إعادة الاتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<source>Disconnection...</source>
|
||||
<source>Disconnecting...</source>
|
||||
<translation>إنهاء الاتصال...</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<source>Connection...</source>
|
||||
<source>Connecting...</source>
|
||||
<translation>در حال ارتباط...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -63,7 +63,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<source>Reconnection...</source>
|
||||
<source>Reconnecting...</source>
|
||||
<translation>اتصال دوباره...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -76,7 +76,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<source>Disconnection...</source>
|
||||
<source>Disconnecting...</source>
|
||||
<translation>قطع ارتباط...</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<source>Connection...</source>
|
||||
<source>Connecting...</source>
|
||||
<translation>ချိတ်ဆက်နေပါပြီ...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -32,7 +32,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<source>Reconnection...</source>
|
||||
<source>Reconnecting...</source>
|
||||
<translation>ပြန်လည်ချိတ်ဆက်နေပါသည်...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -45,7 +45,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<source>Disconnection...</source>
|
||||
<source>Disconnecting...</source>
|
||||
<translation>အဆက်အသွယ်ဖြတ်နေပါသည်...</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<source>Connection...</source>
|
||||
<source>Connecting...</source>
|
||||
<translation>Подключение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -55,7 +55,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<source>Reconnection...</source>
|
||||
<source>Reconnecting...</source>
|
||||
<translation>Переподключение...</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -68,7 +68,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<source>Disconnection...</source>
|
||||
<source>Disconnecting...</source>
|
||||
<translation>Отключение...</translation>
|
||||
</message>
|
||||
</context>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="65"/>
|
||||
<source>Connection...</source>
|
||||
<source>Connecting...</source>
|
||||
<translation>连接中</translation>
|
||||
</message>
|
||||
<message>
|
||||
@@ -44,12 +44,12 @@
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="79"/>
|
||||
<source>Reconnection...</source>
|
||||
<source>Reconnecting...</source>
|
||||
<translation>重连中</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../ui/controllers/connectionController.cpp" line="89"/>
|
||||
<source>Disconnection...</source>
|
||||
<source>Disconnecting...</source>
|
||||
<translation>断开中</translation>
|
||||
</message>
|
||||
<message>
|
||||
|
||||
@@ -62,7 +62,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
m_state = state;
|
||||
|
||||
m_isConnected = false;
|
||||
m_connectionStateText = tr("Connection...");
|
||||
m_connectionStateText = tr("Connecting...");
|
||||
switch (state) {
|
||||
case Vpn::ConnectionState::Connected: {
|
||||
m_isConnectionInProgress = false;
|
||||
@@ -76,7 +76,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
}
|
||||
case Vpn::ConnectionState::Reconnecting: {
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Reconnection...");
|
||||
m_connectionStateText = tr("Reconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnected: {
|
||||
@@ -86,7 +86,7 @@ void ConnectionController::onConnectionStateChanged(Vpn::ConnectionState state)
|
||||
}
|
||||
case Vpn::ConnectionState::Disconnecting: {
|
||||
m_isConnectionInProgress = true;
|
||||
m_connectionStateText = tr("Disconnection...");
|
||||
m_connectionStateText = tr("Disconnecting...");
|
||||
break;
|
||||
}
|
||||
case Vpn::ConnectionState::Preparing: {
|
||||
|
||||
@@ -79,7 +79,7 @@ bool ImportController::extractConfigFromFile(const QString &fileName)
|
||||
return extractConfigFromData(data);
|
||||
}
|
||||
|
||||
emit importErrorOccurred(tr("Unable to open file"));
|
||||
emit importErrorOccurred(tr("Unable to open file"), false);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -117,12 +117,12 @@ bool ImportController::extractConfigFromData(QString data)
|
||||
if (!m_serversModel->getServersCount()) {
|
||||
emit restoreAppConfig(config.toUtf8());
|
||||
} else {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"));
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ConfigTypes::Invalid: {
|
||||
emit importErrorOccurred(tr("Invalid configuration file"));
|
||||
emit importErrorOccurred(tr("Invalid configuration file"), false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -257,6 +257,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
} else {
|
||||
qDebug() << "Key parameter 'Endpoint' is missing";
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (hostNameAndPortMatch.hasCaptured(2)) {
|
||||
@@ -282,7 +283,7 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data)
|
||||
lastConfig[config_key::server_pub_key] = configMap.value("PublicKey");
|
||||
} else {
|
||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError));
|
||||
emit importErrorOccurred(errorString(ErrorCode::ImportInvalidConfigError), false);
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ public slots:
|
||||
signals:
|
||||
void importFinished();
|
||||
void importErrorOccurred(const QString &errorMessage, bool goToPageHome);
|
||||
void importErrorOccurred(const QString &errorMessage);
|
||||
|
||||
void qrDecodingFinished();
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ SettingsController::SettingsController(const QSharedPointer<ServersModel> &serve
|
||||
m_settings(settings)
|
||||
{
|
||||
m_appVersion = QString("%1 (%2, %3)").arg(QString(APP_VERSION), __DATE__, GIT_COMMIT_HASH);
|
||||
checkIfNeedDisableLogs();
|
||||
}
|
||||
|
||||
void SettingsController::toggleAmneziaDns(bool enable)
|
||||
@@ -71,8 +72,11 @@ void SettingsController::toggleLogging(bool enable)
|
||||
{
|
||||
m_settings->setSaveLogs(enable);
|
||||
#ifdef Q_OS_IOS
|
||||
AmneziaVPN::toggleLogging(enable);
|
||||
AmneziaVPN::toggleLogging(enable);
|
||||
#endif
|
||||
if (enable == true) {
|
||||
checkIfNeedDisableLogs();
|
||||
}
|
||||
emit loggingStateChanged();
|
||||
}
|
||||
|
||||
@@ -205,3 +209,13 @@ bool SettingsController::isCameraPresent()
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void SettingsController::checkIfNeedDisableLogs()
|
||||
{
|
||||
m_loggingDisableDate = m_settings->getLogEnableDate().addDays(14);
|
||||
if (m_loggingDisableDate <= QDateTime::currentDateTime()) {
|
||||
toggleLogging(false);
|
||||
clearLogs();
|
||||
emit loggingDisableByWatcher();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ signals:
|
||||
|
||||
void amneziaDnsToggled(bool enable);
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
private:
|
||||
QSharedPointer<ServersModel> m_serversModel;
|
||||
QSharedPointer<ContainersModel> m_containersModel;
|
||||
@@ -85,6 +87,10 @@ private:
|
||||
std::shared_ptr<Settings> m_settings;
|
||||
|
||||
QString m_appVersion;
|
||||
|
||||
QDateTime m_loggingDisableDate;
|
||||
|
||||
void checkIfNeedDisableLogs();
|
||||
};
|
||||
|
||||
#endif // SETTINGSCONTROLLER_H
|
||||
|
||||
@@ -81,6 +81,15 @@ int LanguageModel::getCurrentLanguageIndex()
|
||||
}
|
||||
}
|
||||
|
||||
int LanguageModel::getLineHeightAppend()
|
||||
{
|
||||
int langIndex = getCurrentLanguageIndex();
|
||||
switch (langIndex) {
|
||||
case 5: return 10; break; // Burmese
|
||||
default: return 0; break;
|
||||
}
|
||||
}
|
||||
|
||||
QString LanguageModel::getCurrentLanguageName()
|
||||
{
|
||||
return m_availableLanguages[getCurrentLanguageIndex()].name;
|
||||
|
||||
@@ -49,10 +49,12 @@ public:
|
||||
|
||||
Q_PROPERTY(QString currentLanguageName READ getCurrentLanguageName NOTIFY translationsUpdated)
|
||||
Q_PROPERTY(int currentLanguageIndex READ getCurrentLanguageIndex NOTIFY translationsUpdated)
|
||||
Q_PROPERTY(int lineHeightAppend READ getLineHeightAppend NOTIFY translationsUpdated)
|
||||
|
||||
public slots:
|
||||
void changeLanguage(const LanguageSettings::AvailableLanguageEnum language);
|
||||
int getCurrentLanguageIndex();
|
||||
int getLineHeightAppend();
|
||||
QString getCurrentLanguageName();
|
||||
|
||||
signals:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 16
|
||||
lineHeight: 16 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#0E0E11"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 38
|
||||
lineHeight: 38 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 30
|
||||
lineHeight: 30 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 16
|
||||
lineHeight: 16 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#878B91"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 21.6
|
||||
lineHeight: 21.6 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 24
|
||||
lineHeight: 24 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import QtQuick
|
||||
|
||||
Text {
|
||||
lineHeight: 20
|
||||
lineHeight: 20 + LanguageModel.getLineHeightAppend()
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
||||
color: "#D7D8DB"
|
||||
|
||||
@@ -33,44 +33,71 @@ PageType {
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: drawer.collapsedHeight
|
||||
|
||||
ConnectButton {
|
||||
id: connectButton
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
BasicButtonType {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 34
|
||||
anchors.bottomMargin: 34
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
implicitHeight: 36
|
||||
BasicButtonType {
|
||||
property bool isLoggingEnabled: SettingsController.isLoggingEnabled
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
leftImageColor: "transparent"
|
||||
borderWidth: 0
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled ||
|
||||
(ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
|
||||
implicitHeight: 36
|
||||
|
||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
borderWidth: 0
|
||||
|
||||
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||
visible: isLoggingEnabled ? true : false
|
||||
text: qsTr("Logging enabled")
|
||||
|
||||
onClicked: {
|
||||
homeSplitTunnelingDrawer.open()
|
||||
onClicked: {
|
||||
PageController.goToPage(PageEnum.PageSettingsLogging)
|
||||
}
|
||||
}
|
||||
|
||||
HomeSplitTunnelingDrawer {
|
||||
id: homeSplitTunnelingDrawer
|
||||
ConnectButton {
|
||||
id: connectButton
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignCenter
|
||||
}
|
||||
|
||||
parent: root
|
||||
BasicButtonType {
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
Layout.bottomMargin: 34
|
||||
leftPadding: 16
|
||||
rightPadding: 16
|
||||
|
||||
implicitHeight: 36
|
||||
|
||||
defaultColor: "transparent"
|
||||
hoveredColor: Qt.rgba(1, 1, 1, 0.08)
|
||||
pressedColor: Qt.rgba(1, 1, 1, 0.12)
|
||||
disabledColor: "#878B91"
|
||||
textColor: "#878B91"
|
||||
leftImageColor: "transparent"
|
||||
borderWidth: 0
|
||||
|
||||
property bool isSplitTunnelingEnabled: SitesModel.isTunnelingEnabled ||
|
||||
(ServersModel.isDefaultServerDefaultContainerHasSplitTunneling && ServersModel.getDefaultServerData("isServerFromApi"))
|
||||
|
||||
text: isSplitTunnelingEnabled ? qsTr("Split tunneling enabled") : qsTr("Split tunneling disabled")
|
||||
|
||||
imageSource: isSplitTunnelingEnabled ? "qrc:/images/controls/split-tunneling.svg" : ""
|
||||
rightImageSource: "qrc:/images/controls/chevron-down.svg"
|
||||
|
||||
onClicked: {
|
||||
homeSplitTunnelingDrawer.open()
|
||||
}
|
||||
|
||||
HomeSplitTunnelingDrawer {
|
||||
id: homeSplitTunnelingDrawer
|
||||
parent: root
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,18 @@ import "../Controls2/TextTypes"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onLoggingStateChanged() {
|
||||
if (SettingsController.isLoggingEnabled) {
|
||||
var message = qsTr("Logging is enabled. Note that logs will be automatically \
|
||||
disabled after 14 days, and all log files will be deleted.")
|
||||
PageController.showNotificationMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ PageType {
|
||||
PageController.showBusyIndicator(false)
|
||||
}
|
||||
|
||||
function onImportErrorOccurred(errorMessage) {
|
||||
function onImportErrorOccurred(errorMessage, goToPageHome) {
|
||||
PageController.showErrorMessage(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ PageType {
|
||||
} else {
|
||||
PageController.closePage()
|
||||
}
|
||||
PageController.showErrorMessage(errorMessage)
|
||||
}
|
||||
|
||||
function onImportFinished() {
|
||||
|
||||
@@ -124,11 +124,19 @@ PageType {
|
||||
Connections {
|
||||
target: ImportController
|
||||
|
||||
function onImportErrorOccurred(errorMessage) {
|
||||
function onImportErrorOccurred(errorMessage, goToPageHome) {
|
||||
PageController.showErrorMessage(errorMessage)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onLoggingDisableByWatcher() {
|
||||
PageController.showNotificationMessage(qsTr("Logging was disabled after 14 days, log files were deleted"))
|
||||
}
|
||||
}
|
||||
|
||||
StackViewType {
|
||||
id: tabBarStackView
|
||||
|
||||
|
||||
@@ -95,6 +95,7 @@ Window {
|
||||
|
||||
Connections {
|
||||
target: SettingsController
|
||||
|
||||
function onChangeSettingsFinished(finishedMessage) {
|
||||
PageController.showNotificationMessage(finishedMessage)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user