diff --git a/mullvad-relay-selector/src/constants.rs b/mullvad-relay-selector/src/constants.rs index 5e6b51119566..5999bef11db3 100644 --- a/mullvad-relay-selector/src/constants.rs +++ b/mullvad-relay-selector/src/constants.rs @@ -2,3 +2,7 @@ /// All the valid ports when using UDP2TCP obfuscation. pub(crate) const UDP2TCP_PORTS: [u16; 2] = [80, 5001]; + +/// The standard port on which an exit relay accepts connections from an entry relay in a +/// multihop circuit. +pub(crate) const WIREGUARD_EXIT_PORT: u16 = 51820; diff --git a/mullvad-relay-selector/src/relay_selector/detailer.rs b/mullvad-relay-selector/src/relay_selector/detailer.rs index 0e54f8f1a03a..27715c66515a 100644 --- a/mullvad-relay-selector/src/relay_selector/detailer.rs +++ b/mullvad-relay-selector/src/relay_selector/detailer.rs @@ -18,199 +18,13 @@ use talpid_types::net::{ all_of_the_internet, wireguard::PeerConfig, Endpoint, IpVersion, TransportProtocol, }; +use crate::constants::WIREGUARD_EXIT_PORT; + use super::{ query::{BridgeQuery, OpenVpnRelayQuery, WireguardRelayQuery}, WireguardConfig, }; -/// Given a Wireguad relay (and optionally an entry relay if multihop is used) and the original -/// query the relay selector used, fill in all connection details to produce a valid -/// [`MullvadWireguardEndpoint`] by calling [`to_endpoint`]. -/// -/// [`to_endpoint`]: WireguardDetailer::to_endpoint -pub struct WireguardDetailer<'a> { - wireguard_constraints: &'a WireguardRelayQuery, - config: &'a WireguardConfig, - data: &'a WireguardEndpointData, -} - -impl<'a> WireguardDetailer<'a> { - /// The standard port on which an exit relay accepts connections from an entry relay in a - /// multihop circuit. - pub const WIREGUARD_EXIT_PORT: u16 = 51820; - - /// Create a new [`WireguardDetailer`]. - pub const fn new( - query: &'a WireguardRelayQuery, - config: &'a WireguardConfig, - data: &'a WireguardEndpointData, - ) -> WireguardDetailer<'a> { - Self { - wireguard_constraints: query, - config, - data, - } - } - - /// Constructs a [`MullvadWireguardEndpoint`] with details for a Wireguard circuit. - /// - /// If entry is `None`, `to_endpoint` configures a single-hop connection using the exit relay data. - /// Otherwise, it constructs a multihop setup using both entry and exit to set up appropriate peer - /// configurations. - /// - /// # Returns - /// - A configured Mullvad endpoint for Wireguard, encapsulating either a single-hop or multi-hop connection setup. - /// - Returns `None` if the desired port is not in a valid port range (see - /// [`WireguradRelayQuery::port`]) or relay addresses cannot be resolved. - pub fn to_endpoint(&self) -> Option { - match &self.config { - WireguardConfig::Singlehop { exit } => self.to_singlehop_endpoint(exit), - WireguardConfig::Multihop { exit, entry } => self.to_multihop_endpoint(exit, entry), - } - } - - /// Configure a single-hop connection using the exit relay data. - fn to_singlehop_endpoint(&self, exit: &Relay) -> Option { - let endpoint = { - let host = self.get_address_for_wireguard_relay(exit)?; - let port = self.get_port_for_wireguard_relay(self.data)?; - SocketAddr::new(host, port) - }; - let peer_config = PeerConfig { - public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(), - endpoint, - allowed_ips: all_of_the_internet(), - // This will be filled in later, not the relay selector's problem - psk: None, - }; - Some(MullvadWireguardEndpoint { - peer: peer_config, - exit_peer: None, - ipv4_gateway: self.data.ipv4_gateway, - ipv6_gateway: self.data.ipv6_gateway, - }) - } - - /// Configure a multihop connection using the entry & exit relay data. - /// - /// # Note - /// In a multihop circuit, we need to provide an exit peer configuration in addition to the - /// peer configuration. - fn to_multihop_endpoint( - &self, - exit: &Relay, - entry: &Relay, - ) -> Option { - let exit_endpoint = { - let ip = exit.ipv4_addr_in; - // The port that the exit relay listens for incoming connections from entry - // relays is *not* derived from the original query / user settings. - let port = Self::WIREGUARD_EXIT_PORT; - SocketAddrV4::new(ip, port).into() - }; - let exit = PeerConfig { - public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(), - endpoint: exit_endpoint, - // The exit peer should be able to route incomming VPN traffic to the rest of - // the internet. - allowed_ips: all_of_the_internet(), - // This will be filled in later, not the relay selector's problem - psk: None, - }; - - let entry_endpoint = { - let host = self.get_address_for_wireguard_relay(entry)?; - let port = self.get_port_for_wireguard_relay(self.data)?; - SocketAddr::new(host, port) - }; - let entry = PeerConfig { - public_key: entry - .endpoint_data - .unwrap_wireguard_ref() - .public_key - .clone(), - endpoint: entry_endpoint, - // The entry peer should only be able to route incomming VPN traffic to the - // exit peer. - allowed_ips: vec![IpNetwork::from(exit.endpoint.ip())], - // This will be filled in later - psk: None, - }; - - Some(MullvadWireguardEndpoint { - peer: entry, - exit_peer: Some(exit), - ipv4_gateway: self.data.ipv4_gateway, - ipv6_gateway: self.data.ipv6_gateway, - }) - } - - /// Get the correct IP address for the given relay. - fn get_address_for_wireguard_relay(&self, relay: &Relay) -> Option { - match self.wireguard_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()), - } - } - - // Try to pick a valid Wireguard port. - fn get_port_for_wireguard_relay(&self, data: &WireguardEndpointData) -> Option { - match self.wireguard_constraints.port { - Constraint::Any => { - let random_port = Self::select_random_port(&data.port_ranges); - if random_port.is_none() { - log::error!("Port selection algorithm is broken!"); - } - random_port - } - Constraint::Only(port) => { - if data - .port_ranges - .iter() - .any(|range| (range.0 <= port && port <= range.1)) - { - Some(port) - } else { - None - } - } - } - } - - /// Selects a random port number from a list of provided port ranges. - /// - /// This function iterates over a list of port ranges, each represented as a tuple (u16, u16) - /// where the first element is the start of the range and the second is the end (inclusive), - /// and selects a random port from the set of all ranges. - /// - /// # Parameters - /// - `port_ranges`: A slice of tuples, each representing a range of valid port numbers. - /// - /// # Returns - /// - `Option`: A randomly selected port number within the given ranges, or `None` if - /// the input is empty or the total number of available ports is zero. - fn select_random_port(port_ranges: &[(u16, u16)]) -> Option { - use rand::Rng; - let get_port_amount = |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 }; - let port_amount: u64 = 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 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; - } - None - } -} - /// Given an OpenVPN relay and the original query the relay selector used, /// fill in all connection details to produce a valid [`Endpoint`] by /// calling [`to_endpoint`]. @@ -298,3 +112,175 @@ impl<'a> OpenVpnDetailer<'a> { } } } + +// -- These shall overthrow the tyranny of the structs -- + +/// Constructs a [`MullvadWireguardEndpoint`] with details for a Wireguard circuit. +/// +/// If entry is `None`, `to_endpoint` configures a single-hop connection using the exit relay data. +/// Otherwise, it constructs a multihop setup using both entry and exit to set up appropriate peer +/// configurations. +/// +/// # Returns +/// - A configured Mullvad endpoint for Wireguard, encapsulating either a single-hop or multi-hop connection setup. +/// - Returns `None` if the desired port is not in a valid port range (see +/// [`WireguradRelayQuery::port`]) or relay addresses cannot be resolved. +pub fn wireguard_endpoint( + query: &WireguardRelayQuery, + data: &WireguardEndpointData, + relay: &WireguardConfig, +) -> Option { + match relay { + WireguardConfig::Singlehop { exit } => to_singlehop_endpoint(query, data, exit), + WireguardConfig::Multihop { exit, entry } => to_multihop_endpoint(query, data, exit, entry), + } +} + +/// Configure a single-hop connection using the exit relay data. +fn to_singlehop_endpoint( + query: &WireguardRelayQuery, + data: &WireguardEndpointData, + exit: &Relay, +) -> Option { + let endpoint = { + let host = get_address_for_wireguard_relay(query, exit)?; + let port = get_port_for_wireguard_relay(query, data)?; + SocketAddr::new(host, port) + }; + let peer_config = PeerConfig { + public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(), + endpoint, + allowed_ips: all_of_the_internet(), + // This will be filled in later, not the relay selector's problem + psk: None, + }; + Some(MullvadWireguardEndpoint { + peer: peer_config, + exit_peer: None, + ipv4_gateway: data.ipv4_gateway, + ipv6_gateway: data.ipv6_gateway, + }) +} + +/// Configure a multihop connection using the entry & exit relay data. +/// +/// # Note +/// In a multihop circuit, we need to provide an exit peer configuration in addition to the +/// peer configuration. +fn to_multihop_endpoint( + query: &WireguardRelayQuery, + data: &WireguardEndpointData, + exit: &Relay, + entry: &Relay, +) -> Option { + let exit_endpoint = { + let ip = exit.ipv4_addr_in; + // The port that the exit relay listens for incoming connections from entry + // relays is *not* derived from the original query / user settings. + let port = WIREGUARD_EXIT_PORT; + SocketAddrV4::new(ip, port).into() + }; + let exit = PeerConfig { + public_key: exit.endpoint_data.unwrap_wireguard_ref().public_key.clone(), + endpoint: exit_endpoint, + // The exit peer should be able to route incomming VPN traffic to the rest of + // the internet. + allowed_ips: all_of_the_internet(), + // This will be filled in later, not the relay selector's problem + psk: None, + }; + + let entry_endpoint = { + let host = get_address_for_wireguard_relay(query, entry)?; + let port = get_port_for_wireguard_relay(query, data)?; + SocketAddr::new(host, port) + }; + let entry = PeerConfig { + public_key: entry + .endpoint_data + .unwrap_wireguard_ref() + .public_key + .clone(), + endpoint: entry_endpoint, + // The entry peer should only be able to route incomming VPN traffic to the + // exit peer. + allowed_ips: vec![IpNetwork::from(exit.endpoint.ip())], + // This will be filled in later + psk: None, + }; + + Some(MullvadWireguardEndpoint { + peer: entry, + exit_peer: Some(exit), + ipv4_gateway: data.ipv4_gateway, + ipv6_gateway: data.ipv6_gateway, + }) +} + +/// Get the correct IP address for the given relay. +fn get_address_for_wireguard_relay(query: &WireguardRelayQuery, relay: &Relay) -> Option { + match query.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()), + } +} + +// Try to pick a valid Wireguard port. +fn get_port_for_wireguard_relay( + query: &WireguardRelayQuery, + data: &WireguardEndpointData, +) -> Option { + match query.port { + Constraint::Any => { + let random_port = select_random_port(&data.port_ranges); + if random_port.is_none() { + log::error!("Port selection algorithm is broken!"); + } + random_port + } + Constraint::Only(port) => { + if data + .port_ranges + .iter() + .any(|range| (range.0 <= port && port <= range.1)) + { + Some(port) + } else { + None + } + } + } +} + +/// Selects a random port number from a list of provided port ranges. +/// +/// This function iterates over a list of port ranges, each represented as a tuple (u16, u16) +/// where the first element is the start of the range and the second is the end (inclusive), +/// and selects a random port from the set of all ranges. +/// +/// # Parameters +/// - `port_ranges`: A slice of tuples, each representing a range of valid port numbers. +/// +/// # Returns +/// - `Option`: A randomly selected port number within the given ranges, or `None` if +/// the input is empty or the total number of available ports is zero. +fn select_random_port(port_ranges: &[(u16, u16)]) -> Option { + use rand::Rng; + let get_port_amount = |range: &(u16, u16)| -> u64 { (1 + range.1 - range.0) as u64 }; + let port_amount: u64 = 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 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; + } + None +} diff --git a/mullvad-relay-selector/src/relay_selector/mod.rs b/mullvad-relay-selector/src/relay_selector/mod.rs index 598e82f37dd5..51db73977c95 100644 --- a/mullvad-relay-selector/src/relay_selector/mod.rs +++ b/mullvad-relay-selector/src/relay_selector/mod.rs @@ -40,7 +40,7 @@ use talpid_types::{ use crate::error::{EndpointError, Error}; use self::{ - detailer::{OpenVpnDetailer, WireguardDetailer}, + detailer::{wireguard_endpoint, OpenVpnDetailer}, matcher::AnyTunnelMatcher, parsed_relays::ParsedRelays, query::{BridgeQuery, Intersection, OpenVpnRelayQuery, RelayQuery, WireguardRelayQuery}, @@ -526,7 +526,7 @@ impl RelaySelector { } else { Self::get_wireguard_multihop_config(query, config, parsed_relays)? }; - let endpoint = Self::get_wireguard_endpoint(query, &inner, parsed_relays)?; + let endpoint = Self::get_wireguard_endpoint(query, parsed_relays, &inner)?; let obfuscator = Self::get_wireguard_obfuscator(query, inner.clone(), &endpoint, parsed_relays)?; @@ -607,15 +607,14 @@ impl RelaySelector { /// Constructs a `MullvadEndpoint` with details for how to connect to `relay`. fn get_wireguard_endpoint( query: &RelayQuery, - relay: &WireguardConfig, parsed_relays: &ParsedRelays, + relay: &WireguardConfig, ) -> Result { - WireguardDetailer::new( + wireguard_endpoint( &query.wireguard_constraints, - relay, &parsed_relays.parsed_list().wireguard, + relay, ) - .to_endpoint() .ok_or(EndpointError::from_wireguard(relay.clone())) }