VPN Tunnel needs to be recreated on network change

I am developing a VPN app that uses Network Extension and Wireguard protocol on Ventura (13.4). I am testing the following scenario:

  1. Connect my Mac to WiFi (en0 = index 16) and to an iPad (via USB-C) which works as hotspot (en11 = index 26).
  2. Make sure in System Settings that WiFi is before iPad hotspot in Service order
  3. Connect to the VPN client (utun3 = index 28 is created)
  4. Disable WiFi

When I disable WiFi I lose access to the internet. To regain the internet access on a new interface I need to restart the tunnel completely. From a user perspective, on a Mac it seems fine, but on an iOS device it feels like suboptimal solution - the user might frequently move between WiFi/cellular connectivity and we need to reestablish VPN connection every time the default interface changes (I tested that scenario as well and it behaves exactly the same as on macOS).

So far I have implemented a workaround to avoid such reconnection, and in our tunnel implementation I use good, old bind to rebind our tunnel socket (UDP) to the primary interface (en0/en11).

I checked the NECP logs for more details, and in a scenario, when I don't use the custom bind, I see that some rules are created which seem to be skipped:

2023-06-27 08:53:25.277090+0200 0x43e4a5   Error       0x0                  0      0    kernel: necp_socket_find_policy_match_with_info_locked: DATA-TRACE <SOCKET <private>>: MATCHED POLICY - proto 17 port <local 64002/64002 remote 0/0> <drop-all order 9001> <pid=73678 Application 66744 Real Application 66744 BoundInterface 26> (policy id=29157 session_order=2002 policy_order=10802 result=IP_TUNNEL)

2023-06-27 08:53:25.277097+0200 0x43e4a5   Default     0x0                  0      0    kernel: necp_socket_find_policy_match: Socket Policy: <private> (BoundInterface 26 Proto 17) Policy 29157 Skip 10494 Result 6 Parameter 26

My application PID is 73678 and iPad hotspot network interface (en11) is 26. To me it looks like the packets which are supposed go to the en11 interface are skipped.

Is this expected/desired behavior, that while changing network interfaces, the tunnel should be completely restarted?

Is this expected/desired behavior, that while changing network interfaces, the tunnel should be completely restarted?

You will need to account for the network interface changing, yes, but you should make it easier on yourself by not using a BSD socket API and rather instead using an in-provider networking API (such as NWTCPConnection) which should provide access to your virtual interface when your tunnel has reconnected.

Thanks for your answer.

I'm trying to understand how NWTCPConnection can possibly help in my case? Even if I use Apple API only for establishing the connection I don't see a way to nicely switch the connection to the new (possibly better) interface.

Let's take as an example the NWConnection API. I see there is an API to handle a better path:

var betterPathUpdateHandler: (Bool) -> Void)?

A handler that receives updates when an alternative network path is preferred over the current path.

Better path events are an indication that a more preferable network path is available. If you can migrate your work to a new connection, try establishing a new connection. Once that new connection is ready, cancel the original connection.

But I don't know how to really use it? It says that I should establish new connection. Does that mean that I should create new NWConnection instance and cancel the old one? If so, it is much less efficient than using simply bind to bind the socket to a new interface.

As to the suggestion of using NWTCPConnection I don't see anything in the API documentation which might help in my case? Maybe do you have some example code which uses its API?

As to the suggestion of using NWTCPConnection I don't see anything in the API documentation which might help in my case?

The in-provider networking API should always be bound to the tunnels interface. However, if you need to re-establish how the tunnel is connected you could try to monitor a path for the tunnel by using NWPathMonitor. This should at least give updates for when a better path has become available.

VPN Tunnel needs to be recreated on network change
 
 
Q