Skip to content

feat: allow selecting the VPN protocol from the CLI - #27

Open
aislandener wants to merge 1 commit into
ProtonVPN:stablefrom
aislandener:feat/protocol-selection
Open

feat: allow selecting the VPN protocol from the CLI#27
aislandener wants to merge 1 commit into
ProtonVPN:stablefrom
aislandener:feat/protocol-selection

Conversation

@aislandener

Copy link
Copy Markdown

Closes #26.

What this does

Exposes the VPN protocol as a configurable setting, so it can be changed
without hand-editing settings.json:

protonvpn config set protocol wireguard
protonvpn config set protocol openvpn-udp
protonvpn config set protocol openvpn-tcp

It also shows up in protonvpn config list:

Current configuration
Setting                  Value
-----------------------  --------------------
protocol                 openvpn-tcp
netshield                malware-ads-trackers
kill-switch              off
...

Why

On a public WiFi that drops VPN UDP traffic, protonvpn connect always failed with
the generic Connection failed. Try connecting to a different server or check your network settings. The tunnel came up and got an address, but the WireGuard handshake
never got a reply:

proton0 10.2.0.2/32  TX: 148 / RX: 0
proton0 10.2.0.2/32  TX: 296 / RX: 0

148 bytes is exactly one WireGuard handshake initiation; two were sent and nothing came
back. TCP/443 to the same server IP worked fine, and arbitrary outbound UDP worked
(STUN on 3478/19302 responded) — the network selectively drops UDP on the ports Proton
uses. Since the local agent lives inside the tunnel, it can never answer, and the CLI
times out.

Because the protocol was not selectable, there was no way to recover from the CLI.
Switching to openvpn-tcp connects on that same network.

Implementation notes

Everything needed was already in place, so this is mostly wiring:

  • create_registry() in api-core already registers the openvpn and wireguard backends
  • VPNConnector.connect() already takes a protocol argument
  • Settings.protocol is already persisted to settings.json
  • _connect() in the controller already passes settings.protocol through

So this adds a ProtocolType click arg type and a PROTOCOL_FEATURE, and registers the
config set protocol command. get_feature_setting / save_feature_setting work
unchanged via setting_path="protocol".

Changing the protocol requires an inactive connection, following the same pattern the
kill switch command already uses — the setting only takes effect when the tunnel is
established, and save_settings() otherwise waits for a CONNECTED event that a
protocol change never produces.

OPENVPN_UDP / OPENVPN_TCP moved from server.py into feature_setting_definitions.py
so the new choices and the existing OpenVPN warning share one definition rather than
duplicating the strings.

On the protun-* protocols

protun-udp, protun-tcp, protun-tls and protun-smart are deliberately not
offered, because they cannot currently be established at all:

CONN.CONNECT:START | ... / Protocol: protun-tcp / Backend: None
gi.repository.GLib.GError: nm-manager-error-quark:
    The 'protun' plugin doesn't support private connections. (4)

The nm-protun.name shipped by python-proton-vpn-api-core does not declare
supports-safe-private-file-access=true, which NetworkManager requires for the
in-memory connections this client creates (the bundled nm-openvpn-service.name does
declare it). Adding that key locally makes NetworkManager accept the connection and
nm-protun-service start, but it then stops at:

INFO proton_vpn_platform::protun::nm_protun_service::interfaces::network_manager]
    Secrets are needed for this connection

because protun.py uses STORE_PRIVATE_KEY_IN_KEYRING = "1" (AGENT_OWNED), so
NetworkManager asks a registered secret agent for the private key — and on a desktop
without nm-applet/plasma-nm nothing answers.

That is a separate bug and likely belongs in python-proton-vpn-api-core. It is worth
flagging here because protun-tcp / protun-tls are exactly the protocols that would
best solve the blocked-UDP case. They can be added to ProtocolType in a one-line change
once they work.

Testing

  • pytest tests/unit — 271 passed (6 new)
  • flake8 proton/ — clean
  • pylint — 9.95/10; the two remaining R0917 in server.py are pre-existing and
    present on stable unchanged
  • Manually verified against a live account: --help output, config list, the
    disconnect-required guard, case-insensitive values (OpenVPN-UDP), and that each
    value is persisted to settings.json

New tests cover the disconnect-required guard (parametrised over connected/disconnected),
persistence of the chosen value, and rejection of unknown protocols. The existing
ALL_FEATURES-driven tests cover the new setting automatically.

The CLI always connected with the protocol stored in settings.json and
offered no way to change it, so it was effectively locked to the default
(WireGuard over UDP).

On networks that drop VPN UDP traffic - public hotspots, hotels, some
corporate WiFi - the tunnel interface comes up and gets an address, but
the WireGuard handshake never gets a reply. The local agent lives inside
the tunnel, so it can never answer either, and the connection fails with
a generic "Connection failed" message. There was no way to recover from
the CLI; the only workaround was hand-editing settings.json.

Everything needed was already in place: the api-core registry provides
the openvpn and wireguard backends, VPNConnector.connect() takes a
protocol argument, and Settings.protocol is already persisted. This only
exposes it.

  protonvpn config set protocol wireguard
  protonvpn config set protocol openvpn-udp
  protonvpn config set protocol openvpn-tcp

The protocol also shows up in 'protonvpn config list'.

Changing the protocol requires an inactive connection, following the
same pattern already used by the kill switch command, since the setting
only takes effect when the tunnel is established.

The protun-* protocols are deliberately left out: they cannot currently
be established at all, because the bundled nm-protun.name does not
declare supports-safe-private-file-access, which NetworkManager requires
for the in-memory connections this client creates. That is tracked
separately.

OPENVPN_UDP/OPENVPN_TCP move to feature_setting_definitions so the new
choices and the existing OpenVPN warning share one definition.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014uK1QX6VfA9kJt43DFubBM
@trustmirror

Copy link
Copy Markdown

hi! will it also be possible to use the Stealth protocol via the CLI with this commit? other protocols are being blocked by so many sites/apps now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow selecting the VPN protocol from the CLI (blocked-UDP networks make WireGuard unusable)

2 participants