Skip to content

Commit

Permalink
Merge branch 'improve-custom-lists-api' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
dlon committed Sep 27, 2023
2 parents 8030071 + 52cdf0b commit 1e02e54
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 740 deletions.
3 changes: 2 additions & 1 deletion mullvad-cli/src/cmds/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,8 @@ impl Bridge {
Self::update_bridge_settings(&mut rpc, Some(location), None, None).await
}
SetCommands::CustomList { custom_list_name } => {
let list = rpc.get_custom_list(custom_list_name).await?;
let list =
super::custom_list::find_list_by_name(&mut rpc, &custom_list_name).await?;
let location =
Constraint::Only(LocationConstraint::CustomList { list_id: list.id });
Self::update_bridge_settings(&mut rpc, Some(location), None, None).await
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ use super::{
relay::{find_relay_by_hostname, get_filtered_relays},
relay_constraints::LocationArgs,
};
use anyhow::Result;
use anyhow::{anyhow, Result};
use clap::Subcommand;
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::{
custom_list::CustomListLocationUpdate,
relay_constraints::{Constraint, GeographicLocationConstraint},
relay_list::RelayList,
};
Expand Down Expand Up @@ -86,7 +85,7 @@ impl CustomList {
async fn list() -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let cache = rpc.get_relay_locations().await?;
for custom_list in rpc.list_custom_lists().await? {
for custom_list in rpc.get_settings().await?.custom_lists {
Self::print_custom_list(&custom_list, &cache)
}
Ok(())
Expand All @@ -96,7 +95,7 @@ impl CustomList {
/// If the list does not exist, print an error.
async fn get(name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let custom_list = rpc.get_custom_list(name).await?;
let custom_list = find_list_by_name(&mut rpc, &name).await?;
let cache = rpc.get_relay_locations().await?;
Self::print_custom_list_content(&custom_list, &cache);
Ok(())
Expand All @@ -111,30 +110,47 @@ impl CustomList {
async fn add_location(name: String, location_args: LocationArgs) -> Result<()> {
let countries = get_filtered_relays().await?;
let location = find_relay_by_hostname(&countries, &location_args.country)
.map_or(Constraint::from(location_args), Constraint::Only);
let update = CustomListLocationUpdate::Add { name, location };
.map_or(Constraint::from(location_args), Constraint::Only)
.option()
.ok_or(anyhow!("\"any\" is not a valid location"))?;

let mut rpc = MullvadProxyClient::new().await?;
rpc.update_custom_list_location(update).await?;

let mut list = find_list_by_name(&mut rpc, &name).await?;
list.locations.insert(location);
rpc.update_custom_list(list).await?;

Ok(())
}

async fn remove_location(name: String, location_args: LocationArgs) -> Result<()> {
let location = Constraint::<GeographicLocationConstraint>::from(location_args);
let update = CustomListLocationUpdate::Remove { name, location };
let location = Constraint::<GeographicLocationConstraint>::from(location_args)
.option()
.ok_or(anyhow!("\"any\" is not a valid location"))?;

let mut rpc = MullvadProxyClient::new().await?;
rpc.update_custom_list_location(update).await?;

let mut list = find_list_by_name(&mut rpc, &name).await?;
list.locations.remove(&location);
rpc.update_custom_list(list).await?;

Ok(())
}

async fn delete_list(name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
rpc.delete_custom_list(name).await?;
let list = find_list_by_name(&mut rpc, &name).await?;
rpc.delete_custom_list(list.id.to_string()).await?;
Ok(())
}

async fn rename_list(name: String, new_name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
rpc.rename_custom_list(name, new_name).await?;

let mut list = find_list_by_name(&mut rpc, &name).await?;
list.name = new_name;
rpc.update_custom_list(list).await?;

Ok(())
}

Expand Down Expand Up @@ -217,3 +233,15 @@ impl<'a> std::fmt::Display for GeographicLocationConstraintFormatter<'a> {
}
}
}

pub async fn find_list_by_name(
rpc: &mut MullvadProxyClient,
name: &str,
) -> Result<mullvad_types::custom_list::CustomList> {
rpc.get_settings()
.await?
.custom_lists
.into_iter()
.find(|list| list.name == name)
.ok_or(anyhow!("List not found"))
}
2 changes: 1 addition & 1 deletion mullvad-cli/src/cmds/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pub mod account;
pub mod auto_connect;
pub mod beta_program;
pub mod bridge;
pub mod custom_lists;
pub mod custom_list;
pub mod dns;
pub mod lan;
pub mod lockdown;
Expand Down
10 changes: 7 additions & 3 deletions mullvad-cli/src/cmds/relay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,9 @@ impl Relay {

async fn set_custom_list(custom_list_name: String) -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let list_id = rpc.get_custom_list(custom_list_name).await?.id;
let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name)
.await?
.id;
rpc.update_relay_settings(RelaySettingsUpdate::Normal(RelayConstraintsUpdate {
location: Some(Constraint::Only(LocationConstraint::CustomList { list_id })),
..Default::default()
Expand Down Expand Up @@ -617,9 +619,11 @@ impl Relay {
};
}
Some(EntryLocation::CustomList { custom_list_name }) => {
let list = rpc.get_custom_list(custom_list_name).await?;
let list_id = super::custom_list::find_list_by_name(&mut rpc, &custom_list_name)
.await?
.id;
wireguard_constraints.entry_location =
Constraint::Only(LocationConstraint::CustomList { list_id: list.id });
Constraint::Only(LocationConstraint::CustomList { list_id });
}
None => (),
}
Expand Down
2 changes: 1 addition & 1 deletion mullvad-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ enum Cli {

/// Manage custom lists
#[clap(subcommand)]
CustomList(custom_lists::CustomList),
CustomList(custom_list::CustomList),
}

#[tokio::main]
Expand Down
178 changes: 178 additions & 0 deletions mullvad-daemon/src/custom_list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use crate::{new_selector_config, Daemon, Error, EventListener};
use mullvad_types::{
custom_list::{CustomList, Id},
relay_constraints::{
BridgeSettings, BridgeState, Constraint, LocationConstraint, RelaySettings,
},
};
use talpid_types::net::TunnelType;

impl<L> Daemon<L>
where
L: EventListener + Clone + Send + 'static,
{
pub async fn create_custom_list(&mut self, name: String) -> Result<Id, crate::Error> {
if self
.settings
.custom_lists
.iter()
.any(|list| list.name == name)
{
return Err(Error::CustomListExists);
}

let new_list = CustomList::new(name);
let id = new_list.id;

let settings_changed = self
.settings
.update(|settings| {
settings.custom_lists.add(new_list);
})
.await
.map_err(Error::SettingsError);

if let Ok(true) = settings_changed {
self.event_listener
.notify_settings(self.settings.to_settings());
self.relay_selector
.set_config(new_selector_config(&self.settings));
}

settings_changed?;
Ok(id)
}

pub async fn delete_custom_list(&mut self, id: Id) -> Result<(), Error> {
let Some(list_index) = self
.settings
.custom_lists
.iter()
.position(|elem| elem.id == id)
else {
return Err(Error::CustomListNotFound);
};
let settings_changed = self
.settings
.update(|settings| {
// NOTE: Not using swap remove because it would make user output slightly
// more confusing and the cost is so small.
settings.custom_lists.remove(list_index);
})
.await
.map_err(Error::SettingsError);

if let Ok(true) = settings_changed {
self.event_listener
.notify_settings(self.settings.to_settings());
self.relay_selector
.set_config(new_selector_config(&self.settings));

if self.change_should_cause_reconnect(id) {
log::info!("Initiating tunnel restart because a selected custom list was deleted");
self.reconnect_tunnel();
}
}

settings_changed?;
Ok(())
}

pub async fn update_custom_list(&mut self, new_list: CustomList) -> Result<(), Error> {
let Some((list_index, old_list)) = self
.settings
.custom_lists
.iter()
.enumerate()
.find(|elem| elem.1.id == new_list.id)
else {
return Err(Error::CustomListNotFound);
};
let id = old_list.id;

if old_list.name != new_list.name
&& self
.settings
.custom_lists
.iter()
.any(|list| list.name == new_list.name)
{
return Err(Error::CustomListExists);
}

let settings_changed = self
.settings
.update(|settings| {
settings.custom_lists[list_index] = new_list;
})
.await
.map_err(Error::SettingsError);

if let Ok(true) = settings_changed {
self.event_listener
.notify_settings(self.settings.to_settings());
self.relay_selector
.set_config(new_selector_config(&self.settings));

if self.change_should_cause_reconnect(id) {
log::info!("Initiating tunnel restart because a selected custom list changed");
self.reconnect_tunnel();
}
}

settings_changed?;
Ok(())
}

fn change_should_cause_reconnect(&self, custom_list_id: Id) -> bool {
use mullvad_types::states::TunnelState;
let mut need_to_reconnect = false;

if let RelaySettings::Normal(relay_settings) = &self.settings.relay_settings {
if let Constraint::Only(LocationConstraint::CustomList { list_id }) =
&relay_settings.location
{
need_to_reconnect |= list_id == &custom_list_id;
}

if let TunnelState::Connecting {
endpoint,
location: _,
}
| TunnelState::Connected {
endpoint,
location: _,
} = &self.tunnel_state
{
match endpoint.tunnel_type {
TunnelType::Wireguard => {
if relay_settings.wireguard_constraints.use_multihop {
if let Constraint::Only(LocationConstraint::CustomList { list_id }) =
&relay_settings.wireguard_constraints.entry_location
{
need_to_reconnect |= list_id == &custom_list_id;
}
}
}

TunnelType::OpenVpn => {
if !matches!(self.settings.bridge_state, BridgeState::Off) {
if let BridgeSettings::Normal(bridge_settings) =
&self.settings.bridge_settings
{
if let Constraint::Only(LocationConstraint::CustomList {
list_id,
}) = &bridge_settings.location
{
need_to_reconnect |= list_id == &custom_list_id;
}
}
}
}
}
}
}

need_to_reconnect
}
}
Loading

0 comments on commit 1e02e54

Please sign in to comment.