Skip to content

Commit 132cff7

Browse files
Fix bug selecting OpenVPN bridge if transport protocol was set to Auto
Add a test case to cover this case. Thanks Sebastian for reporting the bug!
1 parent b828fb7 commit 132cff7

File tree

3 files changed

+95
-25
lines changed

3 files changed

+95
-25
lines changed

mullvad-relay-selector/src/relay_selector/detailer.rs

+48-19
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ use mullvad_types::{
1414
relay_constraints::TransportPort,
1515
relay_list::{OpenVpnEndpoint, OpenVpnEndpointData, Relay, WireguardEndpointData},
1616
};
17-
use talpid_types::net::{all_of_the_internet, wireguard::PeerConfig, Endpoint, IpVersion};
17+
use talpid_types::net::{
18+
all_of_the_internet, wireguard::PeerConfig, Endpoint, IpVersion, TransportProtocol,
19+
};
1820

1921
use super::{
20-
query::{OpenVpnRelayQuery, WireguardRelayQuery},
22+
query::{OpenVpnRelayQuery, WireguardRelayQuery, BridgeQuery},
2123
WireguardConfig,
2224
};
2325

@@ -62,14 +64,13 @@ impl WireguardDetailer {
6264
/// [`WireguradRelayQuery::port`]) or relay addresses cannot be resolved.
6365
pub fn to_endpoint(&self) -> Option<MullvadEndpoint> {
6466
match &self.config {
65-
WireguardConfig::Singlehop { exit } => self.tmp_singlehop(exit),
66-
WireguardConfig::Multihop { exit, entry } => self.tmp_multihop(exit, entry),
67+
WireguardConfig::Singlehop { exit } => self.to_singlehop_endpoint(exit),
68+
WireguardConfig::Multihop { exit, entry } => self.to_multihop_endpoint(exit, entry),
6769
}
6870
}
6971

7072
/// Configure a single-hop connection using the exit relay data.
71-
/// TODO(markus): Rename
72-
fn tmp_singlehop(&self, exit: &Relay) -> Option<MullvadEndpoint> {
73+
fn to_singlehop_endpoint(&self, exit: &Relay) -> Option<MullvadEndpoint> {
7374
let endpoint = {
7475
let host = self.get_address_for_wireguard_relay(exit)?;
7576
let port = self.get_port_for_wireguard_relay(&self.data)?;
@@ -95,8 +96,7 @@ impl WireguardDetailer {
9596
/// # Note
9697
/// In a multihop circuit, we need to provide an exit peer configuration in addition to the
9798
/// peer configuration.
98-
/// TODO(markus): Rename
99-
fn tmp_multihop(&self, exit: &Relay, entry: &Relay) -> Option<MullvadEndpoint> {
99+
fn to_multihop_endpoint(&self, exit: &Relay, entry: &Relay) -> Option<MullvadEndpoint> {
100100
let exit_endpoint = {
101101
let ip = exit.ipv4_addr_in;
102102
// The port that the exit relay listens for incoming connections from entry
@@ -228,35 +228,64 @@ impl OpenVpnDetailer {
228228
}
229229
}
230230

231+
/// TODO(markus): Clean up.
231232
/// Map `self` to a [`MullvadEndpoint`].
232233
///
234+
/// If this endpoint is to be used in conjunction with a bridge, the resulting endpoint is
235+
/// guaranteed to use transport protocol `TCP`.
236+
///
233237
/// This function can fail if no valid port + transport protocol combination is found.
234238
/// See [`OpenVpnEndpointData`] for more details.
235239
pub fn to_endpoint(&self) -> Option<MullvadEndpoint> {
236-
self.get_random_transport_port().map(|endpoint| {
237-
MullvadEndpoint::OpenVpn(Endpoint::new(
238-
self.exit.ipv4_addr_in,
239-
endpoint.port,
240-
endpoint.protocol,
241-
))
242-
})
240+
// If `bridge_mode` is true, this function may only return endpoints which use TCP, not UDP.
241+
if BridgeQuery::should_use_bridge(&self.openvpn_constraints.bridge_settings) {
242+
self.to_bridged_endpoint()
243+
} else {
244+
self.to_single_endpoint()
245+
}
246+
}
247+
248+
/// TODO(markus): Document
249+
fn to_single_endpoint(&self) -> Option<MullvadEndpoint> {
250+
use rand::seq::IteratorRandom;
251+
let constraints_port = self.openvpn_constraints.port;
252+
self.data
253+
.ports
254+
.iter()
255+
.filter(|&endpoint| Self::compatible_port_combo(&constraints_port, endpoint))
256+
.choose(&mut rand::thread_rng())
257+
.map(|endpoint| {
258+
MullvadEndpoint::OpenVpn(Endpoint::new(
259+
self.exit.ipv4_addr_in,
260+
endpoint.port,
261+
endpoint.protocol,
262+
))
263+
})
243264
}
244265

245-
/// Try to pick a valid OpenVPN port.
246-
fn get_random_transport_port(&self) -> Option<&OpenVpnEndpoint> {
266+
/// TODO(markus): Document
267+
fn to_bridged_endpoint(&self) -> Option<MullvadEndpoint> {
247268
use rand::seq::IteratorRandom;
248269
let constraints_port = self.openvpn_constraints.port;
249270
self.data
250271
.ports
251272
.iter()
252-
.filter(|endpoint| Self::compatible_port_combo(constraints_port, endpoint))
273+
.filter(|endpoint| matches!(endpoint.protocol, TransportProtocol::Tcp))
274+
.filter(|endpoint| Self::compatible_port_combo(&constraints_port, endpoint))
253275
.choose(&mut rand::thread_rng())
276+
.map(|endpoint| {
277+
MullvadEndpoint::OpenVpn(Endpoint::new(
278+
self.exit.ipv4_addr_in,
279+
endpoint.port,
280+
endpoint.protocol,
281+
))
282+
})
254283
}
255284

256285
/// Returns true if `port_constraint` can be used to connect to `endpoint`.
257286
/// Otherwise, false is returned.
258287
fn compatible_port_combo(
259-
port_constraint: Constraint<TransportPort>,
288+
port_constraint: &Constraint<TransportPort>,
260289
endpoint: &OpenVpnEndpoint,
261290
) -> bool {
262291
match port_constraint {

mullvad-relay-selector/src/relay_selector/mod.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ impl RelaySelector {
619619
/// - `custom_lists`: User-defined or application-specific settings that may influence bridge selection.
620620
///
621621
/// # Returns
622-
/// * On success, returns an `Option` containing the selected bridge, if one is found. Returns `None` if no suitable bridge meets the criteria.
622+
/// * On success, returns an `Option` containing the selected bridge, if one is found. Returns `None` if no suitable bridge meets the criteria or bridges should not be used.
623623
/// * `Error::NoBridge` if attempting to use OpenVPN bridges over UDP, as this is unsupported.
624624
/// * `Error::NoRelay` if `relay` does not have a location set.
625625
fn get_openvpn_bridge(
@@ -804,11 +804,7 @@ impl RelaySelector {
804804
parsed_relays: &ParsedRelays,
805805
) -> Option<Relay> {
806806
// Filter among all valid relays
807-
let relays = Self::get_tunnel_endpoints(
808-
query,
809-
config,
810-
parsed_relays,
811-
);
807+
let relays = Self::get_tunnel_endpoints(query, config, parsed_relays);
812808
// Pick one of the valid relays.
813809
helpers::pick_random_relay(&relays).cloned()
814810
}

mullvad-relay-selector/tests/relay_selector.rs

+45
Original file line numberDiff line numberDiff line change
@@ -902,3 +902,48 @@ fn openvpn_handle_bridge_settings() {
902902
),
903903
};
904904
}
905+
906+
/// Verify that the relay selector correctly gives back an OpenVPN relay + bridge when the user's
907+
/// settings indicate that bridge mode is on, but the transport protocol is set to auto. Note that
908+
/// it is only valid to use TCP with bridges. Trying to use UDP over bridges is not allowed, and
909+
/// the relay selector should fail to select a relay in these cases.
910+
#[test]
911+
fn openvpn_bridge_with_automatic_transport_protocol() {
912+
// Enable bridge mode.
913+
let config = SelectorConfig {
914+
bridge_state: BridgeState::On,
915+
..SelectorConfig::default()
916+
};
917+
let relay_selector = RelaySelector::from_list(config, RELAYS.clone());
918+
919+
// First, construct a query to choose an OpenVPN relay and bridge.
920+
let mut query = RelayQueryBuilder::new().openvpn().bridge().build();
921+
// Forcefully modify the transport protocol, as the builder will ensure that the transport
922+
// protocol is set to TCP.
923+
query.openvpn_constraints.port = Constraint::Any;
924+
925+
for _ in 0..100 {
926+
let relay = relay_selector.get_relay_by_query(query.clone()).unwrap();
927+
// Assert that the relay selector is able to cope with the transport protocol being set to
928+
// auto.
929+
match relay {
930+
GetRelay::OpenVpn { endpoint, .. } => {
931+
assert_eq!(endpoint.unwrap_openvpn().protocol, TCP);
932+
}
933+
wrong_relay => panic!(
934+
"Relay selector should have picked an OpenVPN relay, instead chose {wrong_relay:?}"
935+
),
936+
}
937+
}
938+
939+
// Modify the query slightly to forcefully use UDP. This should not be allowed!
940+
let query = RelayQueryBuilder::new()
941+
.openvpn()
942+
.bridge()
943+
.transport_protocol(UDP)
944+
.build();
945+
for _ in 0..100 {
946+
let relay = relay_selector.get_relay_by_query(query.clone());
947+
assert!(relay.is_err())
948+
}
949+
}

0 commit comments

Comments
 (0)