diff options
| author | Markus Pettersson <markus.pettersson@mullvad.net> | 2026-04-01 15:32:27 +0200 |
|---|---|---|
| committer | Markus Pettersson <markus.pettersson@mullvad.net> | 2026-04-01 15:32:27 +0200 |
| commit | 46ee7f776f3afcda36374457e774fcf5b23b7700 (patch) | |
| tree | cc7c21a38a3a957da371cdbd9a6984f0bd6bf9ff | |
| parent | a514646285bb9ead4e0e9c3d5bf13ed169a06e11 (diff) | |
| parent | 85ed0c63b6da085adb21de7a5fe79b32f27b1a52 (diff) | |
| download | mullvadvpn-android/test-hardware-key-2.tar.xz mullvadvpn-android/test-hardware-key-2.zip | |
Merge branch 'add-way-to-force-userspace-wireguard-in-e2e-tests-des-2679'android/test-hardware-key-2android/test-hardware-key-1
13 files changed, 133 insertions, 5 deletions
diff --git a/desktop/packages/management-interface/dist/management_interface_grpc_pb.d.ts b/desktop/packages/management-interface/dist/management_interface_grpc_pb.d.ts index 0ea103aab0..51d3363bb6 100644 --- a/desktop/packages/management-interface/dist/management_interface_grpc_pb.d.ts +++ b/desktop/packages/management-interface/dist/management_interface_grpc_pb.d.ts @@ -44,6 +44,7 @@ interface IManagementServiceService extends grpc.ServiceDefinition<grpc.UntypedS setRelayOverride: IManagementServiceService_ISetRelayOverride; clearAllRelayOverrides: IManagementServiceService_IClearAllRelayOverrides; setEnableRecents: IManagementServiceService_ISetEnableRecents; + setUserspaceWireguard: IManagementServiceService_ISetUserspaceWireguard; createNewAccount: IManagementServiceService_ICreateNewAccount; loginAccount: IManagementServiceService_ILoginAccount; logoutAccount: IManagementServiceService_ILogoutAccount; @@ -392,6 +393,15 @@ interface IManagementServiceService_ISetEnableRecents extends grpc.MethodDefinit responseSerialize: grpc.serialize<google_protobuf_empty_pb.Empty>; responseDeserialize: grpc.deserialize<google_protobuf_empty_pb.Empty>; } +interface IManagementServiceService_ISetUserspaceWireguard extends grpc.MethodDefinition<google_protobuf_wrappers_pb.BoolValue, google_protobuf_empty_pb.Empty> { + path: "/mullvad_daemon.management_interface.ManagementService/SetUserspaceWireguard"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize<google_protobuf_wrappers_pb.BoolValue>; + requestDeserialize: grpc.deserialize<google_protobuf_wrappers_pb.BoolValue>; + responseSerialize: grpc.serialize<google_protobuf_empty_pb.Empty>; + responseDeserialize: grpc.deserialize<google_protobuf_empty_pb.Empty>; +} interface IManagementServiceService_ICreateNewAccount extends grpc.MethodDefinition<google_protobuf_empty_pb.Empty, google_protobuf_wrappers_pb.StringValue> { path: "/mullvad_daemon.management_interface.ManagementService/CreateNewAccount"; requestStream: false; @@ -950,6 +960,7 @@ export interface IManagementServiceServer extends grpc.UntypedServiceImplementat setRelayOverride: grpc.handleUnaryCall<management_interface_pb.RelayOverride, google_protobuf_empty_pb.Empty>; clearAllRelayOverrides: grpc.handleUnaryCall<google_protobuf_empty_pb.Empty, google_protobuf_empty_pb.Empty>; setEnableRecents: grpc.handleUnaryCall<google_protobuf_wrappers_pb.BoolValue, google_protobuf_empty_pb.Empty>; + setUserspaceWireguard: grpc.handleUnaryCall<google_protobuf_wrappers_pb.BoolValue, google_protobuf_empty_pb.Empty>; createNewAccount: grpc.handleUnaryCall<google_protobuf_empty_pb.Empty, google_protobuf_wrappers_pb.StringValue>; loginAccount: grpc.handleUnaryCall<google_protobuf_wrappers_pb.StringValue, google_protobuf_empty_pb.Empty>; logoutAccount: grpc.handleUnaryCall<google_protobuf_wrappers_pb.StringValue, google_protobuf_empty_pb.Empty>; @@ -1106,6 +1117,9 @@ export interface IManagementServiceClient { setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; createNewAccount(request: google_protobuf_empty_pb.Empty, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; createNewAccount(request: google_protobuf_empty_pb.Empty, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; createNewAccount(request: google_protobuf_empty_pb.Empty, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; @@ -1376,6 +1390,9 @@ export class ManagementServiceClient extends grpc.Client implements IManagementS public setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; public setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; public setEnableRecents(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + public setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + public setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; + public setUserspaceWireguard(request: google_protobuf_wrappers_pb.BoolValue, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_empty_pb.Empty) => void): grpc.ClientUnaryCall; public createNewAccount(request: google_protobuf_empty_pb.Empty, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; public createNewAccount(request: google_protobuf_empty_pb.Empty, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; public createNewAccount(request: google_protobuf_empty_pb.Empty, metadata: grpc.Metadata, options: Partial<grpc.CallOptions>, callback: (error: grpc.ServiceError | null, response: google_protobuf_wrappers_pb.StringValue) => void): grpc.ClientUnaryCall; diff --git a/desktop/packages/management-interface/dist/management_interface_grpc_pb.js b/desktop/packages/management-interface/dist/management_interface_grpc_pb.js index 3510c953af..f07ed529ce 100644 --- a/desktop/packages/management-interface/dist/management_interface_grpc_pb.js +++ b/desktop/packages/management-interface/dist/management_interface_grpc_pb.js @@ -822,6 +822,17 @@ getSettings: { responseSerialize: serialize_google_protobuf_Empty, responseDeserialize: deserialize_google_protobuf_Empty, }, + setUserspaceWireguard: { + path: '/mullvad_daemon.management_interface.ManagementService/SetUserspaceWireguard', + requestStream: false, + responseStream: false, + requestType: google_protobuf_wrappers_pb.BoolValue, + responseType: google_protobuf_empty_pb.Empty, + requestSerialize: serialize_google_protobuf_BoolValue, + requestDeserialize: deserialize_google_protobuf_BoolValue, + responseSerialize: serialize_google_protobuf_Empty, + responseDeserialize: deserialize_google_protobuf_Empty, + }, // Account management createNewAccount: { path: '/mullvad_daemon.management_interface.ManagementService/CreateNewAccount', diff --git a/desktop/packages/management-interface/dist/management_interface_pb.d.ts b/desktop/packages/management-interface/dist/management_interface_pb.d.ts index 9ac57603c3..7bf62a78f9 100644 --- a/desktop/packages/management-interface/dist/management_interface_pb.d.ts +++ b/desktop/packages/management-interface/dist/management_interface_pb.d.ts @@ -2238,6 +2238,8 @@ export class TunnelOptions extends jspb.Message { clearDnsOptions(): void; getDnsOptions(): DnsOptions | undefined; setDnsOptions(value?: DnsOptions): TunnelOptions; + getUserspace(): boolean; + setUserspace(value: boolean): TunnelOptions; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): TunnelOptions.AsObject; @@ -2257,6 +2259,7 @@ export namespace TunnelOptions { daita?: DaitaSettings.AsObject, enableIpv6: boolean, dnsOptions?: DnsOptions.AsObject, + userspace: boolean, } } diff --git a/desktop/packages/management-interface/dist/management_interface_pb.js b/desktop/packages/management-interface/dist/management_interface_pb.js index 9c96e0e09c..a8375479f3 100644 --- a/desktop/packages/management-interface/dist/management_interface_pb.js +++ b/desktop/packages/management-interface/dist/management_interface_pb.js @@ -17429,7 +17429,8 @@ proto.mullvad_daemon.management_interface.TunnelOptions.toObject = function(incl quantumResistant: (f = msg.getQuantumResistant()) && proto.mullvad_daemon.management_interface.QuantumResistantState.toObject(includeInstance, f), daita: (f = msg.getDaita()) && proto.mullvad_daemon.management_interface.DaitaSettings.toObject(includeInstance, f), enableIpv6: jspb.Message.getBooleanFieldWithDefault(msg, 5, false), - dnsOptions: (f = msg.getDnsOptions()) && proto.mullvad_daemon.management_interface.DnsOptions.toObject(includeInstance, f) + dnsOptions: (f = msg.getDnsOptions()) && proto.mullvad_daemon.management_interface.DnsOptions.toObject(includeInstance, f), + userspace: jspb.Message.getBooleanFieldWithDefault(msg, 7, false) }; if (includeInstance) { @@ -17494,6 +17495,10 @@ proto.mullvad_daemon.management_interface.TunnelOptions.deserializeBinaryFromRea reader.readMessage(value,proto.mullvad_daemon.management_interface.DnsOptions.deserializeBinaryFromReader); msg.setDnsOptions(value); break; + case 7: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setUserspace(value); + break; default: reader.skipField(); break; @@ -17569,6 +17574,13 @@ proto.mullvad_daemon.management_interface.TunnelOptions.serializeBinaryToWriter proto.mullvad_daemon.management_interface.DnsOptions.serializeBinaryToWriter ); } + f = message.getUserspace(); + if (f) { + writer.writeBool( + 7, + f + ); + } }; @@ -17774,6 +17786,24 @@ proto.mullvad_daemon.management_interface.TunnelOptions.prototype.hasDnsOptions }; +/** + * optional bool userspace = 7; + * @return {boolean} + */ +proto.mullvad_daemon.management_interface.TunnelOptions.prototype.getUserspace = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 7, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.mullvad_daemon.management_interface.TunnelOptions} returns this + */ +proto.mullvad_daemon.management_interface.TunnelOptions.prototype.setUserspace = function(value) { + return jspb.Message.setProto3BooleanField(this, 7, value); +}; + + diff --git a/mullvad-cli/src/cmds/tunnel.rs b/mullvad-cli/src/cmds/tunnel.rs index 570133e46f..97dc9fb248 100644 --- a/mullvad-cli/src/cmds/tunnel.rs +++ b/mullvad-cli/src/cmds/tunnel.rs @@ -54,6 +54,9 @@ pub enum TunnelOptions { /// Enable or disable IPv6 in the tunnel #[clap(arg_required_else_help = true)] Ipv6 { state: BooleanOption }, + + /// Use userspace WireGuard. + Userspace { state: BooleanOption }, } impl Tunnel { @@ -175,6 +178,10 @@ impl Tunnel { rpc.set_enable_ipv6(*state).await?; println!("IPv6: {state}"); } + TunnelOptions::Userspace { state } => { + rpc.set_userspace_wireguard(*state).await?; + println!("Userspace WireGuard: {state}"); + } } Ok(()) diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs index a8f0232c67..ff4a6906d4 100644 --- a/mullvad-daemon/src/lib.rs +++ b/mullvad-daemon/src/lib.rs @@ -295,6 +295,8 @@ pub enum DaemonCommand { SetAutoConnect(ResponseTx<(), settings::Error>, bool), /// Set if IPv6 should be enabled in the tunnel SetEnableIpv6(ResponseTx<(), settings::Error>, bool), + /// Set if userspace WireGuard should be forced. + SetUserspaceWireguard(ResponseTx<(), settings::Error>, bool), /// Set if recents should be enabled SetEnableRecents(ResponseTx<(), settings::Error>, bool), /// Set whether to enable PQ PSK exchange in the tunnel @@ -1515,6 +1517,9 @@ impl Daemon { } SetAutoConnect(tx, auto_connect) => self.on_set_auto_connect(tx, auto_connect).await, SetEnableIpv6(tx, enable_ipv6) => self.on_set_enable_ipv6(tx, enable_ipv6).await, + SetUserspaceWireguard(tx, userspace) => { + self.on_set_userspace_wireguard(tx, userspace).await + } SetEnableRecents(tx, enable_recents) => { self.on_set_enable_recents(tx, enable_recents).await } @@ -2669,6 +2674,32 @@ impl Daemon { } } + async fn on_set_userspace_wireguard( + &mut self, + tx: ResponseTx<(), settings::Error>, + userspace: bool, + ) { + match self + .settings + .update(|settings| settings.tunnel_options.wireguard.userspace = userspace) + .await + { + Ok(settings_changed) => { + Self::oneshot_send(tx, Ok(()), "set_userspace_wireguard response"); + if settings_changed { + log::info!( + "Initiating tunnel restart because the userspace WireGuard setting changed" + ); + self.reconnect_tunnel(); + } + } + Err(e) => { + log::error!("{}", e.display_chain_with_msg("Unable to save settings")); + Self::oneshot_send(tx, Err(e), "set_userspace_wireguard response"); + } + } + } + async fn on_set_enable_recents( &mut self, tx: ResponseTx<(), settings::Error>, diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs index 8d41d10780..fcb24ded90 100644 --- a/mullvad-daemon/src/management_interface.rs +++ b/mullvad-daemon/src/management_interface.rs @@ -321,6 +321,15 @@ impl ManagementService for ManagementServiceImpl { Ok(Response::new(())) } + async fn set_userspace_wireguard(&self, request: Request<bool>) -> ServiceResult<()> { + let userspace = request.into_inner(); + log::debug!("set_userspace_wireguard({})", userspace); + let (tx, rx) = oneshot::channel(); + self.send_command_to_daemon(DaemonCommand::SetUserspaceWireguard(tx, userspace))?; + self.wait_for_result(rx).await??; + Ok(Response::new(())) + } + async fn set_quantum_resistant_tunnel( &self, request: Request<types::QuantumResistantState>, diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto index 72230bfbff..79bd7474ab 100644 --- a/mullvad-management-interface/proto/management_interface.proto +++ b/mullvad-management-interface/proto/management_interface.proto @@ -54,6 +54,7 @@ service ManagementService { rpc SetRelayOverride(RelayOverride) returns (google.protobuf.Empty) {} rpc ClearAllRelayOverrides(google.protobuf.Empty) returns (google.protobuf.Empty) {} rpc SetEnableRecents(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} + rpc SetUserspaceWireguard(google.protobuf.BoolValue) returns (google.protobuf.Empty) {} // Account management rpc CreateNewAccount(google.protobuf.Empty) returns (google.protobuf.StringValue) {} @@ -628,6 +629,9 @@ message TunnelOptions { bool enable_ipv6 = 5; DnsOptions dns_options = 6; + // Force userspace WireGuard. + // This option does not apply to Android or macOS. + bool userspace = 7; } message DefaultDnsOptions { diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs index 9420dccca6..9843c4ade2 100644 --- a/mullvad-management-interface/src/client.rs +++ b/mullvad-management-interface/src/client.rs @@ -287,6 +287,11 @@ impl MullvadProxyClient { Ok(()) } + pub async fn set_userspace_wireguard(&mut self, state: bool) -> Result<()> { + self.0.set_userspace_wireguard(state).await?; + Ok(()) + } + pub async fn set_quantum_resistant_tunnel( &mut self, state: QuantumResistantState, diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs index 3776378bee..020842465c 100644 --- a/mullvad-management-interface/src/types/conversions/settings.rs +++ b/mullvad-management-interface/src/types/conversions/settings.rs @@ -103,6 +103,7 @@ impl From<&mullvad_types::settings::TunnelOptions> for proto::TunnelOptions { daita: None, enable_ipv6: options.generic.enable_ipv6, dns_options: Some(proto::DnsOptions::from(&options.dns_options)), + userspace: options.wireguard.userspace, } } } @@ -242,6 +243,7 @@ impl TryFrom<proto::TunnelOptions> for mullvad_types::settings::TunnelOptions { .ok_or(FromProtobufTypeError::InvalidArgument( "missing daita settings", ))?, + userspace: options.userspace, }, generic: net::GenericTunnelOptions { enable_ipv6: options.enable_ipv6, diff --git a/mullvad-types/src/wireguard.rs b/mullvad-types/src/wireguard.rs index e7d378b288..71497bbb70 100644 --- a/mullvad-types/src/wireguard.rs +++ b/mullvad-types/src/wireguard.rs @@ -233,6 +233,8 @@ pub struct TunnelOptions { /// Configure DAITA #[cfg(daita)] pub daita: DaitaSettings, + /// Use userspace WireGuard. + pub userspace: bool, /// Interval used for automatic key rotation pub rotation_interval: Option<RotationInterval>, } @@ -245,6 +247,7 @@ impl Default for TunnelOptions { quantum_resistant: QuantumResistantState::default(), #[cfg(daita)] daita: DaitaSettings::default(), + userspace: false, rotation_interval: None, } } @@ -257,6 +260,7 @@ impl TunnelOptions { quantum_resistant: self.quantum_resistant.enabled(), #[cfg(daita)] daita: self.daita.enabled, + userspace: self.userspace, } } } diff --git a/talpid-types/src/net/wireguard.rs b/talpid-types/src/net/wireguard.rs index c39236bf16..9748cebd70 100644 --- a/talpid-types/src/net/wireguard.rs +++ b/talpid-types/src/net/wireguard.rs @@ -53,6 +53,11 @@ impl TunnelParameters { pub fn get_exit_hop_endpoint(&self) -> Option<Endpoint> { self.connection.get_exit_endpoint() } + + /// Whether to use userspace WireGuard. + pub fn use_userspace_wg(&self) -> bool { + self.options.userspace || self.options.daita + } } /// Connection-specific configuration in [`TunnelParameters`]. @@ -124,6 +129,8 @@ pub struct TunnelOptions { /// Enable DAITA during tunnel config #[cfg(daita)] pub daita: bool, + /// Use userspace WireGuard. + pub userspace: bool, } /// Wireguard x25519 private key diff --git a/talpid-wireguard/src/lib.rs b/talpid-wireguard/src/lib.rs index f90f4439cb..141146f531 100644 --- a/talpid-wireguard/src/lib.rs +++ b/talpid-wireguard/src/lib.rs @@ -158,13 +158,11 @@ impl WireguardMonitor { args: TunnelArgs<'_>, _log_path: Option<&Path>, ) -> Result<WireguardMonitor> { - let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || params.options.daita; - let userspace_multihop = userspace_wireguard; - + let userspace_wireguard = *FORCE_USERSPACE_WIREGUARD || params.use_userspace_wg(); let route_mtu = args .runtime .block_on(get_route_mtu(params, &args.route_manager)); - let tunnel_mtu = calculate_tunnel_mtu(route_mtu, params, userspace_multihop); + let tunnel_mtu = calculate_tunnel_mtu(route_mtu, params, userspace_wireguard); let mut config = crate::config::Config::from_parameters(params, tunnel_mtu) .map_err(Error::WireguardConfigError)?; |
