Last I checked there’s no good way to get detailed ‘why did I disconnect’ information from a VPN connection. For a provider-based VPN you could write the error information to disk in an App Group that you share between your provider and your app; that’s super ugly, but it’ll work.
Please do file an enhancement request requesting better support for this, then post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
Thanks for the answer. I filed 29375097.
This thread is a couple of years old now, but I have the same question: I want my container app to receive the error passed either to cancelTunnelWithError() or the completionHandler of startTunnel() in my NEPacketTunnelProvider subclass.
I actually did find a way to do it with IPC that works for some cases: my container app calls sendProviderMessage() from NETunnelProviderSession with a responseHandler. This message is received by my packet tunnel provider extension via handleAppMessage and it saves a reference to the completion handler. When I have an error to report, I have a wrapper function that calls the completion handler received in handleAppMessage before calling startTunnel's completion handler (if the tunnel hasn't been set up yet) or cancelTunnelWithError (if the tunnel has already been established). My container app then again has to use sendProviderMessage again to set up the responseHandler for errors, I assume because the extension process exits after closing/cancelling the tunnel.
However I also noticed something in my container app. If I set a breakpoint where I have an observer for NSNotification.Name.NEVPNStatusDidChange, on NETunnelProviderManager.connection there is a field called _lastDisconnectError that appears to be set to the error that was passed by my extension to cancelTunnelWithError() or startTunnel()'s completion handler. Since NETunnelProviderSession/NEVPNConnection is an NSObject subclass, I can access this field in code with something like manager.connection.value(forKey: "_lastDisconnectError"). Is that a bad idea to do? Is there a better way yet to access this error? I would really like to avoid having to serialize and transmit errors myself via sendProviderMessage/handleAppMessage, especially since I'm not confident I'll always be able to catch these errors before my packet tunnel extension process exits.