Two very different jobs get filed under programmatic VPN, and only one of them is available to an ordinary app on either mobile platform. The first is switching on a profile the user already saved; the second is building a tunnel inside your own process. Android hands you the second through VpnService and iOS hands you the second through the Network Extension entitlement, while the first stays with the system, a device owner or an MDM server. Working out which one you actually need usually makes the project smaller.

Two jobs that share a name

Activating a saved profile means telling an existing tunnel to come up: a host, a credential, a protocol, all already configured somewhere the user can see. Building a tunnel means your code owns the interface, the packet loop and the protocol. They look similar in a product backlog and they share almost no implementation.

Neither mobile platform exposes the first to an SDK app. A connection that appears without the user doing anything is indistinguishable from malware, so both systems put a consent gate in front of any app that carries traffic.

Android: a consent gate, and no profile API

VpnService has existed since Android 4.0 and lets an app create a tun interface, then read and write packets. The service must be declared with BIND_VPN_SERVICE, and VpnService.prepare() produces the system dialog that asks the user to allow it. The platform sample, ToyVpn, is the clearest statement of the intended shape: your code owns the interface and everything that flows through it.

What it does not do is add an entry to the system VPN settings screen. There is no public API for that, and the Settings app keeps its profiles in a store an ordinary app cannot reach. Calls into the hidden classes from a normal application produce profiles the system never lists, which is why code that appeared to work on one build silently stops working on the next.

The managed path is separate. A device owner or profile owner can designate an always-on VPN package through the device policy APIs, and an enterprise tool can push a profile that the user then approves. That is device-management authority, not a permission an app requests at install time. If your customer is an organisation, this is the route to plan for; if your customer is a person installing from a store, it is not available at all.

iOS: the entitlement is the boundary

iOS splits the same work differently. A VPN client implements a packet tunnel provider or an app proxy under the Network Extension framework, and the matching entitlement has to be granted to the developer account. A personal VPN configuration through NEVPNManager exists for the simpler case, and it still runs inside the same permission model. Apple documents the whole surface in the Network Extension reference, which is worth reading before you scope the work.

There is no supported way to open the VPN settings screen, read the user's saved configurations, or start one of them. What a client can do is ship a configuration profile for the user to install, and then use the resulting tunnel through ordinary networking calls. Per-app tunnelling is assigned by an MDM server through an on-demand rule, not selected by the app at runtime.

A VPN client from a large vendor is not evidence that your account can ship the same thing. Those entitlements are requested per developer account and reviewed, so the schedule risk sits with Apple, not with your sprint plan.

Desktop reaches further, but ownership is unchanged

Windows is the permissive case. Add-VpnConnection creates a phonebook entry from an elevated PowerShell session, and rasdial connects or disconnects it from a script. An app can also drive a vendor client through its documented command line. Creating the profile is still an administrative action, because the entry lives in a machine-wide store.

macOS behaves the same way with different tools. scutil --nc start and stop operate on services that already exist, while creating one is a system administration task. Owning the process on a laptop does not make you the owner of the configuration.

What to build instead

If the real requirement is that your app reaches an internal API, the tunnel the user already runs is enough. Make normal HTTPS requests, let the system pick the path, and stop trying to control the tunnel. If the requirement is that only your app's traffic uses the tunnel, ask for per-app VPN through the management channel, or terminate TLS inside the app against a service reachable without one.

Hidden APIs are not a shortcut worth taking. They change between releases, they fail app review, and the failures they produce look like ordinary network faults to whoever inherits the code. The comparison is not Android versus iOS; it is owning a tunnel versus asking the platform to route traffic, and only the first one is yours to build.

Tënë sô zo