summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarkus Pettersson <markus.pettersson@mullvad.net>2023-08-24 16:18:06 +0200
committerDavid Lönnhager <david.l@mullvad.net>2023-10-09 14:40:02 +0200
commit585a820373abea33e3338e97fb727028667968bb (patch)
tree104cdb1c31ddd87dca5499c736b20d2e3ca3d160
parent44656b65922ee0b8c76db50a22f93e10158e8b59 (diff)
downloadmullvadvpn-585a820373abea33e3338e97fb727028667968bb.tar.xz
mullvadvpn-585a820373abea33e3338e97fb727028667968bb.zip
Add `mullvad proxy add` command
Add daemon logic for storing custom access methods & allow a user to add a custom socks5 or shadowsocks proxy. Add all the necessary information for establishing Socks5 connections (both using a local Socks-proxy as well as the normal, remote-proxy, use case) and Shadowsocks connections. Add `api_access_settings` to `mullvad-daemon` Naturally, the Protobuf types has to be mirrored on the Rust/daemon side and lots of boilerplate code had to be written to convert between the two.
-rw-r--r--mullvad-cli/src/cmds/proxy.rs126
-rw-r--r--mullvad-daemon/src/access_methods.rs37
-rw-r--r--mullvad-daemon/src/lib.rs24
-rw-r--r--mullvad-management-interface/proto/management_interface.proto39
-rw-r--r--mullvad-management-interface/src/client.rs22
-rw-r--r--mullvad-management-interface/src/types/conversions/api_access_method.rs171
-rw-r--r--mullvad-management-interface/src/types/conversions/mod.rs1
-rw-r--r--mullvad-management-interface/src/types/conversions/settings.rs12
-rw-r--r--mullvad-types/src/api_access_method.rs94
-rw-r--r--mullvad-types/src/lib.rs1
-rw-r--r--mullvad-types/src/settings/mod.rs13
11 files changed, 519 insertions, 21 deletions
diff --git a/mullvad-cli/src/cmds/proxy.rs b/mullvad-cli/src/cmds/proxy.rs
index c12aee3683..fec4a6f966 100644
--- a/mullvad-cli/src/cmds/proxy.rs
+++ b/mullvad-cli/src/cmds/proxy.rs
@@ -1,5 +1,6 @@
use anyhow::Result;
use mullvad_management_interface::MullvadProxyClient;
+use mullvad_types::api_access_method::AccessMethod;
use std::net::IpAddr;
use clap::Subcommand;
@@ -17,12 +18,13 @@ impl Proxy {
match self {
Proxy::Api(cmd) => match cmd {
ApiCommands::List => {
- println!("Listing the API access methods: ..");
+ //println!("Listing the API access methods: ..");
Self::list().await?;
}
- ApiCommands::Add(cmd) => match cmd {
- _ => println!("[NOT IMPEMENTLED YET] Adding custom proxy: {:?}", cmd),
- },
+ ApiCommands::Add(cmd) => {
+ //println!("Adding custom proxy");
+ Self::add(cmd).await?;
+ }
},
};
Ok(())
@@ -37,13 +39,20 @@ impl Proxy {
}
Ok(())
}
+
+ /// Add a custom API access method.
+ async fn add(cmd: AddCustomCommands) -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let proxy = AccessMethod::try_from(cmd.clone())?;
+ rpc.add_access_method(proxy).await?;
+ Ok(())
+ }
}
#[derive(Subcommand, Debug, Clone)]
pub enum ApiCommands {
/// List the configured API proxies
List,
-
/// Add a custom API proxy
#[clap(subcommand)]
Add(AddCustomCommands),
@@ -51,6 +60,28 @@ pub enum ApiCommands {
#[derive(Subcommand, Debug, Clone)]
pub enum AddCustomCommands {
+ /// Configure SOCKS5 proxy
+ #[clap(subcommand)]
+ Socks5(Socks5AddCommands),
+
+ /// Configure bundled Shadowsocks proxy
+ Shadowsocks {
+ /// The IP of the remote Shadowsocks server
+ remote_ip: IpAddr,
+ /// The port of the remote Shadowsocks server
+ #[arg(default_value = "443")]
+ remote_port: u16,
+ /// Password for authentication
+ #[arg(default_value = "mullvad")]
+ password: String,
+ /// Cipher to use
+ #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")]
+ cipher: String,
+ },
+}
+
+#[derive(Subcommand, Debug, Clone)]
+pub enum Socks5AddCommands {
/// Configure a local SOCKS5 proxy
Local {
/// The port that the server on localhost is listening on
@@ -60,14 +91,12 @@ pub enum AddCustomCommands {
/// The port of the remote peer
remote_port: u16,
},
-
/// Configure a remote SOCKS5 proxy
Remote {
/// The IP of the remote proxy server
remote_ip: IpAddr,
/// The port of the remote proxy server
remote_port: u16,
-
/// Username for authentication
#[arg(requires = "password")]
username: Option<String>,
@@ -75,21 +104,76 @@ pub enum AddCustomCommands {
#[arg(requires = "username")]
password: Option<String>,
},
+}
- /// Configure bundled Shadowsocks proxy
- Shadowsocks {
- /// The IP of the remote Shadowsocks server
- remote_ip: IpAddr,
- /// The port of the remote Shadowsocks server
- #[arg(default_value = "443")]
- remote_port: u16,
+/// Implement conversions from CLI types to Daemon types.
+///
+/// Since these are not supposed to be used outside of the CLI,
+/// we define them in a hidden-away module.
+mod conversions {
+ use anyhow::{anyhow, Error};
+ use mullvad_types::api_access_method as daemon_types;
- /// Password for authentication
- #[arg(default_value = "mullvad")]
- password: String,
+ use super::{AddCustomCommands, Socks5AddCommands};
- /// Cipher to use
- #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")]
- cipher: String,
- },
+ impl TryFrom<AddCustomCommands> for daemon_types::AccessMethod {
+ type Error = Error;
+
+ fn try_from(value: AddCustomCommands) -> Result<Self, Self::Error> {
+ Ok(match value {
+ AddCustomCommands::Socks5(variant) => match variant {
+ Socks5AddCommands::Local {
+ local_port,
+ remote_ip,
+ remote_port,
+ } => {
+ println!("Adding LOCAL SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}");
+ let socks_proxy = daemon_types::Socks5::Local(
+ daemon_types::Socks5Local::from_args(
+ remote_ip.to_string(),
+ remote_port,
+ local_port,
+ )
+ .ok_or(anyhow!("Could not create a local Socks5 api proxy"))?,
+ );
+ daemon_types::AccessMethod::Socks5(socks_proxy)
+ }
+ Socks5AddCommands::Remote {
+ remote_ip,
+ remote_port,
+ username,
+ password,
+ } => {
+ println!("Adding REMOTE SOCKS5-proxy: {username:?}+{password:?} @ {remote_ip}:{remote_port}");
+ let socks_proxy = daemon_types::Socks5::Remote(
+ daemon_types::Socks5Remote::from_args(
+ remote_ip.to_string(),
+ remote_port,
+ )
+ .ok_or(anyhow!("Could not create a remote Socks5 api proxy"))?,
+ );
+ daemon_types::AccessMethod::Socks5(socks_proxy)
+ }
+ },
+ AddCustomCommands::Shadowsocks {
+ remote_ip,
+ remote_port,
+ password,
+ cipher,
+ } => {
+ println!(
+ "Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}"
+ );
+ let shadowsocks_proxy = daemon_types::Shadowsocks::from_args(
+ remote_ip.to_string(),
+ remote_port,
+ cipher,
+ password,
+ )
+ .ok_or(anyhow!("Could not create a Shadowsocks api proxy"))?;
+ daemon_types::AccessMethod::Shadowsocks(shadowsocks_proxy)
+ }
+ })
+ }
+ }
}
diff --git a/mullvad-daemon/src/access_methods.rs b/mullvad-daemon/src/access_methods.rs
new file mode 100644
index 0000000000..634914d00a
--- /dev/null
+++ b/mullvad-daemon/src/access_methods.rs
@@ -0,0 +1,37 @@
+use crate::{new_selector_config, settings, Daemon, EventListener};
+use mullvad_types::api_access_method::AccessMethod;
+
+#[derive(err_derive::Error, Debug)]
+pub enum Error {
+ /// Can not add access method
+ #[error(display = "Cannot add custom access method")]
+ Add,
+ /// Access methods settings error
+ #[error(display = "Settings error")]
+ Settings(#[error(source)] settings::Error),
+}
+
+impl<L> Daemon<L>
+where
+ L: EventListener + Clone + Send + 'static,
+{
+ pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<(), Error> {
+ self.settings
+ .update(|settings| {
+ settings
+ .api_access_methods
+ .api_access_methods
+ .push(access_method);
+ })
+ .await
+ .map(|changed| {
+ if changed {
+ self.event_listener
+ .notify_settings(self.settings.to_settings());
+ self.relay_selector
+ .set_config(new_selector_config(&self.settings));
+ };
+ })
+ .map_err(Error::Settings)
+ }
+}
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 81ebe1050c..676e122a64 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -1,6 +1,7 @@
#![deny(rust_2018_idioms)]
#![recursion_limit = "512"]
+mod access_methods;
pub mod account_history;
mod api;
#[cfg(not(target_os = "android"))]
@@ -39,6 +40,7 @@ use mullvad_relay_selector::{
};
use mullvad_types::{
account::{AccountData, AccountToken, VoucherSubmission},
+ api_access_method::AccessMethod,
auth_failed::AuthFailed,
custom_list::CustomList,
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
@@ -170,6 +172,9 @@ pub enum Error {
#[error(display = "A list with that name does not exist")]
CustomListNotFound,
+ #[error(display = "Access method error")]
+ AccessMethodError(#[error(source)] access_methods::Error),
+
#[cfg(target_os = "macos")]
#[error(display = "Failed to set exclusion group")]
GroupIdError(#[error(source)] io::Error),
@@ -255,6 +260,10 @@ pub enum DaemonCommand {
DeleteCustomList(ResponseTx<(), Error>, mullvad_types::custom_list::Id),
/// Update a custom list with a given id
UpdateCustomList(ResponseTx<(), Error>, CustomList),
+ /// Get API access methods
+ GetApiAccessMethods(ResponseTx<Vec<AccessMethod>, Error>),
+ /// Add API access methods
+ AddApiAccessMethod(ResponseTx<(), Error>, AccessMethod),
/// Get information about the currently running and latest app versions
GetVersionInfo(oneshot::Sender<Option<AppVersionInfo>>),
/// Return whether the daemon is performing post-upgrade tasks
@@ -1030,6 +1039,8 @@ where
DeleteCustomList(tx, id) => self.on_delete_custom_list(tx, id).await,
UpdateCustomList(tx, update) => self.on_update_custom_list(tx, update).await,
GetVersionInfo(tx) => self.on_get_version_info(tx),
+ GetApiAccessMethods(tx) => self.on_get_api_access_methods(tx),
+ AddApiAccessMethod(tx, method) => self.on_add_api_access_method(tx, method).await,
IsPerformingPostUpgrade(tx) => self.on_is_performing_post_upgrade(tx),
GetCurrentVersion(tx) => self.on_get_current_version(tx),
#[cfg(not(target_os = "android"))]
@@ -2204,6 +2215,19 @@ where
Self::oneshot_send(tx, result, "update_custom_list response");
}
+ fn on_get_api_access_methods(&mut self, tx: ResponseTx<Vec<AccessMethod>, Error>) {
+ let result = Ok(self.settings.api_access_methods.api_access_methods.clone());
+ Self::oneshot_send(tx, result, "get_api_access_methods response");
+ }
+
+ async fn on_add_api_access_method(&mut self, tx: ResponseTx<(), Error>, method: AccessMethod) {
+ let result = self
+ .add_access_method(method)
+ .await
+ .map_err(Error::AccessMethodError);
+ Self::oneshot_send(tx, result, "add_api_access_method response");
+ }
+
fn on_get_settings(&self, tx: oneshot::Sender<Settings>) {
Self::oneshot_send(tx, self.settings.to_settings(), "get_settings response");
}
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index f4b3eb961d..03c9a2c02c 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -73,6 +73,12 @@ service ManagementService {
rpc DeleteCustomList(google.protobuf.StringValue) returns (google.protobuf.Empty) {}
rpc UpdateCustomList(CustomList) returns (google.protobuf.Empty) {}
+ // API Access methods
+ rpc GetApiAccessMethods(google.protobuf.Empty) returns (ApiAccessMethods) {}
+ rpc AddApiAccessMethod(ApiAccessMethod) returns (google.protobuf.Empty) {
+ // Can I return something useful here instead of Empty?
+ }
+
// Split tunneling (Linux)
rpc GetSplitTunnelProcesses(google.protobuf.Empty) returns (stream google.protobuf.Int32Value) {}
rpc AddSplitTunnelProcess(google.protobuf.Int32Value) returns (google.protobuf.Empty) {}
@@ -324,6 +330,38 @@ message CustomList {
message CustomListSettings { repeated CustomList custom_lists = 1; }
+message ApiAccessMethod {
+ message Socks5Local {
+ string ip = 1;
+ uint32 port = 2;
+ uint32 local_port = 3;
+ }
+ message Socks5Remote {
+ string ip = 1;
+ uint32 port = 2;
+ }
+ message Socks5 {
+ oneof Socks5type {
+ Socks5Local local = 1;
+ Socks5Remote remote = 2;
+ }
+ }
+ message Shadowsocks {
+ string ip = 1;
+ uint32 port = 2;
+ string password = 3;
+ string cipher = 4;
+ }
+ oneof access_method {
+ Socks5 socks5 = 1;
+ Shadowsocks shadowsocks = 2;
+ }
+}
+
+message ApiAccessMethods { repeated ApiAccessMethod api_access_methods = 1; }
+
+message ApiAccessMethodSettings { repeated ApiAccessMethod api_access_methods = 1; }
+
message Settings {
RelaySettings relay_settings = 1;
BridgeSettings bridge_settings = 2;
@@ -336,6 +374,7 @@ message Settings {
SplitTunnelSettings split_tunnel = 9;
ObfuscationSettings obfuscation_settings = 10;
CustomListSettings custom_lists = 11;
+ ApiAccessMethodSettings api_access_methods = 12;
}
message SplitTunnelSettings {
diff --git a/mullvad-management-interface/src/client.rs b/mullvad-management-interface/src/client.rs
index a1ddc5e39a..e7c14800a5 100644
--- a/mullvad-management-interface/src/client.rs
+++ b/mullvad-management-interface/src/client.rs
@@ -4,6 +4,7 @@ use crate::types;
use futures::{Stream, StreamExt};
use mullvad_types::{
account::{AccountData, AccountToken, VoucherSubmission},
+ api_access_method::AccessMethod,
custom_list::{CustomList, Id},
device::{Device, DeviceEvent, DeviceId, DeviceState, RemoveDeviceEvent},
location::GeoIpLocation,
@@ -163,6 +164,19 @@ impl MullvadProxyClient {
mullvad_types::relay_list::RelayList::try_from(list).map_err(Error::InvalidResponse)
}
+ pub async fn get_api_access_methods(&mut self) -> Result<Vec<AccessMethod>> {
+ Ok(self
+ .0
+ .get_api_access_methods(())
+ .await
+ .map_err(Error::Rpc)?
+ .into_inner()
+ .api_access_methods
+ .iter()
+ .map(From::from)
+ .collect())
+ }
+
pub async fn update_relay_locations(&mut self) -> Result<()> {
self.0
.update_relay_locations(())
@@ -457,6 +471,14 @@ impl MullvadProxyClient {
Ok(())
}
+ pub async fn add_access_method(&mut self, access_method: AccessMethod) -> Result<()> {
+ self.0
+ .add_api_access_method(types::ApiAccessMethod::from(access_method))
+ .await
+ .map_err(Error::Rpc)
+ .map(drop)
+ }
+
#[cfg(target_os = "linux")]
pub async fn get_split_tunnel_processes(&mut self) -> Result<Vec<i32>> {
use futures::TryStreamExt;
diff --git a/mullvad-management-interface/src/types/conversions/api_access_method.rs b/mullvad-management-interface/src/types/conversions/api_access_method.rs
new file mode 100644
index 0000000000..a91923e9e9
--- /dev/null
+++ b/mullvad-management-interface/src/types/conversions/api_access_method.rs
@@ -0,0 +1,171 @@
+/// Implements conversions for the auxilliary proto AccessMethod type to the internal AccessMethod data type.
+mod settings {
+ use crate::types::proto;
+ use mullvad_types::api_access_method;
+
+ impl From<&api_access_method::Settings> for proto::ApiAccessMethodSettings {
+ fn from(settings: &api_access_method::Settings) -> Self {
+ Self {
+ api_access_methods: settings
+ .api_access_methods
+ .iter()
+ .map(|method| method.clone().into())
+ .collect(),
+ }
+ }
+ }
+
+ impl From<api_access_method::Settings> for proto::ApiAccessMethodSettings {
+ fn from(settings: api_access_method::Settings) -> Self {
+ proto::ApiAccessMethodSettings::from(&settings)
+ }
+ }
+
+ impl From<proto::ApiAccessMethodSettings> for api_access_method::Settings {
+ fn from(settings: proto::ApiAccessMethodSettings) -> Self {
+ Self {
+ api_access_methods: settings
+ .api_access_methods
+ .iter()
+ .map(api_access_method::AccessMethod::from)
+ .collect(),
+ }
+ }
+ }
+}
+
+/// Implements conversions for the 'main' AccessMethod data type.
+mod data {
+ use crate::types::proto::{self, api_access_method::socks5::Socks5type};
+ use mullvad_types::api_access_method::{
+ AccessMethod, Shadowsocks, Socks5, Socks5Local, Socks5Remote,
+ };
+
+ impl From<proto::ApiAccessMethods> for Vec<AccessMethod> {
+ fn from(api_access_methods: proto::ApiAccessMethods) -> Self {
+ api_access_methods
+ .api_access_methods
+ .iter()
+ .map(AccessMethod::from)
+ .collect()
+ }
+ }
+
+ impl From<proto::ApiAccessMethod> for AccessMethod {
+ fn from(value: proto::ApiAccessMethod) -> Self {
+ // TODO: How to not unwrap?
+ match value.access_method.unwrap() {
+ proto::api_access_method::AccessMethod::Socks5(socks) => {
+ match socks.socks5type.unwrap() {
+ Socks5type::Local(local) => {
+ let local_proxy = Socks5Local::from_args(
+ local.ip,
+ local.port as u16,
+ local.local_port as u16,
+ )
+ .unwrap(); // This is dangerous territory ..
+ AccessMethod::Socks5(Socks5::Local(local_proxy))
+ }
+
+ Socks5type::Remote(remote) => {
+ let remote_proxy =
+ Socks5Remote::from_args(remote.ip, remote.port as u16).unwrap(); // This is dangerous territory ..
+ AccessMethod::Socks5(Socks5::Remote(remote_proxy))
+ }
+ }
+ }
+ proto::api_access_method::AccessMethod::Shadowsocks(ss) => {
+ let shadow_sock =
+ Shadowsocks::from_args(ss.ip, ss.port as u16, ss.cipher, ss.password)
+ .unwrap();
+ AccessMethod::Shadowsocks(shadow_sock)
+ }
+ }
+ }
+ }
+
+ impl From<AccessMethod> for proto::ApiAccessMethod {
+ fn from(value: AccessMethod) -> Self {
+ match value {
+ AccessMethod::Shadowsocks(ss) => proto::api_access_method::Shadowsocks {
+ ip: ss.peer.ip().to_string(),
+ port: ss.peer.port() as u32,
+ password: ss.password,
+ cipher: ss.cipher,
+ }
+ .into(),
+
+ AccessMethod::Socks5(Socks5::Local(Socks5Local { peer, port })) => {
+ proto::api_access_method::Socks5Local {
+ ip: peer.ip().to_string(),
+ port: peer.port() as u32,
+ local_port: port as u32,
+ }
+ .into()
+ }
+ AccessMethod::Socks5(Socks5::Remote(Socks5Remote { peer })) => {
+ proto::api_access_method::Socks5Remote {
+ ip: peer.ip().to_string(),
+ port: peer.port() as u32,
+ }
+ .into()
+ }
+ }
+ }
+ }
+
+ impl From<&proto::ApiAccessMethod> for AccessMethod {
+ fn from(value: &proto::ApiAccessMethod) -> Self {
+ AccessMethod::from(value.clone())
+ }
+ }
+
+ impl From<Vec<AccessMethod>> for proto::ApiAccessMethods {
+ fn from(value: Vec<AccessMethod>) -> proto::ApiAccessMethods {
+ proto::ApiAccessMethods {
+ api_access_methods: value.iter().map(|method| method.clone().into()).collect(),
+ }
+ }
+ }
+
+ impl From<proto::api_access_method::Shadowsocks> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::Shadowsocks) -> Self {
+ proto::api_access_method::AccessMethod::Shadowsocks(value).into()
+ }
+ }
+
+ impl From<proto::api_access_method::Socks5> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::Socks5) -> Self {
+ proto::api_access_method::AccessMethod::Socks5(value).into()
+ }
+ }
+
+ impl From<proto::api_access_method::socks5::Socks5type> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::socks5::Socks5type) -> Self {
+ proto::api_access_method::AccessMethod::Socks5(proto::api_access_method::Socks5 {
+ socks5type: Some(value),
+ })
+ .into()
+ }
+ }
+
+ impl From<proto::api_access_method::Socks5Local> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::Socks5Local) -> Self {
+ proto::api_access_method::socks5::Socks5type::Local(value).into()
+ }
+ }
+
+ impl From<proto::api_access_method::Socks5Remote> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::Socks5Remote) -> Self {
+ proto::api_access_method::socks5::Socks5type::Remote(value).into()
+ }
+ }
+
+ impl From<proto::api_access_method::AccessMethod> for proto::ApiAccessMethod {
+ fn from(value: proto::api_access_method::AccessMethod) -> Self {
+ proto::ApiAccessMethod {
+ access_method: Some(value),
+ }
+ }
+ }
+}
diff --git a/mullvad-management-interface/src/types/conversions/mod.rs b/mullvad-management-interface/src/types/conversions/mod.rs
index d2e8b60265..ddfe3f4282 100644
--- a/mullvad-management-interface/src/types/conversions/mod.rs
+++ b/mullvad-management-interface/src/types/conversions/mod.rs
@@ -1,6 +1,7 @@
use std::str::FromStr;
mod account;
+mod api_access_method;
mod custom_list;
mod custom_tunnel;
mod device;
diff --git a/mullvad-management-interface/src/types/conversions/settings.rs b/mullvad-management-interface/src/types/conversions/settings.rs
index f123b41755..d454af4cb0 100644
--- a/mullvad-management-interface/src/types/conversions/settings.rs
+++ b/mullvad-management-interface/src/types/conversions/settings.rs
@@ -42,6 +42,9 @@ impl From<&mullvad_types::settings::Settings> for proto::Settings {
custom_lists: Some(proto::CustomListSettings::from(
settings.custom_lists.clone(),
)),
+ api_access_methods: Some(proto::ApiAccessMethodSettings::from(
+ &settings.api_access_methods,
+ )),
}
}
}
@@ -140,6 +143,12 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings {
.ok_or(FromProtobufTypeError::InvalidArgument(
"missing custom lists settings",
))?;
+ let api_access_methods_settings =
+ settings
+ .api_access_methods
+ .ok_or(FromProtobufTypeError::InvalidArgument(
+ "missing api access methods settings",
+ ))?;
#[cfg(windows)]
let split_tunnel = settings
.split_tunnel
@@ -171,6 +180,9 @@ impl TryFrom<proto::Settings> for mullvad_types::settings::Settings {
custom_lists: mullvad_types::custom_list::CustomListsSettings::try_from(
custom_lists_settings,
)?,
+ api_access_methods: mullvad_types::api_access_method::Settings::from(
+ api_access_methods_settings,
+ ),
})
}
}
diff --git a/mullvad-types/src/api_access_method.rs b/mullvad-types/src/api_access_method.rs
new file mode 100644
index 0000000000..5a4d32551f
--- /dev/null
+++ b/mullvad-types/src/api_access_method.rs
@@ -0,0 +1,94 @@
+use std::str::FromStr;
+
+use serde::{Deserialize, Serialize};
+use std::net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4};
+
+/// Daemon settings for API access methods.
+#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)]
+pub struct Settings {
+ pub api_access_methods: Vec<AccessMethod>,
+}
+
+/// API access method datastructure.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub enum AccessMethod {
+ Shadowsocks(Shadowsocks),
+ Socks5(Socks5),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Shadowsocks {
+ pub peer: SocketAddr,
+ pub password: String, // TODO: Mask the password (using special type)?
+ pub cipher: String, // Gets validated at a later stage. Is assumed to be valid.
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub enum Socks5 {
+ Local(Socks5Local),
+ Remote(Socks5Remote),
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Socks5Local {
+ pub peer: SocketAddr,
+ /// Port on localhost where the SOCKS5-proxy listens to.
+ pub port: u16,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
+pub struct Socks5Remote {
+ pub peer: SocketAddr,
+}
+
+impl Settings {
+ // TODO: Do I have to clone?
+ pub fn get_access_methods(&self) -> Vec<AccessMethod> {
+ self.api_access_methods.clone()
+ }
+}
+
+impl Shadowsocks {
+ pub fn new(peer: SocketAddr, cipher: String, password: String) -> Self {
+ Shadowsocks {
+ peer,
+ password,
+ cipher,
+ }
+ }
+
+ /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you.
+ /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`].
+ pub fn from_args(ip: String, port: u16, cipher: String, password: String) -> Option<Self> {
+ let peer = SocketAddrV4::new(Ipv4Addr::from_str(&ip).ok()?, port).into();
+ Some(Self::new(peer, password, cipher))
+ }
+}
+
+impl Socks5Local {
+ pub fn new(peer: SocketAddr, port: u16) -> Self {
+ Self { peer, port }
+ }
+
+ /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you.
+ /// If `ip` or `port` are valid [`Some(Socks5Local)`] is returned, otherwise [`None`].
+ pub fn from_args(ip: String, port: u16, localport: u16) -> Option<Self> {
+ let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?);
+ let peer = SocketAddr::new(peer_ip, port);
+ Some(Self::new(peer, localport))
+ }
+}
+
+impl Socks5Remote {
+ pub fn new(peer: SocketAddr) -> Self {
+ Self { peer }
+ }
+
+ /// Like [new()], but tries to parse `ip` and `port` into a [`std::net::SocketAddr`] for you.
+ /// If `ip` or `port` are valid [`Some(Socks5Remote)`] is returned, otherwise [`None`].
+ pub fn from_args(ip: String, port: u16) -> Option<Self> {
+ let peer_ip = IpAddr::V4(Ipv4Addr::from_str(&ip).ok()?);
+ let peer = SocketAddr::new(peer_ip, port);
+ Some(Self::new(peer))
+ }
+}
diff --git a/mullvad-types/src/lib.rs b/mullvad-types/src/lib.rs
index bfac631f82..8b5a501663 100644
--- a/mullvad-types/src/lib.rs
+++ b/mullvad-types/src/lib.rs
@@ -1,6 +1,7 @@
#![deny(rust_2018_idioms)]
pub mod account;
+pub mod api_access_method;
pub mod auth_failed;
pub mod custom_list;
pub mod device;
diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs
index 3b3ca15014..fc66649b8a 100644
--- a/mullvad-types/src/settings/mod.rs
+++ b/mullvad-types/src/settings/mod.rs
@@ -1,4 +1,5 @@
use crate::{
+ api_access_method,
custom_list::CustomListsSettings,
relay_constraints::{
BridgeConstraints, BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint,
@@ -76,6 +77,9 @@ pub struct Settings {
/// All of the custom relay lists
#[cfg_attr(target_os = "android", jnix(skip))]
pub custom_lists: CustomListsSettings,
+ /// API access methods.
+ #[cfg_attr(target_os = "android", jnix(skip))]
+ pub api_access_methods: api_access_method::Settings,
/// If the daemon should allow communication with private (LAN) networks.
pub allow_lan: bool,
/// Extra level of kill switch. When this setting is on, the disconnected state will block
@@ -136,6 +140,15 @@ impl Default for Settings {
split_tunnel: SplitTunnelSettings::default(),
settings_version: CURRENT_SETTINGS_VERSION,
custom_lists: CustomListsSettings::default(),
+ //api_access_methods: api_access_method::Settings::default(),
+ // TODO: Remove this v in favor of this ^ when w can add or own access methods.
+ api_access_methods: {
+ use api_access_method::{AccessMethod, Shadowsocks};
+ let mut xs = api_access_method::Settings::default();
+ let mut ys = vec![];
+ xs.api_access_methods.append(&mut ys);
+ xs
+ },
}
}
}