I've implemented a custom VPN for macOS (system extension, Packet Tunnel Provider, Developer ID). My tunneling logic uses BSD sockets. My VPN is configured with on-demand and should always connect when there's traffic:
targetManager?.isOnDemandEnabled = true
targetManager?.onDemandRules = [NEOnDemandRuleConnect()]
I have encountered some issues when the device enters sleep (or waking up from sleep). I've tried two scenarios.
Scenario 1:
protocolConfiguration?.disconnectOnSleep = true
With this flag set, the OS will disconnect the VPN just before entering to sleep. However, there were cases when the OS disconnected the VPN but immediately restarted it - probably because of how I defined the on-demand rules. This resulted in the VPN disconnection, then trying to reconnect, and then the Mac entered sleep. When the Mac woke up, the VPN didn't work well.
- Is there a way to avoid waking up, just before the Mac enters sleep?
Scenario 2:
protocolConfiguration?.disconnectOnSleep = false
Disconnect on sleep is unset, and I've implemented the sleep/wake functions at the provider.
With this configuration, the OS won't disconnect the VPN, so even in sleep, the extension should stay 'alive,' so it won't have the problem from (1).
But in this case, I had other problems:
-
On sleep, I'm disconnecting the tunnel. But sometimes, on wake(), all my network calls fail. Are the interfaces still down? How can I detect this case from the system extension?
-
Is it possible that the OS would call sleep and then quickly call wake?
-
Is it possible that after sleep, the OS would call the startTunnelWithOptions() function?
-
Is it possible to restart the extension from a clean state right from the wake() function?
I don’t have any good answers for you for scenario 1.
Regarding scenario 2:
On sleep, I'm disconnecting the tunnel. But sometimes, on wake(), all my network calls fail. Are the interfaces still down?
Probably. Your wake code is racing with all the other wake code on the system, including the code that’s bringing up the network interfaces.
My generally recommendation here is that you use a modern networking API, like Network framework, that supports waiting for connectivity. In that case I’d expect to see your NWConnection
enter the .waiting(…)
state and then, once the necessary infrastructure is up, connect and then enter the .ready
state.
So:
-
Are you using
NWConnection
? -
If so, it is working as I described above? If not, how is it failing?
-
If if you’re not using
NWConnection
, can you try it? I’m not suggesting that you rewrite all the networking code (well, not yet :-) but instead just try starting a parallelNWConnection
and see how it behaves.
Is it possible that the OS would call sleep() and then quickly call wake()?
Yes. In sleep/wake scenarios pretty much anything is possible (-:
Is it possible that after sleep(), the OS would call the startTunnelWithOptions() function?
I wouldn’t expect to see that. NE should only start your tunnel if it’s stopped, and it doesn’t know that your tunnel is stopped until you tell it that.
Is it possible to restart the extension from a clean state right from the wake() function?
Not really.
Well, the closest thing to this is to set disconnectOnSleep
, but that brings you back to scenario 1.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"