diff options
| author | Jon Petersson <jon.petersson@mullvad.net> | 2026-02-27 16:25:03 +0100 |
|---|---|---|
| committer | Jon Petersson <jon.petersson@mullvad.net> | 2026-02-27 16:25:03 +0100 |
| commit | 6676acf301c498a8dcb71a35f409b89968b3a750 (patch) | |
| tree | d59d5398b6a3d03f2e2b04498e02d2546610ea3f /ios/MullvadVPN/Debug/DebugViewModel.swift | |
| parent | fe84f9bad91e222ed768ede172fce1e2ada29bea (diff) | |
| download | mullvadvpn-debug-view.tar.xz mullvadvpn-debug-view.zip | |
Add debug viewdebug-view
Diffstat (limited to 'ios/MullvadVPN/Debug/DebugViewModel.swift')
| -rw-r--r-- | ios/MullvadVPN/Debug/DebugViewModel.swift | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/ios/MullvadVPN/Debug/DebugViewModel.swift b/ios/MullvadVPN/Debug/DebugViewModel.swift new file mode 100644 index 0000000000..e6f4ebd1db --- /dev/null +++ b/ios/MullvadVPN/Debug/DebugViewModel.swift @@ -0,0 +1,301 @@ +// +// DebugViewModel.swift +// MullvadVPN +// +// Created by Jon Petersson on 2026-02-27. +// Copyright © 2026 Mullvad VPN AB. All rights reserved. +// + +import MullvadSettings +import Network +import PacketTunnelCore +import SwiftUI + +@MainActor +protocol DebugViewModel: ObservableObject { + typealias Item = (title: String, data: [String]) + + var tunnelSettings: LatestTunnelSettings { get } + var nwPathStatus: NWPath.Status { get } + + var connection: [Item] { get } + var settings: [Item] { get } +} + +class DebugViewModelImpl: DebugViewModel { + var tunnelManager: TunnelManager + var nwPathMonitor: NWPathMonitor + var appPreferences: AppPreferencesDataSource + var tunnelObserver: TunnelBlockObserver! + + var tunnelSettings: LatestTunnelSettings + var tunnelStatus: TunnelStatus + var nwPathStatus = NWPath.Status.unsatisfied + + @Published var connection = [Item]() + @Published var settings = [Item]() + + init( + tunnelManager: TunnelManager, + nwPathMonitor: NWPathMonitor, + appPreferences: AppPreferencesDataSource + ) { + self.tunnelManager = tunnelManager + self.nwPathMonitor = nwPathMonitor + self.appPreferences = appPreferences + + tunnelSettings = tunnelManager.settings + tunnelStatus = tunnelManager.tunnelStatus + + refreshData() + + tunnelObserver = TunnelBlockObserver( + didUpdateTunnelStatus: { _, status in + self.tunnelStatus = status + self.refreshData() + }, + didUpdateDeviceState: { _, _, _ in }, + didUpdateTunnelSettings: { _, settings in + self.tunnelSettings = settings + self.refreshData() + } + ) + self.tunnelManager.addObserver(tunnelObserver) + } + + // Arrange these functions to get the desired section order in the view. + private func refreshData() { + // Connection + setRelays() + setObfuscation() + + // Settings + setRelaySettings() + setMultihopSettings() + setDaitaSettings() + setObfuscationSettings() + setQuantumResistanceSettings() + setIncludeAllNetworksSettings() + setMullvadDnsBlockers() + setCustomDnsBlockers() + } + + private func update(item: Item, in list: inout [Item]) { + guard let index = (list.firstIndex { $0.title == item.title }) else { + list.append(item) + return + } + + list.remove(at: index) + list.insert(item, at: index) + } +} + +// MARK: Connection + +extension DebugViewModelImpl { + private func setRelays() { + let entry = tunnelStatus.state.relays?.entry?.debugDescription ?? "-" + let exit = tunnelStatus.state.relays?.exit.debugDescription ?? "-" + + update( + item: ( + title: "Relays", + data: [ + "Entry: \(entry)", + "Exit: \(exit)", + ] + ), in: &connection + ) + } + + private func setObfuscation() { + update( + item: ( + title: "Obfuscation", + data: [ + tunnelStatus.observedState.connectionState?.obfuscationMethod.description ?? "-" + ] + ), in: &connection + ) + } +} + +// MARK: Settings + +extension DebugViewModelImpl { + func setRelaySettings() { + let entry = tunnelSettings.relayConstraints.entryLocations.value?.locations.first?.stringRepresentation ?? "-" + let exit = tunnelSettings.relayConstraints.exitLocations.value?.locations.first?.stringRepresentation ?? "-" + + update( + item: ( + title: "Relays", + data: [ + "Entry: \(entry)", + "Exit: \(exit)", + ] + ), in: &settings + ) + } + + func setMullvadDnsBlockers() { + let blockingOptions = tunnelSettings.dnsSettings.blockingOptions + var dnsBlockers = [String]() + + if blockingOptions.contains(.blockAdvertising) { + dnsBlockers.append("Advertising (\(DNSBlockingOptions.blockAdvertising.serverAddress!))") + } + if blockingOptions.contains(.blockTracking) { + dnsBlockers.append("Tracking (\(DNSBlockingOptions.blockTracking.serverAddress!))") + } + if blockingOptions.contains(.blockMalware) { + dnsBlockers.append("Malware (\(DNSBlockingOptions.blockMalware.serverAddress!))") + } + if blockingOptions.contains(.blockAdultContent) { + dnsBlockers.append("Adult content (\(DNSBlockingOptions.blockAdultContent.serverAddress!))") + } + if blockingOptions.contains(.blockGambling) { + dnsBlockers.append("Gambling (\(DNSBlockingOptions.blockGambling.serverAddress!))") + } + if blockingOptions.contains(.blockSocialMedia) { + dnsBlockers.append("Social media (\(DNSBlockingOptions.blockSocialMedia.serverAddress!))") + } + + update( + item: ( + title: "Mullvad DNS blockers", + data: dnsBlockers.isEmpty ? ["-"] : dnsBlockers + ), in: &settings + ) + } + + func setCustomDnsBlockers() { + let customAddresses = tunnelSettings.dnsSettings.customDNSDomains.map { $0.debugDescription } + + update( + item: ( + title: "Custom DNS blockers", + data: customAddresses.isEmpty ? ["-"] : customAddresses + ), in: &settings + ) + } + + func setObfuscationSettings() { + let method = tunnelSettings.wireGuardObfuscation.state.description + let udpTcpPort = tunnelSettings.wireGuardObfuscation.udpOverTcpPort.description + let shadowSocksPort = tunnelSettings.wireGuardObfuscation.shadowsocksPort.description + + update( + item: ( + title: "Obfuscation", + data: [ + "Method: \(method)", + "UDP over TCP port: \(udpTcpPort)", + "Shadowsocks port: \(shadowSocksPort)", + ] + ), in: &settings + ) + } + + func setQuantumResistanceSettings() { + update( + item: ( + title: "Quantum resistance", + data: [ + tunnelSettings.tunnelQuantumResistance.isEnabled ? "Enabled" : "Disabled" + ] + ), in: &settings + ) + } + + func setMultihopSettings() { + update( + item: ( + title: "Multihop", + data: [ + tunnelSettings.tunnelMultihopState.isEnabled ? "Enabled" : "Disabled" + ] + ), in: &settings + ) + } + + func setDaitaSettings() { + let daitaIsEnabled = tunnelSettings.daita.daitaState.isEnabled ? "Enabled" : "Disabled" + let directOnlyIsEnabled = tunnelSettings.daita.directOnlyState.isEnabled ? "Enabled" : "Disabled" + + update( + item: ( + title: "DAITA", + data: [ + "DAITA: \(daitaIsEnabled)", + "Direct only: \(directOnlyIsEnabled)", + ] + ), in: &settings + ) + } + + func setIncludeAllNetworksSettings() { + let includeAllNetworksIsEnabled = + tunnelSettings.includeAllNetworks.includeAllNetworksIsEnabled ? "Enabled" : "Disabled" + let localNetworkSharingIsEnabled = + tunnelSettings.includeAllNetworks.localNetworkSharingIsEnabled ? "Enabled" : "Disabled" + let consent = appPreferences.includeAllNetworksConsent ? "True" : "False" + + update( + item: ( + title: "Force all apps", + data: [ + "Force all apps: \(includeAllNetworksIsEnabled)", + "Local network sharing: \(localNetworkSharingIsEnabled)", + "Consent: \(consent)", + ] + ), in: &settings + ) + } +} + +// MARK: Mock + +class MockDebugViewModel: DebugViewModel { + var tunnelSettings: LatestTunnelSettings = LatestTunnelSettings() + var nwPathStatus: NWPath.Status = NWPath.Status.unsatisfied + + // Connection + var connection = [ + ( + title: "Relays", + data: [ + "Entry: Sweden, Gothenburg, se-got-001", + "Exit: Sweden, Gothenburg, se-got-002", + ] + ), + (title: "Obfuscation", data: [""]), + ] + + // Settings + var settings = [ + (title: "Relays", data: ["Entry: se-se-got-se-got-001", "Exit: se-se-got-se-got-001"]), + ( + title: "Mullvad DNS blockers", + data: [ + "Advertising (100.64.0.1)", + "Tracking (100.64.0.2)", + "Social Media (100.64.0.3)", + ] + ), + (title: "Custom DNS blockers", data: ["192.168.1.1", "192.168.1.2"]), + (title: "Obfuscation", data: ["QUIC", "UDP over TCP port: 53", "Shadowsocks port: 53"]), + (title: "Quantum resistance", data: ["Enabled"]), + (title: "Multihop", data: ["Disabled"]), + (title: "DAITA", data: ["DAITA: Enabled", "Direct only: Disabled"]), + ( + title: "Force all apps", + data: [ + "Force all apps: Disabled", + "Local network sharing: Disabled", + "Consent: False", + ] + ), + ] +} |
