summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJonathan <jonathan@mullvad.net>2023-12-05 10:03:08 +0100
committerJonathan <jonathan@mullvad.net>2024-01-03 14:38:41 +0100
commit4fdc34acbba60d5092e45ce3e513d30ec996c317 (patch)
tree80d3a23c1a96bd3d80e05ac66b530e39c252d48a
parentc510df96772b1e4ab7998e739ced42806c78e931 (diff)
downloadmullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.tar.xz
mullvadvpn-4fdc34acbba60d5092e45ce3e513d30ec996c317.zip
Allow app to use custom socks5 and shadwosocks proxies
This PR has a couple of different purposes - Allow users to use socks5 local proxies with the CLI without having to be root nor use split-tunneling. This only works for OpenVPN. - Unify the types used by different proxy parts of the codebase, such as the Access Methods as well as some already existing OpenVPN proxy code. This PR changes the firewall on all desktop platforms as well as changes the routing table slightly on MacOS and Windows. On Linux the firewall code is modified to apply the appropriate firewall marks to all packages that go to a remote endpoint corresponding to the remote part of a local socks5 proxy. The firewall marks will allow the routing to be done without having to modify the routing table. On MacOS and Windows the routing table is modified to allow packages to go to that same endpoint to pass outside the VPN tunnel, it will additionally punch a hole in the firewall. The PR also migrates the settings file from version 7 to version 8 in order to properly and neatly unify Proxy related types. Finally it provides some slight extensions to the gRPC interface in order to allow for control over the custom proxy settings.
-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,