Post

Replies

Boosts

Views

Activity

NETransparentProxyManager: saving isOnDemandEnabled = true disables On-Demand on other proxies
On macOS, multiple NETransparentProxyManager (NEAppProxyProvider) configurations from different vendors can be enabled (isEnabled = true) and run simultaneously. However, we discovered that when one NETransparentProxyManager configuration calls saveToPreferencesWithCompletionHandler: with isOnDemandEnabled = true, /usr/libexec/nehelper (NEConfigurationManager) silently modifies any other enabled NETransparentProxyManager configuration in /Library/Preferences/com.apple.networkextension.plist and flips its OnDemandEnabled to false (and nesessionmanager logs Resetting VPN On Demand and marks the other session not-kernel-activated). Unlike NEVPNManager.isEnabled (which documents mutual exclusion for personal VPNs), NETransparentProxyManager is designed so multiple transparent proxies can coexist simultaneously, and neither NETransparentProxyManager.h nor NEVPNManager.h documents that isOnDemandEnabled is mutually exclusive across coexisting transparent proxy configurations. Root Cause Observed in NetworkExtension.framework When inspecting -[NEConfigurationManager saveConfigurationToDisk:updateSCPreferences:currentSignature:userUUID:notifyNow:isUpgrade:], when iterating over existing NEConfiguration objects of the same grade: If [[newConfig VPN] isEnabled] && [[otherConfig VPN] isEnabled] && [[newConfig VPN] isOnDemandEnabled] && [[otherConfig VPN] isOnDemandEnabled], NEConfigurationManager invokes: +[NEConfigurationManager disableConfiguration:otherConfig onDemandOnly:YES] disableConfiguration:onDemandOnly:YES calls [[otherConfig VPN] setOnDemandEnabled:NO], even when both configurations are NETransparentProxyManager (TunnelType == 2). Real-World Impact When two enterprise security products that both use NETransparentProxyManager are installed on the same Mac: Both configure isEnabled = true, onDemandRules = [NEOnDemandRuleConnect()], and isOnDemandEnabled = true. Whenever Proxy A saves its preferences, nehelper silently flips Proxy B's isOnDemandEnabled to false (and vice versa). After a reboot or network transition where Proxy B's initial startVPNTunnel fails while Wi-Fi is still associating and Proxy B relies on VPN On Demand (NEOnDemandRuleConnect) to start its NEAppProxyProvider once network connectivity comes up, macOS never starts Proxy B because its isOnDemandEnabled flag was cleared. Minimal Reproducer (Swift) Compile this minimal CLI inside an app bundle signed with the com.apple.developer.networking.networkextension = ["app-proxy-provider"] entitlement: import Foundation import NetworkExtension let args = CommandLine.arguments let name = args.count > 1 ? args[1] : "PocProxyA" let action = args.count > 2 ? args[2] : "status" DispatchQueue.main.async { NETransparentProxyManager.loadAllFromPreferences { managers, error in if let error = error { print("[\(name)] loadAllFromPreferences error: \(error)") exit(1) } let all = managers ?? [] let existing = all.first(where: { $0.localizedDescription == name }) if action == "status" { if let mgr = existing { print("[\(name)] -> isEnabled=\(mgr.isEnabled), isOnDemandEnabled=\(mgr.isOnDemandEnabled), rulesCount=\(mgr.onDemandRules?.count ?? 0)") } else { print("[\(name)] -> not found") } exit(0) } if action == "remove" { guard let mgr = existing else { exit(0) } mgr.removeFromPreferences { err in print("[\(name)] removed (err=\(String(describing: err)))") exit(0) } return } // action == "save" let mgr = existing ?? NETransparentProxyManager() let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol() proto.providerBundleIdentifier = "com.example.poc.extension" proto.serverAddress = "127.0.0.1" mgr.protocolConfiguration = proto mgr.localizedDescription = name mgr.isEnabled = true mgr.onDemandRules = [NEOnDemandRuleConnect()] mgr.isOnDemandEnabled = true print("[\(name)] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true...") mgr.saveToPreferences { err in if let err = err { print("[\(name)] saveToPreferences error: \(err)") exit(1) } print("[\(name)] saveToPreferences succeeded! isEnabled=\(mgr.isEnabled), isOnDemandEnabled=\(mgr.isOnDemandEnabled)") exit(0) } } } RunLoop.main.run()Reproduction Output Running save on PocProxyA, then save on PocProxyB, and checking status on both: === Step 1: PocProxyA saves isOnDemandEnabled = true === [PocProxyA] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyA] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true [PocProxyA] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 === Step 2: PocProxyB saves isOnDemandEnabled = true === [PocProxyB] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyB] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true === Step 3: Check BOTH configurations after PocProxyB saved === [PocProxyA] -> isEnabled=true, isOnDemandEnabled=false, rulesCount=1 [PocProxyB] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 === Step 4: PocProxyA saves isOnDemandEnabled = true again === [PocProxyA] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyA] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true === Step 5: Check BOTH configurations after PocProxyA saved === [PocProxyA] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 [PocProxyB] -> isEnabled=true, isOnDemandEnabled=false, rulesCount=1Questions Is disableConfiguration:onDemandOnly:YES stripping isOnDemandEnabled across coexisting NETransparentProxyManager (TunnelType = 2) configurations a bug in NEConfigurationManager, or an intentional limitation? Is there a recommended way for two coexisting NETransparentProxyManager configurations from different vendors to both use On-Demand without disabling each other's isOnDemandEnabled flag on saveToPreferences? Filed in Feedback Assistant as FB25463155.
0
1
184
1d
NETransparentProxyManager: saving isOnDemandEnabled = true disables On-Demand on other proxies
On macOS, multiple NETransparentProxyManager (NEAppProxyProvider) configurations from different vendors can be enabled (isEnabled = true) and run simultaneously. However, we discovered that when one NETransparentProxyManager configuration calls saveToPreferencesWithCompletionHandler: with isOnDemandEnabled = true, /usr/libexec/nehelper (NEConfigurationManager) silently modifies any other enabled NETransparentProxyManager configuration in /Library/Preferences/com.apple.networkextension.plist and flips its OnDemandEnabled to false (and nesessionmanager logs Resetting VPN On Demand and marks the other session not-kernel-activated). Unlike NEVPNManager.isEnabled (which documents mutual exclusion for personal VPNs), NETransparentProxyManager is designed so multiple transparent proxies can coexist simultaneously, and neither NETransparentProxyManager.h nor NEVPNManager.h documents that isOnDemandEnabled is mutually exclusive across coexisting transparent proxy configurations. Root Cause Observed in NetworkExtension.framework When inspecting -[NEConfigurationManager saveConfigurationToDisk:updateSCPreferences:currentSignature:userUUID:notifyNow:isUpgrade:], when iterating over existing NEConfiguration objects of the same grade: If [[newConfig VPN] isEnabled] && [[otherConfig VPN] isEnabled] && [[newConfig VPN] isOnDemandEnabled] && [[otherConfig VPN] isOnDemandEnabled], NEConfigurationManager invokes: +[NEConfigurationManager disableConfiguration:otherConfig onDemandOnly:YES] disableConfiguration:onDemandOnly:YES calls [[otherConfig VPN] setOnDemandEnabled:NO], even when both configurations are NETransparentProxyManager (TunnelType == 2). Real-World Impact When two enterprise security products that both use NETransparentProxyManager are installed on the same Mac: Both configure isEnabled = true, onDemandRules = [NEOnDemandRuleConnect()], and isOnDemandEnabled = true. Whenever Proxy A saves its preferences, nehelper silently flips Proxy B's isOnDemandEnabled to false (and vice versa). After a reboot or network transition where Proxy B's initial startVPNTunnel fails while Wi-Fi is still associating and Proxy B relies on VPN On Demand (NEOnDemandRuleConnect) to start its NEAppProxyProvider once network connectivity comes up, macOS never starts Proxy B because its isOnDemandEnabled flag was cleared. Minimal Reproducer (Swift) Compile this minimal CLI inside an app bundle signed with the com.apple.developer.networking.networkextension = ["app-proxy-provider"] entitlement: import Foundation import NetworkExtension let args = CommandLine.arguments let name = args.count > 1 ? args[1] : "PocProxyA" let action = args.count > 2 ? args[2] : "status" DispatchQueue.main.async { NETransparentProxyManager.loadAllFromPreferences { managers, error in if let error = error { print("[\(name)] loadAllFromPreferences error: \(error)") exit(1) } let all = managers ?? [] let existing = all.first(where: { $0.localizedDescription == name }) if action == "status" { if let mgr = existing { print("[\(name)] -> isEnabled=\(mgr.isEnabled), isOnDemandEnabled=\(mgr.isOnDemandEnabled), rulesCount=\(mgr.onDemandRules?.count ?? 0)") } else { print("[\(name)] -> not found") } exit(0) } if action == "remove" { guard let mgr = existing else { exit(0) } mgr.removeFromPreferences { err in print("[\(name)] removed (err=\(String(describing: err)))") exit(0) } return } // action == "save" let mgr = existing ?? NETransparentProxyManager() let proto = (mgr.protocolConfiguration as? NETunnelProviderProtocol) ?? NETunnelProviderProtocol() proto.providerBundleIdentifier = "com.example.poc.extension" proto.serverAddress = "127.0.0.1" mgr.protocolConfiguration = proto mgr.localizedDescription = name mgr.isEnabled = true mgr.onDemandRules = [NEOnDemandRuleConnect()] mgr.isOnDemandEnabled = true print("[\(name)] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true...") mgr.saveToPreferences { err in if let err = err { print("[\(name)] saveToPreferences error: \(err)") exit(1) } print("[\(name)] saveToPreferences succeeded! isEnabled=\(mgr.isEnabled), isOnDemandEnabled=\(mgr.isOnDemandEnabled)") exit(0) } } } RunLoop.main.run()Reproduction Output Running save on PocProxyA, then save on PocProxyB, and checking status on both: === Step 1: PocProxyA saves isOnDemandEnabled = true === [PocProxyA] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyA] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true [PocProxyA] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 === Step 2: PocProxyB saves isOnDemandEnabled = true === [PocProxyB] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyB] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true === Step 3: Check BOTH configurations after PocProxyB saved === [PocProxyA] -> isEnabled=true, isOnDemandEnabled=false, rulesCount=1 [PocProxyB] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 === Step 4: PocProxyA saves isOnDemandEnabled = true again === [PocProxyA] Saving NETransparentProxyManager with isEnabled=true, isOnDemandEnabled=true... [PocProxyA] saveToPreferences succeeded! isEnabled=true, isOnDemandEnabled=true === Step 5: Check BOTH configurations after PocProxyA saved === [PocProxyA] -> isEnabled=true, isOnDemandEnabled=true, rulesCount=1 [PocProxyB] -> isEnabled=true, isOnDemandEnabled=false, rulesCount=1Questions Is disableConfiguration:onDemandOnly:YES stripping isOnDemandEnabled across coexisting NETransparentProxyManager (TunnelType = 2) configurations a bug in NEConfigurationManager, or an intentional limitation? Is there a recommended way for two coexisting NETransparentProxyManager configurations from different vendors to both use On-Demand without disabling each other's isOnDemandEnabled flag on saveToPreferences? Filed in Feedback Assistant as FB25463155.
Replies
0
Boosts
1
Views
184
Activity
1d