According to this post on SO, there is a bug that will be fixed in future OS updates...
A fix for now, is not to use -testPlan 'APPName-Unit-All' when running test-without-building.
Post
Replies
Boosts
Views
Activity
Any updates on this?
So I did some more testing. Used two types of timers:
Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(doSomeStuff), userInfo: nil, repeats: true)
and
queue = DispatchQueue(label: "Timer", qos: .background)
timerSource = DispatchSource.makeTimerSource(queue: queue)
timerSource.schedule(deadline: .now() + .seconds(Int(nextRunTime.timeIntervalSinceNow)), repeating: 2 * 60, leeway: .seconds(10))
timerSource.setEventHandler { [weak self] in
self?.doSomeOtherStuff()
}
timerSource.resume()
This is all happening inside NetworkExtension with VPN connected at the time of testing, so the process runs all the time, except when it doesn't :)
Following are timestamps from syslog. I selected two of them (first one and the last) that are from the first Timer. Others are just logs from VPN library. As you can see in between there were some things going on, NE was woken up (unfreezed? don't know how to call that state) several times and traffic was flowing, extension was running then got to void again, then running, etc. Still the timer was not called.
The second timer was set to be fired each two minutes, so it was called even less frequently than the first one.
All in all it looks like timer does not respect the wall time and just uses some internal counter that ticks according to its runloop. And if between two runs OS decides to freeze the process, this time just disappears from the counter.
Questions:
Is there any timer or a mechanism like that that can call my code every now and then and in case the process was frozen in between would count time according to real time?
Where can I find any documentation how this freezing/unfreezing works on iOS/ipadOS?
Is there a way to detect that NE was frozen for some time and just got some time to run again?
I was using .info, now changed to .debug. But I have found another "interesting" thing - some messages are visible when looking at them in real-time in console app, but then disappear when I get logs using sudo log collect --device-name .... The only difference is that they are thrown using additional abstraction in code. Example:
func log1(message) {
os_log(...)
}
func log2(message) {
log1(message)
}
log1("message") // Is visible on-line and after retrieving log from device
log2("message") // Is visible only while looking at it on-line, through console app connected to the phone, but NOT when using log collect
PS. I know it's better to use os_log directly, but there are 3rd party libraries and other stuff involved, so we have what we have.
Sorry, I mean the App with GUI as in container app.
What I wanted to say is that as far as I understand from documentation and example code, in the app I just run NSXPCConnection(machServiceName: machServiceName, options: []).remoteObjectProxyWithErrorHandler[...].myWorkingFunction() on an extension that was already installed and approved by user manually in System Preferences -> Security & Privacy, then it should just work. And indeed it works in 99% of the time. Until user restarts his computer and app gets Couldn’t communicate with a helper application error.
Since this is happening on reboot, have you added logging to your provider to make sure that it is active and setup to receive an XPC connection at the point you are expecting to communicate with the provider?
From what I understood from documentation, there is nothing to do, because launchd should start it as soon as I start communicating with it using NSXPCConnection(machServiceName: machServiceName, options: []).remoteObjectProxyWithErrorHandler?
I can add logs for sure, but I would like to know it inside the code, to make the app more robust instead of throwing wrong UI on users.
I have to call API from time to time.
What exactly do you mean by "Queue-base API"? DispatchQueue.main.asyncAfter()?
I'd like to make so API calls in the background and then get back to original thread, for example.
As I see there are more people interested in this topic in other threads, here is what works from inside the System Extension to save/load password to/from the Keychain:
Save password:
var query = [String: Any]()
query[kSecAttrAccessGroup as String] = accessGroup
query[kSecAttrService as String] = context
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
query[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
query[kSecValueData as String] = password.data(using: .utf8)
let status = SecItemAdd(query as CFDictionary, nil)
Load password:
var query = [String: Any]()
query[kSecAttrAccessGroup as String] = accessGroup
query[kSecAttrService as String] = context
query[kSecClass as String] = kSecClassGenericPassword
query[kSecAttrAccount as String] = username
query[kSecMatchLimit as String] = kSecMatchLimitOne
query[kSecReturnData as String] = true
var result: AnyObject?
switch SecItemCopyMatching(query as CFDictionary, &result) {
Password appears in the System keychain
@meaton - so it is not considered a bug? In case where NEPacketTunnelProvider fails connection fast, it's easy to get into situation with hundreds of notifications flooding the app.
Any ideas how to debug this?
Or maybe there is a way to know that connection fill fail with this error beforehand, so we can at least inform user that he has to delete other vpn profiles before connecting?
scutil --nc list doesn't even show VPNs that are visible in the settings.
If you do not include IncludeAllNetworks are you able to connect your tunnel?
Yes, setting it to false works perfectly - you can connect to vpn without problems
Ohh, I'm so sorry! I missed one zero :(
It is #define PRIV_NET_PRIVILEGED_NECP_MATCH 10006 /* Privilege verified by Network Extension policies */. And the problem is in Network Extension indeed.
Maybe it is not because of writing to file, that was just my hypothesis. Any ideas on what can be the cause?
And thanks a lot for log commands, that's actually enough for me, and works much faster!
Can you please explain what do you mean by "the bridge"?
I've found your message - https://developer.apple.com/forums/thread/672155 from a week ago, which suggests that system extension can't read a value from the keychain if it was written there by an app. So essentially there is no way to just pass persistent reference of a keychain entry from the app to sysex?
What do you think about such sequence: Send password from the app to system extension via sendProviderMessage -> handleAppMessage
Save it to the keychain from the system extension itself and keep using it in the system extension
Profit :)
One of the reasons why it should be persisted, is because system extension may be used not only from the app itself but also by the system. For example when connection was dropped or system went to sleep and is back, it asks extension to reconnect to VPN automatically, without the app intervention. At this point extension has to get the password from somewhere by itself.