summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gui/src/main/daemon-rpc.ts23
-rw-r--r--gui/src/main/index.ts13
-rw-r--r--gui/src/renderer/app.tsx44
-rw-r--r--gui/src/renderer/lib/relay-settings-builder.ts22
-rw-r--r--gui/src/renderer/redux/settings/reducers.ts3
-rw-r--r--gui/src/shared/daemon-rpc-types.ts23
-rw-r--r--gui/test/relay-settings-builder.spec.ts20
-rw-r--r--mullvad-cli/src/cmds/relay.rs16
-rw-r--r--mullvad-daemon/src/lib.rs17
-rw-r--r--mullvad-daemon/src/relays.rs132
-rw-r--r--mullvad-jni/src/from_java.rs4
-rw-r--r--mullvad-types/src/relay_constraints.rs62
-rw-r--r--mullvad-types/src/settings/migrations/mod.rs145
-rw-r--r--mullvad-types/src/settings/migrations/v1.rs221
-rw-r--r--mullvad-types/src/settings/mod.rs (renamed from mullvad-types/src/settings.rs)33
15 files changed, 612 insertions, 166 deletions
diff --git a/gui/src/main/daemon-rpc.ts b/gui/src/main/daemon-rpc.ts
index e317ffb794..24671fac23 100644
--- a/gui/src/main/daemon-rpc.ts
+++ b/gui/src/main/daemon-rpc.ts
@@ -101,21 +101,14 @@ const relaySettingsSchema = oneOf(
object({
normal: partialObject({
location: locationConstraintSchema,
- tunnel: constraint(
- oneOf(
- object({
- openvpn: partialObject({
- port: constraint(number),
- protocol: constraint(enumeration('udp', 'tcp')),
- }),
- }),
- object({
- wireguard: partialObject({
- port: constraint(number),
- }),
- }),
- ),
- ),
+ tunnel_protocol: constraint(enumeration('wireguard', 'openvpn')),
+ wireguard_constraints: partialObject({
+ port: constraint(number),
+ }),
+ openvpn_constraints: partialObject({
+ port: constraint(number),
+ protocol: constraint(enumeration('udp', 'tcp')),
+ }),
}),
}),
object({
diff --git a/gui/src/main/index.ts b/gui/src/main/index.ts
index a0dcbeb50b..1d766629cf 100644
--- a/gui/src/main/index.ts
+++ b/gui/src/main/index.ts
@@ -83,7 +83,14 @@ class ApplicationMain {
relaySettings: {
normal: {
location: 'any',
- tunnel: 'any',
+ tunnelProtocol: 'any',
+ openvpnConstraints: {
+ port: 'any',
+ protocol: 'any',
+ },
+ wireguardConstraints: {
+ port: 'any',
+ },
},
},
bridgeSettings: {
@@ -611,8 +618,8 @@ class ApplicationMain {
let fnHasWantedTunnels = hasOpenVpnTunnels;
if ('normal' in relaySettings) {
- const tunnelConstraints = relaySettings.normal.tunnel;
- if (tunnelConstraints !== 'any' && 'wireguard' in tunnelConstraints.only) {
+ const tunnelConstraints = relaySettings.normal.tunnelProtocol;
+ if (tunnelConstraints !== 'any' && 'wireguard' === tunnelConstraints.only) {
fnHasWantedTunnels = hasWireguardTunnels;
}
}
diff --git a/gui/src/renderer/app.tsx b/gui/src/renderer/app.tsx
index 6d5d9ae5ab..d77cf65e89 100644
--- a/gui/src/renderer/app.tsx
+++ b/gui/src/renderer/app.tsx
@@ -37,6 +37,7 @@ import {
IRelayList,
ISettings,
KeygenEvent,
+ liftConstraint,
RelaySettings,
RelaySettingsUpdate,
TunnelState,
@@ -375,43 +376,30 @@ export default class AppRenderer {
if ('normal' in relaySettings) {
const normal = relaySettings.normal;
- const tunnel = normal.tunnel;
+ const tunnelProtocol = normal.tunnelProtocol;
const location = normal.location;
-
const relayLocation = location === 'any' ? 'any' : location.only;
- if (tunnel === 'any') {
+ if (tunnelProtocol === 'any' || tunnelProtocol.only === 'openvpn') {
+ const { port, protocol } = normal.openvpnConstraints;
actions.settings.updateRelay({
normal: {
location: relayLocation,
- port: 'any',
- protocol: 'any',
+ port: port === 'any' ? port : port.only,
+ protocol: protocol === 'any' ? protocol : protocol.only,
+ tunnelProtocol: liftConstraint(tunnelProtocol),
},
});
} else {
- const constraints = tunnel.only;
-
- if ('openvpn' in constraints) {
- const { port, protocol } = constraints.openvpn;
-
- actions.settings.updateRelay({
- normal: {
- location: relayLocation,
- port: port === 'any' ? port : port.only,
- protocol: protocol === 'any' ? protocol : protocol.only,
- },
- });
- } else if ('wireguard' in constraints) {
- const { port } = constraints.wireguard;
-
- actions.settings.updateRelay({
- normal: {
- location: relayLocation,
- port: port === 'any' ? port : port.only,
- protocol: 'udp',
- },
- });
- }
+ const { port } = normal.wireguardConstraints;
+ actions.settings.updateRelay({
+ normal: {
+ tunnelProtocol: liftConstraint(tunnelProtocol),
+ location: relayLocation,
+ port: port === 'any' ? port : port.only,
+ protocol: 'udp',
+ },
+ });
}
} else if ('customTunnelEndpoint' in relaySettings) {
const customTunnelEndpoint = relaySettings.customTunnelEndpoint;
diff --git a/gui/src/renderer/lib/relay-settings-builder.ts b/gui/src/renderer/lib/relay-settings-builder.ts
index 72dc95e759..bbf85b1fed 100644
--- a/gui/src/renderer/lib/relay-settings-builder.ts
+++ b/gui/src/renderer/lib/relay-settings-builder.ts
@@ -80,19 +80,13 @@ class NormalRelaySettingsBuilder {
get tunnel(): ITunnelBuilder {
const updateOpenvpn = (next: Partial<IOpenVpnConstraints>) => {
- const tunnel = this.payload.tunnel;
- if (typeof tunnel === 'string' || typeof tunnel === 'undefined') {
- this.payload.tunnel = {
- only: {
- openvpn: next,
- },
- };
- } else if (typeof tunnel === 'object') {
- const prev = tunnel.only && 'openvpn' in tunnel.only ? tunnel.only.openvpn : {};
- this.payload.tunnel = {
- only: {
- openvpn: { ...prev, ...next },
- },
+ if (this.payload.openvpnConstraints === undefined) {
+ this.payload.openvpnConstraints = next;
+ } else {
+ const prev = this.payload.openvpnConstraints;
+ this.payload.openvpnConstraints = {
+ ...prev,
+ ...next,
};
}
};
@@ -127,7 +121,7 @@ class NormalRelaySettingsBuilder {
return this;
},
any: () => {
- this.payload.tunnel = 'any';
+ this.payload.tunnelProtocol = 'any';
return this;
},
};
diff --git a/gui/src/renderer/redux/settings/reducers.ts b/gui/src/renderer/redux/settings/reducers.ts
index 97a6a21ee4..e41750b496 100644
--- a/gui/src/renderer/redux/settings/reducers.ts
+++ b/gui/src/renderer/redux/settings/reducers.ts
@@ -4,6 +4,7 @@ import {
KeygenEvent,
RelayLocation,
RelayProtocol,
+ TunnelProtocol,
} from '../../../shared/daemon-rpc-types';
import { IGuiSettingsState } from '../../../shared/gui-settings-state';
import { ReduxAction } from '../store';
@@ -11,6 +12,7 @@ import { ReduxAction } from '../store';
export type RelaySettingsRedux =
| {
normal: {
+ tunnelProtocol: 'any' | TunnelProtocol;
location: 'any' | RelayLocation;
port: 'any' | number;
protocol: 'any' | RelayProtocol;
@@ -108,6 +110,7 @@ const initialState: ISettingsReduxState = {
relaySettings: {
normal: {
location: 'any',
+ tunnelProtocol: 'any',
port: 'any',
protocol: 'any',
},
diff --git a/gui/src/shared/daemon-rpc-types.ts b/gui/src/shared/daemon-rpc-types.ts
index 5d2c77d223..8dc87cc2b1 100644
--- a/gui/src/shared/daemon-rpc-types.ts
+++ b/gui/src/shared/daemon-rpc-types.ts
@@ -44,6 +44,13 @@ export function tunnelTypeToString(tunnel: TunnelType): string {
export type RelayProtocol = 'tcp' | 'udp';
+export function liftConstraint<T>(constraint: 'any' | { only: T }): 'any' | T {
+ if (constraint === 'any') {
+ return 'any';
+ }
+ return constraint.only;
+}
+
export type ProxyType = 'shadowsocks' | 'custom';
export function proxyTypeToString(proxy: ProxyType): string {
switch (proxy) {
@@ -101,19 +108,21 @@ export interface IWireguardConstraints {
port: 'any' | { only: number };
}
-type TunnelConstraints<OpenVpn, Wireguard> = { wireguard: Wireguard } | { openvpn: OpenVpn };
+export type TunnelProtocol = 'wireguard' | 'openvpn';
-interface IRelaySettingsNormal<TTunnelConstraints> {
+interface IRelaySettingsNormal<OpenVpn, Wireguard> {
location:
| 'any'
| {
only: RelayLocation;
};
- tunnel:
+ tunnelProtocol:
| 'any'
| {
- only: TTunnelConstraints;
+ only: TunnelProtocol;
};
+ openvpnConstraints: OpenVpn;
+ wireguardConstraints: Wireguard;
}
export type ConnectionConfig =
@@ -150,7 +159,7 @@ export interface IRelaySettingsCustom {
}
export type RelaySettings =
| {
- normal: IRelaySettingsNormal<TunnelConstraints<IOpenVpnConstraints, IWireguardConstraints>>;
+ normal: IRelaySettingsNormal<IOpenVpnConstraints, IWireguardConstraints>;
}
| {
customTunnelEndpoint: IRelaySettingsCustom;
@@ -158,9 +167,7 @@ export type RelaySettings =
// types describing the partial update of RelaySettings
export type RelaySettingsNormalUpdate = Partial<
- IRelaySettingsNormal<
- TunnelConstraints<Partial<IOpenVpnConstraints>, Partial<IWireguardConstraints>>
- >
+ IRelaySettingsNormal<Partial<IOpenVpnConstraints>, Partial<IWireguardConstraints>>
>;
export type RelaySettingsUpdate =
diff --git a/gui/test/relay-settings-builder.spec.ts b/gui/test/relay-settings-builder.spec.ts
index fd7f4c1cf4..f9ceccd744 100644
--- a/gui/test/relay-settings-builder.spec.ts
+++ b/gui/test/relay-settings-builder.spec.ts
@@ -54,13 +54,9 @@ describe('Relay settings builder', () => {
.build(),
).to.deep.equal({
normal: {
- tunnel: {
- only: {
- openvpn: {
- port: 'any',
- protocol: 'any',
- },
- },
+ openvpnConstraints: {
+ port: 'any',
+ protocol: 'any',
},
},
});
@@ -75,13 +71,9 @@ describe('Relay settings builder', () => {
.build(),
).to.deep.equal({
normal: {
- tunnel: {
- only: {
- openvpn: {
- port: { only: 80 },
- protocol: { only: 'tcp' },
- },
- },
+ openvpnConstraints: {
+ port: { only: 80 },
+ protocol: { only: 'tcp' },
},
},
});
diff --git a/mullvad-cli/src/cmds/relay.rs b/mullvad-cli/src/cmds/relay.rs
index c9bf61be43..fe411b06a2 100644
--- a/mullvad-cli/src/cmds/relay.rs
+++ b/mullvad-cli/src/cmds/relay.rs
@@ -9,7 +9,7 @@ use std::{
use mullvad_types::{
relay_constraints::{
Constraint, OpenVpnConstraints, RelayConstraintsUpdate, RelaySettingsUpdate,
- TunnelConstraints, WireguardConstraints,
+ TunnelProtocol, WireguardConstraints,
},
ConnectionConfig, CustomTunnelEndpoint,
};
@@ -271,7 +271,7 @@ impl Relay {
self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate {
location: Some(location_constraint),
- tunnel: None,
+ ..Default::default()
}))
}
@@ -287,17 +287,17 @@ impl Relay {
}
self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate {
location: None,
- tunnel: Some(Constraint::Only(TunnelConstraints::Wireguard(
- WireguardConstraints { port },
- ))),
+ tunnel_protocol: Some(Constraint::Only(TunnelProtocol::Wireguard)),
+ wireguard_constraints: Some(WireguardConstraints { port }),
+ ..Default::default()
}))
}
"openvpn" => {
self.update_constraints(RelaySettingsUpdate::Normal(RelayConstraintsUpdate {
location: None,
- tunnel: Some(Constraint::Only(TunnelConstraints::OpenVpn(
- OpenVpnConstraints { port, protocol },
- ))),
+ tunnel_protocol: Some(Constraint::Any),
+ openvpn_constraints: Some(OpenVpnConstraints { port, protocol }),
+ ..Default::default()
}))
}
_ => unreachable!(),
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index 27594c8adf..70acaead19 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -38,7 +38,7 @@ use mullvad_types::{
location::GeoIpLocation,
relay_constraints::{
BridgeSettings, BridgeState, Constraint, InternalBridgeConstraints, OpenVpnConstraints,
- RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, TunnelConstraints,
+ RelayConstraintsUpdate, RelaySettings, RelaySettingsUpdate, TunnelProtocol,
},
relay_list::{Relay, RelayList},
states::{TargetState, TunnelState},
@@ -1164,16 +1164,13 @@ where
// Set the OpenVPN tunnel to use TCP.
fn apply_proxy_constraints(&mut self) -> settings::Result<bool> {
- let openvpn_constraints = OpenVpnConstraints {
- port: Constraint::Any,
- protocol: Constraint::Only(TransportProtocol::Tcp),
- };
-
- let tunnel_constraints = TunnelConstraints::OpenVpn(openvpn_constraints);
-
let constraints_update = RelayConstraintsUpdate {
- location: None,
- tunnel: Some(Constraint::Only(tunnel_constraints)),
+ tunnel_protocol: Some(Constraint::Only(TunnelProtocol::OpenVpn)),
+ openvpn_constraints: Some(OpenVpnConstraints {
+ protocol: Constraint::Only(TransportProtocol::Tcp),
+ port: Constraint::Any,
+ }),
+ ..Default::default()
};
let settings_update = RelaySettingsUpdate::Normal(constraints_update);
diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs
index 5f238244fd..025c535e6c 100644
--- a/mullvad-daemon/src/relays.rs
+++ b/mullvad-daemon/src/relays.rs
@@ -6,7 +6,7 @@ use mullvad_types::{
location::Location,
relay_constraints::{
Constraint, InternalBridgeConstraints, LocationConstraint, Match, OpenVpnConstraints,
- RelayConstraints, TunnelConstraints, WireguardConstraints,
+ RelayConstraints, TunnelProtocol, WireguardConstraints,
},
relay_list::{Relay, RelayList, RelayTunnels, WireguardEndpointData},
};
@@ -239,40 +239,48 @@ impl RelaySelector {
_ => (Constraint::Any, TransportProtocol::Tcp),
};
+ let mut relay_constraints = RelayConstraints {
+ location: original_constraints.location.clone(),
+ tunnel_protocol: original_constraints.tunnel_protocol.clone(),
+ ..Default::default()
+ };
// Highest priority preference. Where we prefer OpenVPN using UDP. But without changing
// any constraints that are explicitly specified.
- let tunnel_constraints = match original_constraints.tunnel {
- // No constraints, we use our preferred ones.
+ match original_constraints.tunnel_protocol {
+ // If no tunnel protocol is selected, use preferred constraints
#[cfg(not(target_os = "android"))]
- Constraint::Any => TunnelConstraints::OpenVpn(OpenVpnConstraints {
- port: preferred_port,
- protocol: Constraint::Only(preferred_protocol),
- }),
- #[cfg(target_os = "android")]
- Constraint::Any => TunnelConstraints::Wireguard(WireguardConstraints {
- port: Constraint::Any,
- }),
- Constraint::Only(TunnelConstraints::OpenVpn(ref openvpn_constraints)) => {
- match openvpn_constraints {
- // Constrained to OpenVpn, but port/protocol not constrained. Use our preferred.
- OpenVpnConstraints {
- port: Constraint::Any,
- protocol: Constraint::Any,
- } => TunnelConstraints::OpenVpn(OpenVpnConstraints {
+ Constraint::Any => {
+ if original_constraints.openvpn_constraints.port.is_any()
+ && original_constraints.openvpn_constraints.protocol.is_any()
+ {
+ relay_constraints.openvpn_constraints = OpenVpnConstraints {
port: preferred_port,
protocol: Constraint::Only(preferred_protocol),
- }),
- // Other constraints, use the original constraints.
- openvpn_constraints => TunnelConstraints::OpenVpn(openvpn_constraints.clone()),
+ };
+ } else {
+ relay_constraints.openvpn_constraints = OpenVpnConstraints {
+ port: original_constraints.openvpn_constraints.port,
+ protocol: original_constraints.openvpn_constraints.protocol,
+ };
}
}
- // Non-OpenVPN constraints. Respect and keep those constraints.
- Constraint::Only(ref tunnel_constraints) => tunnel_constraints.clone(),
- };
- RelayConstraints {
- location: original_constraints.location.clone(),
- tunnel: Constraint::Only(tunnel_constraints),
+ #[cfg(not(target_os = "android"))]
+ Constraint::Only(TunnelProtocol::OpenVpn) => {
+ relay_constraints.openvpn_constraints = original_constraints.openvpn_constraints;
+ }
+ #[cfg(not(target_os = "android"))]
+ Constraint::Only(TunnelProtocol::Wireguard) => {
+ relay_constraints.wireguard_constraints =
+ original_constraints.wireguard_constraints;
+ }
+ #[cfg(target_os = "android")]
+ _ => {
+ relay_constraints.wireguard_constraints =
+ original_constraints.wireguard_constraints;
+ }
}
+
+ relay_constraints
}
pub fn get_auto_proxy_settings(
@@ -350,7 +358,7 @@ impl RelaySelector {
"Selected relay {} at {}",
selected_relay.hostname, selected_relay.ipv4_addr_in
);
- self.get_random_tunnel(&selected_relay, &constraints.tunnel)
+ self.get_random_tunnel(&selected_relay, &constraints)
.map(|endpoint| (selected_relay.clone(), endpoint))
})
}
@@ -362,22 +370,34 @@ impl RelaySelector {
return None;
}
- let relay = match constraints.tunnel {
+ let relay = match constraints.tunnel_protocol {
Constraint::Any => relay.clone(),
- Constraint::Only(ref tunnel_constraints) => {
+ Constraint::Only(TunnelProtocol::Wireguard) => {
let mut relay = relay.clone();
- relay.tunnels = Self::matching_tunnels(&relay.tunnels, tunnel_constraints);
+ relay.tunnels = Self::matching_wireguard_tunnels(
+ &relay.tunnels,
+ &constraints.wireguard_constraints,
+ );
+ relay
+ }
+
+ Constraint::Only(TunnelProtocol::OpenVpn) => {
+ let mut relay = relay.clone();
+ relay.tunnels = Self::matching_openvpn_tunnels(
+ &relay.tunnels,
+ &constraints.openvpn_constraints,
+ );
relay
}
};
- let relay_matches = match constraints.tunnel {
+
+
+ let relay_matches = match constraints.tunnel_protocol {
Constraint::Any => {
!relay.tunnels.openvpn.is_empty() || !relay.tunnels.wireguard.is_empty()
}
- Constraint::Only(TunnelConstraints::OpenVpn(_)) => !relay.tunnels.openvpn.is_empty(),
- Constraint::Only(TunnelConstraints::Wireguard(_)) => {
- !relay.tunnels.wireguard.is_empty()
- }
+ Constraint::Only(TunnelProtocol::OpenVpn) => !relay.tunnels.openvpn.is_empty(),
+ Constraint::Only(TunnelProtocol::Wireguard) => !relay.tunnels.wireguard.is_empty(),
};
if relay_matches {
@@ -432,29 +452,35 @@ impl RelaySelector {
Some(filtered_relay)
}
- /// Takes a `RelayTunnels` object which in turn is a collection of tunnel configurations for
- /// a given relay. Then returns a new `RelayTunnels` instance with only the entries that
- /// matches the given `TunnelConstraints`.
- fn matching_tunnels(
+ fn matching_openvpn_tunnels(
tunnels: &RelayTunnels,
- tunnel_constraints: &TunnelConstraints,
+ constraints: &OpenVpnConstraints,
) -> RelayTunnels {
RelayTunnels {
openvpn: tunnels
.openvpn
.iter()
- .filter(|endpoint| tunnel_constraints.matches(*endpoint))
+ .filter(|endpoint| constraints.matches(*endpoint))
.cloned()
.collect(),
+ wireguard: vec![],
+ }
+ }
+
+ fn matching_wireguard_tunnels(
+ tunnels: &RelayTunnels,
+ constraints: &WireguardConstraints,
+ ) -> RelayTunnels {
+ RelayTunnels {
+ openvpn: vec![],
wireguard: tunnels
.wireguard
.iter()
- .filter(|endpoint| tunnel_constraints.matches(*endpoint))
+ .filter(|endpoint| constraints.matches(*endpoint))
.cloned()
.collect(),
}
}
-
/// Pick a random relay from the given slice. Will return `None` if the given slice is empty
/// or all relays in it has zero weight.
fn pick_random_relay<'a>(&mut self, relays: &'a [Relay]) -> Option<&'a Relay> {
@@ -493,25 +519,29 @@ impl RelaySelector {
fn get_random_tunnel(
&mut self,
relay: &Relay,
- constraints: &Constraint<TunnelConstraints>,
+ constraints: &RelayConstraints,
) -> Option<MullvadEndpoint> {
- match constraints {
+ match constraints.tunnel_protocol {
// TODO: Handle Constraint::Any case by selecting from both openvpn and wireguard
// tunnels once wireguard is mature enough
#[cfg(not(target_os = "android"))]
- Constraint::Only(TunnelConstraints::OpenVpn(_)) | Constraint::Any => relay
+ Constraint::Only(TunnelProtocol::OpenVpn) | Constraint::Any => relay
.tunnels
.openvpn
.choose(&mut self.rng)
.cloned()
.map(|endpoint| endpoint.into_mullvad_endpoint(relay.ipv4_addr_in.into())),
- Constraint::Only(TunnelConstraints::Wireguard(wg_constraints)) => relay
+ Constraint::Only(TunnelProtocol::Wireguard) => relay
.tunnels
.wireguard
.choose(&mut self.rng)
.cloned()
.and_then(|wg_tunnel| {
- self.wg_data_to_endpoint(relay.ipv4_addr_in.into(), wg_tunnel, wg_constraints)
+ self.wg_data_to_endpoint(
+ relay.ipv4_addr_in.into(),
+ wg_tunnel,
+ &constraints.wireguard_constraints,
+ )
}),
#[cfg(target_os = "android")]
Constraint::Any => relay
@@ -527,7 +557,7 @@ impl RelaySelector {
)
}),
#[cfg(target_os = "android")]
- Constraint::Only(TunnelConstraints::OpenVpn(_)) => None,
+ Constraint::Only(TunnelProtocol::OpenVpn) => None,
}
}
@@ -537,7 +567,7 @@ impl RelaySelector {
data: WireguardEndpointData,
constraints: &WireguardConstraints,
) -> Option<MullvadEndpoint> {
- let port = self.get_port_for_wireguard_relay(&data, constraints)?;
+ let port = self.get_port_for_wireguard_relay(&data, &constraints)?;
let peer_config = wireguard::PeerConfig {
public_key: data.public_key,
endpoint: SocketAddr::new(host, port),
diff --git a/mullvad-jni/src/from_java.rs b/mullvad-jni/src/from_java.rs
index 3acd9717ef..c016134f23 100644
--- a/mullvad-jni/src/from_java.rs
+++ b/mullvad-jni/src/from_java.rs
@@ -110,7 +110,9 @@ impl<'env> FromJava<'env> for RelayConstraintsUpdate {
RelayConstraintsUpdate {
location: FromJava::from_java(env, location),
- tunnel: None,
+ tunnel_protocol: None,
+ openvpn_constraints: None,
+ wireguard_constraints: None,
}
}
}
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index cd901c6bbc..562bf0bee8 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -33,6 +33,13 @@ impl<T: fmt::Debug + Clone + Eq + PartialEq> Constraint<T> {
Constraint::Only(value) => Constraint::Only(value),
}
}
+
+ pub fn is_any(&self) -> bool {
+ match self {
+ Constraint::Any => true,
+ Constraint::Only(_value) => false,
+ }
+ }
}
impl<T: fmt::Debug + Clone + Eq + PartialEq> Default for Constraint<T> {
@@ -89,23 +96,43 @@ impl RelaySettings {
#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct RelayConstraints {
pub location: Constraint<LocationConstraint>,
- pub tunnel: Constraint<TunnelConstraints>,
+ pub tunnel_protocol: Constraint<TunnelProtocol>,
+ pub wireguard_constraints: WireguardConstraints,
+ pub openvpn_constraints: OpenVpnConstraints,
}
impl RelayConstraints {
pub fn merge(&self, update: RelayConstraintsUpdate) -> Self {
RelayConstraints {
location: update.location.unwrap_or_else(|| self.location.clone()),
- tunnel: update.tunnel.unwrap_or_else(|| self.tunnel.clone()),
+ tunnel_protocol: update
+ .tunnel_protocol
+ .unwrap_or_else(|| self.tunnel_protocol.clone()),
+ wireguard_constraints: update
+ .wireguard_constraints
+ .unwrap_or_else(|| self.wireguard_constraints.clone()),
+ openvpn_constraints: update
+ .openvpn_constraints
+ .unwrap_or_else(|| self.openvpn_constraints.clone()),
}
}
}
impl fmt::Display for RelayConstraints {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- match self.tunnel {
- Constraint::Any => write!(f, "any relay")?,
- Constraint::Only(ref tunnel_constraint) => tunnel_constraint.fmt(f)?,
+ match self.tunnel_protocol {
+ Constraint::Any => write!(f, "any tunnel protocol")?,
+ Constraint::Only(ref tunnel_protocol) => {
+ tunnel_protocol.fmt(f)?;
+ match tunnel_protocol {
+ TunnelProtocol::Wireguard => {
+ write!(f, " over {}", &self.wireguard_constraints)?;
+ }
+ TunnelProtocol::OpenVpn => {
+ write!(f, " over {}", &self.openvpn_constraints)?;
+ }
+ };
+ }
}
write!(f, " in ")?;
match self.location {
@@ -140,6 +167,23 @@ impl fmt::Display for LocationConstraint {
}
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+pub enum TunnelProtocol {
+ #[serde(rename = "wireguard")]
+ Wireguard,
+ #[serde(rename = "openvpn")]
+ OpenVpn,
+}
+
+impl fmt::Display for TunnelProtocol {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
+ match self {
+ TunnelProtocol::Wireguard => write!(f, "WireGuard"),
+ TunnelProtocol::OpenVpn => write!(f, "OpenVPN"),
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub enum TunnelConstraints {
#[serde(rename = "openvpn")]
OpenVpn(OpenVpnConstraints),
@@ -180,7 +224,7 @@ impl Match<WireguardEndpointData> for TunnelConstraints {
}
}
-#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct OpenVpnConstraints {
pub port: Constraint<u16>,
pub protocol: Constraint<TransportProtocol>,
@@ -206,7 +250,7 @@ impl Match<OpenVpnEndpointData> for OpenVpnConstraints {
}
}
-#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct WireguardConstraints {
pub port: Constraint<u16>,
}
@@ -296,5 +340,7 @@ pub enum RelaySettingsUpdate {
#[serde(default)]
pub struct RelayConstraintsUpdate {
pub location: Option<Constraint<LocationConstraint>>,
- pub tunnel: Option<Constraint<TunnelConstraints>>,
+ pub tunnel_protocol: Option<Constraint<TunnelProtocol>>,
+ pub wireguard_constraints: Option<WireguardConstraints>,
+ pub openvpn_constraints: Option<OpenVpnConstraints>,
}
diff --git a/mullvad-types/src/settings/migrations/mod.rs b/mullvad-types/src/settings/migrations/mod.rs
new file mode 100644
index 0000000000..521d462f81
--- /dev/null
+++ b/mullvad-types/src/settings/migrations/mod.rs
@@ -0,0 +1,145 @@
+use super::{Error, Result, Settings};
+use serde::{Deserialize, Deserializer, Serialize, Serializer};
+use std::io::Read;
+mod v1;
+
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+#[repr(u32)]
+pub enum SettingsVersion {
+ V2 = 2,
+}
+
+impl SettingsVersion {
+ pub fn as_u32(&self) -> u32 {
+ unsafe { ::std::mem::transmute(*self) }
+ }
+
+ pub fn max_version() -> Self {
+ SettingsVersion::V2
+ }
+
+ pub fn min_version() -> Self {
+ SettingsVersion::V2
+ }
+}
+
+impl<'de> Deserialize<'de> for SettingsVersion {
+ fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
+ where
+ D: Deserializer<'de>,
+ {
+ let version = <u32>::deserialize(deserializer)?;
+ if version < SettingsVersion::min_version().as_u32() {
+ return Err(serde::de::Error::custom(format!(
+ "Version number {} too small",
+ version
+ )));
+ }
+
+ if version > SettingsVersion::max_version().as_u32() {
+ return Err(serde::de::Error::custom(format!(
+ "Version number {} too large",
+ version
+ )));
+ }
+
+ unsafe { Ok(::std::mem::transmute(version)) }
+ }
+}
+
+impl Serialize for SettingsVersion {
+ fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
+ where
+ S: Serializer,
+ {
+ serializer.serialize_u32(self.as_u32())
+ }
+}
+
+
+#[derive(Debug)]
+enum VersionedSettings {
+ V1(v1::Settings),
+ V2(crate::settings::Settings),
+}
+
+impl VersionedSettings {
+ /// Unrwaps the latest version of settings or panics.
+ fn unwrap(self) -> Settings {
+ match self {
+ VersionedSettings::V2(settings) => settings,
+ lower => {
+ panic!("Unexpected settings version - {:?}", lower);
+ }
+ }
+ }
+}
+
+
+trait SettingsMigration {
+ fn read(&self, reader: &mut dyn Read) -> Result<VersionedSettings>;
+ fn migrate(&self, settings: VersionedSettings) -> VersionedSettings;
+}
+
+fn migrations() -> Vec<Box<dyn SettingsMigration>> {
+ vec![Box::new(v1::Migration)]
+}
+
+pub fn try_migrate_settings(mut settings_file: &[u8]) -> Result<crate::settings::Settings> {
+ let mut migrations_to_apply = vec![];
+ let mut valid_settings = None;
+
+ let migrations = migrations();
+ for migration in migrations.iter() {
+ match migration.read(&mut settings_file) {
+ Ok(settings) => {
+ valid_settings = Some(migration.migrate(settings));
+ break;
+ }
+ Err(_e) => {
+ migrations_to_apply.push(migration);
+ }
+ };
+ }
+
+ if let Some(settings) = valid_settings {
+ let upgraded_settings = migrations_to_apply
+ .iter()
+ .rev()
+ .fold(settings, |old_settings, migration| {
+ migration.migrate(old_settings)
+ });
+ return Ok(upgraded_settings.unwrap());
+ }
+ return Err(Error::NoMatchingVersion);
+}
+
+#[cfg(test)]
+mod test {
+ use super::SettingsVersion;
+
+ #[test]
+ #[should_panic]
+ fn test_deserialization_failure_version_too_small() {
+ let _version: SettingsVersion = serde_json::from_str("1").expect("Version too small");
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_deserialization_failure_version_too_big() {
+ let _version: SettingsVersion = serde_json::from_str("3").expect("Version too big");
+ }
+
+ #[test]
+ fn test_deserialization_success() {
+ let _version: SettingsVersion = serde_json::from_str("2").expect("Failed to deserialize valid version");
+ }
+
+ #[test]
+ fn test_serialization_success() {
+ let version = SettingsVersion::V2;
+ let s = serde_json::to_string(&version).expect("Failed to serialize");
+ assert_eq!(s, "2");
+ }
+}
diff --git a/mullvad-types/src/settings/migrations/v1.rs b/mullvad-types/src/settings/migrations/v1.rs
new file mode 100644
index 0000000000..a5b4e7c618
--- /dev/null
+++ b/mullvad-types/src/settings/migrations/v1.rs
@@ -0,0 +1,221 @@
+use super::{Error, Result, VersionedSettings};
+use crate::{
+ custom_tunnel::CustomTunnelEndpoint,
+ relay_constraints::{
+ BridgeSettings, BridgeState, Constraint, LocationConstraint, OpenVpnConstraints,
+ RelaySettings as NewRelaySettings, TunnelProtocol, WireguardConstraints,
+ },
+ settings::TunnelOptions,
+};
+use serde::{Deserialize, Serialize};
+use std::io::Read;
+
+
+/// Mullvad daemon settings.
+#[derive(Debug, Clone, Deserialize, Serialize)]
+pub struct Settings {
+ account_token: Option<String>,
+ relay_settings: RelaySettings,
+ bridge_settings: BridgeSettings,
+ bridge_state: BridgeState,
+ /// If the daemon should allow communication with private (LAN) networks.
+ allow_lan: bool,
+ /// Extra level of kill switch. When this setting is on, the disconnected state will block
+ /// the firewall to not allow any traffic in or out.
+ block_when_disconnected: bool,
+ /// If the daemon should connect the VPN tunnel directly on start or not.
+ auto_connect: bool,
+ /// Options that should be applied to tunnels of a specific type regardless of where the relays
+ /// might be located.
+ tunnel_options: TunnelOptions,
+}
+
+pub(super) struct Migration;
+impl super::SettingsMigration for Migration {
+ fn read(&self, mut reader: &mut dyn Read) -> Result<VersionedSettings> {
+ serde_json::from_reader(&mut reader)
+ .map(VersionedSettings::V1)
+ .map_err(Error::ParseError)
+ }
+ fn migrate(&self, old: VersionedSettings) -> VersionedSettings {
+ match old {
+ VersionedSettings::V1(old) => VersionedSettings::V2(crate::settings::Settings {
+ account_token: old.account_token,
+ relay_settings: migrate_relay_settings(old.relay_settings),
+ bridge_settings: old.bridge_settings,
+ bridge_state: old.bridge_state,
+ allow_lan: old.allow_lan,
+ block_when_disconnected: old.block_when_disconnected,
+ auto_connect: old.auto_connect,
+ tunnel_options: old.tunnel_options,
+ settings_version: super::SettingsVersion::V2,
+ }),
+ VersionedSettings::V2(new) => VersionedSettings::V2(new),
+ }
+ }
+}
+
+fn migrate_relay_settings(relay_settings: RelaySettings) -> NewRelaySettings {
+ match relay_settings {
+ RelaySettings::CustomTunnelEndpoint(endpoint) => {
+ crate::relay_constraints::RelaySettings::CustomTunnelEndpoint(endpoint)
+ }
+ RelaySettings::Normal(old_constraints) => {
+ let mut new_constraints = crate::relay_constraints::RelayConstraints {
+ location: old_constraints.location,
+ ..Default::default()
+ };
+ match old_constraints.tunnel {
+ Constraint::Any => (),
+ Constraint::Only(TunnelConstraints::OpenVpn(constraints)) => {
+ new_constraints.openvpn_constraints = constraints;
+ }
+ Constraint::Only(TunnelConstraints::Wireguard(constraints)) => {
+ new_constraints.wireguard_constraints = constraints;
+ new_constraints.tunnel_protocol = Constraint::Only(TunnelProtocol::Wireguard);
+ }
+ };
+ crate::relay_constraints::RelaySettings::Normal(new_constraints)
+ }
+ }
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+#[serde(rename_all = "snake_case")]
+pub enum RelaySettings {
+ CustomTunnelEndpoint(CustomTunnelEndpoint),
+ Normal(RelayConstraints),
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+pub struct RelayConstraints {
+ pub location: Constraint<LocationConstraint>,
+ pub tunnel: Constraint<TunnelConstraints>,
+}
+
+#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
+pub enum TunnelConstraints {
+ #[serde(rename = "openvpn")]
+ OpenVpn(OpenVpnConstraints),
+ #[serde(rename = "wireguard")]
+ Wireguard(WireguardConstraints),
+}
+
+#[cfg(test)]
+mod test {
+ use super::super::SettingsMigration;
+ use serde_json;
+ const OLD_SETTINGS: &str = r#"
+{
+ "account_token": "1234",
+ "relay_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "country": "se"
+ }
+ },
+ "tunnel": {
+ "only": {
+ "openvpn": {
+ "port": {
+ "only": 53
+ },
+ "protocol": {
+ "only": "udp"
+ }
+ }
+ }
+ }
+ }
+ },
+ "bridge_settings": {
+ "normal": {
+ "location": "any"
+ }
+ },
+ "bridge_state": "auto",
+ "allow_lan": true,
+ "block_when_disconnected": false,
+ "auto_connect": false,
+ "tunnel_options": {
+ "openvpn": {
+ "mssfix": null
+ },
+ "wireguard": {
+ "mtu": null
+ },
+ "generic": {
+ "enable_ipv6": false
+ }
+ }
+}
+"#;
+
+ const NEW_SETTINGS: &str = r#"
+{
+ "account_token": "1234",
+ "relay_settings": {
+ "normal": {
+ "location": {
+ "only": {
+ "country": "se"
+ }
+ },
+ "tunnel_protocol": "any",
+ "wireguard_constraints": {
+ "port": "any"
+ },
+ "openvpn_constraints": {
+ "port": {
+ "only": 53
+ },
+ "protocol": {
+ "only": "udp"
+ }
+ }
+ }
+ },
+ "bridge_settings": {
+ "normal": {
+ "location": "any"
+ }
+ },
+ "bridge_state": "auto",
+ "allow_lan": true,
+ "block_when_disconnected": false,
+ "auto_connect": false,
+ "tunnel_options": {
+ "openvpn": {
+ "mssfix": null
+ },
+ "wireguard": {
+ "mtu": null
+ },
+ "generic": {
+ "enable_ipv6": false
+ }
+ },
+ "settings_version": 2
+}
+"#;
+
+ #[test]
+ fn test_migration() {
+ let m = super::Migration;
+ let old_settings = m
+ .read(&mut OLD_SETTINGS.as_bytes())
+ .expect("Failed to deserialize old format");
+ let new_settings = serde_json::from_str(&NEW_SETTINGS).unwrap();
+
+ assert_eq!(&m.migrate(old_settings).unwrap(), &new_settings);
+ }
+
+ #[test]
+ #[should_panic]
+ fn test_deserialization_failure() {
+ let m = super::Migration;
+ m.read(&mut NEW_SETTINGS.as_bytes())
+ .expect("Failed to deserialize old format");
+ }
+}
diff --git a/mullvad-types/src/settings.rs b/mullvad-types/src/settings/mod.rs
index dab81bf43a..c2f84cff59 100644
--- a/mullvad-types/src/settings.rs
+++ b/mullvad-types/src/settings/mod.rs
@@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
use serde_json;
use std::{
fs::{self, File},
- io,
+ io::{self, Read},
path::PathBuf,
};
use talpid_types::{
@@ -15,6 +15,7 @@ use talpid_types::{
ErrorExt,
};
+mod migrations;
pub type Result<T> = std::result::Result<T, Error>;
@@ -40,13 +41,16 @@ pub enum Error {
#[error(display = "Invalid OpenVPN proxy configuration: {}", _0)]
InvalidProxyData(String),
+
+ #[error(display = "Unable to read any version of the settings")]
+ NoMatchingVersion,
}
static SETTINGS_FILE: &str = "settings.json";
/// Mullvad daemon settings.
-#[derive(Debug, Clone, Deserialize, Serialize)]
+#[derive(Debug, Clone, Deserialize, Serialize, PartialEq)]
#[serde(default)]
pub struct Settings {
account_token: Option<String>,
@@ -63,6 +67,8 @@ pub struct Settings {
/// Options that should be applied to tunnels of a specific type regardless of where the relays
/// might be located.
tunnel_options: TunnelOptions,
+ /// Specifies settings schema version
+ settings_version: migrations::SettingsVersion,
}
impl Default for Settings {
@@ -71,7 +77,7 @@ impl Default for Settings {
account_token: None,
relay_settings: RelaySettings::Normal(RelayConstraints {
location: Constraint::Only(LocationConstraint::Country("se".to_owned())),
- tunnel: Constraint::Any,
+ ..Default::default()
}),
bridge_settings: BridgeSettings::Normal(BridgeConstraints {
location: Constraint::Any,
@@ -81,6 +87,7 @@ impl Default for Settings {
block_when_disconnected: false,
auto_connect: false,
tunnel_options: TunnelOptions::default(),
+ settings_version: migrations::SettingsVersion::V2,
}
}
}
@@ -92,7 +99,21 @@ impl Settings {
match File::open(&path) {
Ok(file) => {
info!("Loading settings from {}", path.display());
- Self::read_settings(&mut io::BufReader::new(file))
+ let mut settings_bytes = vec![];
+ io::BufReader::new(file)
+ .read_to_end(&mut settings_bytes)
+ .map_err(|e| Error::ReadError("Failed to read settings file".to_owned(), e))?;
+ Self::parse_settings(&mut settings_bytes).or_else(|e| {
+ log::error!(
+ "{}",
+ e.display_chain_with_msg("Failed to parse settings file")
+ );
+ let settings = migrations::try_migrate_settings(&settings_bytes)?;
+ if let Err(e) = settings.save() {
+ log::error!("Failed to save settings after migration: {}", e);
+ }
+ Ok(settings)
+ })
}
Err(e) => Err(Error::ReadError(path.display().to_string(), e)),
}
@@ -132,8 +153,8 @@ impl Settings {
Ok(dir.join(SETTINGS_FILE))
}
- fn read_settings<T: io::Read>(file: &mut T) -> Result<Settings> {
- serde_json::from_reader(file).map_err(Error::ParseError)
+ fn parse_settings(bytes: &[u8]) -> Result<Settings> {
+ serde_json::from_slice(bytes).map_err(Error::ParseError)
}
pub fn get_account_token(&self) -> Option<String> {