summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonathan <jonathan@mullvad.net>2024-01-03 14:39:12 +0100
committerJonathan <jonathan@mullvad.net>2024-01-03 14:39:12 +0100
commit711d4e439866ab12e03d33d5efae3c2355c0c229 (patch)
tree80d3a23c1a96bd3d80e05ac66b530e39c252d48a
parentc510df96772b1e4ab7998e739ced42806c78e931 (diff)
parent4fdc34acbba60d5092e45ce3e513d30ec996c317 (diff)
downloadmullvadvpn-711d4e439866ab12e03d33d5efae3c2355c0c229.tar.xz
mullvadvpn-711d4e439866ab12e03d33d5efae3c2355c0c229.zip
Merge branch 'implement-custom-openvpn-socks5-bridge-client-in-daemon-des-430'
-rw-r--r--CHANGELOG.md2
-rw-r--r--gui/src/main/daemon-rpc.ts138
-rw-r--r--gui/src/main/default-settings.ts2
-rw-r--r--gui/src/renderer/app.tsx18
-rw-r--r--gui/src/renderer/components/select-location/select-location-hooks.ts14
-rw-r--r--gui/src/renderer/lib/utilityHooks.ts2
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts15
-rw-r--r--gui/src/shared/bridge-settings-builder.ts2
-rw-r--r--gui/src/shared/daemon-rpc-types.ts24
-rw-r--r--mullvad-api/src/https_client_with_sni.rs43
-rw-r--r--mullvad-api/src/proxy.rs37
-rw-r--r--mullvad-cli/src/cmds/api_access.rs286
-rw-r--r--mullvad-cli/src/cmds/bridge.rs350
-rw-r--r--mullvad-cli/src/cmds/mod.rs1
-rw-r--r--mullvad-cli/src/cmds/proxies.rs214
-rw-r--r--mullvad-daemon/src/api.rs27
-rw-r--r--mullvad-daemon/src/custom_list.rs6
-rw-r--r--mullvad-daemon/src/lib.rs20
-rw-r--r--mullvad-daemon/src/management_interface.rs2
-rw-r--r--mullvad-daemon/src/migrations/mod.rs2
-rw-r--r--mullvad-daemon/src/migrations/v7.rs995
-rw-r--r--mullvad-daemon/src/settings/mod.rs12
-rw-r--r--mullvad-management-interface/proto/management_interface.proto82
-rw-r--r--mullvad-management-interface/src/types/conversions/access_method.rs109
-rw-r--r--mullvad-management-interface/src/types/conversions/net.rs142
-rw-r--r--mullvad-management-interface/src/types/conversions/relay_constraints.rs175
-rw-r--r--mullvad-relay-selector/src/lib.rs53
-rw-r--r--mullvad-types/src/access_method.rs136
-rw-r--r--mullvad-types/src/custom_tunnel.rs4
-rw-r--r--mullvad-types/src/relay_constraints.rs51
-rw-r--r--mullvad-types/src/relay_list.rs14
-rw-r--r--mullvad-types/src/settings/mod.rs8
-rw-r--r--talpid-core/src/firewall/linux.rs21
-rw-r--r--talpid-core/src/firewall/macos.rs26
-rw-r--r--talpid-core/src/firewall/mod.rs14
-rw-r--r--talpid-core/src/firewall/windows.rs77
-rw-r--r--talpid-core/src/tunnel/mod.rs33
-rw-r--r--talpid-core/src/tunnel_state_machine/connected_state.rs31
-rw-r--r--talpid-core/src/tunnel_state_machine/connecting_state.rs22
-rw-r--r--talpid-openvpn/src/lib.rs90
-rw-r--r--talpid-openvpn/src/process/openvpn.rs24
-rw-r--r--talpid-openvpn/src/proxy/mod.rs33
-rw-r--r--talpid-openvpn/src/proxy/shadowsocks.rs25
-rw-r--r--talpid-types/src/net/mod.rs24
-rw-r--r--talpid-types/src/net/openvpn.rs163
-rw-r--r--talpid-types/src/net/proxy.rs137
-rw-r--r--test/test-manager/src/tests/tunnel.rs13
-rw-r--r--windows/winfw/src/winfw/fwcontext.cpp10
-rw-r--r--windows/winfw/src/winfw/fwcontext.h4
-rw-r--r--windows/winfw/src/winfw/rules/multi/permitvpnrelay.cpp9
-rw-r--r--windows/winfw/src/winfw/rules/multi/permitvpnrelay.h4
-rw-r--r--windows/winfw/src/winfw/winfw.cpp32
-rw-r--r--windows/winfw/src/winfw/winfw.h6
53 files changed, 2512 insertions, 1272 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f6c4e06fa1..08e03e88a2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -35,6 +35,8 @@ Line wrap the file at 100 chars. Th
### Changed
- Remove `--location` flag from `mullvad status` CLI. Location and IP will now always
be printed (if available). `mullvad status listen` no longer prints location info.
+- Custom socks5 bridges get a new CLI interface and now work without split tunneling or root.
+ In the CLI these can be found under `mullvad bridge set custom`.
#### Android
- Migrate to Compose Navigation which also improves screen transition animations.
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index ad7b60dec2..d5d29ad709 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -15,6 +15,7 @@ import {
AuthFailedError,
BridgeSettings,
BridgeState,
+ BridgeType,
ConnectionConfig,
Constraint,
CustomListError,
@@ -51,7 +52,6 @@ import {
ObfuscationSettings,
ObfuscationType,
Ownership,
- ProxySettings,
ProxyType,
RelayEndpointType,
RelayLocation,
@@ -341,13 +341,52 @@ export class DaemonRpc {
public async setBridgeSettings(bridgeSettings: BridgeSettings): Promise<void> {
const grpcBridgeSettings = new grpcTypes.BridgeSettings();
- if ('normal' in bridgeSettings) {
- const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal);
- grpcBridgeSettings.setNormal(normalSettings);
+ if (bridgeSettings.type === 'custom') {
+ throw configNotSupported;
}
- if ('custom' in bridgeSettings) {
- throw configNotSupported;
+ grpcBridgeSettings.setBridgeType(grpcTypes.BridgeSettings.BridgeType.NORMAL);
+
+ const normalSettings = convertToNormalBridgeSettings(bridgeSettings.normal);
+ grpcBridgeSettings.setNormal(normalSettings);
+
+ if (bridgeSettings.custom) {
+ const customProxy = new grpcTypes.CustomProxy();
+
+ const customSettings = bridgeSettings.custom;
+
+ if ('local' in customSettings) {
+ const local = customSettings.local;
+ const socks5Local = new grpcTypes.Socks5Local();
+ socks5Local.setLocalPort(local.localPort);
+ socks5Local.setRemoteIp(local.remoteIp);
+ socks5Local.setRemotePort(local.remotePort);
+ customProxy.setSocks5local(socks5Local);
+ }
+ if ('remote' in customSettings) {
+ const remote = customSettings.remote;
+ const socks5Remote = new grpcTypes.Socks5Remote();
+ if (remote.auth) {
+ const auth = new grpcTypes.SocksAuth();
+ auth.setUsername(remote.auth.username);
+ auth.setPassword(remote.auth.password);
+ socks5Remote.setAuth(auth);
+ }
+ socks5Remote.setIp(remote.ip);
+ socks5Remote.setPort(remote.port);
+ customProxy.setSocks5remote(socks5Remote);
+ }
+ if ('shadowsocks' in customSettings) {
+ const shadowsocks = customSettings.shadowsocks;
+ const shadowOut = new grpcTypes.Shadowsocks();
+ shadowOut.setCipher(shadowsocks.cipher);
+ shadowOut.setIp(shadowsocks.ip);
+ shadowOut.setPort(shadowsocks.port);
+ shadowOut.setPassword(shadowsocks.password);
+ customProxy.setShadowsocks(shadowOut);
+ }
+
+ grpcBridgeSettings.setCustom(customProxy);
}
await this.call<grpcTypes.BridgeSettings, Empty>(
@@ -1140,49 +1179,64 @@ function convertFromRelaySettings(
function convertFromBridgeSettings(bridgeSettings: grpcTypes.BridgeSettings): BridgeSettings {
const bridgeSettingsObject = bridgeSettings.toObject();
+
+ const detailsMap: Record<grpcTypes.BridgeSettings.BridgeType, BridgeType> = {
+ [grpcTypes.BridgeSettings.BridgeType.NORMAL]: 'normal',
+ [grpcTypes.BridgeSettings.BridgeType.CUSTOM]: 'custom',
+ };
+ const type = detailsMap[bridgeSettingsObject.bridgeType];
+
const normalSettings = bridgeSettingsObject.normal;
- if (normalSettings) {
- const locationConstraint = convertFromLocationConstraint(
- bridgeSettings.getNormal()?.getLocation(),
- );
- const location = wrapConstraint(locationConstraint);
- const providers = normalSettings.providersList;
- const ownership = convertFromOwnership(normalSettings.ownership);
- return {
- normal: {
- location,
- providers,
- ownership,
- },
- };
- }
+ const locationConstraint = convertFromLocationConstraint(
+ bridgeSettings.getNormal()?.getLocation(),
+ );
+ const location = wrapConstraint(locationConstraint);
+ const providers = normalSettings!.providersList;
+ const ownership = convertFromOwnership(normalSettings!.ownership);
- const customSettings = (settings: ProxySettings): BridgeSettings => {
- return { custom: settings };
+ const normal = {
+ location,
+ providers,
+ ownership,
};
- const localSettings = bridgeSettingsObject.local;
- if (localSettings) {
- return customSettings({
- port: localSettings.port,
- peer: localSettings.peer,
- });
- }
+ let custom = undefined;
- const remoteSettings = bridgeSettingsObject.remote;
- if (remoteSettings) {
- return customSettings({
- address: remoteSettings.address,
- auth: remoteSettings.auth && { ...remoteSettings.auth },
- });
+ if (bridgeSettingsObject.custom) {
+ const localSettings = bridgeSettingsObject.custom.socks5local;
+ if (localSettings) {
+ custom = {
+ local: {
+ localPort: localSettings.localPort,
+ remoteIp: localSettings.remoteIp,
+ remotePort: localSettings.remotePort,
+ },
+ };
+ }
+ const remoteSettings = bridgeSettingsObject.custom.socks5remote;
+ if (remoteSettings) {
+ custom = {
+ remote: {
+ ip: remoteSettings.ip,
+ port: remoteSettings.port,
+ auth: remoteSettings.auth && { ...remoteSettings.auth },
+ },
+ };
+ }
+ const shadowsocksSettings = bridgeSettingsObject.custom.shadowsocks;
+ if (shadowsocksSettings) {
+ custom = {
+ shadowsocks: {
+ ip: shadowsocksSettings.ip,
+ port: shadowsocksSettings.port,
+ password: shadowsocksSettings.password,
+ cipher: shadowsocksSettings.cipher,
+ },
+ };
+ }
}
- const shadowsocksSettings = bridgeSettingsObject.shadowsocks!;
- return customSettings({
- peer: shadowsocksSettings.peer!,
- password: shadowsocksSettings.password!,
- cipher: shadowsocksSettings.cipher!,
- });
+ return { type, normal, custom };
}
function convertFromConnectionConfig(
diff --git a/gui/src/main/default-settings.ts b/gui/src/main/default-settings.ts
index 55f420b659..fea88e4c27 100644
--- a/gui/src/main/default-settings.ts
+++ b/gui/src/main/default-settings.ts
@@ -29,11 +29,13 @@ export function getDefaultSettings(): ISettings {
},
},
bridgeSettings: {
+ type: 'normal',
normal: {
location: 'any',
providers: [],
ownership: Ownership.any,
},
+ custom: undefined,
},
bridgeState: 'auto',
tunnelOptions: {
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index b5eacf17b3..3806c12413 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -624,17 +624,13 @@ export default class AppRenderer {
private setBridgeSettings(bridgeSettings: BridgeSettings) {
const actions = this.reduxActions;
- if ('normal' in bridgeSettings) {
- actions.settings.updateBridgeSettings({
- normal: {
- location: liftConstraint(bridgeSettings.normal.location),
- },
- });
- } else if ('custom' in bridgeSettings) {
- actions.settings.updateBridgeSettings({
- custom: bridgeSettings.custom,
- });
- }
+ actions.settings.updateBridgeSettings({
+ type: bridgeSettings.type,
+ normal: {
+ location: liftConstraint(bridgeSettings.normal.location),
+ },
+ custom: bridgeSettings.custom,
+ });
}
private onDaemonConnected() {
diff --git a/gui/src/renderer/components/select-location/select-location-hooks.ts b/gui/src/renderer/components/select-location/select-location-hooks.ts
index 48d81e594c..b795f4a74f 100644
--- a/gui/src/renderer/components/select-location/select-location-hooks.ts
+++ b/gui/src/renderer/components/select-location/select-location-hooks.ts
@@ -11,6 +11,7 @@ import log from '../../../shared/logging';
import { useAppContext } from '../../context';
import { useRelaySettingsModifier } from '../../lib/constraint-updater';
import { useHistory } from '../../lib/history';
+import { useSelector } from '../../redux/store';
import { LocationType, SpecialBridgeLocationType } from './select-location-types';
import { useSelectLocationContext } from './SelectLocationContainer';
@@ -88,6 +89,7 @@ function useOnSelectLocation() {
export function useOnSelectBridgeLocation() {
const { updateBridgeSettings } = useAppContext();
const { setLocationType } = useSelectLocationContext();
+ const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
const setLocation = useCallback(async (bridgeUpdate: BridgeSettings) => {
if (bridgeUpdate) {
@@ -101,10 +103,14 @@ export function useOnSelectBridgeLocation() {
}
}, []);
- const onSelectRelay = useCallback((location: RelayLocation) => {
- const bridgeUpdate = new BridgeSettingsBuilder().location.fromRaw(location).build();
- return setLocation(bridgeUpdate);
- }, []);
+ const onSelectRelay = useCallback(
+ (location: RelayLocation) => {
+ const bridgeUpdate = new BridgeSettingsBuilder().location.fromRaw(location).build();
+ bridgeUpdate.custom = bridgeSettings.custom;
+ return setLocation(bridgeUpdate);
+ },
+ [bridgeSettings],
+ );
const onSelectSpecial = useCallback((location: SpecialBridgeLocationType) => {
switch (location) {
diff --git a/gui/src/renderer/lib/utilityHooks.ts b/gui/src/renderer/lib/utilityHooks.ts
index 49f508a883..8c3925762e 100644
--- a/gui/src/renderer/lib/utilityHooks.ts
+++ b/gui/src/renderer/lib/utilityHooks.ts
@@ -67,5 +67,5 @@ export function useNormalRelaySettings() {
export function useNormalBridgeSettings() {
const bridgeSettings = useSelector((state) => state.settings.bridgeSettings);
- return 'normal' in bridgeSettings ? bridgeSettings.normal : undefined;
+ return bridgeSettings.normal;
}
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index b400799095..3d03093e0a 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -1,6 +1,7 @@
import { IWindowsApplication } from '../../../shared/application-types';
import {
BridgeState,
+ BridgeType,
CustomLists,
IDnsOptions,
IpVersion,
@@ -51,13 +52,11 @@ export type RelaySettingsRedux =
};
};
-export type BridgeSettingsRedux =
- | {
- normal: NormalBridgeSettingsRedux;
- }
- | {
- custom: ProxySettings;
- };
+export type BridgeSettingsRedux = {
+ type: BridgeType;
+ normal: NormalBridgeSettingsRedux;
+ custom?: ProxySettings;
+};
export interface IRelayLocationRelayRedux {
hostname: string;
@@ -140,9 +139,11 @@ const initialState: ISettingsReduxState = {
allowLan: false,
enableIpv6: true,
bridgeSettings: {
+ type: 'normal',
normal: {
location: 'any',
},
+ custom: undefined,
},
bridgeState: 'auto',
blockWhenDisconnected: false,
diff --git a/gui/src/shared/bridge-settings-builder.ts b/gui/src/shared/bridge-settings-builder.ts
index 858bea055d..2ee5469707 100644
--- a/gui/src/shared/bridge-settings-builder.ts
+++ b/gui/src/shared/bridge-settings-builder.ts
@@ -7,11 +7,13 @@ export default class BridgeSettingsBuilder {
public build(): BridgeSettings {
if (this.payload.location) {
return {
+ type: 'normal',
normal: {
location: this.payload.location,
providers: this.payload.providers ?? [],
ownership: this.payload.ownership ?? Ownership.any,
},
+ custom: undefined,
};
} else {
throw new Error('Unsupported configuration');
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index e83f9e5afc..48a4110e13 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -345,15 +345,20 @@ export interface IDnsOptions {
};
}
-export type ProxySettings = ILocalProxySettings | IRemoteProxySettings | IShadowsocksProxySettings;
+export type ProxySettings =
+ | { local: ILocalProxySettings }
+ | { remote: IRemoteProxySettings }
+ | { shadowsocks: IShadowsocksProxySettings };
export interface ILocalProxySettings {
- port: number;
- peer: string;
+ localPort: number;
+ remoteIp: string;
+ remotePort: number;
}
export interface IRemoteProxySettings {
- address: string;
+ ip: string;
+ port: number;
auth?: IRemoteProxyAuth;
}
@@ -363,7 +368,8 @@ export interface IRemoteProxyAuth {
}
export interface IShadowsocksProxySettings {
- peer: string;
+ ip: string;
+ port: number;
password: string;
cipher: string;
}
@@ -451,7 +457,13 @@ export interface IBridgeConstraints {
ownership: Ownership;
}
-export type BridgeSettings = { normal: IBridgeConstraints } | { custom: ProxySettings };
+export type BridgeType = 'normal' | 'custom';
+
+export interface BridgeSettings {
+ type: BridgeType;
+ normal: IBridgeConstraints;
+ custom?: ProxySettings;
+}
export interface ISocketAddress {
host: string;
diff --git a/mullvad-api/src/https_client_with_sni.rs b/mullvad-api/src/https_client_with_sni.rs
index 7d559faa7a..3a9bb8d75f 100644
--- a/mullvad-api/src/https_client_with_sni.rs
+++ b/mullvad-api/src/https_client_with_sni.rs
@@ -236,15 +236,15 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode {
type Error = ProxyConfigError;
fn try_from(config: ApiConnectionMode) -> Result<Self, Self::Error> {
- use mullvad_types::access_method;
use std::net::Ipv4Addr;
+ use talpid_types::net::proxy;
Ok(match config {
ApiConnectionMode::Direct => InnerConnectionMode::Direct,
ApiConnectionMode::Proxied(proxy_settings) => match proxy_settings {
ProxyConfig::Shadowsocks(config) => {
InnerConnectionMode::Shadowsocks(ShadowsocksConfig {
params: ParsedShadowsocksConfig {
- peer: config.peer,
+ peer: config.endpoint,
password: config.password,
cipher: CipherKind::from_str(&config.cipher)
.map_err(|_| ProxyConfigError::InvalidCipher(config.cipher))?,
@@ -252,29 +252,22 @@ impl TryFrom<ApiConnectionMode> for InnerConnectionMode {
proxy_context: SsContext::new_shared(ServerType::Local),
})
}
- ProxyConfig::Socks(config) => match config {
- access_method::Socks5::Local(config) => {
- InnerConnectionMode::Socks5(SocksConfig {
- peer: SocketAddr::new(
- IpAddr::from(Ipv4Addr::LOCALHOST),
- config.local_port,
- ),
- authentication: SocksAuth::None,
- })
- }
- access_method::Socks5::Remote(config) => {
- let authentication = match config.authentication {
- Some(access_method::SocksAuth { username, password }) => {
- SocksAuth::Password { username, password }
- }
- None => SocksAuth::None,
- };
- InnerConnectionMode::Socks5(SocksConfig {
- peer: config.peer,
- authentication,
- })
- }
- },
+ ProxyConfig::Socks5Local(config) => InnerConnectionMode::Socks5(SocksConfig {
+ peer: SocketAddr::new(IpAddr::from(Ipv4Addr::LOCALHOST), config.local_port),
+ authentication: SocksAuth::None,
+ }),
+ ProxyConfig::Socks5Remote(config) => {
+ let authentication = match config.auth {
+ Some(proxy::SocksAuth { username, password }) => {
+ SocksAuth::Password { username, password }
+ }
+ None => SocksAuth::None,
+ };
+ InnerConnectionMode::Socks5(SocksConfig {
+ peer: config.endpoint,
+ authentication,
+ })
+ }
},
})
}
diff --git a/mullvad-api/src/proxy.rs b/mullvad-api/src/proxy.rs
index 783cc8a157..caf2068abb 100644
--- a/mullvad-api/src/proxy.rs
+++ b/mullvad-api/src/proxy.rs
@@ -1,6 +1,5 @@
use futures::Stream;
use hyper::client::connect::Connected;
-use mullvad_types::access_method;
use serde::{Deserialize, Serialize};
use std::{
fmt, io,
@@ -8,6 +7,7 @@ use std::{
pin::Pin,
task::{self, Poll},
};
+use talpid_types::net::proxy;
use talpid_types::{
net::{AllowedClients, Endpoint, TransportProtocol},
ErrorExt,
@@ -38,8 +38,9 @@ impl fmt::Display for ApiConnectionMode {
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum ProxyConfig {
- Shadowsocks(access_method::Shadowsocks),
- Socks(access_method::Socks5),
+ Shadowsocks(proxy::Shadowsocks),
+ Socks5Local(proxy::Socks5Local),
+ Socks5Remote(proxy::Socks5Remote),
}
impl ProxyConfig {
@@ -47,14 +48,12 @@ impl ProxyConfig {
fn get_endpoint(&self) -> Endpoint {
match self {
ProxyConfig::Shadowsocks(shadowsocks) => {
- Endpoint::from_socket_address(shadowsocks.peer, TransportProtocol::Tcp)
+ Endpoint::from_socket_address(shadowsocks.endpoint, TransportProtocol::Tcp)
+ }
+ ProxyConfig::Socks5Local(local) => local.remote_endpoint,
+ ProxyConfig::Socks5Remote(remote) => {
+ Endpoint::from_socket_address(remote.endpoint, TransportProtocol::Tcp)
}
- ProxyConfig::Socks(socks) => match socks {
- access_method::Socks5::Local(local) => local.remote_endpoint,
- access_method::Socks5::Remote(remote) => {
- Endpoint::from_socket_address(remote.peer, TransportProtocol::Tcp)
- }
- },
}
}
}
@@ -64,12 +63,10 @@ impl fmt::Display for ProxyConfig {
let endpoint = self.get_endpoint();
match self {
ProxyConfig::Shadowsocks(_) => write!(f, "Shadowsocks {}", endpoint),
- ProxyConfig::Socks(socks) => match socks {
- access_method::Socks5::Remote(_) => write!(f, "Socks5 {}", endpoint),
- access_method::Socks5::Local(local) => {
- write!(f, "Socks5 {} via localhost:{}", endpoint, local.local_port)
- }
- },
+ ProxyConfig::Socks5Remote(_) => write!(f, "Socks5 {}", endpoint),
+ ProxyConfig::Socks5Local(local) => {
+ write!(f, "Socks5 {} via localhost:{}", endpoint, local.local_port)
+ }
}
}
}
@@ -145,20 +142,16 @@ impl ApiConnectionMode {
#[cfg(unix)]
pub fn allowed_clients(&self) -> AllowedClients {
- use access_method::Socks5;
match self {
- ApiConnectionMode::Proxied(ProxyConfig::Socks(Socks5::Local(_))) => AllowedClients::All,
+ ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::All,
ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => AllowedClients::Root,
}
}
#[cfg(windows)]
pub fn allowed_clients(&self) -> AllowedClients {
- use access_method::Socks5;
match self {
- ApiConnectionMode::Proxied(ProxyConfig::Socks(Socks5::Local(_))) => {
- AllowedClients::all()
- }
+ ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(_)) => AllowedClients::all(),
ApiConnectionMode::Direct | ApiConnectionMode::Proxied(_) => {
let daemon_exe = std::env::current_exe().expect("failed to obtain executable path");
vec![
diff --git a/mullvad-cli/src/cmds/api_access.rs b/mullvad-cli/src/cmds/api_access.rs
index c6e01c52d6..887d378fa4 100644
--- a/mullvad-cli/src/cmds/api_access.rs
+++ b/mullvad-cli/src/cmds/api_access.rs
@@ -1,10 +1,11 @@
use anyhow::{anyhow, Result};
use mullvad_management_interface::MullvadProxyClient;
-use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, CustomAccessMethod};
-use std::net::IpAddr;
+use mullvad_types::access_method::{AccessMethod, AccessMethodSetting};
+use talpid_types::net::proxy::CustomProxy;
use clap::{Args, Subcommand};
-use talpid_types::net::{openvpn::SHADOWSOCKS_CIPHERS, TransportProtocol};
+
+use super::proxies::{ProxyEditParams, ShadowsocksAdd, Socks5LocalAdd, Socks5RemoteAdd};
#[derive(Subcommand, Debug, Clone)]
pub enum ApiAccess {
@@ -99,9 +100,7 @@ impl ApiAccess {
/// Edit the data of an API access method.
async fn edit(cmd: EditCustomCommands) -> Result<()> {
- use mullvad_types::access_method::{
- Shadowsocks, Socks5, Socks5Local, Socks5Remote, SocksAuth,
- };
+ use talpid_types::net::proxy::{Shadowsocks, Socks5Local, Socks5Remote, SocksAuth};
let mut rpc = MullvadProxyClient::new().await?;
let mut api_access_method = Self::get_access_method(&mut rpc, &cmd.item).await?;
@@ -109,49 +108,47 @@ impl ApiAccess {
let access_method = match api_access_method.as_custom() {
None => return Err(anyhow!("Can not edit built-in access method")),
Some(x) => match x.clone() {
- CustomAccessMethod::Shadowsocks(shadowsocks) => {
- let ip = cmd.params.ip.unwrap_or(shadowsocks.peer.ip());
- let port = cmd.params.port.unwrap_or(shadowsocks.peer.port());
+ CustomProxy::Shadowsocks(shadowsocks) => {
+ let ip = cmd.params.ip.unwrap_or(shadowsocks.endpoint.ip());
+ let port = cmd.params.port.unwrap_or(shadowsocks.endpoint.port());
let password = cmd.params.password.unwrap_or(shadowsocks.password);
let cipher = cmd.params.cipher.unwrap_or(shadowsocks.cipher);
AccessMethod::from(Shadowsocks::new((ip, port), cipher, password))
}
- CustomAccessMethod::Socks5(socks) => match socks {
- Socks5::Local(local) => {
- let remote_ip = cmd.params.ip.unwrap_or(local.remote_endpoint.address.ip());
- let remote_port = cmd
- .params
- .port
- .unwrap_or(local.remote_endpoint.address.port());
- let local_port = cmd.params.local_port.unwrap_or(local.local_port);
- let remote_peer_transport_protocol = cmd
- .params
- .transport_protocol
- .unwrap_or(local.remote_endpoint.protocol);
- AccessMethod::from(Socks5Local::new_with_transport_protocol(
- (remote_ip, remote_port),
- local_port,
- remote_peer_transport_protocol,
- ))
- }
- Socks5::Remote(remote) => {
- let ip = cmd.params.ip.unwrap_or(remote.peer.ip());
- let port = cmd.params.port.unwrap_or(remote.peer.port());
- AccessMethod::from(match remote.authentication {
- None => Socks5Remote::new((ip, port)),
- Some(SocksAuth { username, password }) => {
- let username = cmd.params.username.unwrap_or(username);
- let password = cmd.params.password.unwrap_or(password);
- let auth = SocksAuth { username, password };
- Socks5Remote::new_with_authentication((ip, port), auth)
- }
- })
- }
- },
+ CustomProxy::Socks5Local(local) => {
+ let remote_ip = cmd.params.ip.unwrap_or(local.remote_endpoint.address.ip());
+ let remote_port = cmd
+ .params
+ .port
+ .unwrap_or(local.remote_endpoint.address.port());
+ let local_port = cmd.params.local_port.unwrap_or(local.local_port);
+ let remote_peer_transport_protocol = cmd
+ .params
+ .transport_protocol
+ .unwrap_or(local.remote_endpoint.protocol);
+ AccessMethod::from(Socks5Local::new_with_transport_protocol(
+ (remote_ip, remote_port),
+ local_port,
+ remote_peer_transport_protocol,
+ ))
+ }
+ CustomProxy::Socks5Remote(remote) => {
+ let ip = cmd.params.ip.unwrap_or(remote.endpoint.ip());
+ let port = cmd.params.port.unwrap_or(remote.endpoint.port());
+ AccessMethod::from(match remote.auth {
+ None => Socks5Remote::new((ip, port)),
+ Some(SocksAuth { username, password }) => {
+ let username = cmd.params.username.unwrap_or(username);
+ let password = cmd.params.password.unwrap_or(password);
+ let auth = SocksAuth { username, password };
+ Socks5Remote::new_with_authentication((ip, port), auth)
+ }
+ })
+ }
},
};
- if let Some(name) = cmd.params.name {
+ if let Some(name) = cmd.name {
api_access_method.name = name;
};
api_access_method.access_method = access_method;
@@ -259,19 +256,12 @@ pub enum AddCustomCommands {
Shadowsocks {
/// An easy to remember name for this custom proxy
name: String,
- /// The IP of the remote Shadowsocks-proxy
- remote_ip: IpAddr,
- /// Port on which the remote Shadowsocks-proxy listens for traffic
- remote_port: u16,
- /// Password for authentication
- password: String,
- /// Cipher to use
- #[arg(long, value_parser = SHADOWSOCKS_CIPHERS)]
- cipher: String,
/// Disable the use of this custom access method. It has to be manually
/// enabled at a later stage to be used when accessing the Mullvad API.
#[arg(default_value_t = false, short, long)]
disabled: bool,
+ #[clap(flatten)]
+ add: ShadowsocksAdd,
},
}
@@ -281,53 +271,26 @@ pub enum AddSocks5Commands {
Remote {
/// An easy to remember name for this custom proxy
name: String,
- /// IP of the remote SOCKS5-proxy
- remote_ip: IpAddr,
- /// Port on which the remote SOCKS5-proxy listens for traffic
- remote_port: u16,
- #[clap(flatten)]
- authentication: Option<SocksAuthentication>,
/// Disable the use of this custom access method. It has to be manually
/// enabled at a later stage to be used when accessing the Mullvad API.
#[arg(default_value_t = false, short, long)]
disabled: bool,
+ #[clap(flatten)]
+ add: Socks5RemoteAdd,
},
/// Configure a local SOCKS5 proxy
Local {
/// An easy to remember name for this custom proxy
name: String,
- /// The port that the server on localhost is listening on
- local_port: u16,
- /// The IP of the remote peer
- remote_ip: IpAddr,
- /// The port of the remote peer
- remote_port: u16,
- /// The Mullvad App can not know which transport protocol that the
- /// remote peer accepts, but it needs to know this in order to correctly
- /// exempt the connection traffic in the firewall.
- ///
- /// By default, the transport protocol is assumed to be `TCP`, but it
- /// can optionally be set to `UDP` as well.
- #[arg(long, default_value_t = TransportProtocol::Tcp)]
- transport_protocol: TransportProtocol,
/// Disable the use of this custom access method. It has to be manually
/// enabled at a later stage to be used when accessing the Mullvad API.
#[arg(default_value_t = false, short, long)]
disabled: bool,
+ #[clap(flatten)]
+ add: Socks5LocalAdd,
},
}
-#[derive(Args, Debug, Clone)]
-#[group(requires_all = ["username", "password"])] // https://github.com/clap-rs/clap/issues/5092
-pub struct SocksAuthentication {
- /// Username for authentication against a remote SOCKS5 proxy
- #[arg(short, long, required = false)]
- username: String,
- /// Password for authentication against a remote SOCKS5 proxy
- #[arg(short, long, required = false)]
- password: String,
-}
-
impl AddCustomCommands {
fn name(&self) -> &str {
match self {
@@ -374,9 +337,12 @@ pub struct EditCustomCommands {
/// Which API access method to edit
#[clap(flatten)]
item: SelectItem,
+ /// Name of the API access method in the Mullvad client [All]
+ #[arg(long)]
+ name: Option<String>,
/// Editing parameters
#[clap(flatten)]
- params: EditParams,
+ params: ProxyEditParams,
}
#[derive(Args, Debug, Clone)]
@@ -384,27 +350,8 @@ pub struct EditParams {
/// Name of the API access method in the Mullvad client [All]
#[arg(long)]
name: Option<String>,
- /// Username for authentication [Socks5 (Remote proxy)]
- #[arg(long)]
- username: Option<String>,
- /// Password for authentication [Socks5 (Remote proxy), Shadowsocks]
- #[arg(long)]
- password: Option<String>,
- /// Cipher to use [Shadowsocks]
- #[arg(value_parser = SHADOWSOCKS_CIPHERS, long)]
- cipher: Option<String>,
- /// The IP of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks]
- #[arg(long)]
- ip: Option<IpAddr>,
- /// The port of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks]
- #[arg(long)]
- port: Option<u16>,
- /// The port that the server on localhost is listening on [Socks5 (Local proxy)]
- #[arg(long)]
- local_port: Option<u16>,
- /// The transport protocol used by the remote proxy [Socks5 (Local proxy)]
- #[arg(long)]
- transport_protocol: Option<TransportProtocol>,
+ #[clap(flatten)]
+ edit_params: ProxyEditParams,
}
/// Implement conversions from CLI types to Daemon types.
@@ -412,23 +359,29 @@ pub struct EditParams {
/// Since these are not supposed to be used outside of the CLI,
/// we define them in a hidden-away module.
mod conversions {
- use super::{AddCustomCommands, AddSocks5Commands, SocksAuthentication};
+ use crate::cmds::proxies::SocksAuthentication;
+
+ use super::{AddCustomCommands, AddSocks5Commands};
use mullvad_types::access_method as daemon_types;
+ use talpid_types::net::proxy as talpid_types;
impl From<AddCustomCommands> for daemon_types::AccessMethod {
fn from(value: AddCustomCommands) -> Self {
match value {
AddCustomCommands::Socks5(socks) => match socks {
AddSocks5Commands::Local {
- local_port,
- remote_ip,
- remote_port,
name: _,
disabled: _,
- transport_protocol,
+ add,
} => {
+ let (local_port, remote_ip, remote_port, transport_protocol) = (
+ add.local_port,
+ add.remote_ip,
+ add.remote_port,
+ add.transport_protocol,
+ );
println!("Adding SOCKS5-proxy: localhost:{local_port} => {remote_ip}:{remote_port}/{transport_protocol}");
- daemon_types::Socks5Local::new_with_transport_protocol(
+ talpid_types::Socks5Local::new_with_transport_protocol(
(remote_ip, remote_port),
local_port,
transport_protocol,
@@ -436,41 +389,38 @@ mod conversions {
.into()
}
AddSocks5Commands::Remote {
- remote_ip,
- remote_port,
- authentication,
+ add,
name: _,
disabled: _,
- } => daemon_types::AccessMethod::from(daemon_types::Socks5::Remote(
- match authentication {
- Some(SocksAuthentication { username, password }) => {
- println!("Adding SOCKS5-proxy: {username}:{password}@{remote_ip}:{remote_port}");
- let auth =
- mullvad_types::access_method::SocksAuth { username, password };
- daemon_types::Socks5Remote::new_with_authentication(
- (remote_ip, remote_port),
- auth,
- )
- }
- None => {
- println!("Adding SOCKS5-proxy: {remote_ip}:{remote_port}");
- daemon_types::Socks5Remote::new((remote_ip, remote_port))
- }
- },
- )),
+ } => daemon_types::AccessMethod::from(match add.authentication {
+ Some(SocksAuthentication { username, password }) => {
+ println!(
+ "Adding SOCKS5-proxy: {username}:{password}@{}:{}",
+ add.remote_ip, add.remote_port
+ );
+ let auth = talpid_types::SocksAuth { username, password };
+ talpid_types::Socks5Remote::new_with_authentication(
+ (add.remote_ip, add.remote_port),
+ auth,
+ )
+ }
+ None => {
+ println!("Adding SOCKS5-proxy: {}:{}", add.remote_ip, add.remote_port);
+ talpid_types::Socks5Remote::new((add.remote_ip, add.remote_port))
+ }
+ }),
},
AddCustomCommands::Shadowsocks {
- remote_ip,
- remote_port,
- password,
- cipher,
+ add,
name: _,
disabled: _,
} => {
+ let (password, cipher, remote_ip, remote_port) =
+ (add.password, add.cipher, add.remote_ip, add.remote_port);
println!(
"Adding Shadowsocks-proxy: {password} @ {remote_ip}:{remote_port} using {cipher}"
);
- daemon_types::AccessMethod::from(daemon_types::Shadowsocks::new(
+ daemon_types::AccessMethod::from(talpid_types::Shadowsocks::new(
(remote_ip, remote_port),
cipher,
password,
@@ -483,9 +433,8 @@ mod conversions {
/// Pretty printing of [`ApiAccessMethod`]s
mod pp {
- use mullvad_types::access_method::{
- AccessMethod, AccessMethodSetting, CustomAccessMethod, Socks5, SocksAuth,
- };
+ use crate::cmds::proxies::pp::CustomProxyFormatter;
+ use mullvad_types::access_method::{AccessMethod, AccessMethodSetting};
pub struct ApiAccessMethodFormatter<'a> {
api_access_method: &'a AccessMethodSetting,
@@ -517,8 +466,6 @@ mod pp {
impl<'a> std::fmt::Display for ApiAccessMethodFormatter<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- use crate::print_option;
-
let write_status = |f: &mut std::fmt::Formatter<'_>, enabled: bool| {
if enabled {
write!(f, " *")
@@ -535,55 +482,18 @@ mod pp {
}
Ok(())
}
- AccessMethod::Custom(method) => match &method {
- CustomAccessMethod::Shadowsocks(shadowsocks) => {
- write!(f, "{}", self.api_access_method.get_name())?;
- if self.settings.write_enabled {
- write_status(f, self.api_access_method.enabled())?;
- }
- writeln!(f)?;
- print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher));
- print_option!("Peer", shadowsocks.peer);
- print_option!("Password", shadowsocks.password);
- Ok(())
+ AccessMethod::Custom(method) => {
+ write!(f, "{}", self.api_access_method.get_name())?;
+ if self.settings.write_enabled {
+ write_status(f, self.api_access_method.enabled())?;
}
- CustomAccessMethod::Socks5(socks) => match socks {
- Socks5::Remote(remote) => {
- write!(f, "{}", self.api_access_method.get_name())?;
- if self.settings.write_enabled {
- write_status(f, self.api_access_method.enabled())?;
- }
- writeln!(f)?;
- print_option!("Protocol", "Socks5");
- print_option!("Peer", remote.peer);
- match &remote.authentication {
- Some(SocksAuth { username, password }) => {
- print_option!("Username", username);
- print_option!("Password", password);
- }
- None => (),
- }
- Ok(())
- }
- Socks5::Local(local) => {
- write!(f, "{}", self.api_access_method.get_name())?;
- if self.settings.write_enabled {
- write_status(f, self.api_access_method.enabled())?;
- }
- writeln!(f)?;
- print_option!("Protocol", "Socks5 (local)");
- print_option!(
- "Peer",
- format!(
- "{}/{}",
- local.remote_endpoint.address, local.remote_endpoint.protocol
- )
- );
- print_option!("Local port", local.local_port);
- Ok(())
- }
- },
- },
+ writeln!(f)?;
+ let formatter = CustomProxyFormatter {
+ custom_proxy: method,
+ };
+ write!(f, "{}", formatter)?;
+ Ok(())
+ }
}
}
}
diff --git a/mullvad-cli/src/cmds/bridge.rs b/mullvad-cli/src/cmds/bridge.rs
index 4abccb99e8..329921868b 100644
--- a/mullvad-cli/src/cmds/bridge.rs
+++ b/mullvad-cli/src/cmds/bridge.rs
@@ -1,17 +1,22 @@
-use anyhow::Result;
+use anyhow::{bail, Result};
use clap::Subcommand;
use mullvad_management_interface::MullvadProxyClient;
use mullvad_types::{
relay_constraints::{
- BridgeConstraints, BridgeConstraintsFormatter, BridgeSettings, BridgeState, Constraint,
- LocationConstraint, Ownership, Provider, Providers,
+ BridgeConstraintsFormatter, BridgeState, BridgeType, Constraint, LocationConstraint,
+ Ownership, Provider, Providers,
},
relay_list::RelayEndpointData,
};
-use std::net::{IpAddr, SocketAddr};
-use talpid_types::net::openvpn::{self, SHADOWSOCKS_CIPHERS};
+use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote};
-use super::{relay::resolve_location_constraint, relay_constraints::LocationArgs};
+use crate::cmds::proxies::pp::CustomProxyFormatter;
+
+use super::{
+ proxies::{ProxyEditParams, ShadowsocksAdd, Socks5LocalAdd, Socks5RemoteAdd},
+ relay::resolve_location_constraint,
+ relay_constraints::LocationArgs,
+};
#[derive(Subcommand, Debug)]
pub enum Bridge {
@@ -73,73 +78,48 @@ pub enum SetCommands {
/// Configure a SOCKS5 proxy
#[clap(subcommand)]
- Custom(SetCustomCommands),
+ Custom(CustomCommands),
+}
+
+#[derive(Subcommand, Debug, Clone)]
+pub enum CustomCommands {
+ /// Create or update and enable the custom bridge configuration.
+ #[clap(subcommand)]
+ Set(AddCustomCommands),
+ /// Edit an existing custom bridge configuration.
+ Edit(ProxyEditParams),
+ /// Use an existing custom bridge configuration.
+ Use,
+ /// Stop using the custom bridge configuration.
+ Disable,
+}
+
+#[derive(Subcommand, Debug, Clone)]
+pub enum AddCustomCommands {
+ #[clap(subcommand)]
+ Socks5(AddSocks5Commands),
+ /// Configure bundled Shadowsocks proxy
+ Shadowsocks {
+ #[clap(flatten)]
+ add: ShadowsocksAdd,
+ },
}
#[derive(Subcommand, Debug, Clone)]
-pub enum SetCustomCommands {
+pub enum AddSocks5Commands {
/// Configure a local SOCKS5 proxy
- #[cfg_attr(
- target_os = "linux",
- clap(
- about = "Registers a local SOCKS5 proxy. The server must be excluded using \
- 'mullvad-exclude', or `SO_MARK` must be set to '0x6d6f6c65', in order \
- to bypass firewall restrictions"
- )
- )]
- #[cfg_attr(
- target_os = "windows",
- clap(
- about = "Registers a local SOCKS5 proxy. The server must be excluded using \
- split tunneling in order to bypass firewall restrictions"
- )
- )]
- #[cfg_attr(
- target_os = "macos",
- clap(
- about = "Registers a local SOCKS5 proxy. The server must run as root to bypass \
- firewall restrictions"
- )
+ #[clap(
+ about = "Registers a local SOCKS5 proxy. Will allow all local programs to leak traffic *only* to the remote endpoint."
)]
Local {
- /// The port that the server on localhost is listening on
- local_port: u16,
- /// The IP of the remote peer
- remote_ip: IpAddr,
- /// The port of the remote peer
- remote_port: u16,
+ #[clap(flatten)]
+ add: Socks5LocalAdd,
},
/// 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>,
- /// Password for authentication
- #[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,
-
- /// Password for authentication
- #[arg(default_value = "mullvad")]
- password: String,
-
- /// Cipher to use
- #[arg(value_parser = SHADOWSOCKS_CIPHERS, default_value = "aes-256-gcm")]
- cipher: String,
+ #[clap(flatten)]
+ add: Socks5RemoteAdd,
},
}
@@ -188,133 +168,35 @@ impl Bridge {
};
Self::update_bridge_settings(&mut rpc, None, Some(providers), None).await
}
- SetCommands::Custom(subcmd) => Self::set_custom(subcmd).await,
+ SetCommands::Custom(subcmd) => Self::handle_custom(subcmd).await,
}
}
- async fn set_custom(subcmd: SetCustomCommands) -> Result<()> {
- match subcmd {
- SetCustomCommands::Local {
- local_port,
- remote_ip,
- remote_port,
- } => {
- let local_proxy = openvpn::LocalProxySettings {
- port: local_port,
- peer: SocketAddr::new(remote_ip, remote_port),
- };
- let packed_proxy = openvpn::ProxySettings::Local(local_proxy);
- if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
- panic!("{}", error);
- }
-
- let mut rpc = MullvadProxyClient::new().await?;
- rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))
- .await?;
- }
- SetCustomCommands::Remote {
- remote_ip,
- remote_port,
- username,
- password,
- } => {
- let auth = match (username, password) {
- (Some(username), Some(password)) => {
- Some(openvpn::ProxyAuth { username, password })
- }
- _ => None,
- };
- let proxy = openvpn::RemoteProxySettings {
- address: SocketAddr::new(remote_ip, remote_port),
- auth,
- };
- let packed_proxy = openvpn::ProxySettings::Remote(proxy);
- if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
- panic!("{}", error);
- }
-
- let mut rpc = MullvadProxyClient::new().await?;
- rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))
- .await?;
- }
- SetCustomCommands::Shadowsocks {
- remote_ip,
- remote_port,
- password,
- cipher,
- } => {
- let proxy = openvpn::ShadowsocksProxySettings {
- peer: SocketAddr::new(remote_ip, remote_port),
- password,
- cipher,
- #[cfg(target_os = "linux")]
- fwmark: None,
- };
- let packed_proxy = openvpn::ProxySettings::Shadowsocks(proxy);
- if let Err(error) = openvpn::validate_proxy_settings(&packed_proxy) {
- panic!("{}", error);
- }
-
- let mut rpc = MullvadProxyClient::new().await?;
- rpc.set_bridge_settings(BridgeSettings::Custom(packed_proxy))
- .await?;
- }
- }
-
- println!("Updated bridge settings");
- Ok(())
- }
-
async fn get() -> Result<()> {
let mut rpc = MullvadProxyClient::new().await?;
let settings = rpc.get_settings().await?;
println!("Bridge state: {}", settings.bridge_state);
- match settings.bridge_settings {
- BridgeSettings::Custom(proxy) => match proxy {
- openvpn::ProxySettings::Local(local_proxy) => Self::print_local_proxy(&local_proxy),
- openvpn::ProxySettings::Remote(remote_proxy) => {
- Self::print_remote_proxy(&remote_proxy)
- }
- openvpn::ProxySettings::Shadowsocks(shadowsocks_proxy) => {
- Self::print_shadowsocks_proxy(&shadowsocks_proxy)
- }
- },
- BridgeSettings::Normal(ref constraints) => {
- println!(
- "Bridge constraints: {}",
- BridgeConstraintsFormatter {
- constraints,
- custom_lists: &settings.custom_lists
- }
- )
- }
- };
- Ok(())
- }
-
- fn print_local_proxy(proxy: &openvpn::LocalProxySettings) {
- println!("proxy: local");
- println!(" local port: {}", proxy.port);
- println!(" peer address: {}", proxy.peer);
- }
+ println!(
+ "Active bridge type: {}",
+ settings.bridge_settings.bridge_type
+ );
- fn print_remote_proxy(proxy: &openvpn::RemoteProxySettings) {
- println!("proxy: remote");
- println!(" server address: {}", proxy.address);
+ println!("Normal constraints");
+ println!(
+ "{:<4}{}",
+ "",
+ BridgeConstraintsFormatter {
+ constraints: &settings.bridge_settings.normal,
+ custom_lists: &settings.custom_lists
+ }
+ );
- if let Some(ref auth) = proxy.auth {
- println!(" auth username: {}", auth.username);
- println!(" auth password: {}", auth.password);
- } else {
- println!(" auth: none");
+ if let Some(ref custom_proxy) = settings.bridge_settings.custom {
+ println!("Custom proxy");
+ println!("{}", CustomProxyFormatter { custom_proxy });
}
- }
- fn print_shadowsocks_proxy(proxy: &openvpn::ShadowsocksProxySettings) {
- println!("proxy: Shadowsocks");
- println!(" peer address: {}", proxy.peer);
- println!(" password: {}", proxy.password);
- println!(" cipher: {}", proxy.cipher);
+ Ok(())
}
async fn list() -> Result<()> {
@@ -379,33 +261,99 @@ impl Bridge {
providers: Option<Constraint<Providers>>,
ownership: Option<Constraint<Ownership>>,
) -> Result<()> {
- let constraints = match rpc.get_settings().await?.bridge_settings {
- BridgeSettings::Normal(mut constraints) => {
- if let Some(new_location) = location {
- constraints.location = new_location;
- }
- if let Some(new_providers) = providers {
- constraints.providers = new_providers;
- }
- if let Some(new_ownership) = ownership {
- constraints.ownership = new_ownership;
- }
- constraints
+ let mut settings = rpc.get_settings().await?.bridge_settings;
+ if let Some(new_location) = location {
+ settings.normal.location = new_location;
+ }
+ if let Some(new_providers) = providers {
+ settings.normal.providers = new_providers;
+ }
+ if let Some(new_ownership) = ownership {
+ settings.normal.ownership = new_ownership;
+ }
+
+ settings.bridge_type = BridgeType::Normal;
+
+ rpc.set_bridge_settings(settings).await?;
+
+ println!("Updated bridge settings");
+
+ Ok(())
+ }
+
+ pub async fn handle_custom(subcmd: CustomCommands) -> Result<()> {
+ match subcmd {
+ CustomCommands::Set(set_custom_commands) => {
+ Self::custom_bridge_set(set_custom_commands).await
}
- _ => BridgeConstraints {
- location: location
- .unwrap_or(Constraint::Any)
- .map(LocationConstraint::from),
- providers: providers.unwrap_or(Constraint::Any),
- ownership: ownership.unwrap_or(Constraint::Any),
- },
+ CustomCommands::Edit(edit) => Self::custom_bridge_edit(edit).await,
+ CustomCommands::Use => Self::custom_bridge_use().await,
+ CustomCommands::Disable => Self::custom_bridge_disable().await,
+ }
+ }
+
+ async fn custom_bridge_edit(edit: ProxyEditParams) -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let mut settings = rpc.get_settings().await?;
+
+ let Some(ref mut custom_bridge) = settings.bridge_settings.custom else {
+ bail!("Can not edit as there is no currently saved custom bridge");
+ };
+
+ match custom_bridge {
+ CustomProxy::Shadowsocks(ss) => *ss = edit.merge_shadowsocks(ss),
+ CustomProxy::Socks5Local(local) => *local = edit.merge_socks_local(local),
+ CustomProxy::Socks5Remote(remote) => *remote = edit.merge_socks_remote(remote),
};
- rpc.set_bridge_settings(BridgeSettings::Normal(constraints))
- .await?;
+ rpc.set_bridge_settings(settings.bridge_settings)
+ .await
+ .map_err(anyhow::Error::from)
+ }
- println!("Updated bridge settings");
+ async fn custom_bridge_use() -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+
+ let mut settings = rpc.get_settings().await?;
+ if settings.bridge_settings.custom.is_none() {
+ bail!("Cannot enable custom bridge as there are no settings");
+ }
+ settings.bridge_settings.bridge_type = BridgeType::Custom;
+ rpc.set_bridge_settings(settings.bridge_settings).await?;
+
+ Ok(())
+ }
+ async fn custom_bridge_disable() -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let mut settings = rpc.get_settings().await?;
+
+ settings.bridge_settings.bridge_type = BridgeType::Normal;
+
+ rpc.set_bridge_settings(settings.bridge_settings).await?;
Ok(())
}
+
+ async fn custom_bridge_set(set_commands: AddCustomCommands) -> Result<()> {
+ let mut rpc = MullvadProxyClient::new().await?;
+ let mut settings = rpc.get_settings().await?;
+
+ settings.bridge_settings.custom = Some(match set_commands {
+ AddCustomCommands::Socks5(AddSocks5Commands::Local { add }) => {
+ CustomProxy::Socks5Local(Socks5Local::from(add))
+ }
+ AddCustomCommands::Socks5(AddSocks5Commands::Remote { add }) => {
+ CustomProxy::Socks5Remote(Socks5Remote::from(add))
+ }
+ AddCustomCommands::Shadowsocks { add } => {
+ CustomProxy::Shadowsocks(Shadowsocks::from(add))
+ }
+ });
+
+ settings.bridge_settings.bridge_type = BridgeType::Custom;
+
+ rpc.set_bridge_settings(settings.bridge_settings)
+ .await
+ .map_err(anyhow::Error::from)
+ }
}
diff --git a/mullvad-cli/src/cmds/mod.rs b/mullvad-cli/src/cmds/mod.rs
index 1984f1493b..29e0508d80 100644
--- a/mullvad-cli/src/cmds/mod.rs
+++ b/mullvad-cli/src/cmds/mod.rs
@@ -13,6 +13,7 @@ pub mod import_settings;
pub mod lan;
pub mod lockdown;
pub mod obfuscation;
+pub mod proxies;
pub mod relay;
pub mod relay_constraints;
pub mod reset;
diff --git a/mullvad-cli/src/cmds/proxies.rs b/mullvad-cli/src/cmds/proxies.rs
new file mode 100644
index 0000000000..6e32836a25
--- /dev/null
+++ b/mullvad-cli/src/cmds/proxies.rs
@@ -0,0 +1,214 @@
+use std::net::{IpAddr, SocketAddr};
+use talpid_types::net::{
+ proxy::{Shadowsocks, Socks5Local, Socks5Remote, SocksAuth, SHADOWSOCKS_CIPHERS},
+ Endpoint, TransportProtocol,
+};
+
+use clap::Args;
+
+#[derive(Args, Debug, Clone)]
+pub struct Socks5LocalAdd {
+ /// The port that the server on localhost is listening on
+ pub local_port: u16,
+ /// The IP of the remote peer
+ pub remote_ip: IpAddr,
+ /// The port of the remote peer
+ pub remote_port: u16,
+ /// The Mullvad App can not know which transport protocol that the
+ /// remote peer accepts, but it needs to know this in order to correctly
+ /// exempt the connection traffic in the firewall.
+ ///
+ /// By default, the transport protocol is assumed to be `TCP`, but it
+ /// can optionally be set to `UDP` as well.
+ #[arg(long, default_value_t = TransportProtocol::Tcp)]
+ pub transport_protocol: TransportProtocol,
+}
+
+impl From<Socks5LocalAdd> for Socks5Local {
+ fn from(add: Socks5LocalAdd) -> Self {
+ Self {
+ remote_endpoint: Endpoint {
+ address: SocketAddr::new(add.remote_ip, add.remote_port),
+ protocol: add.transport_protocol,
+ },
+ local_port: add.local_port,
+ }
+ }
+}
+
+// We do not support setting the protocol as anything other than tcp for remote socks5 servers
+#[derive(Args, Debug, Clone)]
+pub struct Socks5RemoteAdd {
+ /// The IP of the remote proxy server
+ pub remote_ip: IpAddr,
+ /// The port of the remote proxy server
+ pub remote_port: u16,
+
+ #[clap(flatten)]
+ pub authentication: Option<SocksAuthentication>,
+}
+
+impl From<Socks5RemoteAdd> for Socks5Remote {
+ fn from(add: Socks5RemoteAdd) -> Self {
+ Self {
+ endpoint: SocketAddr::new(add.remote_ip, add.remote_port),
+ auth: add.authentication.map(|auth| SocksAuth {
+ username: auth.username,
+ password: auth.password,
+ }),
+ }
+ }
+}
+
+#[derive(Args, Debug, Clone)]
+pub struct ShadowsocksAdd {
+ /// The IP of the remote Shadowsocks-proxy
+ pub remote_ip: IpAddr,
+ /// Port on which the remote Shadowsocks-proxy listens for traffic
+ pub remote_port: u16,
+ /// Password for authentication
+ pub password: String,
+ /// Cipher to use
+ #[arg(long, value_parser = SHADOWSOCKS_CIPHERS)]
+ pub cipher: String,
+}
+
+impl From<ShadowsocksAdd> for Shadowsocks {
+ fn from(add: ShadowsocksAdd) -> Self {
+ Self {
+ endpoint: SocketAddr::new(add.remote_ip, add.remote_port),
+ password: add.password,
+ cipher: add.cipher,
+ }
+ }
+}
+
+#[derive(Args, Debug, Clone)]
+#[group(requires_all = ["username", "password"])] // https://github.com/clap-rs/clap/issues/5092
+pub struct SocksAuthentication {
+ /// Username for authentication against a remote SOCKS5 proxy
+ #[arg(short, long, required = false)]
+ pub username: String,
+ /// Password for authentication against a remote SOCKS5 proxy
+ #[arg(short, long, required = false)]
+ pub password: String,
+}
+
+#[derive(Args, Debug, Clone)]
+pub struct ProxyEditParams {
+ /// Username for authentication [Socks5 (Remote proxy)]
+ #[arg(long)]
+ pub username: Option<String>,
+ /// Password for authentication [Socks5 (Remote proxy), Shadowsocks]
+ #[arg(long)]
+ pub password: Option<String>,
+ /// Cipher to use [Shadowsocks]
+ #[arg(value_parser = SHADOWSOCKS_CIPHERS, long)]
+ pub cipher: Option<String>,
+ /// The IP of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks]
+ #[arg(long)]
+ pub ip: Option<IpAddr>,
+ /// The port of the remote proxy server [Socks5 (Local & Remote proxy), Shadowsocks]
+ #[arg(long)]
+ pub port: Option<u16>,
+ /// The port that the server on localhost is listening on [Socks5 (Local proxy)]
+ #[arg(long)]
+ pub local_port: Option<u16>,
+ /// The transport protocol used by the remote proxy [Socks5 (Local proxy)]
+ #[arg(long)]
+ pub transport_protocol: Option<TransportProtocol>,
+}
+
+impl ProxyEditParams {
+ pub fn merge_socks_local(self, local: &Socks5Local) -> Socks5Local {
+ let remote_ip = self.ip.unwrap_or(local.remote_endpoint.address.ip());
+ let remote_port = self.port.unwrap_or(local.remote_endpoint.address.port());
+ let local_port = self.local_port.unwrap_or(local.local_port);
+ let remote_peer_transport_protocol = self
+ .transport_protocol
+ .unwrap_or(local.remote_endpoint.protocol);
+ Socks5Local::new_with_transport_protocol(
+ (remote_ip, remote_port),
+ local_port,
+ remote_peer_transport_protocol,
+ )
+ }
+
+ pub fn merge_socks_remote(self, remote: &Socks5Remote) -> Socks5Remote {
+ let ip = self.ip.unwrap_or(remote.endpoint.ip());
+ let port = self.port.unwrap_or(remote.endpoint.port());
+ match &remote.auth {
+ None => match (self.username, self.password) {
+ (Some(username), Some(password)) => {
+ let auth = SocksAuth { username, password };
+ Socks5Remote::new_with_authentication((ip, port), auth)
+ }
+ (None, None) => Socks5Remote::new((ip, port)),
+ _ => {
+ println!("Remote SOCKS5 proxy does not have a username and password set already, so you must provide both or neither when you edit.");
+ Socks5Remote::new((ip, port))
+ }
+ },
+ Some(SocksAuth { username, password }) => {
+ let username = self.username.unwrap_or(username.to_owned());
+ let password = self.password.unwrap_or(password.to_owned());
+ let auth = SocksAuth { username, password };
+ Socks5Remote::new_with_authentication((ip, port), auth)
+ }
+ }
+ }
+
+ pub fn merge_shadowsocks(self, shadowsocks: &Shadowsocks) -> Shadowsocks {
+ let ip = self.ip.unwrap_or(shadowsocks.endpoint.ip());
+ let port = self.port.unwrap_or(shadowsocks.endpoint.port());
+ let password = self.password.unwrap_or(shadowsocks.password.to_owned());
+ let cipher = self.cipher.unwrap_or(shadowsocks.cipher.to_owned());
+ Shadowsocks::new((ip, port), cipher, password)
+ }
+}
+
+pub mod pp {
+ use crate::print_option;
+ use talpid_types::net::proxy::{CustomProxy, SocksAuth};
+
+ pub struct CustomProxyFormatter<'a> {
+ pub custom_proxy: &'a CustomProxy,
+ }
+
+ impl<'a> std::fmt::Display for CustomProxyFormatter<'a> {
+ fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self.custom_proxy {
+ CustomProxy::Shadowsocks(shadowsocks) => {
+ print_option!("Protocol", format!("Shadowsocks [{}]", shadowsocks.cipher));
+ print_option!("Peer", shadowsocks.endpoint);
+ print_option!("Password", shadowsocks.password);
+ Ok(())
+ }
+ CustomProxy::Socks5Remote(remote) => {
+ print_option!("Protocol", "Socks5");
+ print_option!("Peer", remote.endpoint);
+ match &remote.auth {
+ Some(SocksAuth { username, password }) => {
+ print_option!("Username", username);
+ print_option!("Password", password);
+ }
+ None => (),
+ }
+ Ok(())
+ }
+ CustomProxy::Socks5Local(local) => {
+ print_option!("Protocol", "Socks5 (local)");
+ print_option!(
+ "Peer",
+ format!(
+ "{}/{}",
+ local.remote_endpoint.address, local.remote_endpoint.protocol
+ )
+ );
+ print_option!("Local port", local.local_port);
+ Ok(())
+ }
+ }
+ }
+ }
+}
diff --git a/mullvad-daemon/src/api.rs b/mullvad-daemon/src/api.rs
index 82f8e94fbc..c13bf77ded 100644
--- a/mullvad-daemon/src/api.rs
+++ b/mullvad-daemon/src/api.rs
@@ -16,7 +16,7 @@ use mullvad_api::{
ApiEndpointUpdateCallback,
};
use mullvad_relay_selector::RelaySelector;
-use mullvad_types::access_method::{self, AccessMethod, AccessMethodSetting, BuiltInAccessMethod};
+use mullvad_types::access_method::{AccessMethod, AccessMethodSetting, BuiltInAccessMethod};
use std::{
path::PathBuf,
sync::{Arc, Mutex, Weak},
@@ -24,7 +24,7 @@ use std::{
#[cfg(target_os = "android")]
use talpid_core::mpsc::Sender;
use talpid_core::tunnel_state_machine::TunnelCommand;
-use talpid_types::net::{openvpn::ProxySettings, AllowedEndpoint, Endpoint};
+use talpid_types::net::{AllowedEndpoint, Endpoint};
pub enum Message {
Get(ResponseTx<AccessMethodSetting>),
@@ -283,6 +283,7 @@ impl AccessModeSelector {
/// [`ApiConnectionModeProvider`] the standard [`std::convert::From`] trait
/// can not be implemented.
fn from(&mut self, access_method: AccessMethod) -> ApiConnectionMode {
+ use talpid_types::net::proxy;
match access_method {
AccessMethod::BuiltIn(access_method) => match access_method {
BuiltInAccessMethod::Direct => ApiConnectionMode::Direct,
@@ -290,13 +291,12 @@ impl AccessModeSelector {
.relay_selector
.get_bridge_forced()
.and_then(|settings| match settings {
- ProxySettings::Shadowsocks(ss_settings) => {
- let ss_settings: access_method::Shadowsocks =
- access_method::Shadowsocks::new(
- ss_settings.peer,
- ss_settings.cipher,
- ss_settings.password,
- );
+ proxy::CustomProxy::Shadowsocks(ss_settings) => {
+ let ss_settings: proxy::Shadowsocks = proxy::Shadowsocks::new(
+ ss_settings.endpoint,
+ ss_settings.cipher,
+ ss_settings.password,
+ );
Some(ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(
ss_settings,
)))
@@ -309,11 +309,14 @@ impl AccessModeSelector {
.unwrap_or(ApiConnectionMode::Direct),
},
AccessMethod::Custom(access_method) => match access_method {
- access_method::CustomAccessMethod::Shadowsocks(shadowsocks_config) => {
+ proxy::CustomProxy::Shadowsocks(shadowsocks_config) => {
ApiConnectionMode::Proxied(ProxyConfig::Shadowsocks(shadowsocks_config))
}
- access_method::CustomAccessMethod::Socks5(socks_config) => {
- ApiConnectionMode::Proxied(ProxyConfig::Socks(socks_config))
+ proxy::CustomProxy::Socks5Local(socks_config) => {
+ ApiConnectionMode::Proxied(ProxyConfig::Socks5Local(socks_config))
+ }
+ proxy::CustomProxy::Socks5Remote(socks_config) => {
+ ApiConnectionMode::Proxied(ProxyConfig::Socks5Remote(socks_config))
}
},
}
diff --git a/mullvad-daemon/src/custom_list.rs b/mullvad-daemon/src/custom_list.rs
index 8d244765cc..ce41b0059b 100644
--- a/mullvad-daemon/src/custom_list.rs
+++ b/mullvad-daemon/src/custom_list.rs
@@ -2,7 +2,7 @@ use crate::{new_selector_config, Daemon, Error, EventListener};
use mullvad_types::{
custom_list::{CustomList, Id},
relay_constraints::{
- BridgeSettings, BridgeState, Constraint, LocationConstraint, RelaySettings,
+ BridgeState, Constraint, LocationConstraint, RelaySettings, ResolvedBridgeSettings,
},
};
use talpid_types::net::TunnelType;
@@ -157,8 +157,8 @@ where
TunnelType::OpenVpn => {
if !matches!(self.settings.bridge_state, BridgeState::Off) {
- if let BridgeSettings::Normal(bridge_settings) =
- &self.settings.bridge_settings
+ if let Ok(ResolvedBridgeSettings::Normal(bridge_settings)) =
+ self.settings.bridge_settings.resolve()
{
if let Constraint::Only(LocationConstraint::CustomList {
list_id,
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 72f5a87190..b429366492 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -49,7 +49,7 @@ use mullvad_types::{
device::{Device, DeviceEvent, DeviceEventCause, DeviceId, DeviceState, RemoveDeviceEvent},
location::{GeoIpLocation, LocationEventData},
relay_constraints::{
- BridgeSettings, BridgeState, ObfuscationSettings, RelayOverride, RelaySettings,
+ BridgeSettings, BridgeState, BridgeType, ObfuscationSettings, RelayOverride, RelaySettings,
},
relay_list::RelayList,
settings::{DnsOptions, Settings},
@@ -182,6 +182,8 @@ pub enum Error {
#[error(display = "API connection mode error")]
ApiConnectionModeError(#[error(source)] api::Error),
+ #[error(display = "No custom bridge has been specified")]
+ NoCustomProxySaved,
#[cfg(target_os = "macos")]
#[error(display = "Failed to set exclusion group")]
@@ -248,7 +250,7 @@ pub enum DaemonCommand {
/// Set the mssfix argument for OpenVPN
SetOpenVpnMssfix(ResponseTx<(), settings::Error>, Option<u16>),
/// Set proxy details for OpenVPN
- SetBridgeSettings(ResponseTx<(), settings::Error>, BridgeSettings),
+ SetBridgeSettings(ResponseTx<(), Error>, BridgeSettings),
/// Set proxy state
SetBridgeState(ResponseTx<(), settings::Error>, BridgeState),
/// Set if IPv6 should be enabled in the tunnel
@@ -2027,9 +2029,19 @@ where
async fn on_set_bridge_settings(
&mut self,
- tx: ResponseTx<(), settings::Error>,
+ tx: ResponseTx<(), Error>,
new_settings: BridgeSettings,
) {
+ if new_settings.custom.is_none() && new_settings.bridge_type == BridgeType::Custom {
+ log::info!("Tried to select custom bridge but no custom bridge settings exist");
+ Self::oneshot_send(
+ tx,
+ Err(Error::NoCustomProxySaved),
+ "set_bridge_settings response",
+ );
+ return;
+ }
+
match self
.settings
.update(move |settings| settings.bridge_settings = new_settings)
@@ -2050,7 +2062,7 @@ where
"{}",
e.display_chain_with_msg("Failed to set new bridge settings")
);
- Self::oneshot_send(tx, Err(e), "set_bridge_settings");
+ Self::oneshot_send(tx, Err(Error::SettingsError(e)), "set_bridge_settings");
}
}
}
diff --git a/mullvad-daemon/src/management_interface.rs b/mullvad-daemon/src/management_interface.rs
index cf007e665d..6985af685c 100644
--- a/mullvad-daemon/src/management_interface.rs
+++ b/mullvad-daemon/src/management_interface.rs
@@ -202,7 +202,7 @@ impl ManagementService for ManagementServiceImpl {
let (tx, rx) = oneshot::channel();
self.send_command_to_daemon(DaemonCommand::SetBridgeSettings(tx, settings))?;
- self.wait_for_result(rx).await??;
+ self.wait_for_result(rx).await?.map_err(map_daemon_error)?;
Ok(Response::new(()))
}
diff --git a/mullvad-daemon/src/migrations/mod.rs b/mullvad-daemon/src/migrations/mod.rs
index e6977d9e22..5d13372a07 100644
--- a/mullvad-daemon/src/migrations/mod.rs
+++ b/mullvad-daemon/src/migrations/mod.rs
@@ -51,6 +51,7 @@ mod v3;
mod v4;
mod v5;
mod v6;
+mod v7;
const SETTINGS_FILE: &str = "settings.json";
@@ -148,6 +149,7 @@ pub async fn migrate_all(cache_dir: &Path, settings_dir: &Path) -> Result<Option
let migration_data = v5::migrate(&mut settings)?;
v6::migrate(&mut settings)?;
+ v7::migrate(&mut settings)?;
if settings == old_settings {
// Nothing changed
diff --git a/mullvad-daemon/src/migrations/v7.rs b/mullvad-daemon/src/migrations/v7.rs
new file mode 100644
index 0000000000..1453685e57
--- /dev/null
+++ b/mullvad-daemon/src/migrations/v7.rs
@@ -0,0 +1,995 @@
+use std::net::SocketAddr;
+
+use super::{Error, Result};
+use mullvad_types::{
+ relay_constraints::{BridgeConstraints, BridgeSettings as NewBridgeSettings, BridgeType},
+ settings::SettingsVersion,
+};
+use serde::{Deserialize, Serialize};
+use talpid_types::net::{
+ proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote, SocksAuth},
+ Endpoint, TransportProtocol,
+};
+
+// ======================================================
+// Section for vendoring types and values that
+// this settings version depend on. See `mod.rs`.
+
+/// Specifies a specific endpoint or [`BridgeConstraints`] to use when `mullvad-daemon` selects a
+/// bridge server.
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum BridgeSettings {
+ /// Let the relay selection algorithm decide on bridges, based on the relay list.
+ Normal(BridgeConstraints),
+ Custom(ProxySettings),
+}
+
+/// Proxy server options to be used by `OpenVpnMonitor` when starting a tunnel.
+#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum ProxySettings {
+ Local(LocalProxySettings),
+ Remote(RemoteProxySettings),
+ Shadowsocks(ShadowsocksProxySettings),
+}
+
+/// Options for a generic proxy running on localhost.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
+pub struct LocalProxySettings {
+ pub port: u16,
+ pub peer: SocketAddr,
+}
+
+/// Options for a generic proxy running on remote host.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
+pub struct RemoteProxySettings {
+ pub address: SocketAddr,
+ pub auth: Option<ProxyAuth>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
+pub struct ProxyAuth {
+ pub username: String,
+ pub password: String,
+}
+
+/// Options for a bundled Shadowsocks proxy.
+#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
+pub struct ShadowsocksProxySettings {
+ pub peer: SocketAddr,
+ /// Password on peer.
+ pub password: String,
+ pub cipher: String,
+ #[cfg(target_os = "linux")]
+ pub fwmark: Option<u32>,
+}
+
+// ======================================================
+/// This is a closed migration.
+
+/// We change bridge settings to no longer be an enum with custom and normal variants. It now is a
+/// struct which contains a bridge type, a normal relay constraint and optional custom constraints.
+///
+/// We also migrate api access methods to use a slightly more shallow CustomProxy implementation
+/// that instead of having a Socks5 and Shadowsocks variant instead has a Socks5Local, Socks5Remote
+/// and Shadowsocks variant.
+///
+/// We also take the oppertunity to rename a couple of fields that relate to proxy types.
+/// We rename
+/// - shadowsocks.peer to shadowsocks.endpoint
+/// - socks5_remote.authentication to socks5_remote.auth
+/// - socks5_remote.peer to socks5_remote.endpoint
+///
+pub fn migrate(settings: &mut serde_json::Value) -> Result<()> {
+ if !version_matches(settings) {
+ return Ok(());
+ }
+
+ log::info!("Migrating settings format to V8");
+
+ migrate_bridge_settings(settings)?;
+
+ migrate_api_access_settings(settings)?;
+
+ settings["settings_version"] = serde_json::json!(SettingsVersion::V8);
+
+ Ok(())
+}
+
+fn migrate_api_access_settings(settings: &mut serde_json::Value) -> Result<()> {
+ if let Some(access_method_settings_list) = settings
+ .get_mut("api_access_methods")
+ .and_then(|api_access_methods| api_access_methods.get_mut("access_method_settings"))
+ .and_then(|access_method_settings| access_method_settings.as_array_mut())
+ {
+ for access_method_setting in access_method_settings_list {
+ let access_method = access_method_setting
+ .get_mut("access_method")
+ .ok_or(Error::InvalidSettingsContent)?;
+ match access_method.get_mut("custom") {
+ None => continue,
+ Some(custom_access_method) => {
+ if let Some(shadowsocks) = custom_access_method.get_mut("shadowsocks") {
+ rename_field(shadowsocks, "peer", "endpoint")?;
+ } else if let Some(new_socks5_local) = custom_access_method
+ .get("socks5")
+ .and_then(|socks5| socks5.get("local"))
+ {
+ custom_access_method["socks5_local"] = new_socks5_local.clone();
+ custom_access_method
+ .as_object_mut()
+ .ok_or(Error::InvalidSettingsContent)?
+ .remove("socks5");
+ } else if let Some(socks5_remote) = custom_access_method
+ .get("socks5")
+ .and_then(|socks5| socks5.get("remote"))
+ {
+ let mut new_socks5_remote = socks5_remote.clone();
+ rename_field(&mut new_socks5_remote, "authentication", "auth")?;
+ rename_field(&mut new_socks5_remote, "peer", "endpoint")?;
+
+ custom_access_method["socks5_remote"] = new_socks5_remote;
+ custom_access_method
+ .as_object_mut()
+ .ok_or(Error::InvalidSettingsContent)?
+ .remove("socks5");
+ } else {
+ return Err(Error::InvalidSettingsContent);
+ }
+ }
+ }
+ }
+ }
+
+ Ok(())
+}
+
+fn migrate_bridge_settings(settings: &mut serde_json::Value) -> Result<()> {
+ let new = if let Some(custom_bridge_local) = settings
+ .get_mut("bridge_settings")
+ .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
+ .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("local"))
+ {
+ NewBridgeSettings {
+ bridge_type: BridgeType::Custom,
+ normal: BridgeConstraints::default(),
+ custom: Some(CustomProxy::Socks5Local(Socks5Local {
+ remote_endpoint: Endpoint {
+ address: extract_str(custom_bridge_local.get("peer"))?
+ .parse()
+ .map_err(|_| Error::InvalidSettingsContent)?,
+ protocol: TransportProtocol::Tcp,
+ },
+ local_port: custom_bridge_local
+ .get("port")
+ .ok_or(Error::InvalidSettingsContent)?
+ .as_u64()
+ .ok_or(Error::InvalidSettingsContent)?
+ .try_into()
+ .map_err(|_| Error::InvalidSettingsContent)?,
+ })),
+ }
+ } else if let Some(custom_bridge_remote) = settings
+ .get_mut("bridge_settings")
+ .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
+ .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("remote"))
+ {
+ NewBridgeSettings {
+ bridge_type: BridgeType::Custom,
+ normal: BridgeConstraints::default(),
+ custom: Some(CustomProxy::Socks5Remote(Socks5Remote {
+ endpoint: extract_str(custom_bridge_remote.get("address"))?
+ .parse()
+ .map_err(|_| Error::InvalidSettingsContent)?,
+ auth: custom_bridge_remote.get("auth").and_then(|auth| {
+ Some(SocksAuth {
+ username: auth.get("username")?.to_string(),
+ password: auth.get("password")?.to_string(),
+ })
+ }),
+ })),
+ }
+ } else if let Some(custom_bridge_shadowsocks) = settings
+ .get_mut("bridge_settings")
+ .and_then(|bridge_settings| bridge_settings.get_mut("custom"))
+ .and_then(|bridge_settings_custom| bridge_settings_custom.get_mut("shadowsocks"))
+ {
+ NewBridgeSettings {
+ bridge_type: BridgeType::Custom,
+ normal: BridgeConstraints::default(),
+ custom: Some(CustomProxy::Shadowsocks(Shadowsocks {
+ endpoint: extract_str(custom_bridge_shadowsocks.get("peer"))?
+ .parse()
+ .map_err(|_| Error::InvalidSettingsContent)?,
+ password: extract_str(custom_bridge_shadowsocks.get("password"))?.to_string(),
+ cipher: extract_str(custom_bridge_shadowsocks.get("cipher"))?.to_string(),
+ })),
+ }
+ } else if let Some(normal_bridge) = settings
+ .get_mut("bridge_settings")
+ .and_then(|bridge_settings| bridge_settings.get_mut("normal"))
+ {
+ NewBridgeSettings {
+ bridge_type: BridgeType::Normal,
+ normal: serde_json::from_value(normal_bridge.clone()).map_err(Error::Serialize)?,
+ custom: None,
+ }
+ } else {
+ return Ok(());
+ };
+
+ settings["bridge_settings"] = serde_json::json!(new);
+
+ Ok(())
+}
+
+fn extract_str(opt: Option<&serde_json::Value>) -> Result<&str> {
+ opt.ok_or(Error::InvalidSettingsContent)?
+ .as_str()
+ .ok_or(Error::InvalidSettingsContent)
+}
+
+fn rename_field(object: &mut serde_json::Value, old_name: &str, new_name: &str) -> Result<()> {
+ object[new_name] = object
+ .get(old_name)
+ .ok_or(Error::InvalidSettingsContent)?
+ .clone();
+ object
+ .as_object_mut()
+ .ok_or(Error::InvalidSettingsContent)?
+ .remove(old_name);
+ Ok(())
+}
+
+fn version_matches(settings: &mut serde_json::Value) -> bool {
+ settings
+ .get("settings_version")
+ .map(|version| version == SettingsVersion::V7 as u64)
+ .unwrap_or(false)
+}
+
+#[cfg(test)]
+mod test {
+ use crate::migrations::v7::{migrate_api_access_settings, migrate_bridge_settings};
+
+ use super::{migrate, version_matches};
+ use serde_json;
+
+ pub const V7_SETTINGS: &str = r#"
+{
+
+ "relay_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "hostname": [
+ "ch",
+ "zrh",
+ "ch-zrh-ovpn-001"
+ ]
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any",
+ "tunnel_protocol": "any",
+ "wireguard_constraints": {
+ "port": "any",
+ "ip_version": "any",
+ "use_multihop": false,
+ "entry_location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ }
+ },
+ "openvpn_constraints": {
+ "port": {
+ "only": {
+ "protocol": "udp",
+ "port": {
+ "only": 1195
+ }
+ }
+ }
+ }
+ }
+ },
+ "bridge_settings": {
+ "custom": {
+ "local": {
+ "port": 1080,
+ "peer": "1.3.3.7:22"
+ }
+ }
+ },
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
+ "name": "Direct",
+ "enabled": true,
+ "access_method": {
+ "built_in": "direct"
+ }
+ },
+ {
+ "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
+ "name": "Mullvad Bridges",
+ "enabled": true,
+ "access_method": {
+ "built_in": "bridge"
+ }
+ },
+ {
+ "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
+ "name": "localsox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5": {
+ "local": {
+ "remote_endpoint": {
+ "address": "1.3.3.7:1080",
+ "protocol": "tcp"
+ },
+ "local_port": 1079
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": "1e377232-8a53-4414-8b8f-f487227aaedb",
+ "name": "remotesox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5": {
+ "remote": {
+ "peer": "1.3.3.7:1080",
+ "authentication": null
+ }
+ }
+ }
+ }
+ },
+ {
+ "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
+ "name": "remotess",
+ "enabled": true,
+ "access_method": {
+ "custom": {
+ "shadowsocks": {
+ "peer": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "obfuscation_settings": {
+ "selected_obfuscation": "udp2_tcp",
+ "udp2tcp": {
+ "port": "any"
+ }
+ },
+ "bridge_state": "auto",
+ "allow_lan": true,
+ "block_when_disconnected": false,
+ "auto_connect": false,
+ "tunnel_options": {
+ "openvpn": {
+ "mssfix": null
+ },
+ "wireguard": {
+ "mtu": null,
+ "rotation_interval": {
+ "secs": 86400,
+ "nanos": 0
+ },
+ "quantum_resistant": "auto"
+ },
+ "generic": {
+ "enable_ipv6": false
+ },
+ "dns_options": {
+ "state": "default",
+ "default_options": {
+ "block_ads": false,
+ "block_trackers": false
+ },
+ "custom_options": {
+ "addresses": [
+ "1.1.1.1",
+ "1.2.3.4"
+ ]
+ }
+ }
+ },
+ "settings_version": 7
+}
+"#;
+
+ pub const V8_SETTINGS: &str = r#"
+{
+
+ "relay_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "hostname": [
+ "ch",
+ "zrh",
+ "ch-zrh-ovpn-001"
+ ]
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any",
+ "tunnel_protocol": "any",
+ "wireguard_constraints": {
+ "port": "any",
+ "ip_version": "any",
+ "use_multihop": false,
+ "entry_location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ }
+ },
+ "openvpn_constraints": {
+ "port": {
+ "only": {
+ "protocol": "udp",
+ "port": {
+ "only": 1195
+ }
+ }
+ }
+ }
+ }
+ },
+ "bridge_settings": {
+ "bridge_type": "custom",
+ "normal": {
+ "location": "any",
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": {
+ "socks5_local": {
+ "local_port": 1080,
+ "remote_endpoint": {
+ "address": "1.3.3.7:22",
+ "protocol": "tcp"
+ }
+ }
+ }
+ },
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "8cbdcfc8-fa7b-41de-8d12-26fa37439f89",
+ "name": "Direct",
+ "enabled": true,
+ "access_method": {
+ "built_in": "direct"
+ }
+ },
+ {
+ "id": "1d0d8891-dbb3-4439-a8f7-0e7d742ddbe4",
+ "name": "Mullvad Bridges",
+ "enabled": true,
+ "access_method": {
+ "built_in": "bridge"
+ }
+ },
+ {
+ "id": "1aaff7ab-e09f-4c03-af02-765e41943a7b",
+ "name": "localsox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5_local": {
+ "remote_endpoint": {
+ "address": "1.3.3.7:1080",
+ "protocol": "tcp"
+ },
+ "local_port": 1079
+ }
+ }
+ }
+ },
+ {
+ "id": "1e377232-8a53-4414-8b8f-f487227aaedb",
+ "name": "remotesox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5_remote": {
+ "endpoint": "1.3.3.7:1080",
+ "auth": null
+ }
+ }
+ }
+ },
+ {
+ "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
+ "name": "remotess",
+ "enabled": true,
+ "access_method": {
+ "custom": {
+ "shadowsocks": {
+ "endpoint": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb"
+ }
+ }
+ }
+ }
+ ]
+ },
+ "obfuscation_settings": {
+ "selected_obfuscation": "udp2_tcp",
+ "udp2tcp": {
+ "port": "any"
+ }
+ },
+ "bridge_state": "auto",
+ "allow_lan": true,
+ "block_when_disconnected": false,
+ "auto_connect": false,
+ "tunnel_options": {
+ "openvpn": {
+ "mssfix": null
+ },
+ "wireguard": {
+ "mtu": null,
+ "rotation_interval": {
+ "secs": 86400,
+ "nanos": 0
+ },
+ "quantum_resistant": "auto"
+ },
+ "generic": {
+ "enable_ipv6": false
+ },
+ "dns_options": {
+ "state": "default",
+ "default_options": {
+ "block_ads": false,
+ "block_trackers": false
+ },
+ "custom_options": {
+ "addresses": [
+ "1.1.1.1",
+ "1.2.3.4"
+ ]
+ }
+ }
+ },
+ "settings_version": 8
+}
+"#;
+
+ #[test]
+ fn test_v7_to_v8_migration() {
+ let mut old_settings = serde_json::from_str(V7_SETTINGS).unwrap();
+
+ assert!(version_matches(&mut old_settings));
+ migrate(&mut old_settings).unwrap();
+ let new_settings: serde_json::Value = serde_json::from_str(V8_SETTINGS).unwrap();
+
+ assert_eq!(&old_settings, &new_settings);
+ }
+
+ #[test]
+ fn test_bridge_settings_custom_local_proxy() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "custom": {
+ "local": {
+ "port": 1080,
+ "peer": "1.3.3.7:22"
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "bridge_type": "custom",
+ "normal": {
+ "location": "any",
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": {
+ "socks5_local": {
+ "local_port": 1080,
+ "remote_endpoint": {
+ "address": "1.3.3.7:22",
+ "protocol": "tcp"
+ }
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ migrate_bridge_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_bridge_settings_custom_remote_proxy() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "custom": {
+ "remote": {
+ "address": "1.3.3.7:1080",
+ "auth": null
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "bridge_type": "custom",
+ "normal": {
+ "location": "any",
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": {
+ "socks5_remote": {
+ "endpoint": "1.3.3.7:1080",
+ "auth": null
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ migrate_bridge_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_bridge_settings_custom_shadowsocks_proxy() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "custom": {
+ "shadowsocks": {
+ "peer": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb",
+ "fwmark": 1836018789
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "bridge_type": "custom",
+ "normal": {
+ "location": "any",
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": {
+ "shadowsocks": {
+ "endpoint": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb"
+ }
+ }
+ }
+}"#,
+ )
+ .unwrap();
+ migrate_bridge_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_bridge_settings_normal() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any"
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "bridge_type": "normal",
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": null
+ }
+}"#,
+ )
+ .unwrap();
+ migrate_bridge_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_bridge_settings_specific_location() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any"
+ }
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "bridge_settings": {
+ "bridge_type": "normal",
+ "normal": {
+ "location": {
+ "only": {
+ "location": {
+ "country": "se"
+ }
+ }
+ },
+ "providers": "any",
+ "ownership": "any"
+ },
+ "custom": null
+ }
+}"#,
+ )
+ .unwrap();
+ migrate_bridge_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_api_access_methods_custom_socks5_local() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "5eb9b2ee-f764-47c8-8111-ee95910d0099",
+ "name": "mysocks",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5": {
+ "local": {
+ "remote_endpoint": {
+ "address": "1.3.3.7:22",
+ "protocol": "tcp"
+ },
+ "local_port": 1080
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "5eb9b2ee-f764-47c8-8111-ee95910d0099",
+ "name": "mysocks",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5_local": {
+ "remote_endpoint": {
+ "address": "1.3.3.7:22",
+ "protocol": "tcp"
+ },
+ "local_port": 1080
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+
+ migrate_api_access_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_api_access_methods_custom_socks5_remote() {
+ println!("wew");
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "8e377232-8a53-4414-8b8f-f487227aaedb",
+ "name": "remotesox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5": {
+ "remote": {
+ "peer": "1.3.3.7:1080",
+ "authentication": null
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "8e377232-8a53-4414-8b8f-f487227aaedb",
+ "name": "remotesox",
+ "enabled": false,
+ "access_method": {
+ "custom": {
+ "socks5_remote": {
+ "endpoint": "1.3.3.7:1080",
+ "auth": null
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+
+ migrate_api_access_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+
+ #[test]
+ fn test_api_access_methods_custom_socks5_shadowsocks() {
+ let mut pre: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
+ "name": "remotess",
+ "enabled": true,
+ "access_method": {
+ "custom": {
+ "shadowsocks": {
+ "peer": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb"
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+
+ let post: serde_json::Value = serde_json::from_str(
+ r#"
+{
+ "api_access_methods": {
+ "access_method_settings": [
+ {
+ "id": "74e5c659-acdd-4cad-a632-a25bf63c20e2",
+ "name": "remotess",
+ "enabled": true,
+ "access_method": {
+ "custom": {
+ "shadowsocks": {
+ "endpoint": "1.3.3.7:1080",
+ "password": "mypass",
+ "cipher": "aes-128-cfb"
+ }
+ }
+ }
+ }
+ ]
+ }
+}"#,
+ )
+ .unwrap();
+
+ migrate_api_access_settings(&mut pre).unwrap();
+ assert_eq!(pre, post);
+ }
+}
diff --git a/mullvad-daemon/src/settings/mod.rs b/mullvad-daemon/src/settings/mod.rs
index c5639b8b76..8a63b5c5a6 100644
--- a/mullvad-daemon/src/settings/mod.rs
+++ b/mullvad-daemon/src/settings/mod.rs
@@ -462,8 +462,18 @@ mod test {
}
},
"bridge_settings": {
+ "bridge_type": "normal",
"normal": {
"location": "any"
+ },
+ "custom": {
+ "socks5_local": {
+ "local_port": 1080,
+ "remote_endpoint": {
+ "address": "1.3.3.7:22",
+ "protocol": "tcp"
+ }
+ }
}
},
"bridge_state": "auto",
@@ -482,7 +492,7 @@ mod test {
"enable_ipv6": true
}
},
- "settings_version": 5,
+ "settings_version": 8,
"show_beta_releases": false,
"custom_lists": {
"custom_lists": []
diff --git a/mullvad-management-interface/proto/management_interface.proto b/mullvad-management-interface/proto/management_interface.proto
index eb3aa6bba1..268e70327d 100644
--- a/mullvad-management-interface/proto/management_interface.proto
+++ b/mullvad-management-interface/proto/management_interface.proto
@@ -260,36 +260,19 @@ enum Ownership {
}
message BridgeSettings {
+ enum BridgeType {
+ NORMAL = 0;
+ CUSTOM = 1;
+ }
message BridgeConstraints {
LocationConstraint location = 1;
repeated string providers = 2;
Ownership ownership = 3;
}
- message LocalProxySettings {
- uint32 port = 1;
- string peer = 2;
- }
- message RemoteProxySettings {
- string address = 1;
- RemoteProxyAuth auth = 2;
- }
- message RemoteProxyAuth {
- string username = 1;
- string password = 2;
- }
- message ShadowsocksProxySettings {
- string peer = 1;
- string password = 2;
- string cipher = 3;
- }
-
- oneof type {
- BridgeConstraints normal = 1;
- LocalProxySettings local = 2;
- RemoteProxySettings remote = 3;
- ShadowsocksProxySettings shadowsocks = 4;
- }
+ BridgeType bridge_type = 1;
+ BridgeConstraints normal = 2;
+ CustomProxy custom = 3;
}
message LocationConstraint {
@@ -334,30 +317,39 @@ message CustomList {
message CustomListSettings { repeated CustomList custom_lists = 1; }
+message Socks5Local {
+ string remote_ip = 1;
+ uint32 remote_port = 2;
+ TransportProtocol remote_transport_protocol = 3;
+ uint32 local_port = 4;
+}
+message SocksAuth {
+ string username = 1;
+ string password = 2;
+}
+message Socks5Remote {
+ string ip = 1;
+ uint32 port = 2;
+ SocksAuth auth = 3;
+}
+message Shadowsocks {
+ string ip = 1;
+ uint32 port = 2;
+ string password = 3;
+ string cipher = 4;
+}
+
+message CustomProxy {
+ oneof proxy_method {
+ Socks5Local socks5local = 1;
+ Socks5Remote socks5remote = 2;
+ Shadowsocks shadowsocks = 3;
+ }
+}
+
message AccessMethod {
message Direct {}
message Bridges {}
- message Socks5Local {
- string remote_ip = 1;
- uint32 remote_port = 2;
- TransportProtocol remote_transport_protocol = 3;
- uint32 local_port = 4;
- }
- message SocksAuth {
- string username = 1;
- string password = 2;
- }
- message Socks5Remote {
- string ip = 1;
- uint32 port = 2;
- SocksAuth authentication = 3;
- }
- message Shadowsocks {
- string ip = 1;
- uint32 port = 2;
- string password = 3;
- string cipher = 4;
- }
oneof access_method {
Direct direct = 1;
Bridges bridges = 2;
diff --git a/mullvad-management-interface/src/types/conversions/access_method.rs b/mullvad-management-interface/src/types/conversions/access_method.rs
index 425e0a70da..368528f5bc 100644
--- a/mullvad-management-interface/src/types/conversions/access_method.rs
+++ b/mullvad-management-interface/src/types/conversions/access_method.rs
@@ -46,8 +46,10 @@ mod data {
use crate::types::{proto, FromProtobufTypeError};
use mullvad_types::access_method::{
- AccessMethod, AccessMethodSetting, BuiltInAccessMethod, CustomAccessMethod, Id,
- Shadowsocks, Socks5, Socks5Local, Socks5Remote, SocksAuth,
+ AccessMethod, AccessMethodSetting, BuiltInAccessMethod, Id,
+ };
+ use talpid_types::net::proxy::{
+ CustomProxy, Shadowsocks, Socks5Local, Socks5Remote, SocksAuth,
};
impl TryFrom<proto::AccessMethodSetting> for AccessMethodSetting {
@@ -140,10 +142,10 @@ mod data {
}
}
- impl TryFrom<proto::access_method::Socks5Local> for AccessMethod {
+ impl TryFrom<proto::Socks5Local> for AccessMethod {
type Error = FromProtobufTypeError;
- fn try_from(value: proto::access_method::Socks5Local) -> Result<Self, Self::Error> {
+ fn try_from(value: proto::Socks5Local) -> Result<Self, Self::Error> {
use crate::types::conversions::net::try_transport_protocol_from_i32;
let remote_ip = value.remote_ip.parse::<Ipv4Addr>().map_err(|_| {
FromProtobufTypeError::InvalidArgument(
@@ -160,15 +162,11 @@ mod data {
}
}
- impl TryFrom<proto::access_method::Socks5Remote> for AccessMethod {
+ impl TryFrom<proto::Socks5Remote> for AccessMethod {
type Error = FromProtobufTypeError;
- fn try_from(value: proto::access_method::Socks5Remote) -> Result<Self, Self::Error> {
- let proto::access_method::Socks5Remote {
- ip,
- port,
- authentication,
- } = value;
+ fn try_from(value: proto::Socks5Remote) -> Result<Self, Self::Error> {
+ let proto::Socks5Remote { ip, port, auth } = value;
let ip = ip.parse::<Ipv4Addr>().map_err(|_| {
FromProtobufTypeError::InvalidArgument(
"Could not parse Socks5 (remote) message from protobuf",
@@ -176,19 +174,17 @@ mod data {
})?;
let port = port as u16;
- Ok(AccessMethod::from(
- match authentication.map(SocksAuth::from) {
- Some(auth) => Socks5Remote::new_with_authentication((ip, port), auth),
- None => Socks5Remote::new((ip, port)),
- },
- ))
+ Ok(AccessMethod::from(match auth.map(SocksAuth::from) {
+ Some(auth) => Socks5Remote::new_with_authentication((ip, port), auth),
+ None => Socks5Remote::new((ip, port)),
+ }))
}
}
- impl TryFrom<proto::access_method::Shadowsocks> for AccessMethod {
+ impl TryFrom<proto::Shadowsocks> for AccessMethod {
type Error = FromProtobufTypeError;
- fn try_from(value: proto::access_method::Shadowsocks) -> Result<Self, Self::Error> {
+ fn try_from(value: proto::Shadowsocks) -> Result<Self, Self::Error> {
let ip = value.ip.parse::<Ipv4Addr>().map_err(|_| {
FromProtobufTypeError::InvalidArgument(
"Could not parse Socks5 (remote) message from protobuf",
@@ -219,42 +215,35 @@ mod data {
}
}
- impl From<CustomAccessMethod> for proto::AccessMethod {
- fn from(value: CustomAccessMethod) -> Self {
+ impl From<CustomProxy> for proto::AccessMethod {
+ fn from(value: CustomProxy) -> Self {
let access_method = match value {
- CustomAccessMethod::Shadowsocks(ss) => {
- proto::access_method::AccessMethod::Shadowsocks(
- proto::access_method::Shadowsocks {
- ip: ss.peer.ip().to_string(),
- port: ss.peer.port() as u32,
- password: ss.password,
- cipher: ss.cipher,
- },
- )
+ CustomProxy::Shadowsocks(ss) => {
+ proto::access_method::AccessMethod::Shadowsocks(proto::Shadowsocks {
+ ip: ss.endpoint.ip().to_string(),
+ port: u32::from(ss.endpoint.port()),
+ password: ss.password,
+ cipher: ss.cipher,
+ })
}
- CustomAccessMethod::Socks5(Socks5::Local(Socks5Local {
+ CustomProxy::Socks5Local(Socks5Local {
remote_endpoint,
local_port,
- })) => proto::access_method::AccessMethod::Socks5local(
- proto::access_method::Socks5Local {
- remote_ip: remote_endpoint.address.ip().to_string(),
- remote_port: remote_endpoint.address.port() as u32,
- remote_transport_protocol: i32::from(proto::TransportProtocol::from(
- remote_endpoint.protocol,
- )),
- local_port: local_port as u32,
- },
- ),
- CustomAccessMethod::Socks5(Socks5::Remote(Socks5Remote {
- peer,
- authentication,
- })) => proto::access_method::AccessMethod::Socks5remote(
- proto::access_method::Socks5Remote {
- ip: peer.ip().to_string(),
- port: peer.port() as u32,
- authentication: authentication.map(proto::access_method::SocksAuth::from),
- },
- ),
+ }) => proto::access_method::AccessMethod::Socks5local(proto::Socks5Local {
+ remote_ip: remote_endpoint.address.ip().to_string(),
+ remote_port: remote_endpoint.address.port() as u32,
+ remote_transport_protocol: i32::from(proto::TransportProtocol::from(
+ remote_endpoint.protocol,
+ )),
+ local_port: u32::from(local_port),
+ }),
+ CustomProxy::Socks5Remote(Socks5Remote { endpoint, auth }) => {
+ proto::access_method::AccessMethod::Socks5remote(proto::Socks5Remote {
+ ip: endpoint.ip().to_string(),
+ port: u32::from(endpoint.port()),
+ auth: auth.map(proto::SocksAuth::from),
+ })
+ }
};
proto::AccessMethod {
@@ -281,24 +270,6 @@ mod data {
}
}
- impl From<SocksAuth> for proto::access_method::SocksAuth {
- fn from(value: SocksAuth) -> Self {
- proto::access_method::SocksAuth {
- username: value.username,
- password: value.password,
- }
- }
- }
-
- impl From<proto::access_method::SocksAuth> for SocksAuth {
- fn from(value: proto::access_method::SocksAuth) -> Self {
- Self {
- username: value.username,
- password: value.password,
- }
- }
- }
-
impl TryFrom<&proto::AccessMethodSetting> for AccessMethodSetting {
type Error = FromProtobufTypeError;
diff --git a/mullvad-management-interface/src/types/conversions/net.rs b/mullvad-management-interface/src/types/conversions/net.rs
index e2df5553a0..9a873e82ab 100644
--- a/mullvad-management-interface/src/types/conversions/net.rs
+++ b/mullvad-management-interface/src/types/conversions/net.rs
@@ -182,3 +182,145 @@ pub fn try_transport_protocol_from_i32(
.map_err(|_| FromProtobufTypeError::InvalidArgument("invalid transport protocol"))?
.into())
}
+
+mod proxy {
+ use std::net::Ipv4Addr;
+
+ use crate::types::{proto, FromProtobufTypeError};
+ use talpid_types::net::proxy::{
+ CustomProxy, Shadowsocks, Socks5Local, Socks5Remote, SocksAuth,
+ };
+
+ impl TryFrom<proto::CustomProxy> for CustomProxy {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::CustomProxy) -> Result<Self, Self::Error> {
+ Ok(match value.proxy_method {
+ Some(proto::custom_proxy::ProxyMethod::Socks5local(local)) => {
+ CustomProxy::Socks5Local(Socks5Local::try_from(local)?)
+ }
+ Some(proto::custom_proxy::ProxyMethod::Socks5remote(remote)) => {
+ CustomProxy::Socks5Remote(Socks5Remote::try_from(remote)?)
+ }
+ Some(proto::custom_proxy::ProxyMethod::Shadowsocks(shadowsocks)) => {
+ CustomProxy::Shadowsocks(Shadowsocks::try_from(shadowsocks)?)
+ }
+ None => {
+ return Err(FromProtobufTypeError::InvalidArgument(
+ "CustomProxy missing proxy_method field",
+ ));
+ }
+ })
+ }
+ }
+
+ impl TryFrom<proto::Socks5Local> for Socks5Local {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::Socks5Local) -> Result<Self, Self::Error> {
+ use crate::types::conversions::net::try_transport_protocol_from_i32;
+ let remote_ip = value.remote_ip.parse::<Ipv4Addr>().map_err(|_| {
+ FromProtobufTypeError::InvalidArgument(
+ "Could not parse Socks5 (local) message from protobuf",
+ )
+ })?;
+ Ok(Socks5Local::new_with_transport_protocol(
+ (remote_ip, value.remote_port as u16),
+ value.local_port as u16,
+ try_transport_protocol_from_i32(value.remote_transport_protocol)?,
+ ))
+ }
+ }
+
+ impl TryFrom<proto::Socks5Remote> for Socks5Remote {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::Socks5Remote) -> Result<Self, Self::Error> {
+ let proto::Socks5Remote { ip, port, auth } = value;
+ let ip = ip.parse::<Ipv4Addr>().map_err(|_| {
+ FromProtobufTypeError::InvalidArgument(
+ "Could not parse Socks5 (remote) message from protobuf",
+ )
+ })?;
+ let port = port as u16;
+
+ Ok(match auth.map(SocksAuth::from) {
+ Some(auth) => Socks5Remote::new_with_authentication((ip, port), auth),
+ None => Socks5Remote::new((ip, port)),
+ })
+ }
+ }
+
+ impl TryFrom<proto::Shadowsocks> for Shadowsocks {
+ type Error = FromProtobufTypeError;
+
+ fn try_from(value: proto::Shadowsocks) -> Result<Self, Self::Error> {
+ let ip = value.ip.parse::<Ipv4Addr>().map_err(|_| {
+ FromProtobufTypeError::InvalidArgument(
+ "Could not parse Socks5 (remote) message from protobuf",
+ )
+ })?;
+
+ Ok(Shadowsocks::new(
+ (ip, value.port as u16),
+ value.cipher,
+ value.password,
+ ))
+ }
+ }
+
+ impl From<CustomProxy> for proto::CustomProxy {
+ fn from(value: CustomProxy) -> Self {
+ let proxy_method = match value {
+ CustomProxy::Shadowsocks(ss) => {
+ proto::custom_proxy::ProxyMethod::Shadowsocks(proto::Shadowsocks {
+ ip: ss.endpoint.ip().to_string(),
+ port: ss.endpoint.port() as u32,
+ password: ss.password,
+ cipher: ss.cipher,
+ })
+ }
+ CustomProxy::Socks5Local(Socks5Local {
+ remote_endpoint,
+ local_port,
+ }) => proto::custom_proxy::ProxyMethod::Socks5local(proto::Socks5Local {
+ remote_ip: remote_endpoint.address.ip().to_string(),
+ remote_port: remote_endpoint.address.port() as u32,
+ remote_transport_protocol: i32::from(proto::TransportProtocol::from(
+ remote_endpoint.protocol,
+ )),
+ local_port: local_port as u32,
+ }),
+ CustomProxy::Socks5Remote(Socks5Remote { endpoint, auth }) => {
+ proto::custom_proxy::ProxyMethod::Socks5remote(proto::Socks5Remote {
+ ip: endpoint.ip().to_string(),
+ port: endpoint.port() as u32,
+ auth: auth.map(proto::SocksAuth::from),
+ })
+ }
+ };
+
+ proto::CustomProxy {
+ proxy_method: Some(proxy_method),
+ }
+ }
+ }
+
+ impl From<SocksAuth> for proto::SocksAuth {
+ fn from(value: SocksAuth) -> Self {
+ proto::SocksAuth {
+ username: value.username,
+ password: value.password,
+ }
+ }
+ }
+
+ impl From<proto::SocksAuth> for SocksAuth {
+ fn from(value: proto::SocksAuth) -> Self {
+ Self {
+ username: value.username,
+ password: value.password,
+ }
+ }
+ }
+}
diff --git a/mullvad-management-interface/src/types/conversions/relay_constraints.rs b/mullvad-management-interface/src/types/conversions/relay_constraints.rs
index a1d468c976..cdae59d60f 100644
--- a/mullvad-management-interface/src/types/conversions/relay_constraints.rs
+++ b/mullvad-management-interface/src/types/conversions/relay_constraints.rs
@@ -4,6 +4,7 @@ use mullvad_types::{
relay_constraints::{Constraint, GeographicLocationConstraint},
};
use std::str::FromStr;
+use talpid_types::net::proxy::CustomProxy;
impl TryFrom<&proto::WireguardConstraints>
for mullvad_types::relay_constraints::WireguardConstraints
@@ -178,52 +179,34 @@ impl From<&mullvad_types::relay_constraints::Udp2TcpObfuscationSettings>
impl From<mullvad_types::relay_constraints::BridgeSettings> for proto::BridgeSettings {
fn from(settings: mullvad_types::relay_constraints::BridgeSettings) -> Self {
- use mullvad_types::relay_constraints::BridgeSettings as MullvadBridgeSettings;
use proto::bridge_settings;
- use talpid_types::net as talpid_net;
- let settings = match settings {
- MullvadBridgeSettings::Normal(constraints) => {
- bridge_settings::Type::Normal(bridge_settings::BridgeConstraints {
- location: constraints
- .location
- .clone()
- .option()
- .map(proto::LocationConstraint::from),
- providers: convert_providers_constraint(&constraints.providers),
- ownership: convert_ownership_constraint(&constraints.ownership) as i32,
- })
+ let mode = match settings.bridge_type {
+ mullvad_types::relay_constraints::BridgeType::Normal => {
+ bridge_settings::BridgeType::Normal
}
- MullvadBridgeSettings::Custom(proxy_settings) => match proxy_settings {
- talpid_net::openvpn::ProxySettings::Local(proxy_settings) => {
- bridge_settings::Type::Local(bridge_settings::LocalProxySettings {
- port: u32::from(proxy_settings.port),
- peer: proxy_settings.peer.to_string(),
- })
- }
- talpid_net::openvpn::ProxySettings::Remote(proxy_settings) => {
- bridge_settings::Type::Remote(bridge_settings::RemoteProxySettings {
- address: proxy_settings.address.to_string(),
- auth: proxy_settings.auth.as_ref().map(|auth| {
- bridge_settings::RemoteProxyAuth {
- username: auth.username.clone(),
- password: auth.password.clone(),
- }
- }),
- })
- }
- talpid_net::openvpn::ProxySettings::Shadowsocks(proxy_settings) => {
- bridge_settings::Type::Shadowsocks(bridge_settings::ShadowsocksProxySettings {
- peer: proxy_settings.peer.to_string(),
- password: proxy_settings.password.clone(),
- cipher: proxy_settings.cipher,
- })
- }
- },
+ mullvad_types::relay_constraints::BridgeType::Custom => {
+ bridge_settings::BridgeType::Custom
+ }
+ };
+
+ let normal = bridge_settings::BridgeConstraints {
+ location: settings
+ .normal
+ .location
+ .clone()
+ .option()
+ .map(proto::LocationConstraint::from),
+ providers: convert_providers_constraint(&settings.normal.providers),
+ ownership: i32::from(convert_ownership_constraint(&settings.normal.ownership)),
};
+ let custom = settings.custom.map(proto::CustomProxy::from);
+
proto::BridgeSettings {
- r#type: Some(settings),
+ bridge_type: i32::from(mode),
+ normal: Some(normal),
+ custom,
}
}
}
@@ -389,79 +372,59 @@ impl TryFrom<proto::GeographicLocationConstraint> for GeographicLocationConstrai
}
}
+pub fn try_bridge_mode_from_i32(
+ mode: i32,
+) -> Result<mullvad_types::relay_constraints::BridgeType, FromProtobufTypeError> {
+ proto::bridge_settings::BridgeType::try_from(mode)
+ .map(mullvad_types::relay_constraints::BridgeType::from)
+ .map_err(|_| FromProtobufTypeError::InvalidArgument("invalid bridge mode argument"))
+}
+
+impl From<proto::bridge_settings::BridgeType> for mullvad_types::relay_constraints::BridgeType {
+ fn from(value: proto::bridge_settings::BridgeType) -> Self {
+ use mullvad_types::relay_constraints::BridgeType;
+
+ match value {
+ proto::bridge_settings::BridgeType::Normal => BridgeType::Normal,
+ proto::bridge_settings::BridgeType::Custom => BridgeType::Custom,
+ }
+ }
+}
+
impl TryFrom<proto::BridgeSettings> for mullvad_types::relay_constraints::BridgeSettings {
type Error = FromProtobufTypeError;
fn try_from(settings: proto::BridgeSettings) -> Result<Self, Self::Error> {
- use mullvad_types::relay_constraints as mullvad_constraints;
- use talpid_types::net as talpid_net;
+ use mullvad_types::relay_constraints::{BridgeConstraints, BridgeSettings};
- match settings
- .r#type
+ // convert normal bridge settings
+ let constraints = settings
+ .normal
.ok_or(FromProtobufTypeError::InvalidArgument(
- "no settings provided",
- ))? {
- proto::bridge_settings::Type::Normal(constraints) => {
- let location = match constraints.location {
- None => Constraint::Any,
- Some(location) => Constraint::<
- mullvad_types::relay_constraints::LocationConstraint,
- >::try_from(location)?,
- };
- let providers = try_providers_constraint_from_proto(&constraints.providers)?;
- let ownership = try_ownership_constraint_from_i32(constraints.ownership)?;
-
- Ok(mullvad_constraints::BridgeSettings::Normal(
- mullvad_constraints::BridgeConstraints {
- location,
- providers,
- ownership,
- },
- ))
- }
- proto::bridge_settings::Type::Local(proxy_settings) => {
- let peer = proxy_settings.peer.parse().map_err(|_| {
- FromProtobufTypeError::InvalidArgument("failed to parse peer address")
- })?;
- let proxy_settings = talpid_net::openvpn::ProxySettings::Local(
- talpid_net::openvpn::LocalProxySettings {
- port: proxy_settings.port as u16,
- peer,
- },
- );
- Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings))
- }
- proto::bridge_settings::Type::Remote(proxy_settings) => {
- let address = proxy_settings.address.parse().map_err(|_| {
- FromProtobufTypeError::InvalidArgument("failed to parse IP address")
- })?;
- let auth = proxy_settings
- .auth
- .map(|auth| talpid_net::openvpn::ProxyAuth {
- username: auth.username,
- password: auth.password,
- });
- let proxy_settings = talpid_net::openvpn::ProxySettings::Remote(
- talpid_net::openvpn::RemoteProxySettings { address, auth },
- );
- Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings))
- }
- proto::bridge_settings::Type::Shadowsocks(proxy_settings) => {
- let peer = proxy_settings.peer.parse().map_err(|_| {
- FromProtobufTypeError::InvalidArgument("failed to parse peer address")
- })?;
- let proxy_settings = talpid_net::openvpn::ProxySettings::Shadowsocks(
- talpid_net::openvpn::ShadowsocksProxySettings {
- #[cfg(target_os = "linux")]
- fwmark: Some(mullvad_types::TUNNEL_FWMARK),
- peer,
- password: proxy_settings.password,
- cipher: proxy_settings.cipher,
- },
- );
- Ok(mullvad_constraints::BridgeSettings::Custom(proxy_settings))
+ "missing normal bridge constraints",
+ ))?;
+ let location = match constraints.location {
+ None => Constraint::Any,
+ Some(location) => {
+ Constraint::<mullvad_types::relay_constraints::LocationConstraint>::try_from(
+ location,
+ )?
}
- }
+ };
+ let normal = BridgeConstraints {
+ location,
+ providers: try_providers_constraint_from_proto(&constraints.providers)?,
+ ownership: try_ownership_constraint_from_i32(constraints.ownership)?,
+ };
+
+ // convert custom bridge settings
+ let custom = settings.custom.map(CustomProxy::try_from).transpose()?;
+
+ Ok(BridgeSettings {
+ bridge_type: try_bridge_mode_from_i32(settings.bridge_type)?,
+ normal,
+ custom,
+ })
}
}
diff --git a/mullvad-relay-selector/src/lib.rs b/mullvad-relay-selector/src/lib.rs
index e66e574146..ac0da9f9b3 100644
--- a/mullvad-relay-selector/src/lib.rs
+++ b/mullvad-relay-selector/src/lib.rs
@@ -9,9 +9,10 @@ use mullvad_types::{
location::{Coordinates, Location},
relay_constraints::{
BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint,
- Match, ObfuscationSettings, OpenVpnConstraints, Ownership, Providers, RelayConstraints,
- RelayConstraintsFormatter, RelayOverride, RelaySettings, ResolvedLocationConstraint,
- SelectedObfuscation, Set, TransportPort, Udp2TcpObfuscationSettings,
+ Match, MissingCustomBridgeSettings, ObfuscationSettings, OpenVpnConstraints, Ownership,
+ Providers, RelayConstraints, RelayConstraintsFormatter, RelayOverride, RelaySettings,
+ ResolvedBridgeSettings, ResolvedLocationConstraint, SelectedObfuscation, Set,
+ TransportPort, Udp2TcpObfuscationSettings,
},
relay_list::{BridgeEndpointData, Relay, RelayEndpointData, RelayList},
settings::Settings,
@@ -29,8 +30,8 @@ use std::{
};
use talpid_types::{
net::{
- obfuscation::ObfuscatorConfig, openvpn::ProxySettings, wireguard, IpVersion,
- TransportProtocol, TunnelType,
+ obfuscation::ObfuscatorConfig, proxy::CustomProxy, wireguard, IpVersion, TransportProtocol,
+ TunnelType,
},
ErrorExt,
};
@@ -77,6 +78,9 @@ pub enum Error {
#[error(display = "Downloader already shut down")]
DownloaderShutDown,
+
+ #[error(display = "Invalid bridge settings")]
+ InvalidBridgeSettings(#[error(source)] MissingCustomBridgeSettings),
}
struct ParsedRelays {
@@ -869,8 +873,12 @@ impl RelaySelector {
retry_attempt: u32,
custom_lists: &CustomListsSettings,
) -> Result<Option<SelectedBridge>, Error> {
- match &config.bridge_settings {
- BridgeSettings::Normal(settings) => {
+ match config
+ .bridge_settings
+ .resolve()
+ .map_err(Error::InvalidBridgeSettings)?
+ {
+ ResolvedBridgeSettings::Normal(settings) => {
let bridge_constraints = InternalBridgeConstraints {
location: settings.location.clone(),
providers: settings.providers.clone(),
@@ -896,7 +904,7 @@ impl RelaySelector {
BridgeState::Auto | BridgeState::Off => Ok(None),
}
}
- BridgeSettings::Custom(bridge_settings) => match config.bridge_state {
+ ResolvedBridgeSettings::Custom(bridge_settings) => match config.bridge_state {
BridgeState::On => Ok(Some(SelectedBridge::Custom(bridge_settings.clone()))),
BridgeState::Auto if Self::should_use_bridge(retry_attempt) => {
Ok(Some(SelectedBridge::Custom(bridge_settings.clone())))
@@ -906,8 +914,9 @@ impl RelaySelector {
}
}
- /// Returns a bridge based on the relay and bridge constraints, ignoring the bridge state.
- pub fn get_bridge_forced(&self) -> Option<ProxySettings> {
+ /// Returns a non-custom bridge based on the relay and bridge constraints, ignoring the bridge
+ /// state.
+ pub fn get_bridge_forced(&self) -> Option<CustomProxy> {
let config = self.config.lock();
let near_location = match &config.relay_settings {
@@ -917,14 +926,14 @@ impl RelaySelector {
_ => None,
};
- let constraints = match &config.bridge_settings {
- BridgeSettings::Normal(settings) => InternalBridgeConstraints {
+ let constraints = match config.bridge_settings.resolve() {
+ Ok(ResolvedBridgeSettings::Normal(settings)) => InternalBridgeConstraints {
location: settings.location.clone(),
providers: settings.providers.clone(),
ownership: settings.ownership,
transport_protocol: Constraint::Only(TransportProtocol::Tcp),
},
- BridgeSettings::Custom(_bridge_settings) => InternalBridgeConstraints {
+ _ => InternalBridgeConstraints {
location: Constraint::Any,
providers: Constraint::Any,
ownership: Constraint::Any,
@@ -951,7 +960,7 @@ impl RelaySelector {
constraints: &InternalBridgeConstraints,
location: Option<T>,
custom_lists: &CustomListsSettings,
- ) -> Option<(ProxySettings, Relay)> {
+ ) -> Option<(CustomProxy, Relay)> {
let matcher = RelayMatcher {
locations: ResolvedLocationConstraint::from_constraint(
constraints.location.clone(),
@@ -1236,11 +1245,7 @@ impl RelaySelector {
}
/// Picks a random bridge from a relay.
- fn pick_random_bridge(
- &self,
- data: &BridgeEndpointData,
- relay: &Relay,
- ) -> Option<ProxySettings> {
+ fn pick_random_bridge(&self, data: &BridgeEndpointData, relay: &Relay) -> Option<CustomProxy> {
if relay.endpoint_data != RelayEndpointData::Bridge {
return None;
}
@@ -1254,11 +1259,7 @@ impl RelaySelector {
shadowsocks_endpoint.port,
shadowsocks_endpoint.protocol
);
- shadowsocks_endpoint.to_proxy_settings(
- relay.ipv4_addr_in.into(),
- #[cfg(target_os = "linux")]
- mullvad_types::TUNNEL_FWMARK,
- )
+ shadowsocks_endpoint.to_proxy_settings(relay.ipv4_addr_in.into())
})
}
@@ -1275,12 +1276,12 @@ impl RelaySelector {
#[derive(Debug)]
pub enum SelectedBridge {
Normal(NormalSelectedBridge),
- Custom(ProxySettings),
+ Custom(CustomProxy),
}
#[derive(Debug)]
pub struct NormalSelectedBridge {
- pub settings: ProxySettings,
+ pub settings: CustomProxy,
pub relay: Relay,
}
diff --git a/mullvad-types/src/access_method.rs b/mullvad-types/src/access_method.rs
index 0c697acbbf..73ab671c9c 100644
--- a/mullvad-types/src/access_method.rs
+++ b/mullvad-types/src/access_method.rs
@@ -1,10 +1,9 @@
use std::str::FromStr;
use serde::{Deserialize, Serialize};
-use std::net::SocketAddr;
-use talpid_types::net::{Endpoint, TransportProtocol};
+use talpid_types::net::proxy::{CustomProxy, Shadowsocks, Socks5Local, Socks5Remote};
-/// Daemon settings for API access methods.
+/// Dttings for API access methods.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Settings {
pub access_method_settings: Vec<AccessMethodSetting>,
@@ -136,7 +135,7 @@ impl std::fmt::Display for Id {
#[serde(rename_all = "snake_case")]
pub enum AccessMethod {
BuiltIn(BuiltInAccessMethod),
- Custom(CustomAccessMethod),
+ Custom(CustomProxy),
}
impl AccessMethodSetting {
@@ -179,7 +178,7 @@ impl AccessMethodSetting {
self.enabled
}
- pub fn as_custom(&self) -> Option<&CustomAccessMethod> {
+ pub fn as_custom(&self) -> Option<&CustomProxy> {
self.access_method.as_custom()
}
@@ -206,53 +205,8 @@ pub enum BuiltInAccessMethod {
Bridge,
}
-/// Custom access method datastructure.
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-#[serde(rename_all = "snake_case")]
-pub enum CustomAccessMethod {
- Shadowsocks(Shadowsocks),
- Socks5(Socks5),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-#[serde(rename_all = "snake_case")]
-pub enum Socks5 {
- Local(Socks5Local),
- Remote(Socks5Remote),
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-pub struct Shadowsocks {
- pub peer: SocketAddr,
- pub password: String,
- /// One of [`shadowsocks_ciphers`].
- /// Gets validated at a later stage. Is assumed to be valid.
- ///
- /// shadowsocks_ciphers: talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS
- pub cipher: String,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-pub struct Socks5Local {
- pub remote_endpoint: Endpoint,
- /// Port on localhost where the SOCKS5-proxy listens to.
- pub local_port: u16,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-pub struct Socks5Remote {
- pub peer: SocketAddr,
- pub authentication: Option<SocksAuth>,
-}
-
-#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
-pub struct SocksAuth {
- pub username: String,
- pub password: String,
-}
-
impl AccessMethod {
- pub fn as_custom(&self) -> Option<&CustomAccessMethod> {
+ pub fn as_custom(&self) -> Option<&CustomProxy> {
match self {
AccessMethod::BuiltIn(_) => None,
AccessMethod::Custom(access_method) => Some(access_method),
@@ -269,98 +223,32 @@ impl BuiltInAccessMethod {
}
}
-impl Shadowsocks {
- pub fn new<I: Into<SocketAddr>>(peer: I, cipher: String, password: String) -> Self {
- Shadowsocks {
- peer: peer.into(),
- password,
- cipher,
- }
- }
-}
-
-impl Socks5Local {
- pub fn new<I: Into<SocketAddr>>(remote_peer: I, local_port: u16) -> Self {
- let transport_protocol = TransportProtocol::Tcp;
- Self::new_with_transport_protocol(remote_peer, local_port, transport_protocol)
- }
-
- pub fn new_with_transport_protocol<I: Into<SocketAddr>>(
- remote_peer: I,
- local_port: u16,
- transport_protocol: TransportProtocol,
- ) -> Self {
- let remote_endpoint = Endpoint::from_socket_address(remote_peer.into(), transport_protocol);
- Self {
- remote_endpoint,
- local_port,
- }
- }
-}
-
-impl Socks5Remote {
- pub fn new<I: Into<SocketAddr>>(peer: I) -> Self {
- Self {
- peer: peer.into(),
- authentication: None,
- }
- }
-
- pub fn new_with_authentication<I: Into<SocketAddr>>(
- peer: I,
- authentication: SocksAuth,
- ) -> Self {
- Self {
- peer: peer.into(),
- authentication: Some(authentication),
- }
- }
-}
-
impl From<BuiltInAccessMethod> for AccessMethod {
fn from(value: BuiltInAccessMethod) -> Self {
AccessMethod::BuiltIn(value)
}
}
-impl From<CustomAccessMethod> for AccessMethod {
- fn from(value: CustomAccessMethod) -> Self {
+impl From<CustomProxy> for AccessMethod {
+ fn from(value: CustomProxy) -> Self {
AccessMethod::Custom(value)
}
}
-impl From<Shadowsocks> for AccessMethod {
- fn from(value: Shadowsocks) -> Self {
- CustomAccessMethod::Shadowsocks(value).into()
- }
-}
-
-impl From<Socks5> for AccessMethod {
- fn from(value: Socks5) -> Self {
- AccessMethod::from(CustomAccessMethod::Socks5(value))
- }
-}
-
impl From<Socks5Remote> for AccessMethod {
fn from(value: Socks5Remote) -> Self {
- Socks5::Remote(value).into()
+ CustomProxy::Socks5Remote(value).into()
}
}
impl From<Socks5Local> for AccessMethod {
fn from(value: Socks5Local) -> Self {
- Socks5::Local(value).into()
- }
-}
-
-impl From<Socks5Remote> for Socks5 {
- fn from(value: Socks5Remote) -> Self {
- Socks5::Remote(value)
+ CustomProxy::Socks5Local(value).into()
}
}
-impl From<Socks5Local> for Socks5 {
- fn from(value: Socks5Local) -> Self {
- Socks5::Local(value)
+impl From<Shadowsocks> for AccessMethod {
+ fn from(value: Shadowsocks) -> Self {
+ CustomProxy::Shadowsocks(value).into()
}
}
diff --git a/mullvad-types/src/custom_tunnel.rs b/mullvad-types/src/custom_tunnel.rs
index 9b6ab9bff3..8fdf361447 100644
--- a/mullvad-types/src/custom_tunnel.rs
+++ b/mullvad-types/src/custom_tunnel.rs
@@ -6,7 +6,7 @@ use std::{
fmt, io,
net::{IpAddr, SocketAddr, ToSocketAddrs},
};
-use talpid_types::net::{openvpn, wireguard, Endpoint, TunnelParameters};
+use talpid_types::net::{openvpn, proxy::CustomProxy, wireguard, Endpoint, TunnelParameters};
#[derive(err_derive::Error, Debug)]
pub enum Error {
@@ -42,7 +42,7 @@ impl CustomTunnelEndpoint {
pub fn to_tunnel_parameters(
&self,
tunnel_options: TunnelOptions,
- proxy: Option<openvpn::ProxySettings>,
+ proxy: Option<CustomProxy>,
) -> Result<TunnelParameters, Error> {
let ip = resolve_to_ip(&self.host)?;
let mut config = self.config.clone();
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index 6645fb493d..0669d8bd8b 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -16,7 +16,7 @@ use std::{
net::{Ipv4Addr, Ipv6Addr},
str::FromStr,
};
-use talpid_types::net::{openvpn::ProxySettings, IpVersion, TransportProtocol, TunnelType};
+use talpid_types::net::{proxy::CustomProxy, IpVersion, TransportProtocol, TunnelType};
pub trait Match<T> {
fn matches(&self, other: &T) -> bool;
@@ -831,14 +831,53 @@ struct Port {
value: i32,
}
+#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum BridgeType {
+ /// Let the relay selection algorithm decide on bridges, based on the relay list
+ /// and normal bridge constraints.
+ #[default]
+ Normal,
+ /// Use custom bridge configuration.
+ Custom,
+}
+
+impl fmt::Display for BridgeType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match self {
+ BridgeType::Normal => f.write_str("normal"),
+ BridgeType::Custom => f.write_str("custom"),
+ }
+ }
+}
+
+#[derive(err_derive::Error, Debug)]
+#[error(display = "Missing custom bridge settings")]
+pub struct MissingCustomBridgeSettings(());
+
/// Specifies a specific endpoint or [`BridgeConstraints`] to use when `mullvad-daemon` selects a
/// bridge server.
-#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[derive(Default, Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
-pub enum BridgeSettings {
- /// Let the relay selection algorithm decide on bridges, based on the relay list.
- Normal(BridgeConstraints),
- Custom(ProxySettings),
+pub struct BridgeSettings {
+ pub bridge_type: BridgeType,
+ pub normal: BridgeConstraints,
+ pub custom: Option<CustomProxy>,
+}
+
+pub enum ResolvedBridgeSettings<'a> {
+ Normal(&'a BridgeConstraints),
+ Custom(&'a CustomProxy),
+}
+
+impl BridgeSettings {
+ pub fn resolve(&self) -> Result<ResolvedBridgeSettings<'_>, MissingCustomBridgeSettings> {
+ match (self.bridge_type, &self.custom) {
+ (BridgeType::Normal, _) => Ok(ResolvedBridgeSettings::Normal(&self.normal)),
+ (BridgeType::Custom, Some(custom)) => Ok(ResolvedBridgeSettings::Custom(custom)),
+ (BridgeType::Custom, None) => Err(MissingCustomBridgeSettings(())),
+ }
+ }
}
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Deserialize, Serialize)]
diff --git a/mullvad-types/src/relay_list.rs b/mullvad-types/src/relay_list.rs
index 0edf4f5752..55649b38c1 100644
--- a/mullvad-types/src/relay_list.rs
+++ b/mullvad-types/src/relay_list.rs
@@ -4,7 +4,7 @@ use jnix::IntoJava;
use serde::{Deserialize, Serialize};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
use talpid_types::net::{
- openvpn::{ProxySettings, ShadowsocksProxySettings},
+ proxy::{CustomProxy, Shadowsocks},
wireguard, TransportProtocol,
};
@@ -207,17 +207,11 @@ pub struct ShadowsocksEndpointData {
}
impl ShadowsocksEndpointData {
- pub fn to_proxy_settings(
- &self,
- addr: IpAddr,
- #[cfg(target_os = "linux")] fwmark: u32,
- ) -> ProxySettings {
- ProxySettings::Shadowsocks(ShadowsocksProxySettings {
- peer: SocketAddr::new(addr, self.port),
+ pub fn to_proxy_settings(&self, addr: IpAddr) -> CustomProxy {
+ CustomProxy::Shadowsocks(Shadowsocks {
+ endpoint: SocketAddr::new(addr, self.port),
password: self.password.clone(),
cipher: self.cipher.clone(),
- #[cfg(target_os = "linux")]
- fwmark: Some(fwmark),
})
}
}
diff --git a/mullvad-types/src/settings/mod.rs b/mullvad-types/src/settings/mod.rs
index 612e353487..e6886c9e3c 100644
--- a/mullvad-types/src/settings/mod.rs
+++ b/mullvad-types/src/settings/mod.rs
@@ -2,8 +2,8 @@ use crate::{
access_method,
custom_list::CustomListsSettings,
relay_constraints::{
- BridgeConstraints, BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint,
- LocationConstraint, ObfuscationSettings, RelayConstraints, RelayOverride, RelaySettings,
+ BridgeSettings, BridgeState, Constraint, GeographicLocationConstraint, LocationConstraint,
+ ObfuscationSettings, RelayConstraints, RelayOverride, RelaySettings,
RelaySettingsFormatter, SelectedObfuscation, WireguardConstraints,
},
wireguard,
@@ -32,6 +32,7 @@ pub enum SettingsVersion {
V5 = 5,
V6 = 6,
V7 = 7,
+ V8 = 8,
}
impl<'de> Deserialize<'de> for SettingsVersion {
@@ -46,6 +47,7 @@ impl<'de> Deserialize<'de> for SettingsVersion {
v if v == SettingsVersion::V5 as u32 => Ok(SettingsVersion::V5),
v if v == SettingsVersion::V6 as u32 => Ok(SettingsVersion::V6),
v if v == SettingsVersion::V7 as u32 => Ok(SettingsVersion::V7),
+ v if v == SettingsVersion::V8 as u32 => Ok(SettingsVersion::V8),
v => Err(serde::de::Error::custom(format!(
"{v} is not a valid SettingsVersion"
))),
@@ -128,7 +130,7 @@ impl Default for Settings {
},
..Default::default()
}),
- bridge_settings: BridgeSettings::Normal(BridgeConstraints::default()),
+ bridge_settings: BridgeSettings::default(),
obfuscation_settings: ObfuscationSettings {
selected_obfuscation: SelectedObfuscation::Off,
..Default::default()
diff --git a/talpid-core/src/firewall/linux.rs b/talpid-core/src/firewall/linux.rs
index 6cbff79b32..79ac00fc8d 100644
--- a/talpid-core/src/firewall/linux.rs
+++ b/talpid-core/src/firewall/linux.rs
@@ -567,6 +567,7 @@ impl<'a> PolicyBatch<'a> {
self.add_allow_tunnel_endpoint_rules(peer_endpoint, fwmark);
self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Udp)?;
self.add_allow_dns_rules(tunnel, dns_servers, TransportProtocol::Tcp)?;
+
// Important to block DNS *before* we allow the tunnel and allow LAN. So DNS
// can't leak to the wrong IPs in the tunnel or on the LAN.
self.add_drop_dns_rule();
@@ -607,10 +608,10 @@ impl<'a> PolicyBatch<'a> {
Ok(())
}
- fn add_allow_tunnel_endpoint_rules(&mut self, endpoint: &Endpoint, fwmark: u32) {
+ fn add_allow_tunnel_endpoint_rules(&mut self, endpoint: &AllowedEndpoint, fwmark: u32) {
let mut prerouting_rule = Rule::new(&self.prerouting_chain);
// Mark incoming traffic from endpoint with fwmark
- check_endpoint(&mut prerouting_rule, End::Src, endpoint);
+ check_endpoint(&mut prerouting_rule, End::Src, &endpoint.endpoint);
prerouting_rule.add_expr(&nft_expr!(immediate data fwmark));
prerouting_rule.add_expr(&nft_expr!(meta mark set));
@@ -621,7 +622,7 @@ impl<'a> PolicyBatch<'a> {
self.batch.add(&prerouting_rule, nftnl::MsgType::Add);
let mut in_rule = Rule::new(&self.in_chain);
- check_endpoint(&mut in_rule, End::Src, endpoint);
+ check_endpoint(&mut in_rule, End::Src, &endpoint.endpoint);
// Allow all incoming traffic from established connections to the endpoint
let allowed_states = nftnl::expr::ct::States::ESTABLISHED.bits();
@@ -637,12 +638,24 @@ impl<'a> PolicyBatch<'a> {
// Allow any traffic to the endpoint which is marked with fwmark
let mut out_rule = Rule::new(&self.out_chain);
- check_endpoint(&mut out_rule, End::Dst, endpoint);
+ check_endpoint(&mut out_rule, End::Dst, &endpoint.endpoint);
out_rule.add_expr(&nft_expr!(meta mark));
out_rule.add_expr(&nft_expr!(cmp == fwmark));
add_verdict(&mut out_rule, &Verdict::Accept);
self.batch.add(&out_rule, nftnl::MsgType::Add);
+
+ // Used for local custom bridge, allows some local socks5 proxy to send traffic to the
+ // endpoint
+ if endpoint.clients.allow_all() {
+ let mut rule = Rule::new(&self.mangle_chain);
+ check_endpoint(&mut rule, End::Dst, &endpoint.endpoint);
+ rule.add_expr(&nft_expr!(immediate data split_tunnel::MARK));
+ rule.add_expr(&nft_expr!(ct mark set));
+ rule.add_expr(&nft_expr!(immediate data fwmark));
+ rule.add_expr(&nft_expr!(meta mark set));
+ self.batch.add(&rule, nftnl::MsgType::Add);
+ }
}
/// Adds firewall rules allow traffic to flow to the API. Allows the app to reach the API in
diff --git a/talpid-core/src/firewall/macos.rs b/talpid-core/src/firewall/macos.rs
index 54f72a79c2..27fc6200fa 100644
--- a/talpid-core/src/firewall/macos.rs
+++ b/talpid-core/src/firewall/macos.rs
@@ -121,7 +121,7 @@ impl Firewall {
allowed_endpoint,
allowed_tunnel_traffic,
} => {
- let mut rules = vec![self.get_allow_relay_rule(*peer_endpoint)?];
+ let mut rules = vec![self.get_allow_relay_rule(peer_endpoint)?];
rules.push(self.get_allowed_endpoint_rule(allowed_endpoint)?);
// Important to block DNS after allow relay rule (so the relay can operate
@@ -151,7 +151,7 @@ impl Firewall {
rules.append(&mut self.get_allow_dns_rules_when_connected(tunnel, *server)?);
}
- rules.push(self.get_allow_relay_rule(*peer_endpoint)?);
+ rules.push(self.get_allow_relay_rule(peer_endpoint)?);
// Important to block DNS *before* we allow the tunnel and allow LAN. So DNS
// can't leak to the wrong IPs in the tunnel or on the LAN.
@@ -273,18 +273,26 @@ impl Firewall {
Ok(rules)
}
- fn get_allow_relay_rule(&self, relay_endpoint: net::Endpoint) -> Result<pfctl::FilterRule> {
- let pfctl_proto = as_pfctl_proto(relay_endpoint.protocol);
+ fn get_allow_relay_rule(
+ &self,
+ relay_endpoint: &net::AllowedEndpoint,
+ ) -> Result<pfctl::FilterRule> {
+ let pfctl_proto = as_pfctl_proto(relay_endpoint.endpoint.protocol);
- self.create_rule_builder(FilterRuleAction::Pass)
+ let mut builder = self.create_rule_builder(FilterRuleAction::Pass);
+ builder
.direction(pfctl::Direction::Out)
- .to(relay_endpoint.address)
+ .to(relay_endpoint.endpoint.address)
.proto(pfctl_proto)
.keep_state(pfctl::StatePolicy::Keep)
.tcp_flags(Self::get_tcp_flags())
- .user(Uid::from(super::ROOT_UID))
- .quick(true)
- .build()
+ .quick(true);
+
+ if !relay_endpoint.clients.allow_all() {
+ builder.user(Uid::from(super::ROOT_UID));
+ }
+
+ builder.build()
}
/// Produces a rule that allows traffic to flow to the API. Allows the app (or other apps if configured)
diff --git a/talpid-core/src/firewall/mod.rs b/talpid-core/src/firewall/mod.rs
index 5a35b1a7ae..a0afb39f5d 100644
--- a/talpid-core/src/firewall/mod.rs
+++ b/talpid-core/src/firewall/mod.rs
@@ -1,12 +1,10 @@
use ipnetwork::{IpNetwork, Ipv4Network, Ipv6Network};
use once_cell::sync::Lazy;
-#[cfg(windows)]
-use std::path::PathBuf;
use std::{
fmt,
net::{IpAddr, Ipv4Addr, Ipv6Addr},
};
-use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint};
+use talpid_types::net::{AllowedEndpoint, AllowedTunnelTraffic};
#[cfg(target_os = "macos")]
#[path = "macos.rs"]
@@ -116,7 +114,7 @@ pub enum FirewallPolicy {
/// Allow traffic only to server
Connecting {
/// The peer endpoint that should be allowed.
- peer_endpoint: Endpoint,
+ peer_endpoint: AllowedEndpoint,
/// Metadata about the tunnel and tunnel interface.
tunnel: Option<crate::tunnel::TunnelMetadata>,
/// Flag setting if communication with LAN networks should be possible.
@@ -125,15 +123,12 @@ pub enum FirewallPolicy {
allowed_endpoint: AllowedEndpoint,
/// Networks for which to permit in-tunnel traffic.
allowed_tunnel_traffic: AllowedTunnelTraffic,
- /// A process that is allowed to send packets to the relay.
- #[cfg(windows)]
- relay_client: PathBuf,
},
/// Allow traffic only to server and over tunnel interface
Connected {
/// The peer endpoint that should be allowed.
- peer_endpoint: Endpoint,
+ peer_endpoint: AllowedEndpoint,
/// Metadata about the tunnel and tunnel interface.
tunnel: crate::tunnel::TunnelMetadata,
/// Flag setting if communication with LAN networks should be possible.
@@ -141,9 +136,6 @@ pub enum FirewallPolicy {
/// Servers that are allowed to respond to DNS requests.
#[cfg(not(target_os = "android"))]
dns_servers: Vec<IpAddr>,
- /// A process that is allowed to send packets to the relay.
- #[cfg(windows)]
- relay_client: PathBuf,
},
/// Block all network traffic in and out from the computer.
diff --git a/talpid-core/src/firewall/windows.rs b/talpid-core/src/firewall/windows.rs
index ed3e0a19fe..f26102820a 100644
--- a/talpid-core/src/firewall/windows.rs
+++ b/talpid-core/src/firewall/windows.rs
@@ -1,11 +1,11 @@
use crate::tunnel::TunnelMetadata;
-use std::{ffi::CStr, io, net::IpAddr, path::Path, ptr};
+use std::{ffi::CStr, io, net::IpAddr, ptr};
use self::winfw::*;
use super::{FirewallArguments, FirewallPolicy, InitialFirewallState};
use talpid_types::{
- net::{AllowedEndpoint, AllowedTunnelTraffic, Endpoint},
+ net::{AllowedEndpoint, AllowedTunnelTraffic},
tunnel::FirewallPolicyError,
};
use widestring::WideCString;
@@ -99,7 +99,6 @@ impl Firewall {
allow_lan,
allowed_endpoint,
allowed_tunnel_traffic,
- relay_client,
} => {
let cfg = &WinFwSettings::new(allow_lan);
@@ -109,7 +108,6 @@ impl Firewall {
&tunnel,
&WinFwAllowedEndpointContainer::from(allowed_endpoint).as_endpoint(),
&allowed_tunnel_traffic,
- &relay_client,
)
}
FirewallPolicy::Connected {
@@ -117,10 +115,9 @@ impl Firewall {
tunnel,
allow_lan,
dns_servers,
- relay_client,
} => {
let cfg = &WinFwSettings::new(allow_lan);
- self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_servers, &relay_client)
+ self.set_connected_state(&peer_endpoint, cfg, &tunnel, &dns_servers)
}
FirewallPolicy::Blocked {
allow_lan,
@@ -142,22 +139,33 @@ impl Firewall {
fn set_connecting_state(
&mut self,
- endpoint: &Endpoint,
+ endpoint: &AllowedEndpoint,
winfw_settings: &WinFwSettings,
tunnel_metadata: &Option<TunnelMetadata>,
allowed_endpoint: &WinFwAllowedEndpoint<'_>,
allowed_tunnel_traffic: &AllowedTunnelTraffic,
- relay_client: &Path,
) -> Result<(), Error> {
log::trace!("Applying 'connecting' firewall policy");
- let ip_str = widestring_ip(endpoint.address.ip());
+ let ip_str = widestring_ip(endpoint.endpoint.address.ip());
let winfw_relay = WinFwEndpoint {
ip: ip_str.as_ptr(),
- port: endpoint.address.port(),
- protocol: WinFwProt::from(endpoint.protocol),
+ port: endpoint.endpoint.address.port(),
+ protocol: WinFwProt::from(endpoint.endpoint.protocol),
};
- let relay_client = WideCString::from_os_str_truncate(relay_client);
+ // SAFETY: `endpoint1_ip`, `endpoint2_ip`, `endpoint1`, `endpoint2`, `relay_client_wstrs` must not be dropped
+ // until `WinFw_ApplyPolicyConnecting` has returned.
+
+ let relay_client_wstrs: Vec<_> = endpoint
+ .clients
+ .iter()
+ .map(WideCString::from_os_str_truncate)
+ .collect();
+ let relay_client_wstr_ptrs: Vec<*const u16> = relay_client_wstrs
+ .iter()
+ .map(|wstr| wstr.as_ptr())
+ .collect();
+ let relay_client_wstr_ptrs_len = relay_client_wstr_ptrs.len();
let interface_wstr = tunnel_metadata
.as_ref()
@@ -168,8 +176,6 @@ impl Firewall {
ptr::null()
};
- // SAFETY: `endpoint1_ip`, `endpoint2_ip`, `endpoint1` and `endpoint2` must not be dropped
- // until `WinFw_ApplyPolicyConnecting` has returned.
let mut endpoint1_ip = WideCString::new();
let mut endpoint2_ip = WideCString::new();
let (endpoint1, endpoint2) = match allowed_tunnel_traffic {
@@ -218,7 +224,8 @@ impl Firewall {
WinFw_ApplyPolicyConnecting(
winfw_settings,
&winfw_relay,
- relay_client.as_ptr(),
+ relay_client_wstr_ptrs.as_ptr(),
+ relay_client_wstr_ptrs_len,
interface_wstr_ptr,
allowed_endpoint,
&allowed_tunnel_traffic,
@@ -235,19 +242,19 @@ impl Firewall {
drop(endpoint1);
#[allow(clippy::drop_non_drop)]
drop(endpoint2);
+ drop(relay_client_wstrs);
res
}
fn set_connected_state(
&mut self,
- endpoint: &Endpoint,
+ endpoint: &AllowedEndpoint,
winfw_settings: &WinFwSettings,
tunnel_metadata: &TunnelMetadata,
dns_servers: &[IpAddr],
- relay_client: &Path,
) -> Result<(), Error> {
log::trace!("Applying 'connected' firewall policy");
- let ip_str = widestring_ip(endpoint.address.ip());
+ let ip_str = widestring_ip(endpoint.endpoint.address.ip());
let v4_gateway = widestring_ip(tunnel_metadata.ipv4_gateway.into());
let v6_gateway = tunnel_metadata
.ipv6_gateway
@@ -258,8 +265,8 @@ impl Firewall {
// ip_str, gateway_str and tunnel_alias have to outlive winfw_relay
let winfw_relay = WinFwEndpoint {
ip: ip_str.as_ptr(),
- port: endpoint.address.port(),
- protocol: WinFwProt::from(endpoint.protocol),
+ port: endpoint.endpoint.address.port(),
+ protocol: WinFwProt::from(endpoint.endpoint.protocol),
};
let v6_gateway_ptr = match &v6_gateway {
@@ -267,17 +274,28 @@ impl Firewall {
None => ptr::null(),
};
- let relay_client = WideCString::from_os_str_truncate(relay_client);
+ // SAFETY: `relay_client_wstrs` must not be dropped until `WinFw_ApplyPolicyConnected` has returned.
+ let relay_client_wstrs: Vec<_> = endpoint
+ .clients
+ .iter()
+ .map(WideCString::from_os_str_truncate)
+ .collect();
+ let relay_client_wstr_ptrs: Vec<*const u16> = relay_client_wstrs
+ .iter()
+ .map(|wstr| wstr.as_ptr())
+ .collect();
+ let relay_client_wstr_ptrs_len = relay_client_wstr_ptrs.len();
let dns_servers: Vec<WideCString> =
dns_servers.iter().cloned().map(widestring_ip).collect();
let dns_servers: Vec<*const u16> = dns_servers.iter().map(|ip| ip.as_ptr()).collect();
- unsafe {
+ let result = unsafe {
WinFw_ApplyPolicyConnected(
winfw_settings,
&winfw_relay,
- relay_client.as_ptr(),
+ relay_client_wstr_ptrs.as_ptr(),
+ relay_client_wstr_ptrs_len,
tunnel_alias.as_ptr(),
v4_gateway.as_ptr(),
v6_gateway_ptr,
@@ -286,7 +304,12 @@ impl Firewall {
)
.into_result()
.map_err(Error::ApplyingConnectedPolicy)
- }
+ };
+
+ // SAFETY: `relay_client_wstrs` holds memory pointed to by pointers used in C++ and must
+ // not be dropped until after `WinFw_ApplyPolicyConnected` has returned.
+ drop(relay_client_wstrs);
+ result
}
fn set_blocked_state(
@@ -598,7 +621,8 @@ mod winfw {
pub fn WinFw_ApplyPolicyConnecting(
settings: &WinFwSettings,
relay: &WinFwEndpoint,
- relayClient: *const libc::wchar_t,
+ relayClient: *const *const libc::wchar_t,
+ relayClientLen: usize,
tunnelIfaceAlias: *const libc::wchar_t,
allowedEndpoint: *const WinFwAllowedEndpoint<'_>,
allowedTunnelTraffic: &WinFwAllowedTunnelTraffic,
@@ -608,7 +632,8 @@ mod winfw {
pub fn WinFw_ApplyPolicyConnected(
settings: &WinFwSettings,
relay: &WinFwEndpoint,
- relayClient: *const libc::wchar_t,
+ relayClient: *const *const libc::wchar_t,
+ relayClientLen: usize,
tunnelIfaceAlias: *const libc::wchar_t,
v4Gateway: *const libc::wchar_t,
v6Gateway: *const libc::wchar_t,
diff --git a/talpid-core/src/tunnel/mod.rs b/talpid-core/src/tunnel/mod.rs
index 4ab6bb8d3d..3d953e965a 100644
--- a/talpid-core/src/tunnel/mod.rs
+++ b/talpid-core/src/tunnel/mod.rs
@@ -4,7 +4,7 @@ use futures::channel::oneshot;
use std::path;
#[cfg(not(target_os = "android"))]
use talpid_openvpn;
-#[cfg(any(target_os = "linux", target_os = "windows"))]
+#[cfg(not(target_os = "android"))]
use talpid_routing::RouteManagerHandle;
pub use talpid_tunnel::{TunnelArgs, TunnelEvent, TunnelMetadata};
#[cfg(not(target_os = "android"))]
@@ -91,7 +91,6 @@ impl TunnelMonitor {
args.resource_dir,
args.on_event,
args.tunnel_close_rx,
- #[cfg(target_os = "linux")]
args.route_manager,
)),
#[cfg(target_os = "android")]
@@ -104,20 +103,23 @@ impl TunnelMonitor {
}
/// Returns a path to an executable that communicates with relay servers.
+ /// Returns `None` if the executable is unknown.
#[cfg(windows)]
- pub fn get_relay_client(resource_dir: &path::Path, params: &TunnelParameters) -> path::PathBuf {
+ pub fn get_relay_client(
+ resource_dir: &path::Path,
+ params: &TunnelParameters,
+ ) -> Option<path::PathBuf> {
+ use talpid_types::net::proxy::CustomProxy;
+
let resource_dir = resource_dir.to_path_buf();
- let process_string = match params {
- TunnelParameters::OpenVpn(params) => {
- if let Some(openvpn_types::ProxySettings::Shadowsocks(..)) = &params.proxy {
- return std::env::current_exe().unwrap();
- } else {
- "openvpn.exe"
- }
- }
- _ => return std::env::current_exe().unwrap(),
- };
- resource_dir.join(process_string)
+ match params {
+ TunnelParameters::OpenVpn(params) => match &params.proxy {
+ Some(CustomProxy::Shadowsocks(_)) => Some(std::env::current_exe().unwrap()),
+ Some(CustomProxy::Socks5Local(_)) => None,
+ Some(CustomProxy::Socks5Remote(_)) | None => Some(resource_dir.join("openvpn.exe")),
+ },
+ _ => Some(std::env::current_exe().unwrap()),
+ }
}
fn start_wireguard_tunnel<L>(
@@ -211,7 +213,7 @@ impl TunnelMonitor {
resource_dir: &path::Path,
on_event: L,
tunnel_close_rx: oneshot::Receiver<()>,
- #[cfg(target_os = "linux")] route_manager: RouteManagerHandle,
+ route_manager: RouteManagerHandle,
) -> Result<Self>
where
L: (Fn(TunnelEvent) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>)
@@ -224,7 +226,6 @@ impl TunnelMonitor {
config,
log,
resource_dir,
- #[cfg(target_os = "linux")]
route_manager,
)
.await?;
diff --git a/talpid-core/src/tunnel_state_machine/connected_state.rs b/talpid-core/src/tunnel_state_machine/connected_state.rs
index 21375f056b..6f32b796c3 100644
--- a/talpid-core/src/tunnel_state_machine/connected_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connected_state.rs
@@ -14,7 +14,7 @@ use futures::{
};
use std::net::IpAddr;
use talpid_types::{
- net::TunnelParameters,
+ net::{AllowedClients, AllowedEndpoint, TunnelParameters},
tunnel::{ErrorStateCause, FirewallPolicyError},
BoxedError, ErrorExt,
};
@@ -130,17 +130,34 @@ impl ConnectedState {
}
fn get_firewall_policy(&self, shared_values: &SharedTunnelStateValues) -> FirewallPolicy {
+ let endpoint = self.tunnel_parameters.get_next_hop_endpoint();
+
+ #[cfg(target_os = "windows")]
+ let clients = AllowedClients::from(
+ TunnelMonitor::get_relay_client(&shared_values.resource_dir, &self.tunnel_parameters)
+ .into_iter()
+ .collect::<Vec<_>>(),
+ );
+
+ #[cfg(not(target_os = "windows"))]
+ let clients = if self
+ .tunnel_parameters
+ .get_openvpn_local_proxy_settings()
+ .is_some()
+ {
+ AllowedClients::All
+ } else {
+ AllowedClients::Root
+ };
+
+ let peer_endpoint = AllowedEndpoint { endpoint, clients };
+
FirewallPolicy::Connected {
- peer_endpoint: self.tunnel_parameters.get_next_hop_endpoint(),
+ peer_endpoint,
tunnel: self.metadata.clone(),
allow_lan: shared_values.allow_lan,
#[cfg(not(target_os = "android"))]
dns_servers: self.get_dns_servers(shared_values),
- #[cfg(windows)]
- relay_client: TunnelMonitor::get_relay_client(
- &shared_values.resource_dir,
- &self.tunnel_parameters,
- ),
}
}
diff --git a/talpid-core/src/tunnel_state_machine/connecting_state.rs b/talpid-core/src/tunnel_state_machine/connecting_state.rs
index d7d93da9d4..e49e5b69d8 100644
--- a/talpid-core/src/tunnel_state_machine/connecting_state.rs
+++ b/talpid-core/src/tunnel_state_machine/connecting_state.rs
@@ -21,7 +21,7 @@ use std::{
use talpid_routing::RouteManager;
use talpid_tunnel::{tun_provider::TunProvider, TunnelArgs, TunnelEvent, TunnelMetadata};
use talpid_types::{
- net::{AllowedTunnelTraffic, TunnelParameters},
+ net::{AllowedClients, AllowedEndpoint, AllowedTunnelTraffic, TunnelParameters},
tunnel::{ErrorStateCause, FirewallPolicyError},
ErrorExt,
};
@@ -140,7 +140,23 @@ impl ConnectingState {
#[cfg(target_os = "linux")]
shared_values.disable_connectivity_check();
- let peer_endpoint = params.get_next_hop_endpoint();
+ let endpoint = params.get_next_hop_endpoint();
+
+ #[cfg(target_os = "windows")]
+ let clients = AllowedClients::from(
+ TunnelMonitor::get_relay_client(&shared_values.resource_dir, params)
+ .into_iter()
+ .collect::<Vec<_>>(),
+ );
+
+ #[cfg(not(target_os = "windows"))]
+ let clients = if params.get_openvpn_local_proxy_settings().is_some() {
+ AllowedClients::All
+ } else {
+ AllowedClients::Root
+ };
+
+ let peer_endpoint = AllowedEndpoint { endpoint, clients };
let policy = FirewallPolicy::Connecting {
peer_endpoint,
@@ -148,8 +164,6 @@ impl ConnectingState {
allow_lan: shared_values.allow_lan,
allowed_endpoint: shared_values.allowed_endpoint.clone(),
allowed_tunnel_traffic,
- #[cfg(windows)]
- relay_client: TunnelMonitor::get_relay_client(&shared_values.resource_dir, params),
};
shared_values
.firewall
diff --git a/talpid-openvpn/src/lib.rs b/talpid-openvpn/src/lib.rs
index 9fd0c317ef..a0c4651d13 100644
--- a/talpid-openvpn/src/lib.rs
+++ b/talpid-openvpn/src/lib.rs
@@ -3,7 +3,7 @@
#![deny(missing_docs)]
#![deny(rust_2018_idioms)]
-use crate::proxy::{ProxyMonitor, ProxyResourceData};
+use crate::proxy::ProxyMonitor;
#[cfg(windows)]
use once_cell::sync::Lazy;
use process::openvpn::{OpenVpnCommand, OpenVpnProcHandle};
@@ -21,7 +21,10 @@ use std::{
#[cfg(target_os = "linux")]
use talpid_routing::{self, RequiredRoute};
use talpid_tunnel::TunnelEvent;
-use talpid_types::{net::openvpn, ErrorExt};
+use talpid_types::{
+ net::{openvpn, proxy::CustomProxy},
+ ErrorExt,
+};
use tokio::task;
#[cfg(windows)]
@@ -226,7 +229,7 @@ impl OpenVpnMonitor<OpenVpnCommand> {
params: &openvpn::TunnelParameters,
log_path: Option<PathBuf>,
resource_dir: &Path,
- #[cfg(target_os = "linux")] route_manager: talpid_routing::RouteManagerHandle,
+ route_manager: talpid_routing::RouteManagerHandle,
) -> Result<Self>
where
L: (Fn(TunnelEvent) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>)
@@ -242,19 +245,12 @@ impl OpenVpnMonitor<OpenVpnCommand> {
let user_pass_file_path = user_pass_file.to_path_buf();
let proxy_auth_file_path = proxy_auth_file.as_ref().map(|file| file.to_path_buf());
- let log_dir = log_path.as_ref().map(|log_path| {
- log_path
- .parent()
- .expect("log_path has no parent")
- .to_path_buf()
- });
-
- let proxy_resources = proxy::ProxyResourceData {
- resource_dir: resource_dir.to_path_buf(),
- log_dir,
- };
-
- let proxy_monitor = Self::start_proxy(&params.proxy, &proxy_resources).await?;
+ let proxy_monitor = Self::start_proxy(
+ &params.proxy,
+ #[cfg(target_os = "linux")]
+ params.fwmark,
+ )
+ .await?;
#[cfg(windows)]
let wintun = Self::new_wintun_context(params, resource_dir)?;
@@ -295,7 +291,7 @@ impl OpenVpnMonitor<OpenVpnCommand> {
user_pass_file_path: user_pass_file_path.clone(),
proxy_auth_file_path: proxy_auth_file_path.clone(),
abort_server_tx: event_server_abort_tx,
- #[cfg(target_os = "linux")]
+ proxy: params.proxy.clone(),
route_manager_handle: route_manager,
#[cfg(target_os = "linux")]
ipv6_enabled,
@@ -528,9 +524,9 @@ impl<C: OpenVpnBuilder + Send + 'static> OpenVpnMonitor<C> {
}
fn create_proxy_auth_file(
- proxy_settings: &Option<openvpn::ProxySettings>,
+ proxy_settings: &Option<CustomProxy>,
) -> std::result::Result<Option<mktemp::TempFile>, io::Error> {
- if let Some(openvpn::ProxySettings::Remote(ref remote_proxy)) = proxy_settings {
+ if let Some(CustomProxy::Socks5Remote(ref remote_proxy)) = proxy_settings {
if let Some(ref proxy_auth) = remote_proxy.auth {
return Ok(Some(Self::create_credentials_file(
&proxy_auth.username,
@@ -543,13 +539,17 @@ impl<C: OpenVpnBuilder + Send + 'static> OpenVpnMonitor<C> {
/// Starts a proxy service, as applicable.
async fn start_proxy(
- proxy_settings: &Option<openvpn::ProxySettings>,
- proxy_resources: &ProxyResourceData,
+ proxy_settings: &Option<CustomProxy>,
+ #[cfg(target_os = "linux")] fwmark: u32,
) -> Result<Option<Box<dyn ProxyMonitor>>> {
if let Some(ref settings) = proxy_settings {
- let proxy_monitor = proxy::start_proxy(settings, proxy_resources)
- .await
- .map_err(Error::ProxyError)?;
+ let proxy_monitor = proxy::start_proxy(
+ settings,
+ #[cfg(target_os = "linux")]
+ fwmark,
+ )
+ .await
+ .map_err(Error::ProxyError)?;
return Ok(Some(proxy_monitor));
}
Ok(None)
@@ -751,12 +751,12 @@ mod event_server {
use futures::stream::TryStreamExt;
use parity_tokio_ipc::Endpoint as IpcEndpoint;
use std::{
- collections::HashMap,
+ collections::{HashMap, HashSet},
pin::Pin,
task::{Context, Poll},
};
use talpid_tunnel::TunnelMetadata;
- #[cfg(any(target_os = "linux", windows))]
+ use talpid_types::net::proxy::CustomProxy;
use talpid_types::ErrorExt;
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tonic::{
@@ -798,7 +798,7 @@ mod event_server {
pub user_pass_file_path: super::PathBuf,
pub proxy_auth_file_path: Option<super::PathBuf>,
pub abort_server_tx: triggered::Trigger,
- #[cfg(target_os = "linux")]
+ pub proxy: Option<CustomProxy>,
pub route_manager_handle: talpid_routing::RouteManagerHandle,
#[cfg(target_os = "linux")]
pub ipv6_enabled: bool,
@@ -838,28 +838,33 @@ mod event_server {
let _ = tokio::fs::remove_file(file_path).await;
}
+ let mut routes = HashSet::new();
+ #[cfg(not(target_os = "linux"))]
+ if let Some(CustomProxy::Socks5Local(proxy_settings)) = &self.proxy {
+ let network = proxy_settings.remote_endpoint.address.ip().into();
+ let node = talpid_routing::NetNode::DefaultNode;
+ let route = talpid_routing::RequiredRoute::new(network, node);
+ routes.insert(route);
+ }
+ let route_handle = self.route_manager_handle.clone();
+
#[cfg(target_os = "linux")]
{
- let route_handle = self.route_manager_handle.clone();
let ipv6_enabled = self.ipv6_enabled;
- let routes = super::extract_routes(&env)
+ if let Err(error) = route_handle.create_routing_rules(ipv6_enabled).await {
+ log::error!("{}", error.display_chain());
+ return Err(tonic::Status::failed_precondition("Failed to add routes"));
+ }
+
+ let extracted_routes = super::extract_routes(&env)
.map_err(|err| {
log::error!("{}", err.display_chain_with_msg("Failed to obtain routes"));
tonic::Status::failed_precondition("Failed to obtain routes")
})?
.into_iter()
- .filter(|route| route.prefix.is_ipv4() || ipv6_enabled)
- .collect();
-
- if let Err(error) = route_handle.add_routes(routes).await {
- log::error!("{}", error.display_chain());
- return Err(tonic::Status::failed_precondition("Failed to add routes"));
- }
- if let Err(error) = route_handle.create_routing_rules(ipv6_enabled).await {
- log::error!("{}", error.display_chain());
- return Err(tonic::Status::failed_precondition("Failed to add routes"));
- }
+ .filter(|route| route.prefix.is_ipv4() || ipv6_enabled);
+ routes.extend(extracted_routes);
}
let metadata = Self::get_tunnel_metadata(&env)?;
@@ -883,6 +888,11 @@ mod event_server {
})?;
}
+ if let Err(error) = route_handle.add_routes(routes).await {
+ log::error!("{}", error.display_chain());
+ return Err(tonic::Status::failed_precondition("Failed to add routes"));
+ }
+
(self.on_event)(talpid_tunnel::TunnelEvent::Up(metadata)).await;
Ok(Response::new(()))
diff --git a/talpid-openvpn/src/process/openvpn.rs b/talpid-openvpn/src/process/openvpn.rs
index 8d969d196c..84b9c88534 100644
--- a/talpid-openvpn/src/process/openvpn.rs
+++ b/talpid-openvpn/src/process/openvpn.rs
@@ -7,7 +7,7 @@ use std::{
process::Stdio,
time::Duration,
};
-use talpid_types::net;
+use talpid_types::net::{self, proxy::CustomProxy};
static BASE_ARGUMENTS: &[&[&str]] = &[
&["--client"],
@@ -66,7 +66,7 @@ pub struct OpenVpnCommand {
plugin: Option<(PathBuf, Vec<String>)>,
log: Option<PathBuf>,
tunnel_options: net::openvpn::TunnelOptions,
- proxy_settings: Option<net::openvpn::ProxySettings>,
+ proxy_settings: Option<CustomProxy>,
tunnel_alias: Option<OsString>,
enable_ipv6: bool,
proxy_port: Option<u16>,
@@ -182,7 +182,7 @@ impl OpenVpnCommand {
}
/// Sets the proxy settings.
- pub fn proxy_settings(&mut self, proxy_settings: net::openvpn::ProxySettings) -> &mut Self {
+ pub fn proxy_settings(&mut self, proxy_settings: CustomProxy) -> &mut Self {
self.proxy_settings = Some(proxy_settings);
self
}
@@ -302,19 +302,19 @@ impl OpenVpnCommand {
fn proxy_arguments(&self) -> Vec<String> {
let mut args = vec![];
match self.proxy_settings {
- Some(net::openvpn::ProxySettings::Local(ref local_proxy)) => {
+ Some(CustomProxy::Socks5Local(ref local_proxy)) => {
args.push("--socks-proxy".to_owned());
args.push("127.0.0.1".to_owned());
- args.push(local_proxy.port.to_string());
+ args.push(local_proxy.local_port.to_string());
args.push("--route".to_owned());
- args.push(local_proxy.peer.ip().to_string());
+ args.push(local_proxy.remote_endpoint.address.ip().to_string());
args.push("255.255.255.255".to_owned());
args.push("net_gateway".to_owned());
}
- Some(net::openvpn::ProxySettings::Remote(ref remote_proxy)) => {
+ Some(CustomProxy::Socks5Remote(ref remote_proxy)) => {
args.push("--socks-proxy".to_owned());
- args.push(remote_proxy.address.ip().to_string());
- args.push(remote_proxy.address.port().to_string());
+ args.push(remote_proxy.endpoint.ip().to_string());
+ args.push(remote_proxy.endpoint.port().to_string());
if let Some(ref _auth) = remote_proxy.auth {
if let Some(ref auth_file) = self.proxy_auth_path {
@@ -325,11 +325,11 @@ impl OpenVpnCommand {
}
args.push("--route".to_owned());
- args.push(remote_proxy.address.ip().to_string());
+ args.push(remote_proxy.endpoint.ip().to_string());
args.push("255.255.255.255".to_owned());
args.push("net_gateway".to_owned());
}
- Some(net::openvpn::ProxySettings::Shadowsocks(ref ss)) => {
+ Some(CustomProxy::Shadowsocks(ref ss)) => {
args.push("--socks-proxy".to_owned());
args.push("127.0.0.1".to_owned());
@@ -340,7 +340,7 @@ impl OpenVpnCommand {
}
args.push("--route".to_owned());
- args.push(ss.peer.ip().to_string());
+ args.push(ss.endpoint.ip().to_string());
args.push("255.255.255.255".to_owned());
args.push("net_gateway".to_owned());
}
diff --git a/talpid-openvpn/src/proxy/mod.rs b/talpid-openvpn/src/proxy/mod.rs
index dd0e0b6cc9..6bc0fe92d5 100644
--- a/talpid-openvpn/src/proxy/mod.rs
+++ b/talpid-openvpn/src/proxy/mod.rs
@@ -3,8 +3,8 @@ mod shadowsocks;
use self::shadowsocks::ShadowsocksProxyMonitor;
use async_trait::async_trait;
-use std::{fmt, io, path::PathBuf};
-use talpid_types::net::openvpn;
+use std::{fmt, io};
+use talpid_types::net::proxy::CustomProxy;
#[derive(err_derive::Error, Debug)]
pub enum Error {
@@ -39,33 +39,30 @@ pub trait ProxyMonitorCloseHandle: Send {
fn close(self: Box<Self>) -> Result<()>;
}
-/// Variables that define the environment to help
-/// proxy implementations find their way around.
-/// TODO: Move struct to wider scope and use more generic name.
-pub struct ProxyResourceData {
- pub resource_dir: PathBuf,
- pub log_dir: Option<PathBuf>,
-}
-
pub async fn start_proxy(
- settings: &openvpn::ProxySettings,
- resource_data: &ProxyResourceData,
+ settings: &CustomProxy,
+ #[cfg(target_os = "linux")] fwmark: u32,
) -> Result<Box<dyn ProxyMonitor>> {
match settings {
- openvpn::ProxySettings::Local(local_settings) => {
+ CustomProxy::Socks5Local(local_settings) => {
// These are generic proxy settings with the proxy client not managed by us.
Ok(Box::new(noop::NoopProxyMonitor::start(
- local_settings.port,
+ local_settings.local_port,
)?))
}
- openvpn::ProxySettings::Remote(remote_settings) => {
+ CustomProxy::Socks5Remote(remote_settings) => {
// These are generic proxy settings with the proxy client not managed by us.
Ok(Box::new(noop::NoopProxyMonitor::start(
- remote_settings.address.port(),
+ remote_settings.endpoint.port(),
)?))
}
- openvpn::ProxySettings::Shadowsocks(ss_settings) => Ok(Box::new(
- ShadowsocksProxyMonitor::start(ss_settings, resource_data).await?,
+ CustomProxy::Shadowsocks(ss_settings) => Ok(Box::new(
+ ShadowsocksProxyMonitor::start(
+ ss_settings,
+ #[cfg(target_os = "linux")]
+ fwmark,
+ )
+ .await?,
)),
}
}
diff --git a/talpid-openvpn/src/proxy/shadowsocks.rs b/talpid-openvpn/src/proxy/shadowsocks.rs
index 47fa0e939a..2b88f23068 100644
--- a/talpid-openvpn/src/proxy/shadowsocks.rs
+++ b/talpid-openvpn/src/proxy/shadowsocks.rs
@@ -16,8 +16,8 @@ use shadowsocks_service::{
},
};
-use super::{Error, ProxyMonitor, ProxyMonitorCloseHandle, ProxyResourceData};
-use talpid_types::{net::openvpn::ShadowsocksProxySettings, ErrorExt};
+use super::{Error, ProxyMonitor, ProxyMonitorCloseHandle};
+use talpid_types::{net::proxy::Shadowsocks, ErrorExt};
pub struct ShadowsocksProxyMonitor {
port: u16,
@@ -27,13 +27,22 @@ pub struct ShadowsocksProxyMonitor {
impl ShadowsocksProxyMonitor {
pub async fn start(
- settings: &ShadowsocksProxySettings,
- _resource_data: &ProxyResourceData,
+ settings: &Shadowsocks,
+ #[cfg(target_os = "linux")] fwmark: u32,
) -> super::Result<Self> {
- Self::start_inner(settings).await.map_err(Error::Io)
+ Self::start_inner(
+ settings,
+ #[cfg(target_os = "linux")]
+ fwmark,
+ )
+ .await
+ .map_err(Error::Io)
}
- async fn start_inner(settings: &ShadowsocksProxySettings) -> io::Result<Self> {
+ async fn start_inner(
+ settings: &Shadowsocks,
+ #[cfg(target_os = "linux")] fwmark: u32,
+ ) -> io::Result<Self> {
let mut config = Config::new(ConfigType::Local);
config.fast_open = true;
@@ -50,7 +59,7 @@ impl ShadowsocksProxyMonitor {
.push(LocalInstanceConfig::with_local_config(local));
let server = ServerConfig::new(
- settings.peer,
+ settings.endpoint,
settings.password.clone(),
settings.cipher.parse().map_err(|_| {
io::Error::new(
@@ -66,7 +75,7 @@ impl ShadowsocksProxyMonitor {
#[cfg(target_os = "linux")]
{
- config.outbound_fwmark = settings.fwmark;
+ config.outbound_fwmark = Some(fwmark);
}
let srv = local::Server::new(config).await?;
diff --git a/talpid-types/src/net/mod.rs b/talpid-types/src/net/mod.rs
index de9c5c765d..c7147d00ce 100644
--- a/talpid-types/src/net/mod.rs
+++ b/talpid-types/src/net/mod.rs
@@ -10,6 +10,8 @@ use std::{
str::FromStr,
};
+use self::proxy::{CustomProxy, Socks5Local};
+
pub mod obfuscation;
pub mod openvpn;
pub mod proxy;
@@ -31,7 +33,10 @@ impl TunnelParameters {
tunnel_type: TunnelType::OpenVpn,
quantum_resistant: false,
endpoint: params.config.endpoint,
- proxy: params.proxy.as_ref().map(|proxy| proxy.get_endpoint()),
+ proxy: params
+ .proxy
+ .as_ref()
+ .map(|proxy| proxy.get_remote_endpoint()),
obfuscation: None,
entry_endpoint: None,
tunnel_interface: None,
@@ -60,7 +65,7 @@ impl TunnelParameters {
TunnelParameters::OpenVpn(params) => params
.proxy
.as_ref()
- .map(|proxy| proxy.get_endpoint().endpoint)
+ .map(|proxy| proxy.get_remote_endpoint().endpoint)
.unwrap_or(params.config.endpoint),
TunnelParameters::Wireguard(params) => params
.obfuscation
@@ -93,6 +98,21 @@ impl TunnelParameters {
TunnelParameters::Wireguard(params) => &params.generic_options,
}
}
+
+ pub fn get_openvpn_local_proxy_settings(&self) -> Option<&Socks5Local> {
+ match &self {
+ TunnelParameters::OpenVpn(params) => {
+ params
+ .proxy
+ .as_ref()
+ .and_then(|proxy_settings| match proxy_settings {
+ CustomProxy::Socks5Local(local_settings) => Some(local_settings),
+ _ => None,
+ })
+ }
+ _ => None,
+ }
+ }
}
impl From<wireguard::TunnelParameters> for TunnelParameters {
diff --git a/talpid-types/src/net/openvpn.rs b/talpid-types/src/net/openvpn.rs
index 5968331b52..b2989bb851 100644
--- a/talpid-types/src/net/openvpn.rs
+++ b/talpid-types/src/net/openvpn.rs
@@ -1,9 +1,7 @@
-use crate::net::{
- proxy::{ProxyEndpoint, ProxyType},
- Endpoint, GenericTunnelOptions, TransportProtocol,
-};
+use crate::net::{Endpoint, GenericTunnelOptions};
use serde::{Deserialize, Serialize};
-use std::net::SocketAddr;
+
+use super::proxy::CustomProxy;
/// Information needed by `OpenVpnMonitor` to establish a tunnel connection.
/// See [`crate::net::TunnelParameters`].
@@ -12,7 +10,7 @@ pub struct TunnelParameters {
pub config: ConnectionConfig,
pub options: TunnelOptions,
pub generic_options: GenericTunnelOptions,
- pub proxy: Option<ProxySettings>,
+ pub proxy: Option<CustomProxy>,
#[cfg(target_os = "linux")]
pub fwmark: u32,
}
@@ -45,156 +43,3 @@ pub struct TunnelOptions {
/// as discussed [here](https://openvpn.net/archive/openvpn-users/2003-11/msg00154.html)
pub mssfix: Option<u16>,
}
-
-/// Proxy server options to be used by `OpenVpnMonitor` when starting a tunnel.
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
-#[serde(rename_all = "snake_case")]
-pub enum ProxySettings {
- Local(LocalProxySettings),
- Remote(RemoteProxySettings),
- Shadowsocks(ShadowsocksProxySettings),
-}
-
-impl ProxySettings {
- pub fn get_endpoint(&self) -> ProxyEndpoint {
- match self {
- ProxySettings::Local(settings) => ProxyEndpoint {
- endpoint: settings.get_endpoint(),
- proxy_type: ProxyType::Custom,
- },
- ProxySettings::Remote(settings) => ProxyEndpoint {
- endpoint: settings.get_endpoint(),
- proxy_type: ProxyType::Custom,
- },
- ProxySettings::Shadowsocks(settings) => ProxyEndpoint {
- endpoint: settings.get_endpoint(),
- proxy_type: ProxyType::Shadowsocks,
- },
- }
- }
-}
-
-/// Options for a generic proxy running on localhost.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
-pub struct LocalProxySettings {
- pub port: u16,
- pub peer: SocketAddr,
-}
-
-impl LocalProxySettings {
- pub fn get_endpoint(&self) -> Endpoint {
- Endpoint {
- address: self.peer,
- protocol: TransportProtocol::Tcp,
- }
- }
-}
-
-/// Options for a generic proxy running on remote host.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
-pub struct RemoteProxySettings {
- pub address: SocketAddr,
- pub auth: Option<ProxyAuth>,
-}
-
-impl RemoteProxySettings {
- pub fn get_endpoint(&self) -> Endpoint {
- Endpoint {
- address: self.address,
- protocol: TransportProtocol::Tcp,
- }
- }
-}
-
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
-pub struct ProxyAuth {
- pub username: String,
- pub password: String,
-}
-
-/// Options for a bundled Shadowsocks proxy.
-#[derive(Debug, Clone, Eq, PartialEq, Hash, Deserialize, Serialize)]
-pub struct ShadowsocksProxySettings {
- pub peer: SocketAddr,
- /// Password on peer.
- pub password: String,
- pub cipher: String,
- #[cfg(target_os = "linux")]
- pub fwmark: Option<u32>,
-}
-
-impl ShadowsocksProxySettings {
- pub fn get_endpoint(&self) -> Endpoint {
- Endpoint {
- address: self.peer,
- protocol: TransportProtocol::Tcp,
- }
- }
-}
-
-/// List of ciphers usable by a Shadowsocks proxy.
-/// Cf. [`ShadowsocksProxySettings::cipher`].
-pub const SHADOWSOCKS_CIPHERS: [&str; 19] = [
- // Stream ciphers.
- "aes-128-cfb",
- "aes-128-cfb1",
- "aes-128-cfb8",
- "aes-128-cfb128",
- "aes-256-cfb",
- "aes-256-cfb1",
- "aes-256-cfb8",
- "aes-256-cfb128",
- "rc4",
- "rc4-md5",
- "chacha20",
- "salsa20",
- "chacha20-ietf",
- // AEAD ciphers.
- "aes-128-gcm",
- "aes-256-gcm",
- "chacha20-ietf-poly1305",
- "xchacha20-ietf-poly1305",
- "aes-128-pmac-siv",
- "aes-256-pmac-siv",
-];
-
-/// Checks whether the proxy settings to be used by `OpenVpnMonitor` are valid.
-pub fn validate_proxy_settings(proxy: &ProxySettings) -> Result<(), String> {
- match proxy {
- ProxySettings::Local(local) => {
- if local.port == 0 {
- return Err(String::from("Invalid local port number"));
- }
- if local.peer.ip().is_loopback() {
- return Err(String::from(
- "localhost is not a valid peer in this context",
- ));
- }
- if local.peer.port() == 0 {
- return Err(String::from("Invalid remote port number"));
- }
- }
- ProxySettings::Remote(remote) => {
- if remote.address.port() == 0 {
- return Err(String::from("Invalid port number"));
- }
- if remote.address.ip().is_loopback() {
- return Err(String::from("localhost is not a valid remote server"));
- }
- }
- ProxySettings::Shadowsocks(ss) => {
- if ss.peer.ip().is_loopback() {
- return Err(String::from(
- "localhost is not a valid peer in this context",
- ));
- }
- if ss.peer.port() == 0 {
- return Err(String::from("Invalid remote port number"));
- }
- if !SHADOWSOCKS_CIPHERS.contains(&ss.cipher.as_str()) {
- return Err(String::from("Invalid cipher"));
- }
- }
- };
- Ok(())
-}
diff --git a/talpid-types/src/net/proxy.rs b/talpid-types/src/net/proxy.rs
index 046298557a..5c16f19a7f 100644
--- a/talpid-types/src/net/proxy.rs
+++ b/talpid-types/src/net/proxy.rs
@@ -1,6 +1,8 @@
use crate::net::Endpoint;
use serde::{Deserialize, Serialize};
-use std::fmt;
+use std::{fmt, net::SocketAddr};
+
+use super::TransportProtocol;
/// Types of bridges that can be used to proxy a connection to a tunnel
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
@@ -27,3 +29,136 @@ pub struct ProxyEndpoint {
pub endpoint: Endpoint,
pub proxy_type: ProxyType,
}
+
+/// User customized proxy used for obfuscation.
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+#[serde(rename_all = "snake_case")]
+pub enum CustomProxy {
+ Shadowsocks(Shadowsocks),
+ Socks5Local(Socks5Local),
+ Socks5Remote(Socks5Remote),
+}
+
+impl CustomProxy {
+ pub fn get_remote_endpoint(&self) -> ProxyEndpoint {
+ match self {
+ CustomProxy::Socks5Local(settings) => ProxyEndpoint {
+ endpoint: settings.remote_endpoint,
+ proxy_type: ProxyType::Custom,
+ },
+ CustomProxy::Socks5Remote(settings) => ProxyEndpoint {
+ endpoint: Endpoint::from_socket_address(settings.endpoint, TransportProtocol::Tcp),
+ proxy_type: ProxyType::Custom,
+ },
+ CustomProxy::Shadowsocks(settings) => ProxyEndpoint {
+ endpoint: Endpoint::from_socket_address(settings.endpoint, TransportProtocol::Tcp),
+ proxy_type: ProxyType::Shadowsocks,
+ },
+ }
+ }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub struct Shadowsocks {
+ pub endpoint: SocketAddr,
+ pub password: String,
+ /// One of [`shadowsocks_ciphers`].
+ /// Gets validated at a later stage. Is assumed to be valid.
+ ///
+ /// shadowsocks_ciphers: talpid_types::net::openvpn::SHADOWSOCKS_CIPHERS
+ pub cipher: String,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub struct Socks5Local {
+ pub remote_endpoint: Endpoint,
+ /// Port on localhost where the SOCKS5-proxy listens to.
+ pub local_port: u16,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub struct Socks5Remote {
+ pub endpoint: SocketAddr,
+ pub auth: Option<SocksAuth>,
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
+pub struct SocksAuth {
+ pub username: String,
+ pub password: String,
+}
+
+impl Shadowsocks {
+ pub fn new<I: Into<SocketAddr>>(endpoint: I, cipher: String, password: String) -> Self {
+ Shadowsocks {
+ endpoint: endpoint.into(),
+ password,
+ cipher,
+ }
+ }
+}
+
+impl Socks5Local {
+ pub fn new<I: Into<SocketAddr>>(remote_endpoint: I, local_port: u16) -> Self {
+ let transport_protocol = TransportProtocol::Tcp;
+ Self::new_with_transport_protocol(remote_endpoint, local_port, transport_protocol)
+ }
+
+ pub fn new_with_transport_protocol<I: Into<SocketAddr>>(
+ remote_endpoint: I,
+ local_port: u16,
+ transport_protocol: TransportProtocol,
+ ) -> Self {
+ let remote_endpoint =
+ Endpoint::from_socket_address(remote_endpoint.into(), transport_protocol);
+ Self {
+ remote_endpoint,
+ local_port,
+ }
+ }
+}
+
+impl Socks5Remote {
+ pub fn new<I: Into<SocketAddr>>(endpoint: I) -> Self {
+ Self {
+ endpoint: endpoint.into(),
+ auth: None,
+ }
+ }
+
+ pub fn new_with_authentication<I: Into<SocketAddr>>(
+ endpoint: I,
+ authentication: SocksAuth,
+ ) -> Self {
+ Self {
+ endpoint: endpoint.into(),
+ auth: Some(authentication),
+ }
+ }
+}
+
+/// List of ciphers usable by a Shadowsocks proxy.
+/// Cf. [`ShadowsocksProxySettings::cipher`].
+pub const SHADOWSOCKS_CIPHERS: [&str; 19] = [
+ // Stream ciphers.
+ "aes-128-cfb",
+ "aes-128-cfb1",
+ "aes-128-cfb8",
+ "aes-128-cfb128",
+ "aes-256-cfb",
+ "aes-256-cfb1",
+ "aes-256-cfb8",
+ "aes-256-cfb128",
+ "rc4",
+ "rc4-md5",
+ "chacha20",
+ "salsa20",
+ "chacha20-ietf",
+ // AEAD ciphers.
+ "aes-128-gcm",
+ "aes-256-gcm",
+ "chacha20-ietf-poly1305",
+ "xchacha20-ietf-poly1305",
+ "aes-128-pmac-siv",
+ "aes-256-pmac-siv",
+];
diff --git a/test/test-manager/src/tests/tunnel.rs b/test/test-manager/src/tests/tunnel.rs
index 6f759d7826..d36ba4febe 100644
--- a/test/test-manager/src/tests/tunnel.rs
+++ b/test/test-manager/src/tests/tunnel.rs
@@ -6,8 +6,8 @@ use super::{Error, TestContext};
use crate::network_monitor::{start_packet_monitor, MonitorOptions};
use mullvad_management_interface::{types, ManagementServiceClient};
use mullvad_types::relay_constraints::{
- BridgeConstraints, BridgeSettings, BridgeState, Constraint, ObfuscationSettings,
- OpenVpnConstraints, RelayConstraints, RelaySettings, SelectedObfuscation, TransportPort,
+ BridgeSettings, BridgeState, Constraint, ObfuscationSettings, OpenVpnConstraints,
+ RelayConstraints, RelaySettings, SelectedObfuscation, TransportPort,
Udp2TcpObfuscationSettings, WireguardConstraints,
};
use mullvad_types::wireguard;
@@ -209,12 +209,9 @@ pub async fn test_bridge(
.await
.expect("failed to enable bridge mode");
- set_bridge_settings(
- &mut mullvad_client,
- BridgeSettings::Normal(BridgeConstraints::default()),
- )
- .await
- .expect("failed to update bridge settings");
+ set_bridge_settings(&mut mullvad_client, BridgeSettings::default())
+ .await
+ .expect("failed to update bridge settings");
set_relay_settings(
&mut mullvad_client,
diff --git a/windows/winfw/src/winfw/fwcontext.cpp b/windows/winfw/src/winfw/fwcontext.cpp
index f033830b45..4ed22737fc 100644
--- a/windows/winfw/src/winfw/fwcontext.cpp
+++ b/windows/winfw/src/winfw/fwcontext.cpp
@@ -81,7 +81,7 @@ void AppendRelayRules
(
FwContext::Ruleset &ruleset,
const WinFwEndpoint &relay,
- const std::wstring &relayClient
+ const std::vector<std::wstring> &relayClients
)
{
auto sublayer =
@@ -95,7 +95,7 @@ void AppendRelayRules
wfp::IpAddress(relay.ip),
relay.port,
relay.protocol,
- relayClient,
+ relayClients,
sublayer
));
}
@@ -185,7 +185,7 @@ bool FwContext::applyPolicyConnecting
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClients,
const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<WinFwAllowedEndpoint> &allowedEndpoint,
const WinFwAllowedTunnelTraffic &allowedTunnelTraffic
@@ -195,7 +195,7 @@ bool FwContext::applyPolicyConnecting
AppendNetBlockedRules(ruleset);
AppendSettingsRules(ruleset, settings);
- AppendRelayRules(ruleset, relay, relayClient);
+ AppendRelayRules(ruleset, relay, relayClients);
if (allowedEndpoint.has_value())
{
@@ -280,7 +280,7 @@ bool FwContext::applyPolicyConnected
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClient,
const std::wstring &tunnelInterfaceAlias,
const std::vector<wfp::IpAddress> &tunnelDnsServers,
const std::vector<wfp::IpAddress> &nonTunnelDnsServers
diff --git a/windows/winfw/src/winfw/fwcontext.h b/windows/winfw/src/winfw/fwcontext.h
index 5fc23f09a7..92ecce4f4f 100644
--- a/windows/winfw/src/winfw/fwcontext.h
+++ b/windows/winfw/src/winfw/fwcontext.h
@@ -28,7 +28,7 @@ public:
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClients,
const std::optional<std::wstring> &tunnelInterfaceAlias,
const std::optional<WinFwAllowedEndpoint> &allowedEndpoint,
const WinFwAllowedTunnelTraffic &allowedTunnelTraffic
@@ -38,7 +38,7 @@ public:
(
const WinFwSettings &settings,
const WinFwEndpoint &relay,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClients,
const std::wstring &tunnelInterfaceAlias,
const std::vector<wfp::IpAddress> &tunnelDnsServers,
const std::vector<wfp::IpAddress> &nonTunnelDnsServers
diff --git a/windows/winfw/src/winfw/rules/multi/permitvpnrelay.cpp b/windows/winfw/src/winfw/rules/multi/permitvpnrelay.cpp
index 3c913cab14..19ce09571b 100644
--- a/windows/winfw/src/winfw/rules/multi/permitvpnrelay.cpp
+++ b/windows/winfw/src/winfw/rules/multi/permitvpnrelay.cpp
@@ -52,13 +52,13 @@ PermitVpnRelay::PermitVpnRelay
const wfp::IpAddress &relay,
uint16_t relayPort,
WinFwProtocol protocol,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClients,
Sublayer sublayer
)
: m_relay(relay)
, m_relayPort(relayPort)
, m_protocol(protocol)
- , m_relayClient(relayClient)
+ , m_relayClients(relayClients)
, m_sublayer(sublayer)
{
}
@@ -86,7 +86,10 @@ bool PermitVpnRelay::apply(IObjectInstaller &objectInstaller)
conditionBuilder.add_condition(ConditionIp::Remote(m_relay));
conditionBuilder.add_condition(ConditionPort::Remote(m_relayPort));
conditionBuilder.add_condition(CreateProtocolCondition(m_protocol));
- conditionBuilder.add_condition(std::make_unique<ConditionApplication>(m_relayClient));
+
+ for(auto relayClient : m_relayClients) {
+ conditionBuilder.add_condition(std::make_unique<ConditionApplication>(relayClient));
+ }
return objectInstaller.addFilter(filterBuilder, conditionBuilder);
}
diff --git a/windows/winfw/src/winfw/rules/multi/permitvpnrelay.h b/windows/winfw/src/winfw/rules/multi/permitvpnrelay.h
index d63f27a862..a2bfc16384 100644
--- a/windows/winfw/src/winfw/rules/multi/permitvpnrelay.h
+++ b/windows/winfw/src/winfw/rules/multi/permitvpnrelay.h
@@ -23,7 +23,7 @@ public:
const wfp::IpAddress &relay,
uint16_t relayPort,
WinFwProtocol protocol,
- const std::wstring &relayClient,
+ const std::vector<std::wstring> &relayClients,
Sublayer sublayer
);
@@ -34,7 +34,7 @@ private:
const wfp::IpAddress m_relay;
const uint16_t m_relayPort;
const WinFwProtocol m_protocol;
- const std::wstring m_relayClient;
+ const std::vector<std::wstring> m_relayClients;
const Sublayer m_sublayer;
};
diff --git a/windows/winfw/src/winfw/winfw.cpp b/windows/winfw/src/winfw/winfw.cpp
index 4110dcd2f8..352a91c0d1 100644
--- a/windows/winfw/src/winfw/winfw.cpp
+++ b/windows/winfw/src/winfw/winfw.cpp
@@ -231,7 +231,8 @@ WINFW_API
WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
- const wchar_t *relayClient,
+ const wchar_t **relayClients,
+ size_t relayClientsLen,
const wchar_t *tunnelInterfaceAlias,
const WinFwAllowedEndpoint *allowedEndpoint,
const WinFwAllowedTunnelTraffic *allowedTunnelTraffic
@@ -254,20 +255,21 @@ WinFw_ApplyPolicyConnecting(
THROW_ERROR("Invalid argument: relay");
}
- if (nullptr == relayClient)
- {
- THROW_ERROR("Invalid argument: relayClient");
- }
-
if (nullptr == allowedTunnelTraffic)
{
THROW_ERROR("Invalid argument: allowedTunnelTraffic");
}
+ std::vector<std::wstring> relayClientWstrings;
+ relayClientWstrings.reserve(relayClientsLen);
+ for(int i = 0; i < relayClientsLen; i++) {
+ relayClientWstrings.push_back(relayClients[i]);
+ }
+
return g_fwContext->applyPolicyConnecting(
*settings,
*relay,
- relayClient,
+ relayClientWstrings,
tunnelInterfaceAlias != nullptr ? std::make_optional(tunnelInterfaceAlias) : std::nullopt,
MakeOptional(allowedEndpoint),
*allowedTunnelTraffic
@@ -298,7 +300,8 @@ WINFW_API
WinFw_ApplyPolicyConnected(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
- const wchar_t *relayClient,
+ const wchar_t **relayClients,
+ size_t relayClientsLen,
const wchar_t *tunnelInterfaceAlias,
const wchar_t *v4Gateway,
const wchar_t *v6Gateway,
@@ -323,11 +326,6 @@ WinFw_ApplyPolicyConnected(
THROW_ERROR("Invalid argument: relay");
}
- if (nullptr == relayClient)
- {
- THROW_ERROR("Invalid argument: relayClient");
- }
-
if (nullptr == tunnelInterfaceAlias)
{
THROW_ERROR("Invalid argument: tunnelInterfaceAlias");
@@ -407,10 +405,16 @@ WinFw_ApplyPolicyConnected(
g_logSink(MULLVAD_LOG_LEVEL_DEBUG, ss.str().c_str(), g_logSinkContext);
}
+ std::vector<std::wstring> relayClientWstrings;
+ relayClientWstrings.reserve(relayClientsLen);
+ for(int i = 0; i < relayClientsLen; i++) {
+ relayClientWstrings.push_back(relayClients[i]);
+ }
+
return g_fwContext->applyPolicyConnected(
*settings,
*relay,
- relayClient,
+ relayClientWstrings,
tunnelInterfaceAlias,
tunnelDnsServers,
nonTunnelDnsServers
diff --git a/windows/winfw/src/winfw/winfw.h b/windows/winfw/src/winfw/winfw.h
index 5d61f1029d..b786d943d3 100644
--- a/windows/winfw/src/winfw/winfw.h
+++ b/windows/winfw/src/winfw/winfw.h
@@ -164,7 +164,8 @@ WINFW_API
WinFw_ApplyPolicyConnecting(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
- const wchar_t *relayClient,
+ const wchar_t **relayClient,
+ size_t relayClientLen,
const wchar_t *tunnelInterfaceAlias,
const WinFwAllowedEndpoint *allowedEndpoint,
const WinFwAllowedTunnelTraffic *allowedTunnelTraffic
@@ -194,7 +195,8 @@ WINFW_API
WinFw_ApplyPolicyConnected(
const WinFwSettings *settings,
const WinFwEndpoint *relay,
- const wchar_t *relayClient,
+ const wchar_t **relayClient,
+ size_t relayClientLen,
const wchar_t *tunnelInterfaceAlias,
const wchar_t *v4Gateway,
const wchar_t *v6Gateway,