From 2e4ffb7fe987d98cc5bd1a381fec04499c64ab10 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 15 Aug 2022 08:25:41 +0100 Subject: [PATCH] ios: setting to use .onion hosts (#934) --- .../UserSettings/NetworkAndServers.swift | 85 +++++++++++++++++++ apps/ios/SimpleXChat/APITypes.swift | 36 +++++++- apps/ios/SimpleXChat/AppGroup.swift | 13 +++ 3 files changed, 133 insertions(+), 1 deletion(-) diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift index 8969dd95f4..6b91c02391 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift @@ -7,9 +7,27 @@ // import SwiftUI +import SimpleXChat + +enum OnionHostsAlert: Identifiable { + case update(hosts: OnionHosts) + case error(err: String) + + var id: String { + switch self { + case let .update(hosts): return "update \(hosts)" + case let .error(err): return "error \(err)" + } + } +} struct NetworkAndServers: View { @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false + @State private var cfgLoaded = false + @State private var currentNetCfg = NetCfg.defaults + @State private var netCfg = NetCfg.defaults + @State private var onionHosts: OnionHosts = .no + @State private var showOnionHostsAlert: OnionHostsAlert? var body: some View { VStack { @@ -22,6 +40,10 @@ struct NetworkAndServers: View { settingsRow("server.rack") { Text("SMP servers") } } + Picker("Use .onion hosts", selection: $onionHosts) { + ForEach(OnionHosts.values, id: \.self) { Text($0.text) } + } + if developerTools { NavigationLink { AdvancedNetworkSettings() @@ -33,6 +55,69 @@ struct NetworkAndServers: View { } } } + .onAppear { + if cfgLoaded { return } + cfgLoaded = true + currentNetCfg = getNetCfg() + resetNetCfgView() + } + .onChange(of: onionHosts) { _ in + if onionHosts != OnionHosts(netCfg: currentNetCfg) { + showOnionHostsAlert = .update(hosts: onionHosts) + } + } + .alert(item: $showOnionHostsAlert) { a in + switch a { + case let .update(hosts): + return Alert( + title: Text("Update .onion hosts setting?"), + message: Text(onionHostsInfo()) + Text("\n") + Text("Updating this setting will re-connect the client to all servers."), + primaryButton: .default(Text("Ok")) { + saveNetCfg(hosts) + }, + secondaryButton: .cancel() { + resetNetCfgView() + } + ) + case let .error(err): + return Alert( + title: Text("Error updating settings"), + message: Text(err) + ) + } + } + } + + private func saveNetCfg(_ hosts: OnionHosts) { + do { + let (hostMode, requiredHostMode) = hosts.hostMode + netCfg.hostMode = hostMode + netCfg.requiredHostMode = requiredHostMode + let def = netCfg.hostMode == .onionHost ? NetCfg.proxyDefaults : NetCfg.defaults + netCfg.tcpConnectTimeout = def.tcpConnectTimeout + netCfg.tcpTimeout = def.tcpTimeout + try setNetworkConfig(netCfg) + currentNetCfg = netCfg + setNetCfg(netCfg) + } catch let error { + let err = responseError(error) + resetNetCfgView() + showOnionHostsAlert = .error(err: err) + logger.error("\(err)") + } + } + + private func resetNetCfgView() { + netCfg = currentNetCfg + onionHosts = OnionHosts(netCfg: netCfg) + } + + private func onionHostsInfo() -> LocalizedStringKey { + switch onionHosts { + case .no: return "Onion hosts will not be used." + case .prefer: return "Onion hosts will be used when available. Requires enabling VPN." + case .require: return "Onion hosts will be required for connection. Requires enabling VPN." + } } } diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 746e0366d8..f34a12524e 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -517,7 +517,7 @@ public struct ArchiveConfig: Encodable { public struct NetCfg: Codable, Equatable { public var socksProxy: String? = nil public var hostMode: HostMode = .publicHost - public var requiredHostMode = false + public var requiredHostMode = true public var tcpConnectTimeout: Int // microseconds public var tcpTimeout: Int // microseconds public var tcpKeepAlive: KeepAliveOpts? @@ -548,6 +548,40 @@ public enum HostMode: String, Codable { case publicHost = "public" } +public enum OnionHosts: String, Identifiable { + case no + case prefer + case require + + public var text: LocalizedStringKey { + switch self { + case .no: return "No" + case .prefer: return "When available" + case .require: return "Requred" + } + } + + public var hostMode: (HostMode, Bool) { + switch self { + case .no: return (.publicHost, true) + case .prefer: return (.onionHost, false) + case .require: return (.onionHost, true) + } + } + + public init(netCfg: NetCfg) { + switch netCfg.hostMode { + case .onionViaSocks: self = .no + case .onionHost: self = netCfg.requiredHostMode ? .require : .prefer + case .publicHost: self = .no + } + } + + public var id: OnionHosts { self } + + public static let values: [OnionHosts] = [.no, .prefer, .require] +} + public struct KeepAliveOpts: Codable, Equatable { public var keepIdle: Int // seconds public var keepIntvl: Int // seconds diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index b63db62154..15ff7ee52a 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -15,6 +15,7 @@ public let GROUP_DEFAULT_CHAT_LAST_START = "chatLastStart" let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode" let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" let GROUP_DEFAULT_NTF_BADGE_COUNT = "ntgBadgeCount" +let GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS = "networkUseOnionHosts" let GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT = "networkTCPConnectTimeout" let GROUP_DEFAULT_NETWORK_TCP_TIMEOUT = "networkTCPTimeout" let GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL = "networkSMPPingInterval" @@ -29,6 +30,7 @@ public let groupDefaults = UserDefaults(suiteName: APP_GROUP_NAME)! public func registerGroupDefaults() { groupDefaults.register(defaults: [ + GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS: OnionHosts.no.rawValue, GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT: NetCfg.defaults.tcpConnectTimeout, GROUP_DEFAULT_NETWORK_TCP_TIMEOUT: NetCfg.defaults.tcpTimeout, GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL: NetCfg.defaults.smpPingInterval, @@ -84,6 +86,12 @@ public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults public let ntfBadgeCountGroupDefault = IntDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_NTF_BADGE_COUNT) +public let networkUseOnionHostsGroupDefault = EnumDefault( + defaults: groupDefaults, + forKey: GROUP_DEFAULT_NETWORK_USE_ONION_HOSTS, + withDefault: .no +) + public class DateDefault { var defaults: UserDefaults var key: String @@ -157,6 +165,8 @@ public class Default { } public func getNetCfg() -> NetCfg { + let onionHosts = networkUseOnionHostsGroupDefault.get() + let (hostMode, requiredHostMode) = onionHosts.hostMode let tcpConnectTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) let tcpTimeout = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) let smpPingInterval = groupDefaults.integer(forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL) @@ -171,6 +181,8 @@ public func getNetCfg() -> NetCfg { tcpKeepAlive = nil } return NetCfg( + hostMode: hostMode, + requiredHostMode: requiredHostMode, tcpConnectTimeout: tcpConnectTimeout, tcpTimeout: tcpTimeout, tcpKeepAlive: tcpKeepAlive, @@ -179,6 +191,7 @@ public func getNetCfg() -> NetCfg { } public func setNetCfg(_ cfg: NetCfg) { + networkUseOnionHostsGroupDefault.set(OnionHosts(netCfg: cfg)) groupDefaults.set(cfg.tcpConnectTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_CONNECT_TIMEOUT) groupDefaults.set(cfg.tcpTimeout, forKey: GROUP_DEFAULT_NETWORK_TCP_TIMEOUT) groupDefaults.set(cfg.smpPingInterval, forKey: GROUP_DEFAULT_NETWORK_SMP_PING_INTERVAL)