From 2065040a701f5535cd29d0df9ef1b22c7362716b Mon Sep 17 00:00:00 2001 From: Stossy11 <69031796+stossy11@users.noreply.github.com> Date: Sat, 19 Apr 2025 14:47:55 +1000 Subject: [PATCH] Version 1.1.0 --- .DS_Store | Bin 6148 -> 6148 bytes StosVPN.xcodeproj/project.pbxproj | 8 +- .../UserInterfaceState.xcuserstate | Bin 40775 -> 43819 bytes StosVPN/ContentView.swift | 415 ++++++------------ 4 files changed, 142 insertions(+), 281 deletions(-) diff --git a/.DS_Store b/.DS_Store index 7679cd3e1f46f174be73fafdaf502afef79adc52..f4d8fb58393a79b4f79699d945b41d3eabb5164e 100644 GIT binary patch delta 60 zcmV-C0K@-;FoZC$Kmh^=Ws`gXD3fXd3I>^(qUVplPN7Zyzb delta 61 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�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 [String: Any]? { - return nil - } - - // This function explicitly shows we're not sharing any data - func shareDataWithThirdParties() -> Bool { - return false - } - - private init() {} -} - // MARK: - Tunnel Manager class TunnelManager: ObservableObject { @Published var hasLocalDeviceSupport = false @@ -109,7 +93,6 @@ class TunnelManager: ObservableObject { guard let self = self else { return } DispatchQueue.main.async { - if let error = error { VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") self.tunnelStatus = .error @@ -119,22 +102,16 @@ class TunnelManager: ObservableObject { self.hasLocalDeviceSupport = true if let managers = managers, !managers.isEmpty { + // Look specifically for StosVPN manager for manager in managers { if let proto = manager.protocolConfiguration as? NETunnelProviderProtocol, proto.providerBundleIdentifier == self.tunnelBundleId { self.vpnManager = manager self.updateTunnelStatus(from: manager.connection.status) - VPNLogger.shared.log("Loaded existing tunnel configuration") + VPNLogger.shared.log("Loaded existing StosVPN tunnel configuration") break } } - - // If we didn't find a matching manager, use the first one - if self.vpnManager == nil, let firstManager = managers.first { - self.vpnManager = firstManager - self.updateTunnelStatus(from: firstManager.connection.status) - VPNLogger.shared.log("Using existing tunnel configuration") - } } else { VPNLogger.shared.log("No existing tunnel configuration found") } @@ -143,7 +120,7 @@ class TunnelManager: ObservableObject { } private func setupStatusObserver() { - NotificationCenter.default.addObserver( + vpnObserver = NotificationCenter.default.addObserver( forName: .NEVPNStatusDidChange, object: nil, queue: .main @@ -153,7 +130,11 @@ class TunnelManager: ObservableObject { return } - self.updateTunnelStatus(from: connection.status) + // Only update status if it's our VPN connection + if let manager = self.vpnManager, + connection == manager.connection { + self.updateTunnelStatus(from: connection.status) + } } } @@ -174,73 +155,66 @@ class TunnelManager: ObservableObject { self.tunnelStatus = .error } - VPNLogger.shared.log("VPN status updated: \(self.tunnelStatus.rawValue)") + VPNLogger.shared.log("StosVPN status updated: \(self.tunnelStatus.rawValue)") } } - private func createOrUpdateTunnelConfiguration(completion: @escaping (Bool) -> Void) { - // First check if we already have configurations - NETunnelProviderManager.loadAllFromPreferences { [weak self] (managers, error) in - guard let self = self else { return completion(false) } - - if let error = error { - VPNLogger.shared.log("Error loading preferences: \(error.localizedDescription)") - return completion(false) - } - - let manager: NETunnelProviderManager - if let existingManagers = managers, !existingManagers.isEmpty { - if let matchingManager = existingManagers.first(where: { - ($0.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier == self.tunnelBundleId - }) { - manager = matchingManager - VPNLogger.shared.log("Updating existing tunnel configuration") - } else { - manager = existingManagers[0] - VPNLogger.shared.log("Using first available tunnel configuration") + private func createStosVPNConfiguration(completion: @escaping (NETunnelProviderManager?) -> Void) { + let manager = NETunnelProviderManager() + manager.localizedDescription = "StosVPN" + + let proto = NETunnelProviderProtocol() + proto.providerBundleIdentifier = self.tunnelBundleId + proto.serverAddress = "StosVPN's Local Network Tunnel" + manager.protocolConfiguration = proto + + let onDemandRule = NEOnDemandRuleEvaluateConnection() + onDemandRule.interfaceTypeMatch = .any + onDemandRule.connectionRules = [NEEvaluateConnectionRule( + matchDomains: ["10.7.0.0", "10.7.0.1"], + andAction: .connectIfNeeded + )] + + manager.onDemandRules = [onDemandRule] + manager.isOnDemandEnabled = true + manager.isEnabled = true + + manager.saveToPreferences { error in + DispatchQueue.main.async { + if let error = error { + VPNLogger.shared.log("Error creating StosVPN configuration: \(error.localizedDescription)") + completion(nil) + return } - } else { - // Create a new manager if none exists - manager = NETunnelProviderManager() - VPNLogger.shared.log("Creating new tunnel configuration") - } - - manager.localizedDescription = "StosVPN" - - let proto = NETunnelProviderProtocol() - proto.providerBundleIdentifier = self.tunnelBundleId - proto.serverAddress = "StosVPN's Local Network Tunnel" - manager.protocolConfiguration = proto - - let onDemandRule = NEOnDemandRuleEvaluateConnection() - onDemandRule.interfaceTypeMatch = .any - onDemandRule.connectionRules = [NEEvaluateConnectionRule( - matchDomains: ["localhost"], - andAction: .connectIfNeeded - )] - - manager.onDemandRules = [onDemandRule] - manager.isOnDemandEnabled = true - manager.isEnabled = true - - manager.saveToPreferences { [weak self] error in - guard let self = self else { return completion(false) } - DispatchQueue.main.async { - if let error = error { - VPNLogger.shared.log("Error saving tunnel configuration: \(error.localizedDescription)") - completion(false) - return - } - - self.vpnManager = manager - VPNLogger.shared.log("Tunnel configuration saved successfully") - completion(true) - } + VPNLogger.shared.log("StosVPN configuration created successfully") + completion(manager) } } } + private func getActiveVPNManager(completion: @escaping (NETunnelProviderManager?) -> Void) { + NETunnelProviderManager.loadAllFromPreferences { managers, error in + if let error = error { + VPNLogger.shared.log("Error loading VPN configurations: \(error.localizedDescription)") + completion(nil) + return + } + + guard let managers = managers else { + completion(nil) + return + } + + let activeManager = managers.first { manager in + return manager.connection.status == .connected || + manager.connection.status == .connecting + } + + completion(activeManager) + } + } + // MARK: - Public Methods func toggleVPNConnection() { @@ -252,42 +226,73 @@ class TunnelManager: ObservableObject { } func startVPN() { + getActiveVPNManager { [weak self] activeManager in + guard let self = self else { return } + + if let activeManager = activeManager, + (activeManager.protocolConfiguration as? NETunnelProviderProtocol)?.providerBundleIdentifier != self.tunnelBundleId { + VPNLogger.shared.log("Disconnecting existing VPN connection before starting StosVPN") + + // Set a flag to start StosVPN after disconnection + UserDefaults.standard.set(true, forKey: "ShouldStartStosVPNAfterDisconnect") + activeManager.connection.stopVPNTunnel() + return + } + + + self.initializeAndStartStosVPN() + } + } + + private func initializeAndStartStosVPN() { if let manager = vpnManager { startExistingVPN(manager: manager) } else { - createOrUpdateTunnelConfiguration { [weak self] success in - guard let self = self, success else { return } - self.loadTunnelPreferences() + createStosVPNConfiguration { [weak self] manager in + guard let self = self, let manager = manager else { return } - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - if let manager = self.vpnManager { - self.startExistingVPN(manager: manager) - } - } + self.vpnManager = manager + self.startExistingVPN(manager: manager) } } } private func startExistingVPN(manager: NETunnelProviderManager) { guard tunnelStatus != .connected else { - VPNLogger.shared.log("Network tunnel is already connected") + VPNLogger.shared.log("StosVPN tunnel is already connected") return } - tunnelStatus = .connecting - - let options: [String: NSObject] = [ - "TunnelDeviceIP": tunnelDeviceIp as NSObject, - "TunnelFakeIP": tunnelFakeIp as NSObject, - "TunnelSubnetMask": tunnelSubnetMask as NSObject - ] - - do { - try manager.connection.startVPNTunnel(options: options) - VPNLogger.shared.log("Network tunnel start initiated") - } catch { - tunnelStatus = .error - VPNLogger.shared.log("Failed to start tunnel: \(error.localizedDescription)") + manager.isEnabled = true + manager.saveToPreferences { error in + if let error = error { + VPNLogger.shared.log(error.localizedDescription) + return + } + + // Reload it to apply + manager.loadFromPreferences { error in + if let error = error { + VPNLogger.shared.log(error.localizedDescription) + return + } + + self.tunnelStatus = .connecting + + let options: [String: NSObject] = [ + "TunnelDeviceIP": self.tunnelDeviceIp as NSObject, + "TunnelFakeIP": self.tunnelFakeIp as NSObject, + "TunnelSubnetMask": self.tunnelSubnetMask as NSObject + ] + + do { + try manager.connection.startVPNTunnel(options: options) + VPNLogger.shared.log("StosVPN tunnel start initiated") + } catch { + self.tunnelStatus = .error + VPNLogger.shared.log("Failed to start StosVPN tunnel: \(error.localizedDescription)") + } + } } } @@ -296,7 +301,27 @@ class TunnelManager: ObservableObject { tunnelStatus = .disconnecting manager.connection.stopVPNTunnel() - VPNLogger.shared.log("Network tunnel stop initiated") + VPNLogger.shared.log("StosVPN tunnel stop initiated") + + UserDefaults.standard.removeObject(forKey: "ShouldStartStosVPNAfterDisconnect") + } + + func handleVPNStatusChange(notification: Notification) { + guard let connection = notification.object as? NEVPNConnection else { return } + + if let manager = vpnManager, connection == manager.connection { + updateTunnelStatus(from: connection.status) + return + } + + if connection.status == .disconnected && + UserDefaults.standard.bool(forKey: "ShouldStartStosVPNAfterDisconnect") { + UserDefaults.standard.removeObject(forKey: "ShouldStartStosVPNAfterDisconnect") + + DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in + self?.initializeAndStartStosVPN() + } + } } deinit { @@ -620,7 +645,9 @@ struct SettingsView: View { } Section(header: Text("App Information")) { - NavigationLink(destination: PrivacyPolicyView()) { + Button { + UIApplication.shared.open(URL(string: "https://github.com/stossy11/PrivacyPolicy/blob/main/PrivacyPolicy.md")!, options: [:]) + } label: { Label("Privacy Policy", systemImage: "lock.shield") } @@ -631,7 +658,7 @@ struct SettingsView: View { HStack { Text("App Version") Spacer() - Text("1.0.0") + Text("1.1.0") .foregroundColor(.secondary) } @@ -708,172 +735,6 @@ struct ConnectionLogView: View { } } -// MARK: - Updated PrivacyPolicyView -struct PrivacyPolicyView: View { - var body: some View { - ScrollView { - VStack(alignment: .leading, spacing: 20) { - Text("Privacy Policy") - .font(.title) - .fontWeight(.bold) - .padding(.bottom, 10) - - Text("Last Updated: April 2, 2025") - .font(.caption) - .foregroundColor(.secondary) - .padding(.bottom, 20) - - GroupBox(label: Label("Overview", systemImage: "text.justify").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("StosVPN is designed exclusively to create a purely local network interface for iOS development and testing purposes. This app is fundamentally different from traditional VPN services:") - .padding(.vertical, 5) - - Text("• All network activity remains entirely on your device") - Text("• No external servers are involved in the operation of this app") - Text("• No internet traffic is routed through our servers or any third-party services") - Text("• The app functions entirely locally on your device") - } - .padding(.vertical) - } - - GroupBox(label: Label("Zero Data Collection", systemImage: "lock.shield").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("StosVPN does NOT collect any data whatsoever, including:") - .fontWeight(.medium) - .padding(.bottom, 5) - - Text("• Personal information (name, email, phone number, address)") - Text("• Device identifiers (IP address, IDFA, IDFV, device name)") - Text("• Usage statistics or app analytics") - Text("• Network traffic data or browsing history") - Text("• Location information") - Text("• User content or files") - Text("• Network requests or connection details") - Text("• Technical device information") - - Text("We are committed to absolute zero data collection. No information of any kind is ever transmitted from your device, logged, or stored by our app.") - .fontWeight(.medium) - .padding(.top, 10) - } - .padding(.vertical) - } - - GroupBox(label: Label("How StosVPN Works", systemImage: "gear").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("Technical Implementation:") - .fontWeight(.medium) - .padding(.bottom, 5) - - Text("StosVPN uses Apple's Network Extension framework to create a local network interface directly on your device. This technology:") - .padding(.bottom, 5) - - Text("• Creates a virtual network adapter on your iOS device") - Text("• Configures this adapter with user-specified local IP addresses") - Text("• Enables routing between your apps and locally hosted servers") - Text("• Operates entirely within your device's memory") - Text("• Does not modify, intercept, or process any internet traffic") - - Text("This functionality is specifically designed for developers testing iOS applications that need to communicate with locally hosted web or API servers.") - .padding(.top, 10) - } - .padding(.vertical) - } - - GroupBox(label: Label("Required Permissions", systemImage: "checkmark.shield").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("StosVPN requires network extension permissions for the sole purpose of creating a virtual network interface on your device.") - .padding(.bottom, 5) - - Text("Apple's Privacy Purpose String:") - .fontWeight(.medium) - .padding(.top, 5) - - Text("\"StosVPN requires network extension permissions to create a local virtual network interface used exclusively for development and testing. This permission is not used to monitor, collect, or transmit any user data.\"") - .italic() - .padding(.horizontal) - .padding(.vertical, 5) - - Text("These permissions are never used to:") - .fontWeight(.medium) - .padding(.top, 5) - - Text("• Monitor network traffic") - Text("• Access your browsing history") - Text("• Read or transmit personal information") - Text("• Track your location or device usage") - } - .padding(.vertical) - } - - GroupBox(label: Label("No Third-Party Sharing", systemImage: "person.2.slash").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("StosVPN does not share data with third parties because:") - .padding(.bottom, 5) - - Text("• We collect absolutely no data") - Text("• The app contains no analytics frameworks") - Text("• No advertising or tracking SDKs are included") - Text("• No external servers are contacted during operation") - Text("• No cookies or other tracking technologies are used") - - Text("All functionality is implemented using Apple's native frameworks, with no third-party services or libraries that could potentially access user data.") - .padding(.top, 10) - .fontWeight(.medium) - } - .padding(.vertical) - } - - GroupBox(label: Label("Children's Privacy", systemImage: "person.crop.circle").font(.headline)) { - Text("StosVPN is a developer tool and not intended for use by children under the age of 13. Since we do not collect any personal information from any users, including children, no special provisions are required to comply with children's privacy regulations.") - .padding(.vertical) - } - - GroupBox(label: Label("Changes to This Policy", systemImage: "arrow.triangle.2.circlepath").font(.headline)) { - Text("While our commitment to zero data collection will never change, we may update this privacy policy to clarify our practices or reflect changes in functionality. Any updates will be clearly dated and communicated through app updates.") - .padding(.vertical) - } - - GroupBox(label: Label("Contact Information", systemImage: "envelope").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("If you have any questions, concerns, or requests regarding this privacy policy or StosVPN, please contact us at:") - .padding(.bottom, 5) - - Text("privacy@stossvpn.com") - .fontWeight(.medium) - - Text("We are committed to addressing any questions or concerns you may have about our privacy practices or this app's functionality.") - .padding(.top, 10) - } - .padding(.vertical) - } - - GroupBox(label: Label("Your Rights", systemImage: "person.text.rectangle").font(.headline)) { - VStack(alignment: .leading, spacing: 10) { - Text("Although we collect no personal data, you have the right to:") - .padding(.bottom, 5) - - Text("• Request information about our data practices") - Text("• Verify our zero-collection policy") - Text("• Remove the app and all its local configuration at any time") - - Text("Since all configuration is stored locally on your device, uninstalling the app removes all data created by StosVPN.") - .padding(.top, 10) - } - .padding(.vertical) - } - - GroupBox(label: Label("Apple App Store Compliance", systemImage: "apple.logo").font(.headline)) { - Text("This app complies with all Apple App Store Review Guidelines, including guidelines 2.1 and 5.4 regarding data collection and VPN apps. StosVPN is a local development tool that uses VPN technology solely for creating a local network interface without any remote server connections or data collection.") - .padding(.vertical) - } - } - .padding() - } - .navigationTitle("Privacy Policy") - .navigationBarTitleDisplayMode(.inline) - } -} - // MARK: - Updated HelpView struct HelpView: View { var body: some View {