A small program that connects an existing VPN needs a controller, not a new VPN implementation. The supported engine should handle negotiation and tunnel traffic; your program should express the user's request and explain the result honestly. On Linux, the NetworkManager D-Bus interface gives that controller a concrete model to follow.

Four checks keep the controller from confusing a saved profile, an activation request, and a working session.

Check one: which identity is being selected

Keep the profile UUID as the persistent identity, and resolve it through the manager when you need the object path. A display name can be shared by two profiles, and a runtime bus path is not a sensible user preference to store. Write the selection down in your own configuration, not in a comment about which entry was second in the list.

Verify what the manager returns before you call it a match. A UUID that no longer exists produces a lookup failure, which is a different code path from a profile that exists and refuses to activate.

Check two: what the activation call returns

The documented activation call takes an existing connection object path, a device path, and a specific object. It returns an active-connection path, and that response is a handle rather than a readiness result. Do not set your application's ready flag from the return value; subscribe to the active connection's state and reason instead.

For a VPN, the specific object selects the active base connection. Passing the root path hands the choice of device to NetworkManager, which picks whichever one is currently default. That is a routing-context decision rather than a password field, and it changes which device the tunnel is layered on.

Do not lift a generic add-and-activate example and expect it to create a VPN profile. The documented helper excludes VPN connections, so provisioning or importing the profile is a separate, reviewed step. That matters because provisioning needs authorization and review that a connect button does not. Treat import as an administrative action with its own log line and its own approver.

Check three: how state is represented

An active connection moves through activating, activated, deactivating and deactivated, and the reason field says why. Collapsing all of that into a Boolean called loggedIn loses the information the user needs and the information support will ask for. Surface at least whether the connection is pending, established, or gone, and keep the reason string for the failure case.

Associate asynchronous replies with the request that produced them. A late completion from an older attempt must not overwrite a newer user action, and a duplicate Connect press should be either ignored or treated as a cancel-and-retry with a visible policy.

Check four: what happens after a restart

When the manager restarts or your controller reconnects to the bus, rebuild the observed state from the owner. Do not keep showing a cached session as active because an old object path is still in memory. The state-change signals exist precisely so a controller can resynchronise instead of guessing.

Choose what closing the controller means. Leaving an existing machine-owned connection up can be correct, and tearing down a deliberately short-lived session can also be correct. Leaving an existing machine-owned connection up can be correct; tearing down a deliberately short-lived session can also be correct. Write that ownership contract into the interface rather than assuming process exit performs the network action. Users should not have to guess whether closing the window will drop their tunnel. Put the answer in the interface text, not only in a code comment.

Keep authority and secrets with their owners

Permissions come back as yes, auth or no, and the interface should reflect the difference between permitted, permitted after authentication, and denied. Running the controller as root does not implement a permission policy; it removes the question.

Credentials belong to the supported secret agent or client integration for the configured authentication method. A password in source, in an argument, or in a log is a finding, and local administrative authorization is not the same event as gateway authentication.

Finally, report connection state separately from the task the user wants to perform. A tunnel can be active while a private name or an application remains unavailable, and that second failure deserves its own message and its own status. A controller that says exactly which layer failed is more useful than one that says the VPN is down.

פֿאַרבונדענע אַרטיקלען