summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--mullvad-daemon/src/lib.rs26
-rw-r--r--mullvad-daemon/src/relays.rs1130
-rw-r--r--mullvad-types/src/endpoint.rs29
-rw-r--r--mullvad-types/src/relay_constraints.rs62
4 files changed, 770 insertions, 477 deletions
diff --git a/mullvad-daemon/src/lib.rs b/mullvad-daemon/src/lib.rs
index b2b3468a54..b54eae5908 100644
--- a/mullvad-daemon/src/lib.rs
+++ b/mullvad-daemon/src/lib.rs
@@ -1008,16 +1008,21 @@ where
self.settings.get_wireguard().is_some(),
)
.ok();
- if let Some((relay, entry_relay, endpoint)) = endpoint {
+ if let Some(relays::RelaySelectorResult {
+ exit_relay,
+ entry_relay,
+ endpoint,
+ }) = endpoint
+ {
let result = self
.create_tunnel_parameters(
- &relay,
+ &exit_relay,
endpoint,
account_token,
retry_attempt,
)
.await;
- self.last_generated_relay = Some(relay);
+ self.last_generated_relay = Some(exit_relay);
self.last_generated_entry_relay = entry_relay;
match result {
Ok(result) => Ok(result),
@@ -1123,12 +1128,7 @@ where
}
.into())
}
- MullvadEndpoint::Wireguard {
- peer,
- exit_peer,
- ipv4_gateway,
- ipv6_gateway,
- } => {
+ MullvadEndpoint::Wireguard(endpoint) => {
let wg_data = self.settings.get_wireguard().ok_or(Error::NoKeyAvailable)?;
let tunnel = wireguard::TunnelConfig {
private_key: wg_data.private_key,
@@ -1140,10 +1140,10 @@ where
Ok(wireguard::TunnelParameters {
connection: wireguard::ConnectionConfig {
tunnel,
- peer,
- exit_peer,
- ipv4_gateway,
- ipv6_gateway: Some(ipv6_gateway),
+ peer: endpoint.peer,
+ exit_peer: endpoint.exit_peer,
+ ipv4_gateway: endpoint.ipv4_gateway,
+ ipv6_gateway: Some(endpoint.ipv6_gateway),
},
options: tunnel_options.wireguard.options,
generic_options: tunnel_options.generic,
diff --git a/mullvad-daemon/src/relays.rs b/mullvad-daemon/src/relays.rs
index c5284a38d6..ac56bf75fd 100644
--- a/mullvad-daemon/src/relays.rs
+++ b/mullvad-daemon/src/relays.rs
@@ -10,16 +10,16 @@ use futures::{
use ipnetwork::IpNetwork;
use mullvad_rpc::{availability::ApiAvailabilityHandle, rest::MullvadRestHandle, RelayListProxy};
use mullvad_types::{
- endpoint::MullvadEndpoint,
+ endpoint::{MullvadEndpoint, MullvadWireguardEndpoint},
location::Location,
relay_constraints::{
BridgeState, Constraint, InternalBridgeConstraints, LocationConstraint, Match,
OpenVpnConstraints, Providers, RelayConstraints, Set, TransportPort, WireguardConstraints,
},
- relay_list::{OpenVpnEndpointData, Relay, RelayList, RelayTunnels, WireguardEndpointData},
+ relay_list::{Relay, RelayList, RelayTunnels, WireguardEndpointData},
};
use parking_lot::Mutex;
-use rand::{self, rngs::ThreadRng, seq::SliceRandom, Rng};
+use rand::{self, seq::SliceRandom, Rng};
use std::{
future::Future,
io,
@@ -49,16 +49,14 @@ const UPDATE_INTERVAL: Duration = Duration::from_secs(60 * 60);
const EXPONENTIAL_BACKOFF_INITIAL: Duration = Duration::from_secs(16);
const EXPONENTIAL_BACKOFF_FACTOR: u32 = 8;
-
const DEFAULT_WIREGUARD_PORT: u16 = 51820;
-const WIREGUARD_EXIT_CONSTRAINTS: WireguardConstraints = WireguardConstraints {
+const WIREGUARD_EXIT_CONSTRAINTS: WireguardMatcher = WireguardMatcher {
+ peer: None,
port: Constraint::Only(TransportPort {
protocol: TransportProtocol::Udp,
port: Constraint::Only(DEFAULT_WIREGUARD_PORT),
}),
ip_version: Constraint::Only(IpVersion::V4),
- use_multihop: false,
- entry_location: Constraint::Any,
};
const WIREGUARD_TCP_PORTS: [(u16, u16); 3] = [(80, 80), (443, 443), (5001, 5001)];
@@ -174,7 +172,6 @@ impl ParsedRelays {
pub struct RelaySelector {
parsed_relays: Arc<Mutex<ParsedRelays>>,
- rng: ThreadRng,
updater: Option<RelayListUpdaterHandle>,
}
@@ -216,7 +213,6 @@ impl RelaySelector {
RelaySelector {
parsed_relays,
- rng: rand::thread_rng(),
updater: Some(updater),
}
}
@@ -237,114 +233,245 @@ impl RelaySelector {
/// Returns a random relay and relay endpoint matching the given constraints and with
/// preferences applied.
pub fn get_tunnel_endpoint(
- &mut self,
+ &self,
relay_constraints: &RelayConstraints,
bridge_state: BridgeState,
retry_attempt: u32,
wg_key_exists: bool,
- ) -> Result<(Relay, Option<Relay>, MullvadEndpoint), Error> {
- let mut exit_relay_constraints = relay_constraints.clone();
- let wg_entry_is_subset = if exit_relay_constraints.wireguard_constraints.use_multihop {
- let use_multihop = exit_relay_constraints.wireguard_constraints.use_multihop;
- let entry_location = exit_relay_constraints.wireguard_constraints.entry_location;
- let is_subset = entry_location.is_subset(&exit_relay_constraints.location);
- exit_relay_constraints.wireguard_constraints = WireguardConstraints {
- use_multihop,
- entry_location,
- ..WIREGUARD_EXIT_CONSTRAINTS
- };
- is_subset
+ ) -> Result<RelaySelectorResult, Error> {
+ match relay_constraints.tunnel_protocol {
+ Constraint::Only(TunnelType::OpenVpn) => self.get_openvpn_endpoint(
+ &relay_constraints.location,
+ &relay_constraints.providers,
+ relay_constraints.openvpn_constraints.clone(),
+ bridge_state,
+ retry_attempt,
+ ),
+
+ Constraint::Only(TunnelType::Wireguard) => self.get_wireguard_endpoint(
+ &relay_constraints.location,
+ &relay_constraints.providers,
+ &relay_constraints.wireguard_constraints,
+ retry_attempt,
+ ),
+ Constraint::Any => self.get_any_tunnel_endpoint(
+ relay_constraints,
+ bridge_state,
+ retry_attempt,
+ wg_key_exists,
+ ),
+ }
+ }
+
+ /// Returns an OpenVpn endpoint, should only ever be used when the user has specified the tunnel
+ /// protocol as only OpenVPN.
+ fn get_openvpn_endpoint(
+ &self,
+ location: &Constraint<LocationConstraint>,
+ providers: &Constraint<Providers>,
+ openvpn_constraints: OpenVpnConstraints,
+ bridge_state: BridgeState,
+ retry_attempt: u32,
+ ) -> Result<RelaySelectorResult, Error> {
+ let mut relay_matcher = RelayMatcher {
+ location: location.clone(),
+ providers: providers.clone(),
+ tunnel: openvpn_constraints,
+ };
+
+ if relay_matcher.tunnel.port.is_any() && bridge_state == BridgeState::On {
+ relay_matcher.tunnel.port = Constraint::Only(TransportPort {
+ protocol: TransportProtocol::Tcp,
+ port: Constraint::Any,
+ });
+
+ return self.get_tunnel_endpoint_internal(&relay_matcher);
+ }
+
+ let mut preferred_relay_matcher = relay_matcher.clone();
+
+ let (preferred_port, preferred_protocol) =
+ Self::preferred_openvpn_constraints(retry_attempt);
+ let should_try_preferred = match &mut preferred_relay_matcher.tunnel.port {
+ any @ Constraint::Any => {
+ *any = Constraint::Only(TransportPort {
+ protocol: preferred_protocol,
+ port: preferred_port,
+ });
+ true
+ }
+ Constraint::Only(ref mut port_constraints)
+ if port_constraints.protocol == preferred_protocol
+ && port_constraints.port.is_any() =>
+ {
+ port_constraints.port = preferred_port;
+ true
+ }
+ _ => false,
+ };
+
+ if should_try_preferred {
+ self.get_tunnel_endpoint_internal(&preferred_relay_matcher)
+ .or_else(|_| self.get_tunnel_endpoint_internal(&relay_matcher))
} else {
- false
+ self.get_tunnel_endpoint_internal(&relay_matcher)
+ }
+ }
+
+ fn get_wireguard_multi_hop_endpoint(
+ &self,
+ mut entry_matcher: RelayMatcher<WireguardMatcher>,
+ exit_location: Constraint<LocationConstraint>,
+ ) -> Result<RelaySelectorResult, Error> {
+ let mut exit_matcher = RelayMatcher {
+ location: exit_location,
+ tunnel: WIREGUARD_EXIT_CONSTRAINTS.clone().into(),
+ ..entry_matcher.clone()
};
- let entry_endpoint =
- if wg_entry_is_subset && relay_constraints.wireguard_constraints.use_multihop {
- self.select_entry_endpoint(None, &relay_constraints, retry_attempt)
+ let (exit_relay, entry_relay, exit_endpoint, mut entry_endpoint) =
+ if entry_matcher.location.is_subset(&exit_matcher.location) {
+ let (entry_relay, entry_endpoint) = self.get_entry_endpoint(&entry_matcher)?;
+ exit_matcher.set_peer(entry_relay.clone());
+ let exit_result = self.get_tunnel_endpoint_internal(&exit_matcher)?;
+ (
+ exit_result.exit_relay,
+ entry_relay,
+ exit_result.endpoint,
+ entry_endpoint,
+ )
} else {
- None
+ let exit_result = self.get_tunnel_endpoint_internal(&exit_matcher)?;
+
+ entry_matcher.set_peer(exit_result.exit_relay.clone());
+ let (entry_relay, entry_endpoint) = self.get_entry_endpoint(&entry_matcher)?;
+ (
+ exit_result.exit_relay,
+ entry_relay,
+ exit_result.endpoint,
+ entry_endpoint,
+ )
};
- let (exit_relay, mut endpoint) = self.get_tunnel_exit_endpoint(
- &exit_relay_constraints,
- bridge_state,
- retry_attempt,
- wg_key_exists,
- entry_endpoint.as_ref().and_then(|(_relay, endpoint)| {
- if let MullvadEndpoint::Wireguard { peer, .. } = &endpoint {
- Some(peer)
- } else {
- None
- }
- }),
- )?;
+ Self::set_entry_peers(
+ exit_endpoint
+ .get_peer_config()
+ .expect("Failed to get peer config from WireGuard endpoint"),
+ &mut entry_endpoint,
+ );
- let mut entry_endpoint = entry_endpoint.or_else(|| {
- if !wg_entry_is_subset && relay_constraints.wireguard_constraints.use_multihop {
- if let MullvadEndpoint::Wireguard { peer, .. } = &endpoint {
- self.select_entry_endpoint(Some(peer), &relay_constraints, retry_attempt)
- } else {
- None
- }
- } else {
- None
- }
- });
+ log::info!(
+ "Selected entry relay {} at {} going through {} at {}",
+ entry_relay.hostname,
+ entry_endpoint.peer.endpoint.ip(),
+ exit_relay.hostname,
+ exit_endpoint.to_endpoint().address.ip(),
+ );
+ let result = RelaySelectorResult::wireguard_endpoint_with_entry(
+ exit_relay,
+ entry_endpoint,
+ entry_relay,
+ );
+ return Ok(result);
+ }
- if let MullvadEndpoint::Wireguard { peer, .. } = &mut endpoint {
- if let Some((entry_relay, mut entry_endpoint)) = entry_endpoint.take() {
- self.set_entry_peers(peer, &mut entry_endpoint);
- let addr_in = entry_endpoint.to_endpoint().address.ip();
- log::info!(
- "Selected entry relay {} at {}",
- entry_relay.hostname,
- addr_in
- );
- return Ok((exit_relay, Some(entry_relay), entry_endpoint));
- } else if relay_constraints.wireguard_constraints.use_multihop {
- return Err(Error::NoRelay);
- }
+ /// Returns a WireGuard endpoint, should only ever be used when the user has specified the
+ /// tunnel protocol as only WireGuard.
+ fn get_wireguard_endpoint(
+ &self,
+ location: &Constraint<LocationConstraint>,
+ providers: &Constraint<Providers>,
+ wireguard_constraints: &WireguardConstraints,
+ retry_attempt: u32,
+ ) -> Result<RelaySelectorResult, Error> {
+ let mut entry_relay_matcher = RelayMatcher {
+ location: location.clone(),
+ providers: providers.clone(),
+ tunnel: wireguard_constraints.clone().into(),
+ };
+
+ let mut preferred_matcher: RelayMatcher<WireguardMatcher> = entry_relay_matcher.clone();
+ preferred_matcher.tunnel.port = preferred_matcher
+ .tunnel
+ .port
+ .or(Self::preferred_wireguard_port(retry_attempt));
+
+ if !wireguard_constraints.use_multihop {
+ return self
+ .get_tunnel_endpoint_internal(&preferred_matcher)
+ .or_else(|_| self.get_tunnel_endpoint_internal(&entry_relay_matcher));
}
- Ok((exit_relay, None, endpoint))
+ entry_relay_matcher.location = wireguard_constraints.entry_location.clone();
+ self.get_wireguard_multi_hop_endpoint(entry_relay_matcher, location.clone())
}
- fn get_tunnel_exit_endpoint(
- &mut self,
+ /// Returns a tunnel endpoint of any type, should only be used when the user hasn't specified a
+ /// tunnel protocol.
+ fn get_any_tunnel_endpoint(
+ &self,
relay_constraints: &RelayConstraints,
bridge_state: BridgeState,
retry_attempt: u32,
wg_key_exists: bool,
- wg_entry_peer: Option<&wireguard::PeerConfig>,
- ) -> Result<(Relay, MullvadEndpoint), Error> {
+ ) -> Result<RelaySelectorResult, Error> {
let preferred_constraints = self.preferred_constraints(
&relay_constraints,
bridge_state,
retry_attempt,
wg_key_exists,
);
- if let Some((relay, endpoint)) =
- self.get_tunnel_endpoint_internal(&preferred_constraints, wg_entry_peer)
- {
- log::debug!(
- "Relay matched on highest preference for retry attempt {}",
- retry_attempt
- );
- Ok((relay, endpoint))
- } else if let Some((relay, endpoint)) =
- self.get_tunnel_endpoint_internal(&relay_constraints, wg_entry_peer)
- {
- log::debug!(
- "Relay matched on second preference for retry attempt {}",
- retry_attempt
- );
- Ok((relay, endpoint))
- } else {
- log::warn!("No relays matching {}", &relay_constraints);
- Err(Error::NoRelay)
+ let original_matcher: RelayMatcher<_> = relay_constraints.clone().into();
+
+ let preferred_tunnel_protocol = preferred_constraints.tunnel_protocol;
+ let preferred_matcher: RelayMatcher<_> = preferred_constraints.into();
+
+ match preferred_tunnel_protocol {
+ Constraint::Only(TunnelType::Wireguard)
+ if relay_constraints.wireguard_constraints.use_multihop =>
+ {
+ let exit_location = relay_constraints.location.clone();
+ let mut preferred_entry_matcher = preferred_matcher.to_wireguard_matcher();
+ preferred_entry_matcher.location = relay_constraints
+ .wireguard_constraints
+ .entry_location
+ .clone();
+ let mut original_entry_matcher = original_matcher.to_wireguard_matcher();
+ original_entry_matcher.location = relay_constraints
+ .wireguard_constraints
+ .entry_location
+ .clone();
+ self.get_wireguard_multi_hop_endpoint(
+ preferred_entry_matcher,
+ exit_location.clone(),
+ )
+ .or_else(|_| {
+ self.get_wireguard_multi_hop_endpoint(original_entry_matcher, exit_location)
+ })
+ }
+
+ _ => {
+ if let Ok(result) = self.get_tunnel_endpoint_internal(&preferred_matcher) {
+ log::debug!(
+ "Relay matched on highest preference for retry attempt {}",
+ retry_attempt
+ );
+ Ok(result)
+ } else if let Ok(result) = self.get_tunnel_endpoint_internal(&original_matcher) {
+ log::debug!(
+ "Relay matched on second preference for retry attempt {}",
+ retry_attempt
+ );
+ Ok(result)
+ } else {
+ log::warn!("No relays matching {}", &relay_constraints);
+ Err(Error::NoRelay)
+ }
+ }
}
}
+ // This function ignores the tunnel type constraint on purpose.
fn preferred_constraints(
&self,
original_constraints: &RelayConstraints,
@@ -428,62 +555,55 @@ impl RelaySelector {
});
}
}
+ };
+
+ if relay_constraints.wireguard_constraints.port.is_any() {
+ relay_constraints.wireguard_constraints.port = Constraint::Only(TransportPort {
+ port: preferred_port,
+ protocol: TransportProtocol::Udp,
+ });
}
+ relay_constraints.tunnel_protocol = Constraint::Only(preferred_tunnel);
+
relay_constraints
}
- fn select_entry_endpoint(
- &mut self,
- exit_peer: Option<&wireguard::PeerConfig>,
- relay_constraints: &RelayConstraints,
- retry_attempt: u32,
- ) -> Option<(Relay, MullvadEndpoint)> {
- if !relay_constraints.wireguard_constraints.use_multihop {
- return None;
- }
- let entry_location = relay_constraints
- .wireguard_constraints
- .entry_location
- .clone();
- let entry_constraints = RelayConstraints {
- location: entry_location,
- tunnel_protocol: Constraint::Only(TunnelType::Wireguard),
- ..relay_constraints.clone()
- };
- let entry_constraints =
- self.preferred_constraints(&entry_constraints, BridgeState::Off, retry_attempt, true);
-
+ fn get_entry_endpoint(
+ &self,
+ matcher: &RelayMatcher<WireguardMatcher>,
+ ) -> Result<(Relay, MullvadWireguardEndpoint), Error> {
let matching_relays: Vec<Relay> = self
.parsed_relays
.lock()
.relays()
.iter()
.filter(|relay| relay.active)
- .filter_map(|relay| Self::matching_relay(relay, &entry_constraints, exit_peer))
+ .filter_map(|relay| matcher.matching_relay(relay))
.collect();
let relay = self
.pick_random_relay(&matching_relays)
- .map(|relay| relay.clone())?;
- let endpoint = self.get_random_tunnel(&relay, &entry_constraints)?;
- Some((relay, endpoint))
+ .map(|relay| relay.clone())
+ .ok_or(Error::NoRelay)?;
+ let endpoint = matcher.mullvad_endpoint(&relay).ok_or(Error::NoRelay)?;
+ match endpoint {
+ MullvadEndpoint::Wireguard(endpoint) => Ok((relay, endpoint)),
+ _ => {
+ unreachable!(
+ "Entry endpoints should only ever be WireGuard endpoints, instead got a {:?}",
+ endpoint
+ );
+ }
+ }
}
fn set_entry_peers(
- &mut self,
- new_exit_peer: &wireguard::PeerConfig,
- entry_endpoint: &mut MullvadEndpoint,
+ exit_peer: &wireguard::PeerConfig,
+ entry_endpoint: &mut MullvadWireguardEndpoint,
) {
- if let MullvadEndpoint::Wireguard {
- ref mut peer,
- exit_peer,
- ..
- } = entry_endpoint
- {
- peer.allowed_ips = vec![IpNetwork::from(new_exit_peer.endpoint.ip())];
- *exit_peer = Some(new_exit_peer.clone());
- }
+ entry_endpoint.peer.allowed_ips = vec![IpNetwork::from(exit_peer.endpoint.ip())];
+ entry_endpoint.exit_peer = Some(exit_peer.clone());
}
pub fn get_auto_proxy_settings(
@@ -602,10 +722,26 @@ impl RelaySelector {
}
}
+ fn preferred_wireguard_port(retry_attempt: u32) -> Constraint<TransportPort> {
+ let port = match retry_attempt % 4 {
+ 0 | 1 => Constraint::Any,
+ _ => Constraint::Only(53),
+ };
+ Constraint::Only(TransportPort {
+ port,
+ protocol: TransportProtocol::Udp,
+ })
+ }
+
fn preferred_openvpn_constraints(retry_attempt: u32) -> (Constraint<u16>, TransportProtocol) {
// Prefer UDP by default. But if that has failed a couple of times, then try TCP port
// 443, which works for many with UDP problems. After that, just alternate
// between protocols.
+ // If the tunnel type constraint is set OpenVpn, from the 4th attempt onwards, every two
+ // retry attempts OpenVpn constraints should be set to TCP as a bridge will be used,
+ // and to UDP for the next two attempts. If the tunnel type is specified to be _Any_
+ // and on not-Windows, the first two tries are used for WireGuard and don't
+ // affect counting here.
match retry_attempt {
0 | 1 => (Constraint::Any, TransportProtocol::Udp),
2 | 3 => (Constraint::Only(443), TransportProtocol::Tcp),
@@ -615,115 +751,30 @@ impl RelaySelector {
}
/// Returns a random relay endpoint if any is matching the given constraints.
- fn get_tunnel_endpoint_internal(
- &mut self,
- constraints: &RelayConstraints,
- wg_entry_peer: Option<&wireguard::PeerConfig>,
- ) -> Option<(Relay, MullvadEndpoint)> {
+ fn get_tunnel_endpoint_internal<T: TunnelMatcher>(
+ &self,
+ matcher: &RelayMatcher<T>,
+ ) -> Result<RelaySelectorResult, Error> {
let matching_relays: Vec<Relay> = self
.parsed_relays
.lock()
.relays()
.iter()
.filter(|relay| relay.active)
- .filter_map(|relay| Self::matching_relay(relay, constraints, wg_entry_peer))
+ .filter_map(|relay| matcher.matching_relay(relay))
.collect();
self.pick_random_relay(&matching_relays)
.and_then(|selected_relay| {
- let endpoint = self.get_random_tunnel(&selected_relay, &constraints);
+ let endpoint = matcher.mullvad_endpoint(&selected_relay);
let addr_in = endpoint
.as_ref()
.map(|endpoint| endpoint.to_endpoint().address.ip())
.unwrap_or(IpAddr::from(selected_relay.ipv4_addr_in));
log::info!("Selected relay {} at {}", selected_relay.hostname, addr_in);
- endpoint.map(|endpoint| (selected_relay.clone(), endpoint))
+ endpoint.map(|endpoint| RelaySelectorResult::new(endpoint, selected_relay.clone()))
})
- }
-
- /// Takes a `Relay` and a corresponding `RelayConstraints` and returns a new `Relay` if the
- /// given relay matches the constraints.
- fn matching_relay(
- relay: &Relay,
- constraints: &RelayConstraints,
- skip_wg_peer: Option<&wireguard::PeerConfig>,
- ) -> Option<Relay> {
- if !constraints.location.matches(relay) {
- return None;
- }
- if !constraints.providers.matches(&relay) {
- return None;
- }
-
- let include_wg = if let Some(wg_peer) = skip_wg_peer {
- let peer_ip = wg_peer.endpoint.ip();
- peer_ip != IpAddr::V4(relay.ipv4_addr_in)
- && Some(peer_ip) != relay.ipv6_addr_in.map(IpAddr::V6)
- } else {
- true
- };
-
- let relay = match constraints.tunnel_protocol {
- Constraint::Any => {
- let mut relay = relay.clone();
- relay.tunnels = RelayTunnels {
- wireguard: if include_wg {
- Self::matching_wireguard_tunnels(
- &relay.tunnels,
- &constraints.wireguard_constraints,
- )
- } else {
- vec![]
- },
- openvpn: Self::matching_openvpn_tunnels(
- &relay.tunnels,
- constraints.openvpn_constraints,
- ),
- };
- relay
- }
- Constraint::Only(TunnelType::Wireguard) => {
- let mut relay = relay.clone();
- relay.tunnels = RelayTunnels {
- wireguard: if include_wg {
- Self::matching_wireguard_tunnels(
- &relay.tunnels,
- &constraints.wireguard_constraints,
- )
- } else {
- vec![]
- },
- openvpn: vec![],
- };
- relay
- }
-
- Constraint::Only(TunnelType::OpenVpn) => {
- let mut relay = relay.clone();
- relay.tunnels = RelayTunnels {
- openvpn: Self::matching_openvpn_tunnels(
- &relay.tunnels,
- constraints.openvpn_constraints,
- ),
- wireguard: vec![],
- };
- relay
- }
- };
-
- let relay_matches = match constraints.tunnel_protocol {
- Constraint::Any => {
- !relay.tunnels.openvpn.is_empty() || !relay.tunnels.wireguard.is_empty()
- }
- Constraint::Only(TunnelType::OpenVpn) => !relay.tunnels.openvpn.is_empty(),
- Constraint::Only(TunnelType::Wireguard) => !relay.tunnels.wireguard.is_empty(),
- };
-
- if relay_matches {
- Some(relay)
- } else {
- None
- }
+ .ok_or(Error::NoRelay)
}
fn matching_bridge_relay(
@@ -749,42 +800,19 @@ impl RelaySelector {
Some(filtered_relay)
}
- fn matching_openvpn_tunnels(
- tunnels: &RelayTunnels,
- constraints: OpenVpnConstraints,
- ) -> Vec<OpenVpnEndpointData> {
- tunnels
- .openvpn
- .iter()
- .filter(|endpoint| constraints.matches(*endpoint))
- .cloned()
- .collect()
- }
-
- fn matching_wireguard_tunnels(
- tunnels: &RelayTunnels,
- constraints: &WireguardConstraints,
- ) -> Vec<WireguardEndpointData> {
- tunnels
- .wireguard
- .iter()
- .filter(|endpoint| constraints.matches(*endpoint))
- .cloned()
- .collect()
- }
-
/// Pick a random relay from the given slice. Will return `None` if the given slice is empty.
/// If all of the relays have a weight of 0, one will be picked at random without bias,
/// otherwise roulette wheel selection will be used to pick only relays with non-zero
/// weights.
- fn pick_random_relay<'a>(&mut self, relays: &'a [Relay]) -> Option<&'a Relay> {
+ fn pick_random_relay<'a>(&self, relays: &'a [Relay]) -> Option<&'a Relay> {
let total_weight: u64 = relays.iter().map(|relay| relay.weight).sum();
+ let mut rng = rand::thread_rng();
if total_weight == 0 {
- relays.choose(&mut self.rng)
+ relays.choose(&mut rng)
} else {
// Pick a random number in the range 1 - total_weight. This choses the relay with a
// non-zero weight.
- let mut i: u64 = self.rng.gen_range(1, total_weight + 1);
+ let mut i: u64 = rng.gen_range(1, total_weight + 1);
Some(
relays
.iter()
@@ -798,11 +826,11 @@ impl RelaySelector {
}
/// Picks a random bridge from a relay.
- fn pick_random_bridge(&mut self, relay: &Relay) -> Option<ProxySettings> {
+ fn pick_random_bridge(&self, relay: &Relay) -> Option<ProxySettings> {
relay
.bridges
.shadowsocks
- .choose(&mut self.rng)
+ .choose(&mut rand::thread_rng())
.map(|shadowsocks_endpoint| {
log::info!(
"Selected Shadowsocks bridge {} at {}:{}/{}",
@@ -817,133 +845,6 @@ impl RelaySelector {
})
}
- fn get_random_tunnel(
- &mut self,
- relay: &Relay,
- constraints: &RelayConstraints,
- ) -> Option<MullvadEndpoint> {
- #[cfg(not(target_os = "android"))]
- let mut thread_rng = self.rng.clone();
- #[cfg(not(target_os = "android"))]
- let mut new_openvpn_endpoint = || {
- relay
- .tunnels
- .openvpn
- .choose(&mut thread_rng)
- .cloned()
- .map(|endpoint| endpoint.into_mullvad_endpoint(relay.ipv4_addr_in.into()))
- };
-
- let mut new_wg_endpoint = || {
- relay
- .tunnels
- .wireguard
- .choose(&mut self.rng)
- .cloned()
- .and_then(|wg_tunnel| {
- self.wg_data_to_endpoint(relay, wg_tunnel, &constraints.wireguard_constraints)
- })
- };
-
- #[cfg(not(target_os = "android"))]
- match constraints.tunnel_protocol {
- Constraint::Only(TunnelType::OpenVpn) => new_openvpn_endpoint(),
-
- Constraint::Any => vec![new_openvpn_endpoint(), new_wg_endpoint()]
- .into_iter()
- .filter_map(|relay| relay)
- .collect::<Vec<_>>()
- .choose(&mut self.rng)
- .cloned(),
-
- Constraint::Only(TunnelType::Wireguard) => new_wg_endpoint(),
- }
- #[cfg(target_os = "android")]
- new_wg_endpoint()
- }
-
- fn wg_data_to_endpoint(
- &mut self,
- relay: &Relay,
- data: WireguardEndpointData,
- constraints: &WireguardConstraints,
- ) -> Option<MullvadEndpoint> {
- let host = self.get_address_for_wireguard_relay(relay, 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),
- allowed_ips: all_of_the_internet(),
- protocol: constraints
- .port
- .map(|port| port.protocol)
- .unwrap_or(TransportProtocol::Udp),
- };
- Some(MullvadEndpoint::Wireguard {
- peer: peer_config,
- exit_peer: None,
- ipv4_gateway: data.ipv4_gateway,
- ipv6_gateway: data.ipv6_gateway,
- })
- }
-
- fn get_address_for_wireguard_relay(
- &mut self,
- relay: &Relay,
- constraints: &WireguardConstraints,
- ) -> Option<IpAddr> {
- match constraints.ip_version {
- Constraint::Any | Constraint::Only(IpVersion::V4) => Some(relay.ipv4_addr_in.into()),
- Constraint::Only(IpVersion::V6) => relay.ipv6_addr_in.map(|addr| addr.into()),
- }
- }
-
- fn get_port_for_wireguard_relay(
- &mut self,
- data: &WireguardEndpointData,
- constraints: &WireguardConstraints,
- ) -> Option<u16> {
- match constraints
- .port
- .as_ref()
- .map(|port| port.port)
- .unwrap_or(Constraint::Any)
- {
- Constraint::Any => {
- let get_port_amount =
- |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 };
- let port_amount: u64 = data.port_ranges.iter().map(get_port_amount).sum();
-
- if port_amount < 1 {
- return None;
- }
-
- let mut port_index = self.rng.gen_range(0, port_amount);
-
- for range in data.port_ranges.iter() {
- let ports_in_range = get_port_amount(range);
- if port_index < ports_in_range {
- return Some(port_index as u16 + range.0);
- }
- port_index -= ports_in_range;
- }
- log::error!("Port selection algorithm is broken!");
- None
- }
- Constraint::Only(port) => {
- if data
- .port_ranges
- .iter()
- .any(|range| (range.0 <= port && port <= range.1))
- {
- Some(port)
- } else {
- None
- }
- }
- }
- }
-
/// Try to read the relays from disk, preferring the newer ones.
fn read_relays_from_disk(
cache_path: &Path,
@@ -972,6 +873,35 @@ impl RelaySelector {
}
}
+#[derive(Debug)]
+pub struct RelaySelectorResult {
+ pub exit_relay: Relay,
+ pub endpoint: MullvadEndpoint,
+ pub entry_relay: Option<Relay>,
+}
+
+impl RelaySelectorResult {
+ fn new(endpoint: MullvadEndpoint, exit_relay: Relay) -> Self {
+ Self {
+ exit_relay,
+ endpoint,
+ entry_relay: None,
+ }
+ }
+
+ fn wireguard_endpoint_with_entry(
+ exit_relay: Relay,
+ endpoint: MullvadWireguardEndpoint,
+ entry: Relay,
+ ) -> Self {
+ Self {
+ exit_relay,
+ endpoint: MullvadEndpoint::Wireguard(endpoint),
+ entry_relay: Some(entry),
+ }
+ }
+}
+
#[derive(Clone)]
pub struct RelayListUpdaterHandle {
tx: mpsc::Sender<()>,
@@ -1156,14 +1086,313 @@ impl RelayListUpdater {
}
}
+#[derive(Clone)]
+struct RelayMatcher<T: TunnelMatcher> {
+ location: Constraint<LocationConstraint>,
+ providers: Constraint<Providers>,
+ tunnel: T,
+}
+
+impl From<RelayConstraints> for RelayMatcher<AnyTunnelMatcher> {
+ fn from(constraints: RelayConstraints) -> Self {
+ Self {
+ location: constraints.location,
+ providers: constraints.providers,
+ tunnel: AnyTunnelMatcher {
+ wireguard: constraints.wireguard_constraints.into(),
+ openvpn: constraints.openvpn_constraints,
+ tunnel_type: constraints.tunnel_protocol,
+ },
+ }
+ }
+}
+
+impl RelayMatcher<AnyTunnelMatcher> {
+ fn to_wireguard_matcher(self) -> RelayMatcher<WireguardMatcher> {
+ let Self {
+ location,
+ providers,
+ tunnel,
+ } = self;
+
+ let tunnel = tunnel.wireguard;
+ RelayMatcher {
+ tunnel,
+ location,
+ providers,
+ }
+ }
+}
+
+impl RelayMatcher<WireguardMatcher> {
+ pub fn set_peer(&mut self, peer: Relay) {
+ self.tunnel.peer = Some(peer);
+ }
+}
+
+impl<T: TunnelMatcher> RelayMatcher<T> {
+ fn matching_relay(&self, relay: &Relay) -> Option<Relay> {
+ if !self.location.matches(relay) || !self.providers.matches(relay) {
+ return None;
+ }
+
+ self.tunnel.matching_relay(relay)
+ }
+
+ fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> {
+ self.tunnel.mullvad_endpoint(relay)
+ }
+}
+/// TunnelMatcher allows to abstract over different tunnel-specific constraints,
+/// as to not have false dependencies on OpenVpn specific constraints when
+/// selecting only WireGuard tunnels.
+trait TunnelMatcher: Clone {
+ /// Check if given relay matches tunnel-specific constraints.
+ fn matching_relay(&self, relay: &Relay) -> Option<Relay>;
+ /// Constructs a MullvadEndpoint for a given Relay using extra data from the relay matcher
+ /// itself.
+ fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint>;
+}
+
+impl TunnelMatcher for OpenVpnConstraints {
+ fn matching_relay(&self, relay: &Relay) -> Option<Relay> {
+ let tunnels = relay
+ .tunnels
+ .openvpn
+ .iter()
+ .filter(|endpoint| self.matches(endpoint))
+ .cloned()
+ .collect::<Vec<_>>();
+ if tunnels.is_empty() {
+ return None;
+ }
+ let mut relay = relay.clone();
+ relay.tunnels = RelayTunnels {
+ openvpn: tunnels,
+ wireguard: vec![],
+ };
+ Some(relay)
+ }
+
+ fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> {
+ relay
+ .tunnels
+ .openvpn
+ .choose(&mut rand::thread_rng())
+ .cloned()
+ .map(|endpoint| endpoint.into_mullvad_endpoint(relay.ipv4_addr_in.into()))
+ }
+}
+
+#[derive(Clone)]
+struct AnyTunnelMatcher {
+ wireguard: WireguardMatcher,
+ openvpn: OpenVpnConstraints,
+ /// in the case that a user hasn't specified a tunnel protocol, the relay
+ /// selector might still construct preferred constraints that do select a
+ /// specific tunnel protocol, which is why the tunnel type may be specified
+ /// in the `AnyTunnelMatcher`.
+ tunnel_type: Constraint<TunnelType>,
+}
+
+impl TunnelMatcher for AnyTunnelMatcher {
+ fn matching_relay(&self, relay: &Relay) -> Option<Relay> {
+ match self.tunnel_type {
+ Constraint::Any => {
+ let wireguard_relay = self.wireguard.matching_relay(relay);
+ let openvpn_relay = self.openvpn.matching_relay(relay);
+
+ match (wireguard_relay, openvpn_relay) {
+ (Some(mut matched_relay), Some(openvpn_relay)) => {
+ matched_relay.tunnels.openvpn = openvpn_relay.tunnels.openvpn;
+ Some(matched_relay)
+ }
+ (Some(relay), None) | (None, Some(relay)) => Some(relay),
+ _ => None,
+ }
+ }
+ Constraint::Only(TunnelType::OpenVpn) => self.openvpn.matching_relay(relay),
+ Constraint::Only(TunnelType::Wireguard) => self.wireguard.matching_relay(relay),
+ }
+ }
+
+ fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> {
+ #[cfg(not(target_os = "android"))]
+ match self.tunnel_type {
+ Constraint::Any => vec![
+ self.openvpn.mullvad_endpoint(relay),
+ self.wireguard.mullvad_endpoint(relay),
+ ]
+ .into_iter()
+ .filter_map(|relay| relay)
+ .collect::<Vec<_>>()
+ .choose(&mut rand::thread_rng())
+ .cloned(),
+ Constraint::Only(TunnelType::OpenVpn) => self.openvpn.mullvad_endpoint(relay),
+ Constraint::Only(TunnelType::Wireguard) => self.wireguard.mullvad_endpoint(relay),
+ }
+
+ #[cfg(target_os = "android")]
+ self.wireguard_matcher.mullvad_endpoint(relay)
+ }
+}
+
+#[derive(Clone)]
+struct WireguardMatcher {
+ peer: Option<Relay>,
+ port: Constraint<TransportPort>,
+ ip_version: Constraint<IpVersion>,
+}
+
+impl WireguardMatcher {
+ fn wg_data_to_endpoint(
+ &self,
+ relay: &Relay,
+ data: WireguardEndpointData,
+ ) -> Option<MullvadEndpoint> {
+ let host = self.get_address_for_wireguard_relay(relay)?;
+ let port = self.get_port_for_wireguard_relay(&data)?;
+ let peer_config = wireguard::PeerConfig {
+ public_key: data.public_key,
+ endpoint: SocketAddr::new(host, port),
+ allowed_ips: all_of_the_internet(),
+ protocol: self
+ .port
+ .map(|port| port.protocol)
+ .unwrap_or(TransportProtocol::Udp),
+ };
+ Some(MullvadEndpoint::Wireguard(MullvadWireguardEndpoint {
+ peer: peer_config,
+ exit_peer: None,
+ ipv4_gateway: data.ipv4_gateway,
+ ipv6_gateway: data.ipv6_gateway,
+ }))
+ }
+
+ fn get_address_for_wireguard_relay(&self, relay: &Relay) -> Option<IpAddr> {
+ match self.ip_version {
+ Constraint::Any | Constraint::Only(IpVersion::V4) => Some(relay.ipv4_addr_in.into()),
+ Constraint::Only(IpVersion::V6) => relay.ipv6_addr_in.map(|addr| addr.into()),
+ }
+ }
+
+ fn get_port_for_wireguard_relay(&self, data: &WireguardEndpointData) -> Option<u16> {
+ match self
+ .port
+ .as_ref()
+ .map(|port| port.port)
+ .unwrap_or(Constraint::Any)
+ {
+ Constraint::Any => {
+ let get_port_amount =
+ |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 };
+ let port_amount: u64 = data.port_ranges.iter().map(get_port_amount).sum();
+
+ if port_amount < 1 {
+ return None;
+ }
+
+ let mut port_index = rand::thread_rng().gen_range(0, port_amount);
+
+ for range in data.port_ranges.iter() {
+ let ports_in_range = get_port_amount(range);
+ if port_index < ports_in_range {
+ return Some(port_index as u16 + range.0);
+ }
+ port_index -= ports_in_range;
+ }
+ log::error!("Port selection algorithm is broken!");
+ None
+ }
+ Constraint::Only(port) => {
+ if data
+ .port_ranges
+ .iter()
+ .any(|range| (range.0 <= port && port <= range.1))
+ {
+ Some(port)
+ } else {
+ None
+ }
+ }
+ }
+ }
+}
+
+impl From<WireguardConstraints> for WireguardMatcher {
+ fn from(constraints: WireguardConstraints) -> Self {
+ Self {
+ peer: None,
+ port: constraints.port,
+ ip_version: constraints.ip_version,
+ }
+ }
+}
+
+impl Match<WireguardEndpointData> for WireguardMatcher {
+ fn matches(&self, endpoint: &WireguardEndpointData) -> bool {
+ match self
+ .port
+ .as_ref()
+ .map(|port| port.port)
+ .unwrap_or(Constraint::Any)
+ {
+ Constraint::Any => true,
+ Constraint::Only(port) => endpoint
+ .port_ranges
+ .iter()
+ .any(|range| (port >= range.0 && port <= range.1)),
+ }
+ }
+}
+
+impl TunnelMatcher for WireguardMatcher {
+ fn matching_relay(&self, relay: &Relay) -> Option<Relay> {
+ if self
+ .peer
+ .as_ref()
+ .map(|peer_relay| peer_relay.hostname == relay.hostname)
+ .unwrap_or(false)
+ {
+ return None;
+ }
+
+ let tunnels = relay
+ .tunnels
+ .wireguard
+ .iter()
+ .filter(|endpoint| self.matches(*endpoint))
+ .cloned()
+ .collect::<Vec<_>>();
+ if tunnels.is_empty() {
+ return None;
+ }
+ let mut relay = relay.clone();
+ relay.tunnels = RelayTunnels {
+ wireguard: tunnels,
+ openvpn: vec![],
+ };
+ Some(relay)
+ }
+
+ fn mullvad_endpoint(&self, relay: &Relay) -> Option<MullvadEndpoint> {
+ relay
+ .tunnels
+ .wireguard
+ .choose(&mut rand::thread_rng())
+ .cloned()
+ .and_then(|wg_tunnel| self.wg_data_to_endpoint(relay, wg_tunnel))
+ }
+}
+
#[cfg(test)]
mod test {
use super::*;
use mullvad_types::{
relay_constraints::RelayConstraints,
relay_list::{
- Relay, RelayBridges, RelayListCity, RelayListCountry, RelayTunnels,
- WireguardEndpointData,
+ OpenVpnEndpointData, Relay, RelayBridges, RelayListCity, RelayListCountry,
+ RelayTunnels, WireguardEndpointData,
},
};
use talpid_types::net::wireguard::PublicKey;
@@ -1279,14 +1508,13 @@ mod test {
RELAYS.clone(),
SystemTime::now(),
))),
- rng: rand::thread_rng(),
updater: None,
}
}
#[test]
fn test_preferred_tunnel_protocol() {
- let mut relay_selector = new_relay_selector();
+ let relay_selector = new_relay_selector();
// Prefer WG if the location only supports it
let location = LocationConstraint::Hostname(
@@ -1309,7 +1537,7 @@ mod test {
for attempt in 0..10 {
assert!(relay_selector
- .get_tunnel_exit_endpoint(&relay_constraints, BridgeState::Off, attempt, true, None)
+ .get_any_tunnel_endpoint(&relay_constraints, BridgeState::Off, attempt, true)
.is_ok());
}
@@ -1334,7 +1562,7 @@ mod test {
for attempt in 0..10 {
assert!(relay_selector
- .get_tunnel_exit_endpoint(&relay_constraints, BridgeState::Off, attempt, true, None)
+ .get_any_tunnel_endpoint(&relay_constraints, BridgeState::Off, attempt, true)
.is_ok());
}
@@ -1353,14 +1581,13 @@ mod test {
preferred.tunnel_protocol,
Constraint::Only(TunnelType::OpenVpn)
);
- match relay_selector.get_tunnel_exit_endpoint(
+ match relay_selector.get_any_tunnel_endpoint(
&relay_constraints,
BridgeState::Off,
attempt,
true,
- None,
) {
- Ok((_, MullvadEndpoint::OpenVpn(_))) => (),
+ Ok(result) if matches!(result.endpoint, MullvadEndpoint::OpenVpn(_)) => (),
_ => panic!("OpenVPN endpoint was not selected"),
}
}
@@ -1369,7 +1596,7 @@ mod test {
#[test]
fn test_wg_entry_hostname_collision() {
- let mut relay_selector = new_relay_selector();
+ let relay_selector = new_relay_selector();
let location1 = LocationConstraint::Hostname(
"se".to_string(),
@@ -1406,7 +1633,7 @@ mod test {
#[test]
fn test_wg_entry_filter() -> Result<(), String> {
- let mut relay_selector = new_relay_selector();
+ let relay_selector = new_relay_selector();
let specific_hostname = "se10-wireguard";
@@ -1428,9 +1655,10 @@ mod test {
Constraint::Only(location_specific.clone());
// The exit must not equal the entry
- let (exit_relay, _entry_relay, _exit_endpoint) = relay_selector
+ let exit_relay = relay_selector
.get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, true)
- .map_err(|error| error.to_string())?;
+ .map_err(|error| error.to_string())?
+ .exit_relay;
assert_ne!(exit_relay.hostname, specific_hostname);
@@ -1438,18 +1666,23 @@ mod test {
relay_constraints.wireguard_constraints.entry_location = Constraint::Only(location_general);
// The entry must not equal the exit
- let (exit_relay, _entry_relay, exit_endpoint) = relay_selector
+ let RelaySelectorResult {
+ exit_relay,
+ endpoint,
+ ..
+ } = relay_selector
.get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, true)
.map_err(|error| error.to_string())?;
assert_eq!(exit_relay.hostname, specific_hostname);
- match exit_endpoint {
+ match endpoint {
MullvadEndpoint::OpenVpn { .. } => return Err("Expected WireGuard relay".to_string()),
- MullvadEndpoint::Wireguard {
- peer, exit_peer, ..
- } => {
- assert_eq!(exit_relay.ipv4_addr_in, exit_peer.unwrap().endpoint.ip());
- assert_ne!(exit_relay.ipv4_addr_in, peer.endpoint.ip());
+ MullvadEndpoint::Wireguard(endpoint) => {
+ assert_eq!(
+ exit_relay.ipv4_addr_in,
+ endpoint.exit_peer.unwrap().endpoint.ip()
+ );
+ assert_ne!(exit_relay.ipv4_addr_in, endpoint.peer.endpoint.ip());
}
}
@@ -1543,4 +1776,113 @@ mod test {
Ok(())
}
+
+ #[test]
+ fn test_wg_relay_with_no_key() {
+ let mut relay_constraints = RelayConstraints {
+ tunnel_protocol: Constraint::Only(TunnelType::Wireguard),
+ ..RelayConstraints::default()
+ };
+
+ let relay_selector = new_relay_selector();
+
+ let result = relay_selector
+ .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, false)
+ .expect("Failed to get WireGuard relay when WireGuard relay was specified as the only tunnel protocol");
+
+ assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard(_)));
+
+ relay_constraints.tunnel_protocol = Constraint::Any;
+ let result = relay_selector
+ .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, false)
+ .expect("Failed to get OpenVPN relay with tunnel protocol constraint set to Any and without a WireGuard key");
+
+ assert!(matches!(result.endpoint, MullvadEndpoint::OpenVpn(_)));
+
+ let wireguard_specific_location = LocationConstraint::Hostname(
+ "se".to_string(),
+ "got".to_string(),
+ "se9-wireguard".to_string(),
+ );
+ relay_constraints.location = Constraint::Only(wireguard_specific_location);
+
+ let result = relay_selector
+ .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, false)
+ .expect(
+ "Failed to get a valid WireGuard relay when tunnel constraints are set to any
+ tunnel protocol and with a wireguard specific location without a wireguard key",
+ );
+
+ assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard(_)));
+
+ let result = relay_selector
+ .get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, true)
+ .expect(
+ "Failed to get a valid WireGuard relay when tunnel constraints are set to any
+ tunnel protocol and with a wireguard specific location with a wireguard key",
+ );
+
+ assert!(matches!(result.endpoint, MullvadEndpoint::Wireguard(_)));
+ }
+
+ #[test]
+ fn test_selecting_any_relay_will_consider_multihop() {
+ let relay_constraints = RelayConstraints {
+ wireguard_constraints: WireguardConstraints {
+ use_multihop: true,
+ ..WireguardConstraints::default()
+ },
+ // This has to be explicit otherwise Android will chose WireGuard when default
+ // constructing.
+ tunnel_protocol: Constraint::Any,
+ ..RelayConstraints::default()
+ };
+
+ let relay_selector = new_relay_selector();
+
+ let result = relay_selector.get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, true)
+ .expect("Failed to get relay when tunnel constraints are set to Any and retrying the selection");
+ // Windows will ignore WireGuard until WireGuard is supported well enough
+ // TODO: Remove this caveat once Windows defaults to using WireGuard
+ #[cfg(target_os = "windows")]
+ assert!(
+ matches!(result.endpoint, MullvadEndpoint::OpenVpn(_)) && result.entry_relay.is_none()
+ );
+
+ #[cfg(not(target_os = "windows"))]
+ assert!(
+ matches!(result.endpoint, MullvadEndpoint::Wireguard(_))
+ && result.entry_relay.is_some()
+ );
+ }
+
+ #[test]
+ fn test_selecting_wireguard_location_will_consider_multihop() {
+ let wireguard_specific_location = LocationConstraint::Hostname(
+ "se".to_string(),
+ "got".to_string(),
+ "se9-wireguard".to_string(),
+ );
+
+ let relay_constraints = RelayConstraints {
+ location: Constraint::Only(wireguard_specific_location),
+ wireguard_constraints: WireguardConstraints {
+ use_multihop: true,
+ ..WireguardConstraints::default()
+ },
+ // This has to be explicit otherwise Android will chose WireGuard when default
+ // constructing.
+ tunnel_protocol: Constraint::Any,
+ ..RelayConstraints::default()
+ };
+
+ let relay_selector = new_relay_selector();
+
+ let result = relay_selector.get_tunnel_endpoint(&relay_constraints, BridgeState::Off, 0, true)
+ .expect("Failed to get relay when tunnel constraints are set to Any and retrying the selection");
+ assert!(
+ matches!(result.endpoint, MullvadEndpoint::Wireguard(_))
+ && result.entry_relay.is_some()
+ );
+ }
}
diff --git a/mullvad-types/src/endpoint.rs b/mullvad-types/src/endpoint.rs
index 75e021de8f..11049c0026 100644
--- a/mullvad-types/src/endpoint.rs
+++ b/mullvad-types/src/endpoint.rs
@@ -11,12 +11,16 @@ use crate::relay_list::{OpenVpnEndpointData, WireguardEndpointData};
#[derive(Debug, Clone)]
pub enum MullvadEndpoint {
OpenVpn(Endpoint),
- Wireguard {
- peer: wireguard::PeerConfig,
- exit_peer: Option<wireguard::PeerConfig>,
- ipv4_gateway: Ipv4Addr,
- ipv6_gateway: Ipv6Addr,
- },
+ Wireguard(MullvadWireguardEndpoint),
+}
+
+/// Contains WireGuard server data needed to connect to a WireGuard endpoint
+#[derive(Debug, Clone)]
+pub struct MullvadWireguardEndpoint {
+ pub peer: wireguard::PeerConfig,
+ pub exit_peer: Option<wireguard::PeerConfig>,
+ pub ipv4_gateway: Ipv4Addr,
+ pub ipv6_gateway: Ipv6Addr,
}
impl MullvadEndpoint {
@@ -24,13 +28,20 @@ impl MullvadEndpoint {
pub fn to_endpoint(&self) -> Endpoint {
match self {
MullvadEndpoint::OpenVpn(endpoint) => *endpoint,
- MullvadEndpoint::Wireguard { peer, .. } => Endpoint::new(
- peer.endpoint.ip(),
- peer.endpoint.port(),
+ MullvadEndpoint::Wireguard(wireguard_relay) => Endpoint::new(
+ wireguard_relay.peer.endpoint.ip(),
+ wireguard_relay.peer.endpoint.port(),
TransportProtocol::Udp,
),
}
}
+
+ pub fn get_peer_config(&self) -> Option<&wireguard::PeerConfig> {
+ match self {
+ Self::Wireguard(wireguard_endpoint) => Some(&wireguard_endpoint.peer),
+ _ => None,
+ }
+ }
}
/// TunnelEndpointData contains data required to connect to a given tunnel endpoint.
/// Different endpoint types can require different types of data.
diff --git a/mullvad-types/src/relay_constraints.rs b/mullvad-types/src/relay_constraints.rs
index 18cdfda2d9..6962951eb1 100644
--- a/mullvad-types/src/relay_constraints.rs
+++ b/mullvad-types/src/relay_constraints.rs
@@ -3,7 +3,7 @@
use crate::{
location::{CityCode, CountryCode, Hostname},
- relay_list::{OpenVpnEndpointData, Relay, WireguardEndpointData},
+ relay_list::{OpenVpnEndpointData, Relay},
CustomTunnelEndpoint,
};
#[cfg(target_os = "android")]
@@ -380,48 +380,6 @@ impl fmt::Display for LocationConstraint {
}
}
-/// Deprecated. Contains protocol-specific constraints for relay selection.
-#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
-pub enum TunnelConstraints {
- #[serde(rename = "openvpn")]
- OpenVpn(OpenVpnConstraints),
- #[serde(rename = "wireguard")]
- Wireguard(WireguardConstraints),
-}
-
-impl fmt::Display for TunnelConstraints {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
- match self {
- TunnelConstraints::OpenVpn(openvpn_constraints) => {
- write!(f, "OpenVPN over ")?;
- openvpn_constraints.fmt(f)
- }
- TunnelConstraints::Wireguard(wireguard_constraints) => {
- write!(f, "Wireguard over ")?;
- wireguard_constraints.fmt(f)
- }
- }
- }
-}
-
-impl Match<OpenVpnEndpointData> for TunnelConstraints {
- fn matches(&self, endpoint: &OpenVpnEndpointData) -> bool {
- match *self {
- TunnelConstraints::OpenVpn(ref constraints) => constraints.matches(endpoint),
- _ => false,
- }
- }
-}
-
-impl Match<WireguardEndpointData> for TunnelConstraints {
- fn matches(&self, endpoint: &WireguardEndpointData) -> bool {
- match *self {
- TunnelConstraints::Wireguard(ref constraints) => constraints.matches(endpoint),
- _ => false,
- }
- }
-}
-
#[derive(Debug, Copy, Clone, Eq, PartialEq, Deserialize, Serialize)]
pub struct TransportPort {
pub protocol: TransportProtocol,
@@ -502,24 +460,6 @@ impl fmt::Display for WireguardConstraints {
}
}
-impl Match<WireguardEndpointData> for WireguardConstraints {
- fn matches(&self, endpoint: &WireguardEndpointData) -> bool {
- match self.port {
- Constraint::Any => true,
- Constraint::Only(transport_port) => {
- transport_port.protocol == endpoint.protocol
- && match transport_port.port {
- Constraint::Any => true,
- Constraint::Only(port) => endpoint
- .port_ranges
- .iter()
- .any(|range| (port >= range.0 && port <= range.1)),
- }
- }
- }
- }
-}
-
/// Specifies a specific endpoint or [`BridgeConstraints`] to use when `mullvad-daemon` selects a
/// bridge server.
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]