Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make mullvad status print if lockdown mode is enabled while disconnected #5656

Merged
merged 4 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Line wrap the file at 100 chars. Th
- Respect OS prefer-reduced-motion setting
- Add CLI command for exporting settings patches: `mullvad export-settings`. Currently, it generates
a patch containing all patchable settings, which only includes relay IP overrides.
- Make `mullvad status` prints if lockdown mode is enabled when disconnected.

#### Android
- Add support for all screen orientations.
Expand Down
30 changes: 21 additions & 9 deletions mullvad-cli/src/cmds/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,25 @@ impl Status {
println!("New tunnel state: {new_state:#?}");
} else {
// When we enter the connected or disconnected state, am.i.mullvad.net will
// be polled to get IP information. When it arrives, we will get another
// tunnel state of the same enum type, but with the IP filled in. This
// match statement checks for duplicate tunnel states and skips the second
// print to avoid spamming the user.
// be polled to get exit location. When it arrives, we will get another
// tunnel state of the same enum type, but with the location filled in. This
// match statement checks if the new state is an updated version of the old
// one and if so skips the print to avoid spamming the user. Note that for
// graphical frontends updating the drawn state with an identical one is
// invisible, so this is only an issue for the CLI.
match (&previous_tunnel_state, &new_state) {
(Some(TunnelState::Disconnected(_)), TunnelState::Disconnected(_))
| (
(
Some(TunnelState::Disconnected {
location: _,
locked_down: was_locked_down,
}),
TunnelState::Disconnected {
location: _,
locked_down,
},
// Do print an updated state if the lockdown setting was changed
) if was_locked_down == locked_down => continue,
(
Some(TunnelState::Connected { .. }),
TunnelState::Connected { .. },
) => continue,
Expand Down Expand Up @@ -91,7 +103,7 @@ pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
let state = rpc.get_tunnel_state().await?;
let device = rpc.get_device().await?;

print_account_loggedout(&state, &device);
print_account_logged_out(&state, &device);

if args.debug {
println!("Tunnel state: {state:#?}");
Expand All @@ -106,7 +118,7 @@ pub async fn handle(cmd: Option<Status>, args: StatusArgs) -> Result<()> {
Ok(())
}

fn print_account_loggedout(state: &TunnelState, device: &DeviceState) {
fn print_account_logged_out(state: &TunnelState, device: &DeviceState) {
match state {
TunnelState::Connecting { .. } | TunnelState::Connected { .. } | TunnelState::Error(_) => {
match device {
Expand All @@ -117,6 +129,6 @@ fn print_account_loggedout(state: &TunnelState, device: &DeviceState) {
DeviceState::LoggedIn(_) => (),
}
}
TunnelState::Disconnected(_) | TunnelState::Disconnecting(_) => (),
TunnelState::Disconnected { .. } | TunnelState::Disconnecting(_) => (),
}
}
16 changes: 13 additions & 3 deletions mullvad-cli/src/format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,26 @@ pub fn print_state(state: &TunnelState, verbose: bool) {
format_relay_connection(endpoint, location.as_ref(), verbose)
);
}
Disconnected(_) => {
println!("Disconnected");
Disconnected {
location: _,
locked_down,
} => {
if *locked_down {
println!("Disconnected (Internet access is blocked due to lockdown mode)");
} else {
println!("Disconnected");
}
}
Disconnecting(_) => println!("Disconnecting..."),
}
}

pub fn print_location(state: &TunnelState) {
let location = match state {
TunnelState::Disconnected(location) => location,
TunnelState::Disconnected {
location,
locked_down: _,
} => location,
TunnelState::Connected { location, .. } => location,
_ => return,
};
Expand Down
2 changes: 1 addition & 1 deletion mullvad-daemon/src/device/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,7 @@ impl TunnelStateChangeHandler {
}
TunnelStateTransition::Error(_)
| TunnelStateTransition::Connected(_)
| TunnelStateTransition::Disconnected => 0,
| TunnelStateTransition::Disconnected { .. } => 0,
_ => retry_attempt,
}
}
Expand Down
25 changes: 17 additions & 8 deletions mullvad-daemon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ impl DaemonExecutionState {
match self {
Running => {
match tunnel_state {
TunnelState::Disconnected(_) => mem::replace(self, Finished),
TunnelState::Disconnected { .. } => mem::replace(self, Finished),
_ => mem::replace(self, Exiting),
};
}
Expand Down Expand Up @@ -856,7 +856,10 @@ where
);

let daemon = Daemon {
tunnel_state: TunnelState::Disconnected(None),
tunnel_state: TunnelState::Disconnected {
location: None,
locked_down: settings.block_when_disconnected,
},
target_state,
state: DaemonExecutionState::Running,
#[cfg(target_os = "linux")]
Expand Down Expand Up @@ -999,7 +1002,10 @@ where
.handle_state_transition(&tunnel_state_transition);

let tunnel_state = match tunnel_state_transition {
TunnelStateTransition::Disconnected => TunnelState::Disconnected(None),
TunnelStateTransition::Disconnected { locked_down } => TunnelState::Disconnected {
location: None,
locked_down,
},
TunnelStateTransition::Connecting(endpoint) => TunnelState::Connecting {
endpoint,
location: self.parameters_generator.get_last_location().await,
Expand All @@ -1024,7 +1030,7 @@ where
log::debug!("New tunnel state: {:?}", tunnel_state);

match tunnel_state {
TunnelState::Disconnected(_) => {
TunnelState::Disconnected { .. } => {
self.api_handle.availability.reset_inactivity_timer();
}
_ => {
Expand All @@ -1033,7 +1039,7 @@ where
}

match &tunnel_state {
TunnelState::Disconnected(_) => self.state.disconnected(),
TunnelState::Disconnected { .. } => self.state.disconnected(),
TunnelState::Connecting { .. } => {
log::debug!("Settings: {}", self.settings.summary());
}
Expand Down Expand Up @@ -1079,7 +1085,7 @@ where
TunnelState::Connected { .. } => self.settings.tunnel_options.generic.enable_ipv6,
// If not connected, we have to guess whether the users local connection supports IPv6.
// The only thing we have to go on is the wireguard setting.
TunnelState::Disconnected(_) => {
TunnelState::Disconnected { .. } => {
if let RelaySettings::Normal(relay_constraints) = &self.settings.relay_settings {
// Note that `Constraint::Any` corresponds to just IPv4
matches!(
Expand All @@ -1098,7 +1104,7 @@ where
self.location_handler.send_geo_location_request(use_ipv6);
}

/// Recieves and handles the geographical exit location received from am.i.mullvad.net, i.e. the
/// Receives and handles the geographical exit location received from am.i.mullvad.net, i.e. the
/// [`InternalDaemonEvent::LocationEvent`] event.
fn handle_location_event(&mut self, location_data: LocationEventData) {
let LocationEventData {
Expand All @@ -1112,7 +1118,10 @@ where
}

match self.tunnel_state {
TunnelState::Disconnected(ref mut location) => *location = Some(fetched_location),
TunnelState::Disconnected {
ref mut location,
locked_down: _,
} => *location = Some(fetched_location),
TunnelState::Connected {
ref mut location, ..
} => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ message ErrorState {
}

message TunnelState {
message Disconnected { GeoIpLocation disconnected_location = 1; }
message Disconnected {
GeoIpLocation disconnected_location = 1;
bool locked_down = 2;
}
message Connecting { TunnelStateRelayInfo relay_info = 1; }
message Connected { TunnelStateRelayInfo relay_info = 1; }
message Disconnecting { AfterDisconnect after_disconnect = 1; }
Expand Down
20 changes: 12 additions & 8 deletions mullvad-management-interface/src/types/conversions/states.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ impl From<mullvad_types::states::TunnelState> for proto::TunnelState {
};

let state = match state {
MullvadTunnelState::Disconnected(disconnected_location) => {
proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {
disconnected_location: disconnected_location.map(proto::GeoIpLocation::from),
})
}
MullvadTunnelState::Disconnected {
location: disconnected_location,
locked_down,
} => proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {
disconnected_location: disconnected_location.map(proto::GeoIpLocation::from),
locked_down,
}),
MullvadTunnelState::Connecting { endpoint, location } => {
proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
relay_info: Some(proto::TunnelStateRelayInfo {
Expand Down Expand Up @@ -193,11 +195,13 @@ impl TryFrom<proto::TunnelState> for mullvad_types::states::TunnelState {
let state = match state.state {
Some(proto::tunnel_state::State::Disconnected(proto::tunnel_state::Disconnected {
disconnected_location,
})) => MullvadState::Disconnected(
disconnected_location
locked_down,
})) => MullvadState::Disconnected {
location: disconnected_location
.map(mullvad_types::location::GeoIpLocation::try_from)
.transpose()?,
),
locked_down,
},
Some(proto::tunnel_state::State::Connecting(proto::tunnel_state::Connecting {
relay_info:
Some(proto::TunnelStateRelayInfo {
Expand Down
125 changes: 119 additions & 6 deletions mullvad-types/src/states.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::location::GeoIpLocation;
#[cfg(target_os = "android")]
use jnix::IntoJava;
use serde::{Deserialize, Serialize};
use std::fmt;
use talpid_types::{
Expand Down Expand Up @@ -31,10 +29,12 @@ impl fmt::Display for TargetState {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "state", content = "details")]
#[cfg_attr(target_os = "android", derive(IntoJava))]
#[cfg_attr(target_os = "android", jnix(package = "net.mullvad.mullvadvpn.model"))]
pub enum TunnelState {
Disconnected(Option<GeoIpLocation>),
Disconnected {
location: Option<GeoIpLocation>,
/// Whether internet access is blocked due to lockdown mode
locked_down: bool,
},
Connecting {
endpoint: TunnelEndpoint,
location: Option<GeoIpLocation>,
Expand All @@ -60,6 +60,119 @@ impl TunnelState {

/// Returns true if the tunnel state is in the disconnected state.
pub fn is_disconnected(&self) -> bool {
matches!(self, TunnelState::Disconnected(_))
matches!(self, TunnelState::Disconnected { .. })
}
}

#[cfg(target_os = "android")]
// Here we manually implement the `IntoJava` trait of jnix to skip the `locked_down` field of
// `TunnelState::Disconnected`. The derive macro currently does not support skipping fields in
// struct variants of enums. It was decided that this solution is the preferred to updating the
// macro since the jnix crate will be dropped once android implements gRPC.
impl<'borrow, 'env> jnix::IntoJava<'borrow, 'env> for TunnelState
where
'env: 'borrow,
{
const JNI_SIGNATURE: &'static str = "Lnet/mullvad/mullvadvpn/model/TunnelState;";
type JavaType = jnix::jni::objects::AutoLocal<'env, 'borrow>;
#[allow(non_snake_case)]
fn into_java(self, env: &'borrow jnix::JnixEnv<'env>) -> Self::JavaType {
match self {
Self::Disconnected {
location,
locked_down: _,
} => {
let constructor_signature = format!("({})V", location.jni_signature());

let location_java = location.into_java(env);
let parameters = [jnix::AsJValue::as_jvalue(&location_java)];

let class = env.get_class("net/mullvad/mullvadvpn/model/TunnelState$Disconnected");

let object = env
.new_object(&class, constructor_signature, &parameters)
.expect(
"Failed to convert TunnelState::Disconnected Rust type into net.mullvad.mullvadvpn.model.TunnelState.Disconnected Java object",
);
env.auto_local(object)
}
Self::Connecting { endpoint, location } => {
let constructor_signature = format!(
"({}{})V",
endpoint.jni_signature(),
location.jni_signature()
);

let endpoint_java = endpoint.into_java(env);
let location_java = location.into_java(env);
let parameters = [
jnix::AsJValue::as_jvalue(&endpoint_java),
jnix::AsJValue::as_jvalue(&location_java),
];

let class = env.get_class("net/mullvad/mullvadvpn/model/TunnelState$Connecting");

let object = env
.new_object(&class, constructor_signature, &parameters)
.expect(
"Failed to convert TunnelState::Connecting Rust type into net.mullvad.mullvadvpn.model.TunnelState.Connecting Java object",
);
env.auto_local(object)
}
Self::Connected { endpoint, location } => {
let constructor_signature = format!(
"({}{})V",
endpoint.jni_signature(),
location.jni_signature()
);

let endpoint_java = endpoint.into_java(env);
let location_java = location.into_java(env);
let parameters = [
jnix::AsJValue::as_jvalue(&endpoint_java),
jnix::AsJValue::as_jvalue(&location_java),
];

let class = env.get_class("net/mullvad/mullvadvpn/model/TunnelState$Connected");

let object = env
.new_object(&class, constructor_signature, &parameters)
.expect(
"Failed to convert TunnelState::Connected Rust type into net.mullvad.mullvadvpn.model.TunnelState.Connected Java object",
);
env.auto_local(object)
}
Self::Disconnecting(action_after_disconnect) => {
let constructor_signature =
format!("({})V", action_after_disconnect.jni_signature());

let action_after_disconnect_java = action_after_disconnect.into_java(env);
let parameters = [jnix::AsJValue::as_jvalue(&action_after_disconnect_java)];

let class = env.get_class("net/mullvad/mullvadvpn/model/TunnelState$Disconnecting");

let object = env
.new_object(&class, constructor_signature, &parameters)
.expect(
"Failed to convert TunnelState::Disconnecting Rust type into net.mullvad.mullvadvpn.model.TunnelState.Disconnecting Java object",
);
env.auto_local(object)
}
Self::Error(error_state) => {
let constructor_signature = format!("({})V", error_state.jni_signature());

let error_state_java = error_state.into_java(env);
let parameters = [jnix::AsJValue::as_jvalue(&error_state_java)];

let class = env.get_class("net/mullvad/mullvadvpn/model/TunnelState$Error");

let object = env
.new_object(&class, constructor_signature, &parameters)
.expect(
"Failed to convert TunnelState::Error Rust type into net.mullvad.mullvadvpn.model.TunnelState.Error Java object",
);
env.auto_local(object)
}
}
}
}
Loading
Loading